Treat profile pending-approval as warning instead of throw
Issuance via Request-InfisicalCertificate -CertificateProfileId no longer throws when the API responds without a certificate body (e.g. status pending_approval / pending_validation). InfisicalPkiClient.IssueCertificateByProfile now logs a warning and returns an InfisicalSignedCertificate populated only with Status, StatusMessage, and CertificateRequestId. New Status, StatusMessage, CertificateRequestId properties on InfisicalSignedCertificate and InfisicalCertificateResult propagate the lifecycle state. The cmdlet short-circuits when CertificatePem is empty: it skips key build, install, chain install, and private-key write, scrubs PrivateKeyPem, and emits a status-only result so callers can poll or trigger approval. Whether issuance is immediate is dictated by the certificate policy bound to the profile.
This commit is contained in:
+1
-1
@@ -10,7 +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-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-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.
|
- `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.
|
- `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 body (e.g. status `pending_approval` or `pending_validation`) is logged as a warning and the cmdlet emits a status-only `InfisicalCertificateResult` (new `Status`, `StatusMessage`, `CertificateRequestId` properties) instead of throwing; install / chain / private-key-write steps are skipped in that case. Whether issuance is immediate or pending is dictated by the certificate policy bound to the profile (auto-approve vs. manual review and any required validation).
|
||||||
|
|
||||||
## 2026.06.04.1920
|
## 2026.06.04.1920
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,15 @@ namespace PSInfisicalAPI.Cmdlets
|
|||||||
InfisicalSignedCertificate signed = SignCertificate(client, connection, resolvedProjectId, csr.CsrPem);
|
InfisicalSignedCertificate signed = SignCertificate(client, connection, resolvedProjectId, csr.CsrPem);
|
||||||
signed.PrivateKeyPem = csr.PrivateKeyPem;
|
signed.PrivateKeyPem = csr.PrivateKeyPem;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(signed.CertificatePem))
|
||||||
|
{
|
||||||
|
Logger.Warning(Component, string.Concat("Issuance returned without a certificate (status='", signed.Status ?? "unknown", "'", string.IsNullOrEmpty(signed.StatusMessage) ? "" : string.Concat(", message='", signed.StatusMessage, "'"), string.IsNullOrEmpty(signed.CertificateRequestId) ? "" : string.Concat(", certificateRequestId='", signed.CertificateRequestId, "'"), "). Install / chain / key-write steps are skipped; emitting status-only result."));
|
||||||
|
InfisicalCertificateResult pending = InfisicalCertificateRequestHelpers.BuildResult(null, signed);
|
||||||
|
pending.PrivateKeyPem = null;
|
||||||
|
WriteObject(pending);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
X509KeyStorageFlags resolvedFlags = ResolveEffectiveKeyStorageFlags();
|
X509KeyStorageFlags resolvedFlags = ResolveEffectiveKeyStorageFlags();
|
||||||
X509Certificate2 cert = PemCertificateBuilder.Build(signed.CertificatePem, signed.PrivateKeyPem, signed.CertificateChainPem, resolvedFlags);
|
X509Certificate2 cert = PemCertificateBuilder.Build(signed.CertificatePem, signed.PrivateKeyPem, signed.CertificateChainPem, resolvedFlags);
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ namespace PSInfisicalAPI.Models
|
|||||||
public string CertificatePem { get; set; }
|
public string CertificatePem { get; set; }
|
||||||
public string CertificateChainPem { get; set; }
|
public string CertificateChainPem { get; set; }
|
||||||
public string PrivateKeyPem { get; set; }
|
public string PrivateKeyPem { get; set; }
|
||||||
|
public string Status { get; set; }
|
||||||
|
public string StatusMessage { get; set; }
|
||||||
|
public string CertificateRequestId { get; set; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ namespace PSInfisicalAPI.Models
|
|||||||
public string CertificateChainPem { get; set; }
|
public string CertificateChainPem { get; set; }
|
||||||
public string IssuingCaCertificatePem { get; set; }
|
public string IssuingCaCertificatePem { get; set; }
|
||||||
public string PrivateKeyPem { get; set; }
|
public string PrivateKeyPem { get; set; }
|
||||||
|
public string Status { get; set; }
|
||||||
|
public string StatusMessage { get; set; }
|
||||||
|
public string CertificateRequestId { get; set; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -220,6 +220,9 @@ namespace PSInfisicalAPI.Pki
|
|||||||
result.CertificatePem = signed.CertificatePem;
|
result.CertificatePem = signed.CertificatePem;
|
||||||
result.CertificateChainPem = signed.CertificateChainPem;
|
result.CertificateChainPem = signed.CertificateChainPem;
|
||||||
result.PrivateKeyPem = signed.PrivateKeyPem;
|
result.PrivateKeyPem = signed.PrivateKeyPem;
|
||||||
|
result.Status = signed.Status;
|
||||||
|
result.StatusMessage = signed.StatusMessage;
|
||||||
|
result.CertificateRequestId = signed.CertificateRequestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<X509Certificate2> chainCerts = signed != null ? CollectChainCertificates(signed) : new List<X509Certificate2>();
|
List<X509Certificate2> chainCerts = signed != null ? CollectChainCertificates(signed) : new List<X509Certificate2>();
|
||||||
|
|||||||
@@ -340,7 +340,14 @@ namespace PSInfisicalAPI.Pki
|
|||||||
{
|
{
|
||||||
string status = dto != null ? dto.Status : "unknown";
|
string status = dto != null ? dto.Status : "unknown";
|
||||||
string message = dto != null ? dto.Message : null;
|
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."));
|
string requestId = dto != null ? dto.CertificateRequestId : null;
|
||||||
|
_logger.Warning(Component, string.Concat("Profile issuance did not return a certificate (status='", status ?? "unknown", "'", string.IsNullOrEmpty(message) ? "" : string.Concat(", message='", message, "'"), string.IsNullOrEmpty(requestId) ? "" : string.Concat(", certificateRequestId='", requestId, "'"), "). The profile may require manual approval or additional validation; returning a status-only result."));
|
||||||
|
return new InfisicalSignedCertificate
|
||||||
|
{
|
||||||
|
Status = status,
|
||||||
|
StatusMessage = message,
|
||||||
|
CertificateRequestId = requestId
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
InfisicalSignedCertificate signed = new InfisicalSignedCertificate
|
InfisicalSignedCertificate signed = new InfisicalSignedCertificate
|
||||||
@@ -348,7 +355,10 @@ namespace PSInfisicalAPI.Pki
|
|||||||
SerialNumber = dto.Certificate.SerialNumber,
|
SerialNumber = dto.Certificate.SerialNumber,
|
||||||
CertificatePem = dto.Certificate.Certificate,
|
CertificatePem = dto.Certificate.Certificate,
|
||||||
CertificateChainPem = dto.Certificate.CertificateChain,
|
CertificateChainPem = dto.Certificate.CertificateChain,
|
||||||
IssuingCaCertificatePem = dto.Certificate.IssuingCaCertificate
|
IssuingCaCertificatePem = dto.Certificate.IssuingCaCertificate,
|
||||||
|
Status = dto.Status,
|
||||||
|
StatusMessage = dto.Message,
|
||||||
|
CertificateRequestId = dto.CertificateRequestId
|
||||||
};
|
};
|
||||||
_logger.Information(Component, "Infisical certificate issuance (profile) was successful.");
|
_logger.Information(Component, "Infisical certificate issuance (profile) was successful.");
|
||||||
return signed;
|
return signed;
|
||||||
|
|||||||
Reference in New Issue
Block a user