Add create, update, and delete for Certificate Manager configuration
The module was read-only for PKI configuration, which is why standing up an
environment meant raw REST rather than cmdlets. Adds 15: New-, Set-, and Remove-
for certificate authorities, policies, profiles, applications, and PKI
subscribers.
New-InfisicalCertificateAuthority -Type Intermediate returns an authority that
can actually issue. Infisical creates a subordinate with status
pending-certificate and never invokes generateIntermediateCaCertificate from the
create path, so the cmdlet performs the remaining sequence: read the certificate
signing request, sign it with -ParentCaId, import the signed certificate and
chain back.
Policy and profile bodies are deeply nested, so the constraint objects and
per-enrollment-type config blocks are taken as dictionaries rather than as
dozens of parameters, matching how -Subject and -Metadata already work. Two
conversion details matter and are now pinned by tests:
- PowerShell callers capitalise hashtable keys, and @{ Required = ... } was
reaching the API as "Required", which its schema does not recognise. The
constraint vocabulary is emitted lower-cased while every other key keeps its
camelCase, since those are API field names supplied verbatim and lowercasing
ttlDays or isCA would silently drop them.
- An empty collection is omitted rather than sent. "allowed": [] reads to
Infisical as "allow nothing", never what @{ Allowed = @() } was meant to
express, and an entry carrying only "type" is rejected outright.
-EnrollmentConfig routes to the block matching -EnrollmentType so EST, ACME, and
SCEP settings arrive through one parameter. -AutoRenew is sent only when bound,
because a switch is false when absent and sending it unconditionally would
disable renewal on an update that never mentioned it.
-ProjectId is optional on all 15 and resolves as it does elsewhere. Every one
supports -WhatIf; Remove- cmdlets default to high confirm impact.
Also fixes Update-Changelog, which inserted the version heading above the notes
so the section the release workflow extracts held only the build line while the
notes stayed under Unreleased - every release published an empty changelog.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace PSInfisicalAPI.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// The create/update/delete cmdlets for Certificate Manager configuration. Their bodies are built from
|
||||
/// caller-supplied dictionaries, so the conversion into the exact JSON Infisical's schemas accept is what
|
||||
/// these pin.
|
||||
/// </summary>
|
||||
public class PkiWriteCmdletTests
|
||||
{
|
||||
private static readonly Assembly ModuleAssembly = typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly;
|
||||
|
||||
private static readonly string[] WriteCmdletTypes = new[]
|
||||
{
|
||||
"NewInfisicalCertificateAuthorityCmdlet", "SetInfisicalCertificateAuthorityCmdlet", "RemoveInfisicalCertificateAuthorityCmdlet",
|
||||
"NewInfisicalCertificatePolicyCmdlet", "SetInfisicalCertificatePolicyCmdlet", "RemoveInfisicalCertificatePolicyCmdlet",
|
||||
"NewInfisicalCertificateProfileCmdlet", "SetInfisicalCertificateProfileCmdlet", "RemoveInfisicalCertificateProfileCmdlet",
|
||||
"NewInfisicalCertificateApplicationCmdlet", "SetInfisicalCertificateApplicationCmdlet", "RemoveInfisicalCertificateApplicationCmdlet",
|
||||
"NewInfisicalPkiSubscriberCmdlet", "SetInfisicalPkiSubscriberCmdlet", "RemoveInfisicalPkiSubscriberCmdlet"
|
||||
};
|
||||
|
||||
private static Dictionary<string, object> ToJsonObject(IDictionary source)
|
||||
{
|
||||
Type baseType = ModuleAssembly.GetType("PSInfisicalAPI.Cmdlets.InfisicalPkiWriteCmdletBase", true);
|
||||
MethodInfo method = baseType.GetMethod("ToJsonObject", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
Assert.NotNull(method);
|
||||
return (Dictionary<string, object>)method.Invoke(null, new object[] { source });
|
||||
}
|
||||
|
||||
private static List<Dictionary<string, object>> ToJsonObjectList(IEnumerable source)
|
||||
{
|
||||
Type baseType = ModuleAssembly.GetType("PSInfisicalAPI.Cmdlets.InfisicalPkiWriteCmdletBase", true);
|
||||
MethodInfo method = baseType.GetMethod("ToJsonObjectList", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
Assert.NotNull(method);
|
||||
return (List<Dictionary<string, object>>)method.Invoke(null, new object[] { source });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Every_Write_Cmdlet_Declares_ShouldProcess()
|
||||
{
|
||||
List<string> offenders = new List<string>();
|
||||
|
||||
foreach (string typeName in WriteCmdletTypes)
|
||||
{
|
||||
Type type = ModuleAssembly.GetType(string.Concat("PSInfisicalAPI.Cmdlets.", typeName), true);
|
||||
|
||||
bool supportsShouldProcess = false;
|
||||
foreach (CustomAttributeData attribute in type.GetCustomAttributesData())
|
||||
{
|
||||
if (attribute.AttributeType != typeof(CmdletAttribute)) { continue; }
|
||||
foreach (CustomAttributeNamedArgument named in attribute.NamedArguments)
|
||||
{
|
||||
if (named.MemberName == "SupportsShouldProcess" && (bool)named.TypedValue.Value) { supportsShouldProcess = true; }
|
||||
}
|
||||
}
|
||||
|
||||
if (!supportsShouldProcess) { offenders.Add(typeName); }
|
||||
}
|
||||
|
||||
Assert.Empty(offenders);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Every_Remove_Cmdlet_Defaults_To_High_Confirm_Impact()
|
||||
{
|
||||
List<string> offenders = new List<string>();
|
||||
|
||||
foreach (string typeName in WriteCmdletTypes)
|
||||
{
|
||||
if (!typeName.StartsWith("Remove", StringComparison.Ordinal)) { continue; }
|
||||
|
||||
Type type = ModuleAssembly.GetType(string.Concat("PSInfisicalAPI.Cmdlets.", typeName), true);
|
||||
|
||||
bool high = false;
|
||||
foreach (CustomAttributeData attribute in type.GetCustomAttributesData())
|
||||
{
|
||||
if (attribute.AttributeType != typeof(CmdletAttribute)) { continue; }
|
||||
foreach (CustomAttributeNamedArgument named in attribute.NamedArguments)
|
||||
{
|
||||
if (named.MemberName == "ConfirmImpact" && (ConfirmImpact)named.TypedValue.Value == ConfirmImpact.High) { high = true; }
|
||||
}
|
||||
}
|
||||
|
||||
if (!high) { offenders.Add(typeName); }
|
||||
}
|
||||
|
||||
Assert.Empty(offenders);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Every_Write_Cmdlet_Resolves_The_Project_Instead_Of_Requiring_It()
|
||||
{
|
||||
List<string> offenders = new List<string>();
|
||||
|
||||
foreach (string typeName in WriteCmdletTypes)
|
||||
{
|
||||
Type type = ModuleAssembly.GetType(string.Concat("PSInfisicalAPI.Cmdlets.", typeName), true);
|
||||
PropertyInfo projectId = type.GetProperty("ProjectId");
|
||||
Assert.True(projectId != null, string.Concat(typeName, " has no ProjectId property"));
|
||||
|
||||
foreach (CustomAttributeData attribute in projectId.GetCustomAttributesData())
|
||||
{
|
||||
if (attribute.AttributeType != typeof(ParameterAttribute)) { continue; }
|
||||
foreach (CustomAttributeNamedArgument named in attribute.NamedArguments)
|
||||
{
|
||||
if (named.MemberName == "Mandatory" && (bool)named.TypedValue.Value) { offenders.Add(typeName); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Assert.Empty(offenders);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
// PowerShell callers capitalise hashtable keys; Infisical's schema is lower case.
|
||||
[InlineData("Required", "required")]
|
||||
[InlineData("required", "required")]
|
||||
[InlineData("Allowed", "allowed")]
|
||||
[InlineData("DENIED", "denied")]
|
||||
public void Constraint_Keys_Are_Emitted_In_The_Casing_The_Api_Requires(string supplied, string expected)
|
||||
{
|
||||
Hashtable source = new Hashtable { { supplied, new[] { "server_auth" } } };
|
||||
Dictionary<string, object> result = ToJsonObject(source);
|
||||
|
||||
Assert.True(result.ContainsKey(expected), string.Concat("expected key '", expected, "' but got: ", JsonConvert.SerializeObject(result)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Non_Constraint_Keys_Keep_Their_Camel_Case()
|
||||
{
|
||||
// Field names elsewhere in the body are camelCase and supplied verbatim; lowercasing them would
|
||||
// silently drop settings the API would no longer recognise.
|
||||
Hashtable source = new Hashtable { { "ttlDays", 90 }, { "keyAlgorithm", "RSA_2048" }, { "isCA", "denied" }, { "maxPathLength", 0 } };
|
||||
Dictionary<string, object> result = ToJsonObject(source);
|
||||
|
||||
Assert.True(result.ContainsKey("ttlDays"));
|
||||
Assert.True(result.ContainsKey("keyAlgorithm"));
|
||||
Assert.True(result.ContainsKey("isCA"));
|
||||
Assert.True(result.ContainsKey("maxPathLength"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void An_Empty_Collection_Is_Dropped_Rather_Than_Sent_As_Allow_Nothing()
|
||||
{
|
||||
Hashtable source = new Hashtable { { "Allowed", new string[0] }, { "Required", new[] { "server_auth" } } };
|
||||
Dictionary<string, object> result = ToJsonObject(source);
|
||||
|
||||
Assert.False(result.ContainsKey("allowed"));
|
||||
Assert.True(result.ContainsKey("required"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_Wholly_Empty_Constraint_Becomes_Null_So_It_Is_Omitted()
|
||||
{
|
||||
Assert.Null(ToJsonObject(new Hashtable()));
|
||||
Assert.Null(ToJsonObject(new Hashtable { { "Allowed", new string[0] } }));
|
||||
Assert.Null(ToJsonObject(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_Constraint_List_Drops_Entries_That_Constrain_Nothing()
|
||||
{
|
||||
// An entry carrying only "type" is rejected by Infisical's refinement, and is what a caller writes
|
||||
// when they meant to leave that dimension alone.
|
||||
List<Hashtable> source = new List<Hashtable>
|
||||
{
|
||||
new Hashtable { { "Type", "dns_name" }, { "Allowed", new[] { "*.contoso.com" } } },
|
||||
new Hashtable { { "Type", "ip_address" }, { "Allowed", new string[0] } }
|
||||
};
|
||||
|
||||
List<Dictionary<string, object>> result = ToJsonObjectList(source);
|
||||
|
||||
Dictionary<string, object> only = Assert.Single(result);
|
||||
Assert.Equal("dns_name", only["type"]);
|
||||
Assert.True(only.ContainsKey("allowed"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Nested_Dictionaries_Survive_Conversion()
|
||||
{
|
||||
Hashtable source = new Hashtable
|
||||
{
|
||||
{ "outer", new Hashtable { { "inner", new Hashtable { { "Required", new[] { "a" } } } } } }
|
||||
};
|
||||
|
||||
Dictionary<string, object> result = ToJsonObject(source);
|
||||
string json = JsonConvert.SerializeObject(result);
|
||||
|
||||
Assert.Contains("\"outer\"", json);
|
||||
Assert.Contains("\"inner\"", json);
|
||||
Assert.Contains("\"required\"", json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Profile_Enrollment_Config_Is_Routed_To_The_Block_For_Its_Type()
|
||||
{
|
||||
Type cmdletType = ModuleAssembly.GetType("PSInfisicalAPI.Cmdlets.NewInfisicalCertificateProfileCmdlet", true);
|
||||
MethodInfo build = cmdletType.GetMethod("BuildRequest", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
Assert.NotNull(build);
|
||||
|
||||
Hashtable config = new Hashtable { { "passphrase", "secret" } };
|
||||
|
||||
object estRequest = build.Invoke(null, new object[] { "slug", null, "ca", "policy", "est", "ca", false, null, null, config, false });
|
||||
Assert.NotNull(estRequest.GetType().GetProperty("EstConfig").GetValue(estRequest));
|
||||
Assert.Null(estRequest.GetType().GetProperty("ApiConfig").GetValue(estRequest));
|
||||
|
||||
object scepRequest = build.Invoke(null, new object[] { "slug", null, "ca", "policy", "scep", "ca", false, null, null, config, false });
|
||||
Assert.NotNull(scepRequest.GetType().GetProperty("ScepConfig").GetValue(scepRequest));
|
||||
|
||||
object apiRequest = build.Invoke(null, new object[] { "slug", null, "ca", "policy", "api", "ca", true, 14, null, null, true });
|
||||
object apiConfig = apiRequest.GetType().GetProperty("ApiConfig").GetValue(apiRequest);
|
||||
Assert.NotNull(apiConfig);
|
||||
|
||||
Dictionary<string, object> typed = (Dictionary<string, object>)apiConfig;
|
||||
Assert.Equal(true, typed["autoRenew"]);
|
||||
Assert.Equal(14, typed["renewBeforeDays"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Auto_Renew_Is_Only_Sent_When_The_Caller_Asked_For_It()
|
||||
{
|
||||
// A switch parameter is always false when absent, so sending it unconditionally would silently
|
||||
// disable renewal on an update that never mentioned it.
|
||||
Type cmdletType = ModuleAssembly.GetType("PSInfisicalAPI.Cmdlets.NewInfisicalCertificateProfileCmdlet", true);
|
||||
MethodInfo build = cmdletType.GetMethod("BuildRequest", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
|
||||
object unbound = build.Invoke(null, new object[] { "slug", null, "ca", "policy", "api", "ca", false, null, null, null, false });
|
||||
Assert.Null(unbound.GetType().GetProperty("ApiConfig").GetValue(unbound));
|
||||
|
||||
object bound = build.Invoke(null, new object[] { "slug", null, "ca", "policy", "api", "ca", false, null, null, null, true });
|
||||
Dictionary<string, object> config = (Dictionary<string, object>)bound.GetType().GetProperty("ApiConfig").GetValue(bound);
|
||||
Assert.NotNull(config);
|
||||
Assert.Equal(false, config["autoRenew"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Every_Management_Endpoint_Is_Registered()
|
||||
{
|
||||
string[] names = new[]
|
||||
{
|
||||
PSInfisicalAPI.Endpoints.InfisicalEndpointNames.CreateInternalCertificateAuthority,
|
||||
PSInfisicalAPI.Endpoints.InfisicalEndpointNames.UpdateInternalCertificateAuthority,
|
||||
PSInfisicalAPI.Endpoints.InfisicalEndpointNames.DeleteInternalCertificateAuthority,
|
||||
PSInfisicalAPI.Endpoints.InfisicalEndpointNames.GetCertificateAuthorityCsr,
|
||||
PSInfisicalAPI.Endpoints.InfisicalEndpointNames.SignIntermediateCertificateAuthority,
|
||||
PSInfisicalAPI.Endpoints.InfisicalEndpointNames.ImportCertificateAuthorityCertificate,
|
||||
PSInfisicalAPI.Endpoints.InfisicalEndpointNames.CreateCertificatePolicy,
|
||||
PSInfisicalAPI.Endpoints.InfisicalEndpointNames.UpdateCertificatePolicy,
|
||||
PSInfisicalAPI.Endpoints.InfisicalEndpointNames.DeleteCertificatePolicy,
|
||||
PSInfisicalAPI.Endpoints.InfisicalEndpointNames.CreateCertificateProfile,
|
||||
PSInfisicalAPI.Endpoints.InfisicalEndpointNames.UpdateCertificateProfile,
|
||||
PSInfisicalAPI.Endpoints.InfisicalEndpointNames.DeleteCertificateProfile,
|
||||
PSInfisicalAPI.Endpoints.InfisicalEndpointNames.CreateCertificateApplication,
|
||||
PSInfisicalAPI.Endpoints.InfisicalEndpointNames.UpdateCertificateApplication,
|
||||
PSInfisicalAPI.Endpoints.InfisicalEndpointNames.DeleteCertificateApplication,
|
||||
PSInfisicalAPI.Endpoints.InfisicalEndpointNames.AddCertificateApplicationProfiles,
|
||||
PSInfisicalAPI.Endpoints.InfisicalEndpointNames.RemoveCertificateApplicationProfile,
|
||||
PSInfisicalAPI.Endpoints.InfisicalEndpointNames.CreatePkiSubscriber,
|
||||
PSInfisicalAPI.Endpoints.InfisicalEndpointNames.UpdatePkiSubscriber,
|
||||
PSInfisicalAPI.Endpoints.InfisicalEndpointNames.DeletePkiSubscriber
|
||||
};
|
||||
|
||||
foreach (string name in names)
|
||||
{
|
||||
IReadOnlyList<PSInfisicalAPI.Endpoints.InfisicalEndpointDefinition> candidates =
|
||||
PSInfisicalAPI.Endpoints.InfisicalEndpointRegistry.GetCandidates(name);
|
||||
|
||||
Assert.True(candidates.Count > 0, string.Concat(name, " is not registered"));
|
||||
Assert.All(candidates, c => Assert.True(c.RequiresAuthorization, string.Concat(name, " should require authorization")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Pki;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a certificate application: the grouping the Infisical console presents profiles, members, and
|
||||
/// certificates under.
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.New, "InfisicalCertificateApplication", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(InfisicalCertificateApplication))]
|
||||
public sealed class NewInfisicalCertificateApplicationCmdlet : InfisicalPkiWriteCmdletBase
|
||||
{
|
||||
private const string Component = "NewInfisicalCertificateApplicationCmdlet";
|
||||
|
||||
[Parameter(Mandatory = true, Position = 0)] public string Name { get; set; }
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public string Description { get; set; }
|
||||
|
||||
/// <summary>Certificate profiles to attach on creation.</summary>
|
||||
[Parameter] public string[] ProfileId { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
ProjectId = ResolveCertManagerProjectId(connection, ProjectId);
|
||||
if (string.IsNullOrEmpty(ProjectId)) { return; }
|
||||
|
||||
if (!ShouldProcess(Name, "Create certificate application")) { return; }
|
||||
|
||||
InfisicalCertificateApplicationWriteRequestDto request = new InfisicalCertificateApplicationWriteRequestDto
|
||||
{
|
||||
Name = Name,
|
||||
Description = Description,
|
||||
ProfileIds = ToStringList(ProfileId)
|
||||
};
|
||||
|
||||
InfisicalPkiManagementClient client = new InfisicalPkiManagementClient(HttpClient, Logger);
|
||||
WriteObject(client.WriteCertificateApplication(connection, ProjectId, null, request));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
WriteErrorForException(Component, "CreateCertificateApplication", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renames a certificate application or changes its description, and attaches or detaches profiles.
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "InfisicalCertificateApplication", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(InfisicalCertificateApplication))]
|
||||
public sealed class SetInfisicalCertificateApplicationCmdlet : InfisicalPkiWriteCmdletBase
|
||||
{
|
||||
private const string Component = "SetInfisicalCertificateApplicationCmdlet";
|
||||
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
|
||||
[Alias("Id")]
|
||||
public string ApplicationId { get; set; }
|
||||
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public string Name { get; set; }
|
||||
[Parameter] public string Description { get; set; }
|
||||
|
||||
/// <summary>Certificate profiles to attach. Profiles already attached are left alone.</summary>
|
||||
[Parameter] public string[] AddProfileId { get; set; }
|
||||
|
||||
/// <summary>Certificate profiles to detach.</summary>
|
||||
[Parameter] public string[] RemoveProfileId { get; set; }
|
||||
|
||||
[Parameter] public SwitchParameter PassThru { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
ProjectId = ResolveCertManagerProjectId(connection, ProjectId);
|
||||
if (string.IsNullOrEmpty(ProjectId)) { return; }
|
||||
|
||||
if (!ShouldProcess(ApplicationId, "Update certificate application")) { return; }
|
||||
|
||||
InfisicalPkiManagementClient client = new InfisicalPkiManagementClient(HttpClient, Logger);
|
||||
InfisicalCertificateApplication updated = null;
|
||||
|
||||
// The application record and its profile attachments are separate endpoints, so the name and
|
||||
// description update is skipped entirely when only profiles were supplied.
|
||||
if (!string.IsNullOrEmpty(Name) || !string.IsNullOrEmpty(Description))
|
||||
{
|
||||
InfisicalCertificateApplicationWriteRequestDto request = new InfisicalCertificateApplicationWriteRequestDto
|
||||
{
|
||||
Name = Name,
|
||||
Description = Description
|
||||
};
|
||||
|
||||
updated = client.WriteCertificateApplication(connection, ProjectId, ApplicationId, request);
|
||||
}
|
||||
|
||||
if (ToStringList(AddProfileId) != null)
|
||||
{
|
||||
client.AddCertificateApplicationProfiles(connection, ProjectId, ApplicationId, ToStringList(AddProfileId));
|
||||
}
|
||||
|
||||
foreach (string profileId in ToStringList(RemoveProfileId) ?? new System.Collections.Generic.List<string>())
|
||||
{
|
||||
client.RemoveCertificateApplicationProfile(connection, ProjectId, ApplicationId, profileId);
|
||||
}
|
||||
|
||||
if (PassThru.IsPresent && updated != null) { WriteObject(updated); }
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
WriteErrorForException(Component, "UpdateCertificateApplication", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a certificate application.
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Remove, "InfisicalCertificateApplication", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
|
||||
public sealed class RemoveInfisicalCertificateApplicationCmdlet : InfisicalPkiWriteCmdletBase
|
||||
{
|
||||
private const string Component = "RemoveInfisicalCertificateApplicationCmdlet";
|
||||
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
|
||||
[Alias("Id")]
|
||||
public string ApplicationId { get; set; }
|
||||
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public SwitchParameter PassThru { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
ProjectId = ResolveCertManagerProjectId(connection, ProjectId);
|
||||
if (string.IsNullOrEmpty(ProjectId)) { return; }
|
||||
|
||||
if (!ShouldProcess(ApplicationId, "Delete certificate application")) { return; }
|
||||
|
||||
InfisicalPkiManagementClient client = new InfisicalPkiManagementClient(HttpClient, Logger);
|
||||
client.DeleteCertificateApplication(connection, ProjectId, ApplicationId);
|
||||
|
||||
if (PassThru.IsPresent) { WriteObject(ApplicationId); }
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
WriteErrorForException(Component, "DeleteCertificateApplication", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Pki;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an internal certificate authority, signing a subordinate with its parent so it comes back ready
|
||||
/// to issue.
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.New, "InfisicalCertificateAuthority", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(InfisicalCertificateAuthority))]
|
||||
public sealed class NewInfisicalCertificateAuthorityCmdlet : InfisicalPkiWriteCmdletBase
|
||||
{
|
||||
private const string Component = "NewInfisicalCertificateAuthorityCmdlet";
|
||||
|
||||
[Parameter(Mandatory = true, Position = 0)] public string Name { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true)]
|
||||
[ValidateSet("Root", "Intermediate")]
|
||||
public string Type { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true)] public string CommonName { get; set; }
|
||||
|
||||
/// <summary>Required for -Type Intermediate: the authority that signs this one.</summary>
|
||||
[Parameter] public string ParentCaId { get; set; }
|
||||
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public string Organization { get; set; }
|
||||
[Parameter] public string OrganizationalUnit { get; set; }
|
||||
[Parameter] public string Country { get; set; }
|
||||
[Parameter] public string State { get; set; }
|
||||
[Parameter] public string Locality { get; set; }
|
||||
[Parameter] public string FriendlyName { get; set; }
|
||||
|
||||
[Parameter]
|
||||
[ValidateSet("RSA_2048", "RSA_3072", "RSA_4096", "EC_prime256v1", "EC_secp384r1", "EC_secp521r1")]
|
||||
public string KeyAlgorithm { get; set; } = "RSA_2048";
|
||||
|
||||
/// <summary>Expiry. Defaults to ten years for a root and five for a subordinate.</summary>
|
||||
[Parameter] public DateTimeOffset? NotAfter { get; set; }
|
||||
|
||||
[Parameter] public DateTimeOffset? NotBefore { get; set; }
|
||||
|
||||
/// <summary>Subordinate authorities permitted beneath this one. Defaults to 1 for a root, 0 otherwise.</summary>
|
||||
[Parameter] public int? MaxPathLength { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
ProjectId = ResolveCertManagerProjectId(connection, ProjectId);
|
||||
if (string.IsNullOrEmpty(ProjectId)) { return; }
|
||||
|
||||
bool isRoot = string.Equals(Type, "Root", StringComparison.OrdinalIgnoreCase);
|
||||
if (!isRoot && string.IsNullOrEmpty(ParentCaId))
|
||||
{
|
||||
throw new PSInfisicalAPI.Errors.InfisicalConfigurationException(
|
||||
"-ParentCaId is required for an intermediate certificate authority; it names the authority that signs this one.");
|
||||
}
|
||||
|
||||
if (!ShouldProcess(Name, string.Concat("Create ", Type.ToLowerInvariant(), " certificate authority"))) { return; }
|
||||
|
||||
DateTimeOffset expiry = NotAfter ?? DateTimeOffset.UtcNow.AddYears(isRoot ? 10 : 5);
|
||||
int pathLength = MaxPathLength ?? (isRoot ? 1 : 0);
|
||||
|
||||
InfisicalPkiManagementClient client = new InfisicalPkiManagementClient(HttpClient, Logger);
|
||||
|
||||
InfisicalCertificateAuthority created = client.CreateInternalCertificateAuthority(
|
||||
connection, ProjectId, Name, isRoot ? "root" : "intermediate", CommonName,
|
||||
Organization, OrganizationalUnit, Country, State, Locality, KeyAlgorithm, FriendlyName,
|
||||
ToApiTimestamp(NotBefore), ToApiTimestamp(expiry), pathLength);
|
||||
|
||||
if (created == null)
|
||||
{
|
||||
throw new PSInfisicalAPI.Errors.InfisicalApiException("Creating the certificate authority returned no record.");
|
||||
}
|
||||
|
||||
if (!isRoot)
|
||||
{
|
||||
// Infisical creates a subordinate pending a certificate; without this it exists but cannot
|
||||
// sign anything.
|
||||
Logger.Information(Component, string.Concat("Signing '", Name, "' with parent certificate authority '", ParentCaId, "'."));
|
||||
client.CompleteSubordinateCertificateAuthority(connection, ProjectId, created.Id, ParentCaId, ToApiTimestamp(expiry), pathLength);
|
||||
|
||||
InfisicalPkiClient readClient = new InfisicalPkiClient(HttpClient, Logger);
|
||||
InfisicalCertificateAuthority refreshed = readClient.GetInternalCertificateAuthority(connection, created.Id, ProjectId);
|
||||
if (refreshed != null) { created = refreshed; }
|
||||
}
|
||||
|
||||
WriteObject(created);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
WriteErrorForException(Component, "CreateCertificateAuthority", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renames an internal certificate authority or changes its status.
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "InfisicalCertificateAuthority", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(InfisicalCertificateAuthority))]
|
||||
public sealed class SetInfisicalCertificateAuthorityCmdlet : InfisicalPkiWriteCmdletBase
|
||||
{
|
||||
private const string Component = "SetInfisicalCertificateAuthorityCmdlet";
|
||||
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
|
||||
[Alias("Id")]
|
||||
public string CaId { get; set; }
|
||||
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public string Name { get; set; }
|
||||
|
||||
[Parameter]
|
||||
[ValidateSet("active", "disabled")]
|
||||
public string Status { get; set; }
|
||||
|
||||
[Parameter] public SwitchParameter PassThru { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
ProjectId = ResolveCertManagerProjectId(connection, ProjectId);
|
||||
if (string.IsNullOrEmpty(ProjectId)) { return; }
|
||||
|
||||
if (!ShouldProcess(CaId, "Update certificate authority")) { return; }
|
||||
|
||||
InfisicalPkiManagementClient client = new InfisicalPkiManagementClient(HttpClient, Logger);
|
||||
InfisicalCertificateAuthority updated = client.UpdateInternalCertificateAuthority(connection, ProjectId, CaId, Name, Status);
|
||||
|
||||
if (PassThru.IsPresent) { WriteObject(updated); }
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
WriteErrorForException(Component, "UpdateCertificateAuthority", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes an internal certificate authority.
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Remove, "InfisicalCertificateAuthority", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
|
||||
public sealed class RemoveInfisicalCertificateAuthorityCmdlet : InfisicalPkiWriteCmdletBase
|
||||
{
|
||||
private const string Component = "RemoveInfisicalCertificateAuthorityCmdlet";
|
||||
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
|
||||
[Alias("Id")]
|
||||
public string CaId { get; set; }
|
||||
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public SwitchParameter PassThru { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
ProjectId = ResolveCertManagerProjectId(connection, ProjectId);
|
||||
if (string.IsNullOrEmpty(ProjectId)) { return; }
|
||||
|
||||
if (!ShouldProcess(CaId, "Delete certificate authority")) { return; }
|
||||
|
||||
InfisicalPkiManagementClient client = new InfisicalPkiManagementClient(HttpClient, Logger);
|
||||
client.DeleteInternalCertificateAuthority(connection, ProjectId, CaId);
|
||||
|
||||
if (PassThru.IsPresent) { WriteObject(CaId); }
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
WriteErrorForException(Component, "DeleteCertificateAuthority", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Pki;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a certificate policy: the constraints a profile issues within.
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.New, "InfisicalCertificatePolicy", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(InfisicalCertificatePolicy))]
|
||||
public sealed class NewInfisicalCertificatePolicyCmdlet : InfisicalPkiWriteCmdletBase
|
||||
{
|
||||
private const string Component = "NewInfisicalCertificatePolicyCmdlet";
|
||||
|
||||
[Parameter(Mandatory = true, Position = 0)] public string Name { get; set; }
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public string Description { get; set; }
|
||||
|
||||
/// <summary>Maximum certificate lifetime, for example '90d', '12m', or '1y'.</summary>
|
||||
[Parameter] public string MaxValidity { get; set; }
|
||||
|
||||
/// <summary>Permitted key algorithms, for example RSA_2048 or EC_secp384r1.</summary>
|
||||
[Parameter] public string[] KeyAlgorithm { get; set; }
|
||||
|
||||
/// <summary>Permitted signature algorithms.</summary>
|
||||
[Parameter] public string[] SignatureAlgorithm { get; set; }
|
||||
|
||||
/// <summary>Key usage constraint, as @{ Required = @('digital_signature'); Denied = @(...) }.</summary>
|
||||
[Parameter] public IDictionary KeyUsage { get; set; }
|
||||
|
||||
/// <summary>Extended key usage constraint, as @{ Required = @('server_auth','client_auth') }.</summary>
|
||||
[Parameter] public IDictionary ExtendedKeyUsage { get; set; }
|
||||
|
||||
/// <summary>Subject attribute constraints, as @( @{ Type = 'organization'; Allowed = @('Contoso') } ).</summary>
|
||||
[Parameter] public IDictionary[] Subject { get; set; }
|
||||
|
||||
/// <summary>Subject alternative name constraints, as @( @{ Type = 'dns_name'; Allowed = @('*.contoso.com') } ).</summary>
|
||||
[Parameter] public IDictionary[] SubjectAlternativeName { get; set; }
|
||||
|
||||
/// <summary>Basic constraints, as @{ isCA = 'denied'; maxPathLength = 0 }.</summary>
|
||||
[Parameter] public IDictionary BasicConstraints { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
ProjectId = ResolveCertManagerProjectId(connection, ProjectId);
|
||||
if (string.IsNullOrEmpty(ProjectId)) { return; }
|
||||
|
||||
if (!ShouldProcess(Name, "Create certificate policy")) { return; }
|
||||
|
||||
InfisicalCertificatePolicyWriteRequestDto request = new InfisicalCertificatePolicyWriteRequestDto
|
||||
{
|
||||
Name = Name,
|
||||
Description = Description,
|
||||
Subject = ToJsonObjectList(Subject),
|
||||
Sans = ToJsonObjectList(SubjectAlternativeName),
|
||||
KeyUsages = ToJsonObject(KeyUsage),
|
||||
ExtendedKeyUsages = ToJsonObject(ExtendedKeyUsage),
|
||||
BasicConstraints = ToJsonObject(BasicConstraints)
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(MaxValidity))
|
||||
{
|
||||
request.Validity = new System.Collections.Generic.Dictionary<string, object> { { "max", MaxValidity } };
|
||||
}
|
||||
|
||||
System.Collections.Generic.Dictionary<string, object> algorithms = new System.Collections.Generic.Dictionary<string, object>();
|
||||
if (ToStringList(KeyAlgorithm) != null) { algorithms["keyAlgorithm"] = ToStringList(KeyAlgorithm); }
|
||||
if (ToStringList(SignatureAlgorithm) != null) { algorithms["signature"] = ToStringList(SignatureAlgorithm); }
|
||||
if (algorithms.Count > 0) { request.Algorithms = algorithms; }
|
||||
|
||||
InfisicalPkiManagementClient client = new InfisicalPkiManagementClient(HttpClient, Logger);
|
||||
WriteObject(client.WriteCertificatePolicy(connection, ProjectId, null, request));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
WriteErrorForException(Component, "CreateCertificatePolicy", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a certificate policy. Only the supplied constraints are sent; the rest are left as they are.
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "InfisicalCertificatePolicy", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(InfisicalCertificatePolicy))]
|
||||
public sealed class SetInfisicalCertificatePolicyCmdlet : InfisicalPkiWriteCmdletBase
|
||||
{
|
||||
private const string Component = "SetInfisicalCertificatePolicyCmdlet";
|
||||
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
|
||||
[Alias("Id")]
|
||||
public string PolicyId { get; set; }
|
||||
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public string Name { get; set; }
|
||||
[Parameter] public string Description { get; set; }
|
||||
[Parameter] public string MaxValidity { get; set; }
|
||||
[Parameter] public string[] KeyAlgorithm { get; set; }
|
||||
[Parameter] public string[] SignatureAlgorithm { get; set; }
|
||||
[Parameter] public IDictionary KeyUsage { get; set; }
|
||||
[Parameter] public IDictionary ExtendedKeyUsage { get; set; }
|
||||
[Parameter] public IDictionary[] Subject { get; set; }
|
||||
[Parameter] public IDictionary[] SubjectAlternativeName { get; set; }
|
||||
[Parameter] public IDictionary BasicConstraints { get; set; }
|
||||
[Parameter] public SwitchParameter PassThru { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
ProjectId = ResolveCertManagerProjectId(connection, ProjectId);
|
||||
if (string.IsNullOrEmpty(ProjectId)) { return; }
|
||||
|
||||
if (!ShouldProcess(PolicyId, "Update certificate policy")) { return; }
|
||||
|
||||
InfisicalCertificatePolicyWriteRequestDto request = new InfisicalCertificatePolicyWriteRequestDto
|
||||
{
|
||||
Name = Name,
|
||||
Description = Description,
|
||||
Subject = ToJsonObjectList(Subject),
|
||||
Sans = ToJsonObjectList(SubjectAlternativeName),
|
||||
KeyUsages = ToJsonObject(KeyUsage),
|
||||
ExtendedKeyUsages = ToJsonObject(ExtendedKeyUsage),
|
||||
BasicConstraints = ToJsonObject(BasicConstraints)
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(MaxValidity))
|
||||
{
|
||||
request.Validity = new System.Collections.Generic.Dictionary<string, object> { { "max", MaxValidity } };
|
||||
}
|
||||
|
||||
System.Collections.Generic.Dictionary<string, object> algorithms = new System.Collections.Generic.Dictionary<string, object>();
|
||||
if (ToStringList(KeyAlgorithm) != null) { algorithms["keyAlgorithm"] = ToStringList(KeyAlgorithm); }
|
||||
if (ToStringList(SignatureAlgorithm) != null) { algorithms["signature"] = ToStringList(SignatureAlgorithm); }
|
||||
if (algorithms.Count > 0) { request.Algorithms = algorithms; }
|
||||
|
||||
InfisicalPkiManagementClient client = new InfisicalPkiManagementClient(HttpClient, Logger);
|
||||
InfisicalCertificatePolicy updated = client.WriteCertificatePolicy(connection, ProjectId, PolicyId, request);
|
||||
|
||||
if (PassThru.IsPresent) { WriteObject(updated); }
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
WriteErrorForException(Component, "UpdateCertificatePolicy", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a certificate policy.
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Remove, "InfisicalCertificatePolicy", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
|
||||
public sealed class RemoveInfisicalCertificatePolicyCmdlet : InfisicalPkiWriteCmdletBase
|
||||
{
|
||||
private const string Component = "RemoveInfisicalCertificatePolicyCmdlet";
|
||||
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
|
||||
[Alias("Id")]
|
||||
public string PolicyId { get; set; }
|
||||
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public SwitchParameter PassThru { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
ProjectId = ResolveCertManagerProjectId(connection, ProjectId);
|
||||
if (string.IsNullOrEmpty(ProjectId)) { return; }
|
||||
|
||||
if (!ShouldProcess(PolicyId, "Delete certificate policy")) { return; }
|
||||
|
||||
InfisicalPkiManagementClient client = new InfisicalPkiManagementClient(HttpClient, Logger);
|
||||
client.DeleteCertificatePolicy(connection, ProjectId, PolicyId);
|
||||
|
||||
if (PassThru.IsPresent) { WriteObject(PolicyId); }
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
WriteErrorForException(Component, "DeleteCertificatePolicy", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Pki;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a certificate profile, binding an issuing certificate authority to a certificate policy and
|
||||
/// exposing it for enrollment.
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.New, "InfisicalCertificateProfile", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(InfisicalCertificateProfile))]
|
||||
public sealed class NewInfisicalCertificateProfileCmdlet : InfisicalPkiWriteCmdletBase
|
||||
{
|
||||
private const string Component = "NewInfisicalCertificateProfileCmdlet";
|
||||
|
||||
/// <summary>Lowercase letters, numbers, and hyphens only.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0)] public string Slug { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true)] public string CertificatePolicyId { get; set; }
|
||||
[Parameter] public string CaId { get; set; }
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public string Description { get; set; }
|
||||
|
||||
[Parameter]
|
||||
[ValidateSet("api", "est", "acme", "scep")]
|
||||
public string EnrollmentType { get; set; } = "api";
|
||||
|
||||
[Parameter]
|
||||
[ValidateSet("ca", "self-signed")]
|
||||
public string IssuerType { get; set; } = "ca";
|
||||
|
||||
/// <summary>Renew issued certificates automatically.</summary>
|
||||
[Parameter] public SwitchParameter AutoRenew { get; set; }
|
||||
|
||||
/// <summary>Days before expiry at which automatic renewal runs, 1 to 30.</summary>
|
||||
[Parameter] public int? RenewBeforeDays { get; set; }
|
||||
|
||||
/// <summary>Issuance defaults, as @{ ttlDays = 90; keyAlgorithm = 'RSA_2048' }.</summary>
|
||||
[Parameter] public IDictionary Defaults { get; set; }
|
||||
|
||||
/// <summary>Enrollment configuration for -EnrollmentType est, acme, or scep.</summary>
|
||||
[Parameter] public IDictionary EnrollmentConfig { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
ProjectId = ResolveCertManagerProjectId(connection, ProjectId);
|
||||
if (string.IsNullOrEmpty(ProjectId)) { return; }
|
||||
|
||||
if (!ShouldProcess(Slug, "Create certificate profile")) { return; }
|
||||
|
||||
InfisicalCertificateProfileWriteRequestDto request = BuildRequest(
|
||||
Slug, Description, CaId, CertificatePolicyId, EnrollmentType, IssuerType,
|
||||
AutoRenew.IsPresent, RenewBeforeDays, Defaults, EnrollmentConfig,
|
||||
MyInvocation.BoundParameters.ContainsKey("AutoRenew"));
|
||||
|
||||
InfisicalPkiManagementClient client = new InfisicalPkiManagementClient(HttpClient, Logger);
|
||||
WriteObject(client.WriteCertificateProfile(connection, ProjectId, null, request));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
WriteErrorForException(Component, "CreateCertificateProfile", exception);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Routes the enrollment configuration to the block matching the enrollment type, so callers supply one
|
||||
/// dictionary rather than choosing between four mutually exclusive parameters.
|
||||
/// </summary>
|
||||
internal static InfisicalCertificateProfileWriteRequestDto BuildRequest(
|
||||
string slug, string description, string caId, string certificatePolicyId,
|
||||
string enrollmentType, string issuerType, bool autoRenew, int? renewBeforeDays,
|
||||
IDictionary defaults, IDictionary enrollmentConfig, bool autoRenewBound)
|
||||
{
|
||||
InfisicalCertificateProfileWriteRequestDto request = new InfisicalCertificateProfileWriteRequestDto
|
||||
{
|
||||
Slug = slug,
|
||||
Description = description,
|
||||
CaId = caId,
|
||||
CertificatePolicyId = certificatePolicyId,
|
||||
EnrollmentType = enrollmentType,
|
||||
IssuerType = issuerType,
|
||||
Defaults = ToJsonObject(defaults)
|
||||
};
|
||||
|
||||
System.Collections.Generic.Dictionary<string, object> config = ToJsonObject(enrollmentConfig);
|
||||
|
||||
if (string.Equals(enrollmentType, "est", StringComparison.OrdinalIgnoreCase)) { request.EstConfig = config; }
|
||||
else if (string.Equals(enrollmentType, "acme", StringComparison.OrdinalIgnoreCase)) { request.AcmeConfig = config; }
|
||||
else if (string.Equals(enrollmentType, "scep", StringComparison.OrdinalIgnoreCase)) { request.ScepConfig = config; }
|
||||
else
|
||||
{
|
||||
if (config == null && (autoRenewBound || renewBeforeDays.HasValue))
|
||||
{
|
||||
config = new System.Collections.Generic.Dictionary<string, object>();
|
||||
}
|
||||
|
||||
if (config != null)
|
||||
{
|
||||
if (autoRenewBound) { config["autoRenew"] = autoRenew; }
|
||||
if (renewBeforeDays.HasValue) { config["renewBeforeDays"] = renewBeforeDays.Value; }
|
||||
}
|
||||
|
||||
request.ApiConfig = config;
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a certificate profile. Only the supplied values are sent.
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "InfisicalCertificateProfile", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(InfisicalCertificateProfile))]
|
||||
public sealed class SetInfisicalCertificateProfileCmdlet : InfisicalPkiWriteCmdletBase
|
||||
{
|
||||
private const string Component = "SetInfisicalCertificateProfileCmdlet";
|
||||
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
|
||||
[Alias("Id", "CertificateProfileId")]
|
||||
public string ProfileId { get; set; }
|
||||
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public string Slug { get; set; }
|
||||
[Parameter] public string Description { get; set; }
|
||||
[Parameter] public string CaId { get; set; }
|
||||
[Parameter] public string CertificatePolicyId { get; set; }
|
||||
|
||||
[Parameter]
|
||||
[ValidateSet("api", "est", "acme", "scep")]
|
||||
public string EnrollmentType { get; set; }
|
||||
|
||||
[Parameter] public SwitchParameter AutoRenew { get; set; }
|
||||
[Parameter] public int? RenewBeforeDays { get; set; }
|
||||
[Parameter] public IDictionary Defaults { get; set; }
|
||||
[Parameter] public IDictionary EnrollmentConfig { get; set; }
|
||||
[Parameter] public SwitchParameter PassThru { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
ProjectId = ResolveCertManagerProjectId(connection, ProjectId);
|
||||
if (string.IsNullOrEmpty(ProjectId)) { return; }
|
||||
|
||||
if (!ShouldProcess(ProfileId, "Update certificate profile")) { return; }
|
||||
|
||||
InfisicalCertificateProfileWriteRequestDto request = NewInfisicalCertificateProfileCmdlet.BuildRequest(
|
||||
Slug, Description, CaId, CertificatePolicyId,
|
||||
string.IsNullOrEmpty(EnrollmentType) ? "api" : EnrollmentType,
|
||||
null, AutoRenew.IsPresent, RenewBeforeDays, Defaults, EnrollmentConfig,
|
||||
MyInvocation.BoundParameters.ContainsKey("AutoRenew"));
|
||||
|
||||
// Only sent when the caller asked for it; the API keeps the stored value otherwise.
|
||||
if (!MyInvocation.BoundParameters.ContainsKey("EnrollmentType")) { request.EnrollmentType = null; }
|
||||
|
||||
InfisicalPkiManagementClient client = new InfisicalPkiManagementClient(HttpClient, Logger);
|
||||
InfisicalCertificateProfile updated = client.WriteCertificateProfile(connection, ProjectId, ProfileId, request);
|
||||
|
||||
if (PassThru.IsPresent) { WriteObject(updated); }
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
WriteErrorForException(Component, "UpdateCertificateProfile", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a certificate profile.
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Remove, "InfisicalCertificateProfile", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
|
||||
public sealed class RemoveInfisicalCertificateProfileCmdlet : InfisicalPkiWriteCmdletBase
|
||||
{
|
||||
private const string Component = "RemoveInfisicalCertificateProfileCmdlet";
|
||||
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
|
||||
[Alias("Id", "CertificateProfileId")]
|
||||
public string ProfileId { get; set; }
|
||||
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public SwitchParameter PassThru { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
ProjectId = ResolveCertManagerProjectId(connection, ProjectId);
|
||||
if (string.IsNullOrEmpty(ProjectId)) { return; }
|
||||
|
||||
if (!ShouldProcess(ProfileId, "Delete certificate profile")) { return; }
|
||||
|
||||
InfisicalPkiManagementClient client = new InfisicalPkiManagementClient(HttpClient, Logger);
|
||||
client.DeleteCertificateProfile(connection, ProjectId, ProfileId);
|
||||
|
||||
if (PassThru.IsPresent) { WriteObject(ProfileId); }
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
WriteErrorForException(Component, "DeleteCertificateProfile", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Management.Automation;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
/// <summary>
|
||||
/// Shared plumbing for the cmdlets that create, change, or remove Certificate Manager configuration.
|
||||
/// <para>
|
||||
/// Infisical's policy and profile bodies are deeply nested — allowed/required/denied constraint objects,
|
||||
/// per-enrollment-type config blocks — and expressing every leaf as a parameter would produce cmdlets nobody
|
||||
/// could read. Those structures are accepted as dictionaries instead, matching how <c>-Subject</c> and
|
||||
/// <c>-Metadata</c> already work, and are converted here.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public abstract class InfisicalPkiWriteCmdletBase : InfisicalCmdletBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a caller's dictionary into the plain string-keyed form the serializer emits as a JSON
|
||||
/// object. Nested dictionaries and collections are converted too, so a hashtable of hashtables round
|
||||
/// trips into the nested body Infisical expects.
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// The constraint vocabulary Infisical expects in lower case. PowerShell callers naturally capitalise
|
||||
/// hashtable keys, so <c>@{ Required = ... }</c> has to reach the API as <c>"required"</c> or the request
|
||||
/// is rejected for a missing constraint. Every other key is passed through untouched, since the rest of
|
||||
/// the body uses camelCase field names the caller supplies verbatim.
|
||||
/// </summary>
|
||||
private static readonly Dictionary<string, string> CanonicalKeys = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "allowed", "allowed" },
|
||||
{ "required", "required" },
|
||||
{ "denied", "denied" },
|
||||
{ "type", "type" }
|
||||
};
|
||||
|
||||
internal static Dictionary<string, object> ToJsonObject(IDictionary source)
|
||||
{
|
||||
if (source == null) { return null; }
|
||||
|
||||
Dictionary<string, object> result = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
foreach (DictionaryEntry entry in source)
|
||||
{
|
||||
if (entry.Key == null) { continue; }
|
||||
string key = Convert.ToString(entry.Key, CultureInfo.InvariantCulture);
|
||||
if (string.IsNullOrWhiteSpace(key)) { continue; }
|
||||
|
||||
key = key.Trim();
|
||||
|
||||
string canonical;
|
||||
if (CanonicalKeys.TryGetValue(key, out canonical)) { key = canonical; }
|
||||
|
||||
object value = ToJsonValue(entry.Value);
|
||||
|
||||
// An empty list is dropped rather than sent. "allowed": [] does not read as "unconstrained" to
|
||||
// Infisical, it reads as "allow nothing", which is never what a caller writing @{ Allowed = @() }
|
||||
// intends; omitting the key leaves that dimension genuinely unconstrained.
|
||||
List<object> asList = value as List<object>;
|
||||
if (asList != null && asList.Count == 0) { continue; }
|
||||
|
||||
result[key] = value;
|
||||
}
|
||||
|
||||
return result.Count > 0 ? result : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a list of dictionaries, which is the shape Infisical uses for policy subject and SAN
|
||||
/// constraints.
|
||||
/// </summary>
|
||||
internal static List<Dictionary<string, object>> ToJsonObjectList(IEnumerable source)
|
||||
{
|
||||
if (source == null) { return null; }
|
||||
|
||||
List<Dictionary<string, object>> result = new List<Dictionary<string, object>>();
|
||||
foreach (object item in source)
|
||||
{
|
||||
IDictionary dictionary = UnwrapDictionary(item);
|
||||
if (dictionary == null) { continue; }
|
||||
|
||||
Dictionary<string, object> converted = ToJsonObject(dictionary);
|
||||
if (converted == null) { continue; }
|
||||
|
||||
// An entry carrying only "type" states a dimension without constraining it, which Infisical
|
||||
// rejects outright. Dropping it is what the caller meant by leaving the lists empty.
|
||||
bool hasConstraint = converted.ContainsKey("allowed") || converted.ContainsKey("required") || converted.ContainsKey("denied");
|
||||
if (!hasConstraint) { continue; }
|
||||
|
||||
result.Add(converted);
|
||||
}
|
||||
|
||||
return result.Count > 0 ? result : null;
|
||||
}
|
||||
|
||||
private static object ToJsonValue(object value)
|
||||
{
|
||||
if (value == null) { return null; }
|
||||
|
||||
IDictionary nested = UnwrapDictionary(value);
|
||||
if (nested != null) { return ToJsonObject(nested); }
|
||||
|
||||
if (value is string) { return value; }
|
||||
|
||||
IEnumerable enumerable = UnwrapEnumerable(value);
|
||||
if (enumerable != null)
|
||||
{
|
||||
List<object> items = new List<object>();
|
||||
foreach (object item in enumerable) { items.Add(ToJsonValue(item)); }
|
||||
return items;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PowerShell hands parameters over wrapped in PSObject often enough that unwrapping has to happen at
|
||||
/// every level, not only at the top.
|
||||
/// </summary>
|
||||
private static IDictionary UnwrapDictionary(object value)
|
||||
{
|
||||
if (value == null) { return null; }
|
||||
if (value is IDictionary direct) { return direct; }
|
||||
|
||||
PSObject wrapper = value as PSObject;
|
||||
if (wrapper != null && wrapper.BaseObject is IDictionary wrapped) { return wrapped; }
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static IEnumerable UnwrapEnumerable(object value)
|
||||
{
|
||||
if (value is IEnumerable direct) { return direct; }
|
||||
|
||||
PSObject wrapper = value as PSObject;
|
||||
if (wrapper != null && wrapper.BaseObject is IEnumerable wrapped) { return wrapped; }
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats an expiry the way Infisical's date validator accepts it.
|
||||
/// </summary>
|
||||
internal static string ToApiTimestamp(DateTimeOffset? value)
|
||||
{
|
||||
if (!value.HasValue) { return null; }
|
||||
return value.Value.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
internal static List<string> ToStringList(IEnumerable<string> values)
|
||||
{
|
||||
if (values == null) { return null; }
|
||||
|
||||
List<string> result = new List<string>();
|
||||
foreach (string value in values)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(value)) { result.Add(value.Trim()); }
|
||||
}
|
||||
|
||||
return result.Count > 0 ? result : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Pki;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a PKI subscriber: a named enrollment identity pinning one common name, its SAN allowlist, and its
|
||||
/// key usages.
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.New, "InfisicalPkiSubscriber", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(InfisicalPkiSubscriber))]
|
||||
public sealed class NewInfisicalPkiSubscriberCmdlet : InfisicalPkiWriteCmdletBase
|
||||
{
|
||||
private const string Component = "NewInfisicalPkiSubscriberCmdlet";
|
||||
|
||||
[Parameter(Mandatory = true, Position = 0)] public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The certificate signed for this subscriber must carry exactly this common name; Infisical rejects a
|
||||
/// request whose CSR names anything else.
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true)] public string CommonName { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true)] public string CaId { get; set; }
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public string Ttl { get; set; }
|
||||
|
||||
/// <summary>Allowlist of subject alternative names. A CSR naming anything outside it is rejected.</summary>
|
||||
[Parameter] public string[] SubjectAlternativeName { get; set; }
|
||||
|
||||
[Parameter] public string[] KeyUsage { get; set; }
|
||||
[Parameter] public string[] ExtendedKeyUsage { get; set; }
|
||||
|
||||
[Parameter]
|
||||
[ValidateSet("active", "disabled")]
|
||||
public string Status { get; set; } = "active";
|
||||
|
||||
[Parameter] public SwitchParameter EnableAutoRenewal { get; set; }
|
||||
[Parameter] public int? AutoRenewalPeriodInDays { get; set; }
|
||||
|
||||
/// <summary>Additional subject attributes, as @{ organization = 'Contoso'; country = 'US' }.</summary>
|
||||
[Parameter] public IDictionary Properties { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
ProjectId = ResolveCertManagerProjectId(connection, ProjectId);
|
||||
if (string.IsNullOrEmpty(ProjectId)) { return; }
|
||||
|
||||
if (!ShouldProcess(Name, "Create PKI subscriber")) { return; }
|
||||
|
||||
InfisicalPkiSubscriberWriteRequestDto request = new InfisicalPkiSubscriberWriteRequestDto
|
||||
{
|
||||
Name = Name,
|
||||
CommonName = CommonName,
|
||||
CaId = CaId,
|
||||
Status = Status,
|
||||
Ttl = Ttl,
|
||||
SubjectAlternativeNames = ToStringList(SubjectAlternativeName),
|
||||
KeyUsages = ToStringList(KeyUsage),
|
||||
ExtendedKeyUsages = ToStringList(ExtendedKeyUsage),
|
||||
Properties = ToJsonObject(Properties)
|
||||
};
|
||||
|
||||
if (MyInvocation.BoundParameters.ContainsKey("EnableAutoRenewal")) { request.EnableAutoRenewal = EnableAutoRenewal.IsPresent; }
|
||||
if (AutoRenewalPeriodInDays.HasValue) { request.AutoRenewalPeriodInDays = AutoRenewalPeriodInDays.Value; }
|
||||
|
||||
InfisicalPkiManagementClient client = new InfisicalPkiManagementClient(HttpClient, Logger);
|
||||
WriteObject(client.WritePkiSubscriber(connection, ProjectId, null, request));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
WriteErrorForException(Component, "CreatePkiSubscriber", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a PKI subscriber. Only the supplied values are sent.
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "InfisicalPkiSubscriber", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(InfisicalPkiSubscriber))]
|
||||
public sealed class SetInfisicalPkiSubscriberCmdlet : InfisicalPkiWriteCmdletBase
|
||||
{
|
||||
private const string Component = "SetInfisicalPkiSubscriberCmdlet";
|
||||
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
|
||||
[Alias("SubscriberName", "Slug")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public string NewName { get; set; }
|
||||
[Parameter] public string CommonName { get; set; }
|
||||
[Parameter] public string CaId { get; set; }
|
||||
[Parameter] public string Ttl { get; set; }
|
||||
[Parameter] public string[] SubjectAlternativeName { get; set; }
|
||||
[Parameter] public string[] KeyUsage { get; set; }
|
||||
[Parameter] public string[] ExtendedKeyUsage { get; set; }
|
||||
|
||||
[Parameter]
|
||||
[ValidateSet("active", "disabled")]
|
||||
public string Status { get; set; }
|
||||
|
||||
[Parameter] public SwitchParameter EnableAutoRenewal { get; set; }
|
||||
[Parameter] public int? AutoRenewalPeriodInDays { get; set; }
|
||||
[Parameter] public IDictionary Properties { get; set; }
|
||||
[Parameter] public SwitchParameter PassThru { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
ProjectId = ResolveCertManagerProjectId(connection, ProjectId);
|
||||
if (string.IsNullOrEmpty(ProjectId)) { return; }
|
||||
|
||||
if (!ShouldProcess(Name, "Update PKI subscriber")) { return; }
|
||||
|
||||
InfisicalPkiSubscriberWriteRequestDto request = new InfisicalPkiSubscriberWriteRequestDto
|
||||
{
|
||||
Name = NewName,
|
||||
CommonName = CommonName,
|
||||
CaId = CaId,
|
||||
Status = Status,
|
||||
Ttl = Ttl,
|
||||
SubjectAlternativeNames = ToStringList(SubjectAlternativeName),
|
||||
KeyUsages = ToStringList(KeyUsage),
|
||||
ExtendedKeyUsages = ToStringList(ExtendedKeyUsage),
|
||||
Properties = ToJsonObject(Properties)
|
||||
};
|
||||
|
||||
if (MyInvocation.BoundParameters.ContainsKey("EnableAutoRenewal")) { request.EnableAutoRenewal = EnableAutoRenewal.IsPresent; }
|
||||
if (AutoRenewalPeriodInDays.HasValue) { request.AutoRenewalPeriodInDays = AutoRenewalPeriodInDays.Value; }
|
||||
|
||||
InfisicalPkiManagementClient client = new InfisicalPkiManagementClient(HttpClient, Logger);
|
||||
InfisicalPkiSubscriber updated = client.WritePkiSubscriber(connection, ProjectId, Name, request);
|
||||
|
||||
if (PassThru.IsPresent) { WriteObject(updated); }
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
WriteErrorForException(Component, "UpdatePkiSubscriber", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a PKI subscriber.
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Remove, "InfisicalPkiSubscriber", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
|
||||
public sealed class RemoveInfisicalPkiSubscriberCmdlet : InfisicalPkiWriteCmdletBase
|
||||
{
|
||||
private const string Component = "RemoveInfisicalPkiSubscriberCmdlet";
|
||||
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
|
||||
[Alias("SubscriberName", "Slug")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public SwitchParameter PassThru { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
ProjectId = ResolveCertManagerProjectId(connection, ProjectId);
|
||||
if (string.IsNullOrEmpty(ProjectId)) { return; }
|
||||
|
||||
if (!ShouldProcess(Name, "Delete PKI subscriber")) { return; }
|
||||
|
||||
InfisicalPkiManagementClient client = new InfisicalPkiManagementClient(HttpClient, Logger);
|
||||
client.DeletePkiSubscriber(connection, ProjectId, Name);
|
||||
|
||||
if (PassThru.IsPresent) { WriteObject(Name); }
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
WriteErrorForException(Component, "DeletePkiSubscriber", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,31 @@ namespace PSInfisicalAPI.Endpoints
|
||||
public const string RetrieveCertificate = "RetrieveCertificate";
|
||||
public const string GetCertificateBundle = "GetCertificateBundle";
|
||||
public const string UpdateCertificateMetadata = "UpdateCertificateMetadata";
|
||||
|
||||
public const string CreateInternalCertificateAuthority = "CreateInternalCertificateAuthority";
|
||||
public const string UpdateInternalCertificateAuthority = "UpdateInternalCertificateAuthority";
|
||||
public const string DeleteInternalCertificateAuthority = "DeleteInternalCertificateAuthority";
|
||||
public const string GetCertificateAuthorityCsr = "GetCertificateAuthorityCsr";
|
||||
public const string SignIntermediateCertificateAuthority = "SignIntermediateCertificateAuthority";
|
||||
public const string ImportCertificateAuthorityCertificate = "ImportCertificateAuthorityCertificate";
|
||||
|
||||
public const string CreateCertificatePolicy = "CreateCertificatePolicy";
|
||||
public const string UpdateCertificatePolicy = "UpdateCertificatePolicy";
|
||||
public const string DeleteCertificatePolicy = "DeleteCertificatePolicy";
|
||||
|
||||
public const string CreateCertificateProfile = "CreateCertificateProfile";
|
||||
public const string UpdateCertificateProfile = "UpdateCertificateProfile";
|
||||
public const string DeleteCertificateProfile = "DeleteCertificateProfile";
|
||||
|
||||
public const string CreateCertificateApplication = "CreateCertificateApplication";
|
||||
public const string UpdateCertificateApplication = "UpdateCertificateApplication";
|
||||
public const string DeleteCertificateApplication = "DeleteCertificateApplication";
|
||||
public const string AddCertificateApplicationProfiles = "AddCertificateApplicationProfiles";
|
||||
public const string RemoveCertificateApplicationProfile = "RemoveCertificateApplicationProfile";
|
||||
|
||||
public const string CreatePkiSubscriber = "CreatePkiSubscriber";
|
||||
public const string UpdatePkiSubscriber = "UpdatePkiSubscriber";
|
||||
public const string DeletePkiSubscriber = "DeletePkiSubscriber";
|
||||
public const string SignCertificateBySubscriber = "SignCertificateBySubscriber";
|
||||
public const string SignCertificateByCa = "SignCertificateByCa";
|
||||
public const string IssueCertificateByProfile = "IssueCertificateByProfile";
|
||||
|
||||
@@ -715,6 +715,43 @@ namespace PSInfisicalAPI.Endpoints
|
||||
ContainsSecretMaterialInResponse = true
|
||||
});
|
||||
|
||||
#region PKI configuration management
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.CreateInternalCertificateAuthority, Resource = "Pki", Version = "v1", Method = "POST", Template = "/api/v1/cert-manager/ca/internal", RequiresAuthorization = true });
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.CreateInternalCertificateAuthority, Resource = "Pki", Version = "v1", Method = "POST", Template = "/api/v1/pki/ca/internal", RequiresAuthorization = true });
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.UpdateInternalCertificateAuthority, Resource = "Pki", Version = "v1", Method = "PATCH", Template = "/api/v1/cert-manager/ca/internal/{caId}", RequiresAuthorization = true });
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.UpdateInternalCertificateAuthority, Resource = "Pki", Version = "v1", Method = "PATCH", Template = "/api/v1/pki/ca/internal/{caId}", RequiresAuthorization = true });
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.DeleteInternalCertificateAuthority, Resource = "Pki", Version = "v1", Method = "DELETE", Template = "/api/v1/cert-manager/ca/internal/{caId}", RequiresAuthorization = true });
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.DeleteInternalCertificateAuthority, Resource = "Pki", Version = "v1", Method = "DELETE", Template = "/api/v1/pki/ca/internal/{caId}", RequiresAuthorization = true });
|
||||
|
||||
// Standing up a subordinate takes three calls: Infisical creates it pending, then the CSR is signed
|
||||
// by the parent and imported back.
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.GetCertificateAuthorityCsr, Resource = "Pki", Version = "v1", Method = "GET", Template = "/api/v1/pki/ca/{caId}/csr", RequiresAuthorization = true });
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.SignIntermediateCertificateAuthority, Resource = "Pki", Version = "v1", Method = "POST", Template = "/api/v1/pki/ca/{caId}/sign-intermediate", RequiresAuthorization = true, ContainsSecretMaterialInResponse = true });
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.ImportCertificateAuthorityCertificate, Resource = "Pki", Version = "v1", Method = "POST", Template = "/api/v1/pki/ca/{caId}/import-certificate", RequiresAuthorization = true });
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.CreateCertificatePolicy, Resource = "Pki", Version = "v1", Method = "POST", Template = "/api/v1/cert-manager/certificate-policies", RequiresAuthorization = true });
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.UpdateCertificatePolicy, Resource = "Pki", Version = "v1", Method = "PATCH", Template = "/api/v1/cert-manager/certificate-policies/{policyId}", RequiresAuthorization = true });
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.DeleteCertificatePolicy, Resource = "Pki", Version = "v1", Method = "DELETE", Template = "/api/v1/cert-manager/certificate-policies/{policyId}", RequiresAuthorization = true });
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.CreateCertificateProfile, Resource = "Pki", Version = "v1", Method = "POST", Template = "/api/v1/cert-manager/certificate-profiles", RequiresAuthorization = true });
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.UpdateCertificateProfile, Resource = "Pki", Version = "v1", Method = "PATCH", Template = "/api/v1/cert-manager/certificate-profiles/{profileId}", RequiresAuthorization = true });
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.DeleteCertificateProfile, Resource = "Pki", Version = "v1", Method = "DELETE", Template = "/api/v1/cert-manager/certificate-profiles/{profileId}", RequiresAuthorization = true });
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.CreateCertificateApplication, Resource = "Pki", Version = "v1", Method = "POST", Template = "/api/v1/cert-manager/applications", RequiresAuthorization = true });
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.UpdateCertificateApplication, Resource = "Pki", Version = "v1", Method = "PATCH", Template = "/api/v1/cert-manager/applications/{applicationId}", RequiresAuthorization = true });
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.DeleteCertificateApplication, Resource = "Pki", Version = "v1", Method = "DELETE", Template = "/api/v1/cert-manager/applications/{applicationId}", RequiresAuthorization = true });
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.AddCertificateApplicationProfiles, Resource = "Pki", Version = "v1", Method = "POST", Template = "/api/v1/cert-manager/applications/{applicationId}/profiles", RequiresAuthorization = true });
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.RemoveCertificateApplicationProfile, Resource = "Pki", Version = "v1", Method = "DELETE", Template = "/api/v1/cert-manager/applications/{applicationId}/profiles/{profileId}", RequiresAuthorization = true });
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.CreatePkiSubscriber, Resource = "Pki", Version = "v1", Method = "POST", Template = "/api/v1/pki/subscribers", RequiresAuthorization = true });
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.UpdatePkiSubscriber, Resource = "Pki", Version = "v1", Method = "PATCH", Template = "/api/v1/pki/subscribers/{subscriberName}", RequiresAuthorization = true });
|
||||
Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.DeletePkiSubscriber, Resource = "Pki", Version = "v1", Method = "DELETE", Template = "/api/v1/pki/subscribers/{subscriberName}", RequiresAuthorization = true });
|
||||
|
||||
#endregion
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.UpdateCertificateMetadata,
|
||||
|
||||
@@ -0,0 +1,443 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Endpoints;
|
||||
using PSInfisicalAPI.Errors;
|
||||
using PSInfisicalAPI.Http;
|
||||
using PSInfisicalAPI.Logging;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Serialization;
|
||||
|
||||
namespace PSInfisicalAPI.Pki
|
||||
{
|
||||
/// <summary>
|
||||
/// Create, update, and delete operations for the objects that make up a Certificate Manager environment:
|
||||
/// certificate authorities, policies, profiles, applications, and PKI subscribers.
|
||||
/// <para>
|
||||
/// Kept apart from <see cref="InfisicalPkiClient"/>, which reads and issues. These are the operations that
|
||||
/// change how a project is configured rather than what it has issued.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
internal sealed class InfisicalPkiManagementClient
|
||||
{
|
||||
private const string Component = "PkiManagementClient";
|
||||
|
||||
private readonly InfisicalApiInvoker _invoker;
|
||||
private readonly IInfisicalLogger _logger;
|
||||
private readonly JsonInfisicalSerializer _serializer;
|
||||
|
||||
public InfisicalPkiManagementClient(IInfisicalHttpClient httpClient, IInfisicalLogger logger)
|
||||
{
|
||||
if (httpClient == null) { throw new ArgumentNullException(nameof(httpClient)); }
|
||||
_logger = logger ?? NullInfisicalLogger.Instance;
|
||||
_invoker = new InfisicalApiInvoker(httpClient);
|
||||
_serializer = new JsonInfisicalSerializer();
|
||||
}
|
||||
|
||||
#region Certificate authority
|
||||
|
||||
public InfisicalCertificateAuthority CreateInternalCertificateAuthority(
|
||||
InfisicalConnection connection,
|
||||
string projectId,
|
||||
string name,
|
||||
string type,
|
||||
string commonName,
|
||||
string organization,
|
||||
string organizationalUnit,
|
||||
string country,
|
||||
string state,
|
||||
string locality,
|
||||
string keyAlgorithm,
|
||||
string friendlyName,
|
||||
string notBefore,
|
||||
string notAfter,
|
||||
int? maxPathLength)
|
||||
{
|
||||
Require(connection, projectId);
|
||||
if (string.IsNullOrEmpty(name)) { throw new InfisicalConfigurationException("Name is required."); }
|
||||
if (string.IsNullOrEmpty(type)) { throw new InfisicalConfigurationException("Type is required."); }
|
||||
|
||||
bool isRoot = string.Equals(type, "root", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
InfisicalCreateInternalCaRequestDto request = new InfisicalCreateInternalCaRequestDto
|
||||
{
|
||||
ProjectId = projectId,
|
||||
Name = name,
|
||||
Status = "active",
|
||||
Configuration = new InfisicalCreateInternalCaConfigurationDto
|
||||
{
|
||||
Type = type,
|
||||
CommonName = commonName ?? string.Empty,
|
||||
Organization = organization ?? string.Empty,
|
||||
OrganizationalUnit = organizationalUnit ?? string.Empty,
|
||||
Country = country ?? string.Empty,
|
||||
State = state ?? string.Empty,
|
||||
Locality = locality ?? string.Empty,
|
||||
KeyAlgorithm = keyAlgorithm,
|
||||
FriendlyName = friendlyName,
|
||||
// Infisical self-signs on creation only for a root, and only when given an expiry. A
|
||||
// subordinate is created pending a certificate and signed separately.
|
||||
NotBefore = isRoot ? notBefore : null,
|
||||
NotAfter = isRoot ? notAfter : null,
|
||||
MaxPathLength = isRoot ? maxPathLength : null
|
||||
}
|
||||
};
|
||||
|
||||
return Execute(
|
||||
connection,
|
||||
InfisicalEndpointNames.CreateInternalCertificateAuthority,
|
||||
null,
|
||||
_serializer.Serialize(request),
|
||||
string.Concat("create ", type, " certificate authority '", name, "'"),
|
||||
body =>
|
||||
{
|
||||
InfisicalInternalCaSingleResponseDto dto = _serializer.Deserialize<InfisicalInternalCaSingleResponseDto>(body);
|
||||
InfisicalInternalCaResponseDto inner = dto != null ? (dto.CertificateAuthority ?? dto.Ca) : null;
|
||||
return InfisicalCaMapper.Map(inner, projectId);
|
||||
});
|
||||
}
|
||||
|
||||
public InfisicalCertificateAuthority UpdateInternalCertificateAuthority(InfisicalConnection connection, string projectId, string caId, string name, string status)
|
||||
{
|
||||
Require(connection, projectId);
|
||||
if (string.IsNullOrEmpty(caId)) { throw new InfisicalConfigurationException("CaId is required."); }
|
||||
if (string.IsNullOrEmpty(name) && string.IsNullOrEmpty(status))
|
||||
{
|
||||
throw new InfisicalConfigurationException("Supply -Name or -Status; there is nothing else to update on an internal certificate authority.");
|
||||
}
|
||||
|
||||
InfisicalUpdateInternalCaRequestDto request = new InfisicalUpdateInternalCaRequestDto { ProjectId = projectId, Name = name, Status = status };
|
||||
|
||||
return Execute(
|
||||
connection,
|
||||
InfisicalEndpointNames.UpdateInternalCertificateAuthority,
|
||||
new Dictionary<string, string> { { "caId", caId } },
|
||||
_serializer.Serialize(request),
|
||||
string.Concat("update certificate authority '", caId, "'"),
|
||||
body =>
|
||||
{
|
||||
InfisicalInternalCaSingleResponseDto dto = _serializer.Deserialize<InfisicalInternalCaSingleResponseDto>(body);
|
||||
InfisicalInternalCaResponseDto inner = dto != null ? (dto.CertificateAuthority ?? dto.Ca) : null;
|
||||
return InfisicalCaMapper.Map(inner, projectId);
|
||||
});
|
||||
}
|
||||
|
||||
public void DeleteInternalCertificateAuthority(InfisicalConnection connection, string projectId, string caId)
|
||||
{
|
||||
Require(connection, projectId);
|
||||
if (string.IsNullOrEmpty(caId)) { throw new InfisicalConfigurationException("CaId is required."); }
|
||||
|
||||
Execute<object>(
|
||||
connection,
|
||||
InfisicalEndpointNames.DeleteInternalCertificateAuthority,
|
||||
new Dictionary<string, string> { { "caId", caId } },
|
||||
null,
|
||||
string.Concat("delete certificate authority '", caId, "'"),
|
||||
body => null,
|
||||
BuildProjectQuery(projectId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signs a newly created subordinate with its parent and imports the result, taking it from
|
||||
/// pending-certificate to active. Infisical creates a subordinate without a certificate and exposes no
|
||||
/// single call that does this.
|
||||
/// </summary>
|
||||
public void CompleteSubordinateCertificateAuthority(InfisicalConnection connection, string projectId, string subordinateCaId, string parentCaId, string notAfter, int maxPathLength)
|
||||
{
|
||||
Require(connection, projectId);
|
||||
if (string.IsNullOrEmpty(subordinateCaId)) { throw new InfisicalConfigurationException("CaId is required."); }
|
||||
if (string.IsNullOrEmpty(parentCaId)) { throw new InfisicalConfigurationException("ParentCaId is required."); }
|
||||
if (string.IsNullOrEmpty(notAfter)) { throw new InfisicalConfigurationException("NotAfter is required to sign a subordinate certificate authority."); }
|
||||
|
||||
string csr = Execute(
|
||||
connection,
|
||||
InfisicalEndpointNames.GetCertificateAuthorityCsr,
|
||||
new Dictionary<string, string> { { "caId", subordinateCaId } },
|
||||
null,
|
||||
string.Concat("read the certificate signing request for '", subordinateCaId, "'"),
|
||||
body =>
|
||||
{
|
||||
InfisicalCaCsrResponseDto dto = _serializer.Deserialize<InfisicalCaCsrResponseDto>(body);
|
||||
return dto != null ? dto.Csr : null;
|
||||
});
|
||||
|
||||
if (string.IsNullOrEmpty(csr))
|
||||
{
|
||||
throw new InfisicalApiException(string.Concat("Certificate authority '", subordinateCaId, "' returned no certificate signing request to sign."));
|
||||
}
|
||||
|
||||
InfisicalSignIntermediateRequestDto signRequest = new InfisicalSignIntermediateRequestDto
|
||||
{
|
||||
Csr = csr,
|
||||
NotAfter = notAfter,
|
||||
MaxPathLength = maxPathLength
|
||||
};
|
||||
|
||||
InfisicalSignIntermediateResponseDto signed = Execute(
|
||||
connection,
|
||||
InfisicalEndpointNames.SignIntermediateCertificateAuthority,
|
||||
new Dictionary<string, string> { { "caId", parentCaId } },
|
||||
_serializer.Serialize(signRequest),
|
||||
string.Concat("sign '", subordinateCaId, "' with '", parentCaId, "'"),
|
||||
body => _serializer.Deserialize<InfisicalSignIntermediateResponseDto>(body));
|
||||
|
||||
if (signed == null || string.IsNullOrEmpty(signed.Certificate))
|
||||
{
|
||||
throw new InfisicalApiException("Signing the subordinate certificate authority returned no certificate.");
|
||||
}
|
||||
|
||||
InfisicalImportCaCertificateRequestDto importRequest = new InfisicalImportCaCertificateRequestDto
|
||||
{
|
||||
Certificate = signed.Certificate,
|
||||
CertificateChain = signed.CertificateChain
|
||||
};
|
||||
|
||||
Execute<object>(
|
||||
connection,
|
||||
InfisicalEndpointNames.ImportCertificateAuthorityCertificate,
|
||||
new Dictionary<string, string> { { "caId", subordinateCaId } },
|
||||
_serializer.Serialize(importRequest),
|
||||
string.Concat("import the signed certificate onto '", subordinateCaId, "'"),
|
||||
body => null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Certificate policy
|
||||
|
||||
public InfisicalCertificatePolicy WriteCertificatePolicy(InfisicalConnection connection, string projectId, string policyId, InfisicalCertificatePolicyWriteRequestDto request)
|
||||
{
|
||||
Require(connection, projectId);
|
||||
if (request == null) { throw new ArgumentNullException(nameof(request)); }
|
||||
|
||||
bool creating = string.IsNullOrEmpty(policyId);
|
||||
request.ProjectId = projectId;
|
||||
|
||||
return Execute(
|
||||
connection,
|
||||
creating ? InfisicalEndpointNames.CreateCertificatePolicy : InfisicalEndpointNames.UpdateCertificatePolicy,
|
||||
creating ? null : new Dictionary<string, string> { { "policyId", policyId } },
|
||||
_serializer.Serialize(request),
|
||||
creating ? string.Concat("create certificate policy '", request.Name, "'") : string.Concat("update certificate policy '", policyId, "'"),
|
||||
body =>
|
||||
{
|
||||
InfisicalCertificatePolicySingleResponseDto dto = _serializer.Deserialize<InfisicalCertificatePolicySingleResponseDto>(body);
|
||||
return InfisicalCertificatePolicyMapper.Map(dto != null ? dto.CertificatePolicy : null, projectId);
|
||||
});
|
||||
}
|
||||
|
||||
public void DeleteCertificatePolicy(InfisicalConnection connection, string projectId, string policyId)
|
||||
{
|
||||
Require(connection, projectId);
|
||||
if (string.IsNullOrEmpty(policyId)) { throw new InfisicalConfigurationException("PolicyId is required."); }
|
||||
|
||||
Execute<object>(
|
||||
connection,
|
||||
InfisicalEndpointNames.DeleteCertificatePolicy,
|
||||
new Dictionary<string, string> { { "policyId", policyId } },
|
||||
null,
|
||||
string.Concat("delete certificate policy '", policyId, "'"),
|
||||
body => null,
|
||||
BuildProjectQuery(projectId));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Certificate profile
|
||||
|
||||
public InfisicalCertificateProfile WriteCertificateProfile(InfisicalConnection connection, string projectId, string profileId, InfisicalCertificateProfileWriteRequestDto request)
|
||||
{
|
||||
Require(connection, projectId);
|
||||
if (request == null) { throw new ArgumentNullException(nameof(request)); }
|
||||
|
||||
bool creating = string.IsNullOrEmpty(profileId);
|
||||
request.ProjectId = projectId;
|
||||
|
||||
return Execute(
|
||||
connection,
|
||||
creating ? InfisicalEndpointNames.CreateCertificateProfile : InfisicalEndpointNames.UpdateCertificateProfile,
|
||||
creating ? null : new Dictionary<string, string> { { "profileId", profileId } },
|
||||
_serializer.Serialize(request),
|
||||
creating ? string.Concat("create certificate profile '", request.Slug, "'") : string.Concat("update certificate profile '", profileId, "'"),
|
||||
body =>
|
||||
{
|
||||
InfisicalCertificateProfileSingleResponseDto dto = _serializer.Deserialize<InfisicalCertificateProfileSingleResponseDto>(body);
|
||||
return InfisicalCertificateProfileMapper.Map(dto != null ? dto.CertificateProfile : null, projectId);
|
||||
});
|
||||
}
|
||||
|
||||
public void DeleteCertificateProfile(InfisicalConnection connection, string projectId, string profileId)
|
||||
{
|
||||
Require(connection, projectId);
|
||||
if (string.IsNullOrEmpty(profileId)) { throw new InfisicalConfigurationException("ProfileId is required."); }
|
||||
|
||||
Execute<object>(
|
||||
connection,
|
||||
InfisicalEndpointNames.DeleteCertificateProfile,
|
||||
new Dictionary<string, string> { { "profileId", profileId } },
|
||||
null,
|
||||
string.Concat("delete certificate profile '", profileId, "'"),
|
||||
body => null,
|
||||
BuildProjectQuery(projectId));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Certificate application
|
||||
|
||||
public InfisicalCertificateApplication WriteCertificateApplication(InfisicalConnection connection, string projectId, string applicationId, InfisicalCertificateApplicationWriteRequestDto request)
|
||||
{
|
||||
Require(connection, projectId);
|
||||
if (request == null) { throw new ArgumentNullException(nameof(request)); }
|
||||
|
||||
bool creating = string.IsNullOrEmpty(applicationId);
|
||||
request.ProjectId = projectId;
|
||||
|
||||
return Execute(
|
||||
connection,
|
||||
creating ? InfisicalEndpointNames.CreateCertificateApplication : InfisicalEndpointNames.UpdateCertificateApplication,
|
||||
creating ? null : new Dictionary<string, string> { { "applicationId", applicationId } },
|
||||
_serializer.Serialize(request),
|
||||
creating ? string.Concat("create certificate application '", request.Name, "'") : string.Concat("update certificate application '", applicationId, "'"),
|
||||
body =>
|
||||
{
|
||||
InfisicalCertificateApplicationSingleResponseDto dto = _serializer.Deserialize<InfisicalCertificateApplicationSingleResponseDto>(body);
|
||||
return InfisicalCertificateApplicationMapper.Map(dto != null ? dto.Application : null, projectId);
|
||||
});
|
||||
}
|
||||
|
||||
public void DeleteCertificateApplication(InfisicalConnection connection, string projectId, string applicationId)
|
||||
{
|
||||
Require(connection, projectId);
|
||||
if (string.IsNullOrEmpty(applicationId)) { throw new InfisicalConfigurationException("ApplicationId is required."); }
|
||||
|
||||
Execute<object>(
|
||||
connection,
|
||||
InfisicalEndpointNames.DeleteCertificateApplication,
|
||||
new Dictionary<string, string> { { "applicationId", applicationId } },
|
||||
null,
|
||||
string.Concat("delete certificate application '", applicationId, "'"),
|
||||
body => null,
|
||||
BuildProjectQuery(projectId));
|
||||
}
|
||||
|
||||
public void AddCertificateApplicationProfiles(InfisicalConnection connection, string projectId, string applicationId, IEnumerable<string> profileIds)
|
||||
{
|
||||
Require(connection, projectId);
|
||||
if (string.IsNullOrEmpty(applicationId)) { throw new InfisicalConfigurationException("ApplicationId is required."); }
|
||||
|
||||
List<string> ids = new List<string>();
|
||||
if (profileIds != null) { foreach (string id in profileIds) { if (!string.IsNullOrEmpty(id)) { ids.Add(id); } } }
|
||||
if (ids.Count == 0) { throw new InfisicalConfigurationException("At least one ProfileId is required."); }
|
||||
|
||||
InfisicalCertificateApplicationWriteRequestDto request = new InfisicalCertificateApplicationWriteRequestDto { ProjectId = projectId, ProfileIds = ids };
|
||||
|
||||
Execute<object>(
|
||||
connection,
|
||||
InfisicalEndpointNames.AddCertificateApplicationProfiles,
|
||||
new Dictionary<string, string> { { "applicationId", applicationId } },
|
||||
_serializer.Serialize(request),
|
||||
string.Concat("attach ", ids.Count.ToString(System.Globalization.CultureInfo.InvariantCulture), " profile(s) to application '", applicationId, "'"),
|
||||
body => null);
|
||||
}
|
||||
|
||||
public void RemoveCertificateApplicationProfile(InfisicalConnection connection, string projectId, string applicationId, string profileId)
|
||||
{
|
||||
Require(connection, projectId);
|
||||
if (string.IsNullOrEmpty(applicationId)) { throw new InfisicalConfigurationException("ApplicationId is required."); }
|
||||
if (string.IsNullOrEmpty(profileId)) { throw new InfisicalConfigurationException("ProfileId is required."); }
|
||||
|
||||
Execute<object>(
|
||||
connection,
|
||||
InfisicalEndpointNames.RemoveCertificateApplicationProfile,
|
||||
new Dictionary<string, string> { { "applicationId", applicationId }, { "profileId", profileId } },
|
||||
null,
|
||||
string.Concat("detach profile '", profileId, "' from application '", applicationId, "'"),
|
||||
body => null,
|
||||
BuildProjectQuery(projectId));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PKI subscriber
|
||||
|
||||
public InfisicalPkiSubscriber WritePkiSubscriber(InfisicalConnection connection, string projectId, string subscriberName, InfisicalPkiSubscriberWriteRequestDto request)
|
||||
{
|
||||
Require(connection, projectId);
|
||||
if (request == null) { throw new ArgumentNullException(nameof(request)); }
|
||||
|
||||
bool creating = string.IsNullOrEmpty(subscriberName);
|
||||
request.ProjectId = projectId;
|
||||
|
||||
return Execute(
|
||||
connection,
|
||||
creating ? InfisicalEndpointNames.CreatePkiSubscriber : InfisicalEndpointNames.UpdatePkiSubscriber,
|
||||
creating ? null : new Dictionary<string, string> { { "subscriberName", subscriberName } },
|
||||
_serializer.Serialize(request),
|
||||
creating ? string.Concat("create PKI subscriber '", request.Name, "'") : string.Concat("update PKI subscriber '", subscriberName, "'"),
|
||||
// The subscriber routes return the record unwrapped, matching how GetPkiSubscriber reads it.
|
||||
body => InfisicalPkiSubscriberMapper.Map(_serializer.Deserialize<InfisicalPkiSubscriberResponseDto>(body), projectId));
|
||||
}
|
||||
|
||||
public void DeletePkiSubscriber(InfisicalConnection connection, string projectId, string subscriberName)
|
||||
{
|
||||
Require(connection, projectId);
|
||||
if (string.IsNullOrEmpty(subscriberName)) { throw new InfisicalConfigurationException("Name is required."); }
|
||||
|
||||
Execute<object>(
|
||||
connection,
|
||||
InfisicalEndpointNames.DeletePkiSubscriber,
|
||||
new Dictionary<string, string> { { "subscriberName", subscriberName } },
|
||||
null,
|
||||
string.Concat("delete PKI subscriber '", subscriberName, "'"),
|
||||
body => null,
|
||||
BuildProjectQuery(projectId));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Plumbing
|
||||
|
||||
private static void Require(InfisicalConnection connection, string projectId)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
|
||||
}
|
||||
|
||||
private static List<KeyValuePair<string, string>> BuildProjectQuery(string projectId)
|
||||
{
|
||||
// DELETE carries no body, so the project has to travel on the query string for Infisical's
|
||||
// project-injection middleware to see it.
|
||||
return new List<KeyValuePair<string, string>> { new KeyValuePair<string, string>("projectId", projectId) };
|
||||
}
|
||||
|
||||
private T Execute<T>(
|
||||
InfisicalConnection connection,
|
||||
string endpointName,
|
||||
Dictionary<string, string> pathParameters,
|
||||
string body,
|
||||
string description,
|
||||
Func<string, T> project,
|
||||
List<KeyValuePair<string, string>> query = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Information(Component, string.Concat("Attempting to ", description, ". Please Wait..."));
|
||||
InfisicalHttpResponse response = _invoker.InvokeWithCandidateFallback(connection, endpointName, endpointName, pathParameters, query, body);
|
||||
string payload = response.Body;
|
||||
response.Clear();
|
||||
|
||||
T result = project(payload);
|
||||
_logger.Information(Component, string.Concat("Infisical operation succeeded: ", description, "."));
|
||||
return result;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(Component, string.Concat("Infisical operation failed: ", description, "."));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSInfisicalAPI.Pki
|
||||
{
|
||||
#region Certificate authority
|
||||
|
||||
internal sealed class InfisicalCreateInternalCaRequestDto
|
||||
{
|
||||
[JsonProperty("projectId", NullValueHandling = NullValueHandling.Ignore)] public string ProjectId { get; set; }
|
||||
[JsonProperty("name")] public string Name { get; set; }
|
||||
[JsonProperty("status")] public string Status { get; set; }
|
||||
[JsonProperty("configuration")] public InfisicalCreateInternalCaConfigurationDto Configuration { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalCreateInternalCaConfigurationDto
|
||||
{
|
||||
[JsonProperty("type")] public string Type { get; set; }
|
||||
[JsonProperty("commonName")] public string CommonName { get; set; }
|
||||
[JsonProperty("organization")] public string Organization { get; set; }
|
||||
[JsonProperty("ou")] public string OrganizationalUnit { get; set; }
|
||||
[JsonProperty("country")] public string Country { get; set; }
|
||||
[JsonProperty("province")] public string State { get; set; }
|
||||
[JsonProperty("locality")] public string Locality { get; set; }
|
||||
[JsonProperty("keyAlgorithm")] public string KeyAlgorithm { get; set; }
|
||||
[JsonProperty("friendlyName", NullValueHandling = NullValueHandling.Ignore)] public string FriendlyName { get; set; }
|
||||
[JsonProperty("notBefore", NullValueHandling = NullValueHandling.Ignore)] public string NotBefore { get; set; }
|
||||
[JsonProperty("notAfter", NullValueHandling = NullValueHandling.Ignore)] public string NotAfter { get; set; }
|
||||
[JsonProperty("maxPathLength", NullValueHandling = NullValueHandling.Ignore)] public int? MaxPathLength { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalUpdateInternalCaRequestDto
|
||||
{
|
||||
[JsonProperty("projectId", NullValueHandling = NullValueHandling.Ignore)] public string ProjectId { get; set; }
|
||||
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; set; }
|
||||
[JsonProperty("status", NullValueHandling = NullValueHandling.Ignore)] public string Status { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalCaCsrResponseDto
|
||||
{
|
||||
[JsonProperty("csr")] public string Csr { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalSignIntermediateRequestDto
|
||||
{
|
||||
[JsonProperty("csr")] public string Csr { get; set; }
|
||||
[JsonProperty("notBefore", NullValueHandling = NullValueHandling.Ignore)] public string NotBefore { get; set; }
|
||||
[JsonProperty("notAfter")] public string NotAfter { get; set; }
|
||||
[JsonProperty("maxPathLength")] public int MaxPathLength { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalSignIntermediateResponseDto
|
||||
{
|
||||
[JsonProperty("certificate")] public string Certificate { get; set; }
|
||||
[JsonProperty("certificateChain")] public string CertificateChain { get; set; }
|
||||
[JsonProperty("issuingCaCertificate")] public string IssuingCaCertificate { get; set; }
|
||||
[JsonProperty("serialNumber")] public string SerialNumber { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalImportCaCertificateRequestDto
|
||||
{
|
||||
[JsonProperty("certificate")] public string Certificate { get; set; }
|
||||
[JsonProperty("certificateChain")] public string CertificateChain { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Certificate policy
|
||||
|
||||
internal sealed class InfisicalCertificatePolicyWriteRequestDto
|
||||
{
|
||||
[JsonProperty("projectId", NullValueHandling = NullValueHandling.Ignore)] public string ProjectId { get; set; }
|
||||
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; set; }
|
||||
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)] public string Description { get; set; }
|
||||
[JsonProperty("subject", NullValueHandling = NullValueHandling.Ignore)] public List<Dictionary<string, object>> Subject { get; set; }
|
||||
[JsonProperty("sans", NullValueHandling = NullValueHandling.Ignore)] public List<Dictionary<string, object>> Sans { get; set; }
|
||||
[JsonProperty("keyUsages", NullValueHandling = NullValueHandling.Ignore)] public Dictionary<string, object> KeyUsages { get; set; }
|
||||
[JsonProperty("extendedKeyUsages", NullValueHandling = NullValueHandling.Ignore)] public Dictionary<string, object> ExtendedKeyUsages { get; set; }
|
||||
[JsonProperty("algorithms", NullValueHandling = NullValueHandling.Ignore)] public Dictionary<string, object> Algorithms { get; set; }
|
||||
[JsonProperty("validity", NullValueHandling = NullValueHandling.Ignore)] public Dictionary<string, object> Validity { get; set; }
|
||||
[JsonProperty("basicConstraints", NullValueHandling = NullValueHandling.Ignore)] public Dictionary<string, object> BasicConstraints { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalCertificatePolicySingleResponseDto
|
||||
{
|
||||
[JsonProperty("certificatePolicy")] public InfisicalCertificatePolicyResponseDto CertificatePolicy { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Certificate profile
|
||||
|
||||
internal sealed class InfisicalCertificateProfileWriteRequestDto
|
||||
{
|
||||
[JsonProperty("projectId", NullValueHandling = NullValueHandling.Ignore)] public string ProjectId { get; set; }
|
||||
[JsonProperty("slug", NullValueHandling = NullValueHandling.Ignore)] public string Slug { get; set; }
|
||||
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)] public string Description { get; set; }
|
||||
[JsonProperty("caId", NullValueHandling = NullValueHandling.Ignore)] public string CaId { get; set; }
|
||||
[JsonProperty("certificatePolicyId", NullValueHandling = NullValueHandling.Ignore)] public string CertificatePolicyId { get; set; }
|
||||
[JsonProperty("enrollmentType", NullValueHandling = NullValueHandling.Ignore)] public string EnrollmentType { get; set; }
|
||||
[JsonProperty("issuerType", NullValueHandling = NullValueHandling.Ignore)] public string IssuerType { get; set; }
|
||||
[JsonProperty("apiConfig", NullValueHandling = NullValueHandling.Ignore)] public Dictionary<string, object> ApiConfig { get; set; }
|
||||
[JsonProperty("scepConfig", NullValueHandling = NullValueHandling.Ignore)] public Dictionary<string, object> ScepConfig { get; set; }
|
||||
[JsonProperty("estConfig", NullValueHandling = NullValueHandling.Ignore)] public Dictionary<string, object> EstConfig { get; set; }
|
||||
[JsonProperty("acmeConfig", NullValueHandling = NullValueHandling.Ignore)] public Dictionary<string, object> AcmeConfig { get; set; }
|
||||
[JsonProperty("defaults", NullValueHandling = NullValueHandling.Ignore)] public Dictionary<string, object> Defaults { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalCertificateProfileSingleResponseDto
|
||||
{
|
||||
[JsonProperty("certificateProfile")] public InfisicalCertificateProfileResponseDto CertificateProfile { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Certificate application
|
||||
|
||||
internal sealed class InfisicalCertificateApplicationWriteRequestDto
|
||||
{
|
||||
[JsonProperty("projectId", NullValueHandling = NullValueHandling.Ignore)] public string ProjectId { get; set; }
|
||||
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; set; }
|
||||
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)] public string Description { get; set; }
|
||||
[JsonProperty("profileIds", NullValueHandling = NullValueHandling.Ignore)] public List<string> ProfileIds { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalCertificateApplicationSingleResponseDto
|
||||
{
|
||||
[JsonProperty("application")] public InfisicalCertificateApplicationResponseDto Application { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PKI subscriber
|
||||
|
||||
internal sealed class InfisicalPkiSubscriberWriteRequestDto
|
||||
{
|
||||
[JsonProperty("projectId", NullValueHandling = NullValueHandling.Ignore)] public string ProjectId { get; set; }
|
||||
[JsonProperty("caId", NullValueHandling = NullValueHandling.Ignore)] public string CaId { get; set; }
|
||||
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; set; }
|
||||
[JsonProperty("commonName", NullValueHandling = NullValueHandling.Ignore)] public string CommonName { get; set; }
|
||||
[JsonProperty("status", NullValueHandling = NullValueHandling.Ignore)] public string Status { get; set; }
|
||||
[JsonProperty("ttl", NullValueHandling = NullValueHandling.Ignore)] public string Ttl { get; set; }
|
||||
[JsonProperty("subjectAlternativeNames", NullValueHandling = NullValueHandling.Ignore)] public List<string> SubjectAlternativeNames { get; set; }
|
||||
[JsonProperty("keyUsages", NullValueHandling = NullValueHandling.Ignore)] public List<string> KeyUsages { get; set; }
|
||||
[JsonProperty("extendedKeyUsages", NullValueHandling = NullValueHandling.Ignore)] public List<string> ExtendedKeyUsages { get; set; }
|
||||
[JsonProperty("enableAutoRenewal", NullValueHandling = NullValueHandling.Ignore)] public bool? EnableAutoRenewal { get; set; }
|
||||
[JsonProperty("autoRenewalPeriodInDays", NullValueHandling = NullValueHandling.Ignore)] public int? AutoRenewalPeriodInDays { get; set; }
|
||||
[JsonProperty("properties", NullValueHandling = NullValueHandling.Ignore)] public Dictionary<string, object> Properties { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user