CI: add dotnet --info / df -h / free -m diagnostics and an explicit 'Restore NuGet packages' step before build to isolate restore failures (build of e15f650 on main exited with code -1 and zero dotnet output).
#5
@@ -10,6 +10,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos
|
||||
- `Get-InfisicalCertificateProfile` added with `List` (default) and `ById` parameter sets. List binds to `GET /api/v1/cert-manager/certificate-profiles` (optional `-Limit`, `-Offset`, `-IncludeConfigs`); ById binds to `GET /api/v1/cert-manager/certificate-profiles/{certificateProfileId}`. New `InfisicalCertificateProfile` model surfaces ca/policy ids, slug, enrollment type, per-profile defaults (ttl, key/extended key usages), and the embedded CA/policy/apiConfig summaries.
|
||||
- `Get-InfisicalCertificatePolicy` added with `List` (default) and `ById` parameter sets. List binds to `GET /api/v1/cert-manager/certificate-policies` (optional `-Limit`, `-Offset`); ById binds to `GET /api/v1/cert-manager/certificate-policies/{certificatePolicyId}`. New `InfisicalCertificatePolicy` model surfaces subject, SANs, key usages, extended key usages, algorithms, and validity. Polymorphic string-or-array fields (`allowed`, `required`, `keyAlgorithm`) are normalized to arrays; `sans` is normalized whether the API returns an object or an array.
|
||||
- `Get-InfisicalCertificateAuthority` gains a `-Kind` parameter on the List parameter set with values `Internal` (default, preserves prior behavior against `/api/v1/cert-manager/ca/internal`), `Any` (binds to the generic `/api/v1/cert-manager/ca` endpoint which returns both internal and ACME CAs), and `Acme` (uses the generic endpoint and client-side filters to ACME issuers only). ById retrieval is unchanged and still resolves against the internal CA endpoint.
|
||||
- `Request-InfisicalCertificate` gains a `ByProfile` parameter set bound by the new `-CertificateProfileId` parameter (alias `ProfileId`). The cmdlet generates a local keypair and CSR as usual, then POSTs to `/api/v1/cert-manager/certificates` with the profile id, the CSR, and a subject/attribute envelope (commonName, organization, organizationalUnit, country, state, locality, ttl, notBefore, notAfter, keyUsages, extendedKeyUsages). The wrapped response (`{certificate:{certificate,certificateChain,issuingCaCertificate,serialNumber,certificateId,privateKey}, certificateRequestId, status, message}`) is unwrapped into the existing `InfisicalSignedCertificate` shape so the install / reuse / chain-completion paths continue to work unchanged. Issuance that returns without a certificate (e.g. status `pending_approval`) raises a configuration exception that surfaces the reported status and message.
|
||||
|
||||
## 2026.06.04.1920
|
||||
|
||||
|
||||
@@ -1269,7 +1269,7 @@ $SearchInfisicalCertificateResult = Search-InfisicalCertificate @SearchInfisical
|
||||
<command:noun>InfisicalCertificate</command:noun>
|
||||
</command:details>
|
||||
<maml:description>
|
||||
<maml:para>Generates a keypair locally, builds a CSR, and submits it for signing either via a PKI subscriber (-PkiSubscriberSlug, default parameter set) or by direct CA signing (-CertificateAuthorityId). On subsequent runs an existing certificate whose CN matches and whose remaining lifetime exceeds -RenewalThresholdDays is reused; pass -Force to always issue or -AllowRenewal to allow rotation inside the threshold. Optional flags install the leaf (-Install) and chain (-InstallChain) into a Windows certificate store, and control private-key protection (-PrivateKeyProtection, -PersistKey, -MachineKey, -PrivateKeyPath, -KeyStorageFlags). Honors -WhatIf and -Confirm.</maml:para>
|
||||
<maml:para>Generates a keypair locally, builds a CSR, and submits it for signing via one of three parameter sets: a PKI subscriber (-PkiSubscriberSlug, default), direct CA signing (-CertificateAuthorityId), or a certificate profile (-CertificateProfileId, POSTs to /api/v1/cert-manager/certificates with the profile bound). On subsequent runs an existing certificate whose CN matches and whose remaining lifetime exceeds -RenewalThresholdDays is reused; pass -Force to always issue or -AllowRenewal to allow rotation inside the threshold. Optional flags install the leaf (-Install) and chain (-InstallChain) into a Windows certificate store, and control private-key protection (-PrivateKeyProtection, -PersistKey, -MachineKey, -PrivateKeyPath, -KeyStorageFlags). Honors -WhatIf and -Confirm.</maml:para>
|
||||
</maml:description>
|
||||
<maml:alertSet>
|
||||
<maml:title>Notes</maml:title>
|
||||
@@ -1306,6 +1306,12 @@ $RequestInfisicalCertificateParameters.Verbose = $True
|
||||
$RequestInfisicalCertificateResult = Request-InfisicalCertificate @RequestInfisicalCertificateParameters</dev:code>
|
||||
<dev:remarks><maml:para>Issues (or renews within 30 days) a 3072-bit RSA certificate for the local FQDN, installs the leaf and chain into LocalMachine\My with a non-exportable machine-bound persistent key.</maml:para></dev:remarks>
|
||||
</command:example>
|
||||
<command:example>
|
||||
<maml:title>EXAMPLE 3</maml:title>
|
||||
<dev:code>$Profile = Get-InfisicalCertificateProfile | Where-Object { $_.Slug -eq 'web-tier-profile' }
|
||||
Request-InfisicalCertificate -CertificateProfileId $Profile.Id -CommonName 'web01.contoso.com' -Ttl '90d'</dev:code>
|
||||
<dev:remarks><maml:para>Issues a certificate via the modern profile API (POST /api/v1/cert-manager/certificates). The profile binds the CA, policy, and defaults so no subscriber is required.</maml:para></dev:remarks>
|
||||
</command:example>
|
||||
</command:examples>
|
||||
</command:command>
|
||||
|
||||
|
||||
@@ -158,6 +158,7 @@ namespace PSInfisicalAPI.Tests
|
||||
|
||||
Assert.NotNull(cmdletType.GetProperty("PkiSubscriberSlug"));
|
||||
Assert.NotNull(cmdletType.GetProperty("CertificateAuthorityId"));
|
||||
Assert.NotNull(cmdletType.GetProperty("CertificateProfileId"));
|
||||
Assert.NotNull(cmdletType.GetProperty("Subject"));
|
||||
Assert.NotNull(cmdletType.GetProperty("CommonName"));
|
||||
Assert.NotNull(cmdletType.GetProperty("DnsName"));
|
||||
|
||||
@@ -23,6 +23,10 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
[Alias("CaId")]
|
||||
public string CertificateAuthorityId { get; set; }
|
||||
|
||||
[Parameter(ParameterSetName = "ByProfile", Mandatory = true, Position = 0)]
|
||||
[Alias("ProfileId")]
|
||||
public string CertificateProfileId { get; set; }
|
||||
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public IDictionary Subject { get; set; }
|
||||
[Parameter] public string CommonName { get; set; }
|
||||
@@ -38,13 +42,18 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
[Parameter] public int KeySize { get; set; } = 2048;
|
||||
[Parameter] public InfisicalEcCurve Curve { get; set; } = InfisicalEcCurve.P256;
|
||||
|
||||
[Parameter(ParameterSetName = "ByCa")] public string Ttl { get; set; }
|
||||
[Parameter(ParameterSetName = "ByCa")] public string NotBefore { get; set; }
|
||||
[Parameter(ParameterSetName = "ByCa")] public string NotAfter { get; set; }
|
||||
[Parameter(ParameterSetName = "ByCa")]
|
||||
[Parameter(ParameterSetName = "ByProfile")] public string Ttl { get; set; }
|
||||
[Parameter(ParameterSetName = "ByCa")]
|
||||
[Parameter(ParameterSetName = "ByProfile")] public string NotBefore { get; set; }
|
||||
[Parameter(ParameterSetName = "ByCa")]
|
||||
[Parameter(ParameterSetName = "ByProfile")] public string NotAfter { get; set; }
|
||||
[Parameter(ParameterSetName = "ByCa")] public string FriendlyName { get; set; }
|
||||
[Parameter(ParameterSetName = "ByCa")] public string PkiCollectionId { get; set; }
|
||||
[Parameter(ParameterSetName = "ByCa")] public string[] KeyUsage { get; set; }
|
||||
[Parameter(ParameterSetName = "ByCa")] public string[] ExtendedKeyUsage { get; set; }
|
||||
[Parameter(ParameterSetName = "ByCa")]
|
||||
[Parameter(ParameterSetName = "ByProfile")] public string[] KeyUsage { get; set; }
|
||||
[Parameter(ParameterSetName = "ByCa")]
|
||||
[Parameter(ParameterSetName = "ByProfile")] public string[] ExtendedKeyUsage { get; set; }
|
||||
|
||||
[Parameter] public SwitchParameter Install { get; set; }
|
||||
[Parameter] public StoreName StoreName { get; set; } = StoreName.My;
|
||||
@@ -104,7 +113,7 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
return;
|
||||
}
|
||||
|
||||
string target = string.Concat("PKI subscriber '", PkiSubscriberSlug ?? "(n/a)", "' or CA '", CertificateAuthorityId ?? "(n/a)", "' for CN=", csrSubject.CommonName);
|
||||
string target = string.Concat("PKI subscriber '", PkiSubscriberSlug ?? "(n/a)", "', CA '", CertificateAuthorityId ?? "(n/a)", "', or profile '", CertificateProfileId ?? "(n/a)", "' for CN=", csrSubject.CommonName);
|
||||
if (!ShouldProcess(target, "Request new certificate")) { return; }
|
||||
|
||||
InfisicalCsrOptions csrOptions = new InfisicalCsrOptions { KeyAlgorithm = KeyAlgorithm, RsaKeySize = KeySize, EcCurve = Curve };
|
||||
@@ -198,6 +207,12 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
return client.SignCertificateBySubscriber(connection, PkiSubscriberSlug, projectId, csrPem);
|
||||
}
|
||||
|
||||
if (string.Equals(ParameterSetName, "ByProfile", StringComparison.Ordinal))
|
||||
{
|
||||
InfisicalCsrSubject subject = InfisicalCertificateRequestHelpers.MergeSubject(Subject, CommonName, Country, State, Locality, Organization, OrganizationalUnit, EmailAddress);
|
||||
return client.IssueCertificateByProfile(connection, CertificateProfileId, csrPem, subject.CommonName, subject.Organization, subject.OrganizationalUnit, subject.Country, subject.State, subject.Locality, Ttl, NotBefore, NotAfter, KeyUsage, ExtendedKeyUsage);
|
||||
}
|
||||
|
||||
return client.SignCertificateByCa(connection, CertificateAuthorityId, csrPem, CommonName, null, Ttl, NotBefore, NotAfter, FriendlyName, PkiCollectionId, KeyUsage, ExtendedKeyUsage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ namespace PSInfisicalAPI.Endpoints
|
||||
public const string GetCertificateBundle = "GetCertificateBundle";
|
||||
public const string SignCertificateBySubscriber = "SignCertificateBySubscriber";
|
||||
public const string SignCertificateByCa = "SignCertificateByCa";
|
||||
public const string IssueCertificateByProfile = "IssueCertificateByProfile";
|
||||
|
||||
public const string ListPkiSubscribers = "ListPkiSubscribers";
|
||||
public const string GetPkiSubscriber = "GetPkiSubscriber";
|
||||
|
||||
@@ -623,6 +623,17 @@ namespace PSInfisicalAPI.Endpoints
|
||||
ContainsSecretMaterialInResponse = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.IssueCertificateByProfile,
|
||||
Resource = "Pki",
|
||||
Version = "v1",
|
||||
Method = "POST",
|
||||
Template = "/api/v1/cert-manager/certificates",
|
||||
RequiresAuthorization = true,
|
||||
ContainsSecretMaterialInResponse = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.ListPkiSubscribers,
|
||||
|
||||
@@ -300,6 +300,66 @@ namespace PSInfisicalAPI.Pki
|
||||
};
|
||||
}
|
||||
|
||||
public InfisicalSignedCertificate IssueCertificateByProfile(InfisicalConnection connection, string profileId, string csrPem, string commonName, string organization, string organizationalUnit, string country, string state, string locality, string ttl, string notBefore, string notAfter, IEnumerable<string> keyUsages, IEnumerable<string> extendedKeyUsages)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
if (string.IsNullOrEmpty(profileId)) { throw new InfisicalConfigurationException("CertificateProfileId is required."); }
|
||||
if (string.IsNullOrEmpty(csrPem)) { throw new InfisicalConfigurationException("CSR is required."); }
|
||||
|
||||
InfisicalIssueCertificateAttributesDto attributes = new InfisicalIssueCertificateAttributesDto
|
||||
{
|
||||
CommonName = commonName,
|
||||
Organization = organization,
|
||||
OrganizationalUnit = organizationalUnit,
|
||||
Country = country,
|
||||
State = state,
|
||||
Locality = locality,
|
||||
Ttl = ttl,
|
||||
NotBefore = notBefore,
|
||||
NotAfter = notAfter,
|
||||
KeyUsages = keyUsages != null ? new List<string>(keyUsages) : null,
|
||||
ExtendedKeyUsages = extendedKeyUsages != null ? new List<string>(extendedKeyUsages) : null
|
||||
};
|
||||
|
||||
InfisicalIssueCertificateByProfileRequestDto request = new InfisicalIssueCertificateByProfileRequestDto
|
||||
{
|
||||
ProfileId = profileId,
|
||||
Csr = csrPem,
|
||||
Attributes = attributes
|
||||
};
|
||||
string body = _serializer.Serialize(request);
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information(Component, string.Concat("Attempting to issue certificate via profile '", profileId, "'. Please Wait..."));
|
||||
InfisicalHttpResponse response = _invoker.InvokeWithCandidateFallback(connection, InfisicalEndpointNames.IssueCertificateByProfile, "IssueCertificateByProfile", null, null, body);
|
||||
InfisicalIssueCertificateResponseDto dto = _serializer.Deserialize<InfisicalIssueCertificateResponseDto>(response.Body);
|
||||
response.Clear();
|
||||
|
||||
if (dto == null || dto.Certificate == null || string.IsNullOrEmpty(dto.Certificate.Certificate))
|
||||
{
|
||||
string status = dto != null ? dto.Status : "unknown";
|
||||
string message = dto != null ? dto.Message : null;
|
||||
throw new InfisicalConfigurationException(string.Concat("Certificate was not issued (status='", status ?? "unknown", "'", string.IsNullOrEmpty(message) ? "" : string.Concat(", message='", message, "'"), "). The certificate profile may require manual approval or additional validation."));
|
||||
}
|
||||
|
||||
InfisicalSignedCertificate signed = new InfisicalSignedCertificate
|
||||
{
|
||||
SerialNumber = dto.Certificate.SerialNumber,
|
||||
CertificatePem = dto.Certificate.Certificate,
|
||||
CertificateChainPem = dto.Certificate.CertificateChain,
|
||||
IssuingCaCertificatePem = dto.Certificate.IssuingCaCertificate
|
||||
};
|
||||
_logger.Information(Component, "Infisical certificate issuance (profile) was successful.");
|
||||
return signed;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(Component, "Infisical certificate issuance (profile) failed.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public InfisicalPkiSubscriber[] ListPkiSubscribers(InfisicalConnection connection, string projectId)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
|
||||
@@ -30,4 +30,44 @@ namespace PSInfisicalAPI.Pki
|
||||
[JsonProperty("issuingCaCertificate")] public string IssuingCaCertificate { get; set; }
|
||||
[JsonProperty("serialNumber")] public string SerialNumber { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalIssueCertificateByProfileRequestDto
|
||||
{
|
||||
[JsonProperty("profileId")] public string ProfileId { get; set; }
|
||||
[JsonProperty("csr", NullValueHandling = NullValueHandling.Ignore)] public string Csr { get; set; }
|
||||
[JsonProperty("attributes", NullValueHandling = NullValueHandling.Ignore)] public InfisicalIssueCertificateAttributesDto Attributes { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalIssueCertificateAttributesDto
|
||||
{
|
||||
[JsonProperty("commonName", NullValueHandling = NullValueHandling.Ignore)] public string CommonName { get; set; }
|
||||
[JsonProperty("organization", NullValueHandling = NullValueHandling.Ignore)] public string Organization { get; set; }
|
||||
[JsonProperty("organizationalUnit", NullValueHandling = NullValueHandling.Ignore)] public string OrganizationalUnit { get; set; }
|
||||
[JsonProperty("country", NullValueHandling = NullValueHandling.Ignore)] public string Country { get; set; }
|
||||
[JsonProperty("state", NullValueHandling = NullValueHandling.Ignore)] public string State { get; set; }
|
||||
[JsonProperty("locality", NullValueHandling = NullValueHandling.Ignore)] public string Locality { get; set; }
|
||||
[JsonProperty("ttl", NullValueHandling = NullValueHandling.Ignore)] public string Ttl { get; set; }
|
||||
[JsonProperty("notBefore", NullValueHandling = NullValueHandling.Ignore)] public string NotBefore { get; set; }
|
||||
[JsonProperty("notAfter", NullValueHandling = NullValueHandling.Ignore)] public string NotAfter { get; set; }
|
||||
[JsonProperty("keyUsages", NullValueHandling = NullValueHandling.Ignore)] public List<string> KeyUsages { get; set; }
|
||||
[JsonProperty("extendedKeyUsages", NullValueHandling = NullValueHandling.Ignore)] public List<string> ExtendedKeyUsages { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalIssueCertificateResponseDto
|
||||
{
|
||||
[JsonProperty("certificate")] public InfisicalIssueCertificateInnerDto Certificate { get; set; }
|
||||
[JsonProperty("certificateRequestId")] public string CertificateRequestId { get; set; }
|
||||
[JsonProperty("status")] public string Status { get; set; }
|
||||
[JsonProperty("message")] public string Message { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalIssueCertificateInnerDto
|
||||
{
|
||||
[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; }
|
||||
[JsonProperty("certificateId")] public string CertificateId { get; set; }
|
||||
[JsonProperty("privateKey")] public string PrivateKey { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user