feat: add Import-InfisicalSecret + Get-InfisicalEnvironmentVariable + -Prefix on ConvertTo-InfisicalSecretDictionary
This commit is contained in:
@@ -21,6 +21,9 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
[Parameter]
|
||||
public SwitchParameter AsPlainText { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Prefix { get; set; }
|
||||
|
||||
private readonly List<InfisicalSecret> _buffer = new List<InfisicalSecret>();
|
||||
|
||||
protected override void ProcessRecord()
|
||||
@@ -60,10 +63,11 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
private Dictionary<string, TValue> BuildDictionary<TValue>(Func<InfisicalSecret, TValue> valueSelector)
|
||||
{
|
||||
Dictionary<string, TValue> dictionary = new Dictionary<string, TValue>(StringComparer.OrdinalIgnoreCase);
|
||||
string prefix = Prefix ?? string.Empty;
|
||||
|
||||
foreach (InfisicalSecret secret in _buffer)
|
||||
{
|
||||
string key = secret.SecretName ?? string.Empty;
|
||||
string key = string.Concat(prefix, secret.SecretName ?? string.Empty);
|
||||
|
||||
if (dictionary.ContainsKey(key))
|
||||
{
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Get, "InfisicalEnvironmentVariable")]
|
||||
[OutputType(typeof(string))]
|
||||
public sealed class GetInfisicalEnvironmentVariableCmdlet : PSCmdlet
|
||||
{
|
||||
private static readonly EnvironmentVariableTarget[] TargetOrder = new[]
|
||||
{
|
||||
EnvironmentVariableTarget.Process,
|
||||
EnvironmentVariableTarget.User,
|
||||
EnvironmentVariableTarget.Machine
|
||||
};
|
||||
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Name { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
foreach (EnvironmentVariableTarget target in TargetOrder)
|
||||
{
|
||||
string value;
|
||||
try
|
||||
{
|
||||
value = Environment.GetEnvironmentVariable(Name, target);
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
WriteObject(value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Management.Automation;
|
||||
using System.Security;
|
||||
using PSInfisicalAPI.Errors;
|
||||
using PSInfisicalAPI.Imports;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Security;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsData.Import, "InfisicalSecret")]
|
||||
[OutputType(typeof(Dictionary<string, SecureString>))]
|
||||
[OutputType(typeof(Dictionary<string, string>))]
|
||||
public sealed class ImportInfisicalSecretCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
[ValidateNotNull]
|
||||
public FileInfo Path { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true)]
|
||||
public InfisicalImportFormat Format { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public InfisicalDuplicateKeyBehavior DuplicateKeyBehavior { get; set; } = InfisicalDuplicateKeyBehavior.Error;
|
||||
|
||||
[Parameter]
|
||||
public SwitchParameter AsPlainText { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Prefix { get; set; }
|
||||
|
||||
protected override void EndProcessing()
|
||||
{
|
||||
try
|
||||
{
|
||||
Path.Refresh();
|
||||
if (!Path.Exists)
|
||||
{
|
||||
throw new InfisicalImportException(string.Concat("Import path does not exist: ", Path.FullName));
|
||||
}
|
||||
|
||||
IInfisicalImporter importer = InfisicalImporterFactory.Create(Format);
|
||||
IList<KeyValuePair<string, string>> pairs = importer.Import(Path);
|
||||
|
||||
if (AsPlainText.IsPresent)
|
||||
{
|
||||
Dictionary<string, string> plain = BuildDictionary<string>(pairs, value => value ?? string.Empty);
|
||||
WriteObject(plain);
|
||||
}
|
||||
else
|
||||
{
|
||||
Dictionary<string, SecureString> secure = BuildDictionary<SecureString>(pairs, value => SecureStringUtility.ToReadOnlySecureString(value ?? string.Empty));
|
||||
WriteObject(secure);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("ImportInfisicalSecretCmdlet", "ImportSecret", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, TValue> BuildDictionary<TValue>(
|
||||
IList<KeyValuePair<string, string>> pairs,
|
||||
Func<string, TValue> valueSelector)
|
||||
{
|
||||
Dictionary<string, TValue> dictionary = new Dictionary<string, TValue>(StringComparer.OrdinalIgnoreCase);
|
||||
string prefix = Prefix ?? string.Empty;
|
||||
|
||||
foreach (KeyValuePair<string, string> pair in pairs)
|
||||
{
|
||||
if (pair.Key == null) { continue; }
|
||||
string key = string.Concat(prefix, pair.Key);
|
||||
|
||||
if (dictionary.ContainsKey(key))
|
||||
{
|
||||
if (DuplicateKeyBehavior == InfisicalDuplicateKeyBehavior.Error)
|
||||
{
|
||||
throw new InfisicalConfigurationException(string.Concat("Duplicate secret name encountered: ", key));
|
||||
}
|
||||
|
||||
if (DuplicateKeyBehavior == InfisicalDuplicateKeyBehavior.LastWins)
|
||||
{
|
||||
dictionary[key] = valueSelector(pair.Value);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
dictionary[key] = valueSelector(pair.Value);
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,13 @@ namespace PSInfisicalAPI.Errors
|
||||
public InfisicalExportException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
|
||||
public class InfisicalImportException : InfisicalException
|
||||
{
|
||||
public InfisicalImportException() { }
|
||||
public InfisicalImportException(string message) : base(message) { }
|
||||
public InfisicalImportException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
|
||||
public class InfisicalConfigurationException : InfisicalException
|
||||
{
|
||||
public InfisicalConfigurationException() { }
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using PSInfisicalAPI.Errors;
|
||||
|
||||
namespace PSInfisicalAPI.Imports
|
||||
{
|
||||
public sealed class EnvInfisicalImporter : IInfisicalImporter
|
||||
{
|
||||
public IList<KeyValuePair<string, string>> Import(FileInfo path)
|
||||
{
|
||||
if (path == null) { throw new InfisicalImportException("Path is required for ENV import."); }
|
||||
|
||||
List<KeyValuePair<string, string>> result = new List<KeyValuePair<string, string>>();
|
||||
string[] lines = File.ReadAllLines(path.FullName);
|
||||
|
||||
foreach (string raw in lines)
|
||||
{
|
||||
if (raw == null) { continue; }
|
||||
string line = raw.Trim();
|
||||
if (line.Length == 0) { continue; }
|
||||
if (line[0] == '#') { continue; }
|
||||
|
||||
int idx = line.IndexOf('=');
|
||||
if (idx <= 0) { continue; }
|
||||
|
||||
string key = line.Substring(0, idx).Trim();
|
||||
string value = line.Substring(idx + 1);
|
||||
if (key.Length == 0) { continue; }
|
||||
|
||||
result.Add(new KeyValuePair<string, string>(key, value));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace PSInfisicalAPI.Imports
|
||||
{
|
||||
public interface IInfisicalImporter
|
||||
{
|
||||
IList<KeyValuePair<string, string>> Import(FileInfo path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using PSInfisicalAPI.Errors;
|
||||
using PSInfisicalAPI.Models;
|
||||
|
||||
namespace PSInfisicalAPI.Imports
|
||||
{
|
||||
public static class InfisicalImporterFactory
|
||||
{
|
||||
public static IInfisicalImporter Create(InfisicalImportFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case InfisicalImportFormat.Json: return new JsonInfisicalImporter();
|
||||
case InfisicalImportFormat.Yaml: return new YamlInfisicalImporter();
|
||||
case InfisicalImportFormat.Env: return new EnvInfisicalImporter();
|
||||
case InfisicalImportFormat.Xml: return new XmlInfisicalImporter();
|
||||
default: throw new InfisicalImportException(string.Concat("Unsupported import format: ", format.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PSInfisicalAPI.Errors;
|
||||
|
||||
namespace PSInfisicalAPI.Imports
|
||||
{
|
||||
public sealed class JsonInfisicalImporter : IInfisicalImporter
|
||||
{
|
||||
public IList<KeyValuePair<string, string>> Import(FileInfo path)
|
||||
{
|
||||
if (path == null) { throw new InfisicalImportException("Path is required for JSON import."); }
|
||||
|
||||
List<KeyValuePair<string, string>> result = new List<KeyValuePair<string, string>>();
|
||||
string text = File.ReadAllText(path.FullName);
|
||||
JToken root = JToken.Parse(text);
|
||||
|
||||
if (root.Type == JTokenType.Array)
|
||||
{
|
||||
foreach (JToken item in (JArray)root)
|
||||
{
|
||||
if (item == null || item.Type != JTokenType.Object) { continue; }
|
||||
string key = ReadString((JObject)item, "SecretName") ?? ReadString((JObject)item, "secretName");
|
||||
string value = ReadString((JObject)item, "SecretValue") ?? ReadString((JObject)item, "secretValue");
|
||||
if (string.IsNullOrEmpty(key)) { continue; }
|
||||
result.Add(new KeyValuePair<string, string>(key, value ?? string.Empty));
|
||||
}
|
||||
}
|
||||
else if (root.Type == JTokenType.Object)
|
||||
{
|
||||
foreach (JProperty prop in ((JObject)root).Properties())
|
||||
{
|
||||
if (prop == null || string.IsNullOrEmpty(prop.Name)) { continue; }
|
||||
string value = prop.Value != null && prop.Value.Type != JTokenType.Null ? prop.Value.ToString() : string.Empty;
|
||||
result.Add(new KeyValuePair<string, string>(prop.Name, value));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InfisicalImportException("JSON import expects an array of secret objects or a flat key/value object.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string ReadString(JObject obj, string name)
|
||||
{
|
||||
JToken token;
|
||||
if (!obj.TryGetValue(name, out token) || token == null || token.Type == JTokenType.Null) { return null; }
|
||||
return token.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using PSInfisicalAPI.Errors;
|
||||
|
||||
namespace PSInfisicalAPI.Imports
|
||||
{
|
||||
public sealed class XmlInfisicalImporter : IInfisicalImporter
|
||||
{
|
||||
public IList<KeyValuePair<string, string>> Import(FileInfo path)
|
||||
{
|
||||
if (path == null) { throw new InfisicalImportException("Path is required for XML import."); }
|
||||
|
||||
List<KeyValuePair<string, string>> result = new List<KeyValuePair<string, string>>();
|
||||
XmlDocument document = new XmlDocument();
|
||||
document.Load(path.FullName);
|
||||
|
||||
XmlNode root = document.DocumentElement;
|
||||
if (root == null || !string.Equals(root.LocalName, "Secrets", System.StringComparison.Ordinal))
|
||||
{
|
||||
throw new InfisicalImportException("XML import expects a root <Secrets> element.");
|
||||
}
|
||||
|
||||
foreach (XmlNode node in root.ChildNodes)
|
||||
{
|
||||
if (node == null || node.NodeType != XmlNodeType.Element) { continue; }
|
||||
if (!string.Equals(node.LocalName, "Secret", System.StringComparison.Ordinal)) { continue; }
|
||||
|
||||
string key = ReadChild(node, "SecretName");
|
||||
string value = ReadChild(node, "SecretValue");
|
||||
if (string.IsNullOrEmpty(key)) { continue; }
|
||||
result.Add(new KeyValuePair<string, string>(key, value ?? string.Empty));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string ReadChild(XmlNode parent, string name)
|
||||
{
|
||||
foreach (XmlNode child in parent.ChildNodes)
|
||||
{
|
||||
if (child == null || child.NodeType != XmlNodeType.Element) { continue; }
|
||||
if (string.Equals(child.LocalName, name, System.StringComparison.Ordinal))
|
||||
{
|
||||
return child.InnerText;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using PSInfisicalAPI.Errors;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
namespace PSInfisicalAPI.Imports
|
||||
{
|
||||
public sealed class YamlInfisicalImporter : IInfisicalImporter
|
||||
{
|
||||
public IList<KeyValuePair<string, string>> Import(FileInfo path)
|
||||
{
|
||||
if (path == null) { throw new InfisicalImportException("Path is required for YAML import."); }
|
||||
|
||||
List<KeyValuePair<string, string>> result = new List<KeyValuePair<string, string>>();
|
||||
string text = File.ReadAllText(path.FullName);
|
||||
|
||||
IDeserializer deserializer = new DeserializerBuilder().Build();
|
||||
object root = deserializer.Deserialize<object>(text);
|
||||
if (root == null) { return result; }
|
||||
|
||||
IDictionary rootMap = root as IDictionary;
|
||||
if (rootMap != null && rootMap.Contains("Secrets"))
|
||||
{
|
||||
IList entries = rootMap["Secrets"] as IList;
|
||||
if (entries != null)
|
||||
{
|
||||
foreach (object entry in entries)
|
||||
{
|
||||
IDictionary map = entry as IDictionary;
|
||||
if (map == null) { continue; }
|
||||
string key = AsString(map["SecretName"]);
|
||||
string value = AsString(map["SecretValue"]);
|
||||
if (string.IsNullOrEmpty(key)) { continue; }
|
||||
result.Add(new KeyValuePair<string, string>(key, value ?? string.Empty));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (rootMap != null)
|
||||
{
|
||||
foreach (DictionaryEntry kvp in rootMap)
|
||||
{
|
||||
string key = AsString(kvp.Key);
|
||||
if (string.IsNullOrEmpty(key)) { continue; }
|
||||
result.Add(new KeyValuePair<string, string>(key, AsString(kvp.Value) ?? string.Empty));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new InfisicalImportException("YAML import expects a 'Secrets' root list or a flat key/value mapping.");
|
||||
}
|
||||
|
||||
private static string AsString(object value)
|
||||
{
|
||||
if (value == null) { return null; }
|
||||
return value.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace PSInfisicalAPI.Models
|
||||
{
|
||||
public enum InfisicalImportFormat
|
||||
{
|
||||
Json,
|
||||
Yaml,
|
||||
Env,
|
||||
Xml
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user