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

Merged
gsadmin merged 27 commits from dev into main 2026-06-05 01:24:51 +00:00
8 changed files with 142 additions and 7 deletions
Showing only changes of commit ebabd6cf26 - Show all commits
+1
View File
@@ -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; }
}
}