Make the seeding script survive real API responses and emit complete policies
Ran the script against a live Infisical instance for the first time. It failed
at four separate points, each fixed here.
Responses are read through Get-ApiProperty/Get-ApiCollection/Get-ApiObject
rather than directly. Set-StrictMode makes an absent property terminating, and
the cert-manager routes return bare payloads - a naked array for a list, the
resource itself for a create - where the secrets and pki routes wrap theirs in
a named property. All eighteen reads now handle either shape. The
application-to-profile listing is a join row carrying profileId, not a profile
object with an id, so it is read accordingly.
Renamed the local $configuration inside the CA loop to $caConfiguration.
PowerShell variable names are case-insensitive, so from the second CA onward it
shadowed the script-level $Configuration block and every lookup into it failed.
Renamed the $profile loop variables for the same reason: $profile is an
automatic variable.
Policies now carry subject, sans, and signature algorithms, and profiles carry
defaults. The previous comment claimed omitting subject and sans left them
unconstrained; the opposite is true. Infisical refuses any attribute with no
policy entry ("no subject policies defined"), refuses a policy that defines no
signature algorithm, and refuses a request missing a usage the policy marks
required - Request-InfisicalCertificate sends none of its own, so the profile
default has to supply it. The enumeration list in the header dropped
domain_component, upn, and any_purpose, which the API does not accept.
Verified end to end: a clean run builds both CA hierarchies including the
subordinate CSR-sign-import sequence, and a re-run creates nothing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,10 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 2026.08.01.0140
|
||||
|
||||
_Build produced from commit 74d22a941c33._
|
||||
|
||||
## 2026.08.01.0123
|
||||
|
||||
_Build produced from commit f2a1492b66de._
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@{
|
||||
RootModule = 'PSInfisicalAPI.psm1'
|
||||
ModuleVersion = '2026.08.01.0123'
|
||||
ModuleVersion = '2026.08.01.0140'
|
||||
GUID = 'b8a2f3d4-7c51-4d2f-9e6a-1f0c8b3d4e51'
|
||||
Author = 'Grace Solutions'
|
||||
CompanyName = 'Grace Solutions'
|
||||
@@ -89,7 +89,7 @@
|
||||
LicenseUri = 'https://www.gnu.org/licenses/agpl-3.0.html'
|
||||
ProjectUri = 'https://prod.git.gracesolution.info/gsadmin/PSInfisicalAPI'
|
||||
ReleaseNotes = 'See CHANGELOG.md in the project repository for release history.'
|
||||
CommitHash = 'f2a1492b66de'
|
||||
CommitHash = '74d22a941c33'
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -116,13 +116,15 @@ Set-StrictMode -Version Latest
|
||||
|
||||
Enumeration values are the ones Infisical accepts verbatim:
|
||||
KeyAlgorithm RSA_2048 | RSA_3072 | RSA_4096 | EC_prime256v1 | EC_secp384r1 | EC_secp521r1
|
||||
SignatureAlgorithm RSA-SHA256 | RSA-SHA384 | RSA-SHA512 | ECDSA-SHA256 | ECDSA-SHA384 |
|
||||
ECDSA-SHA512
|
||||
KeyUsages digital_signature | key_encipherment | non_repudiation | data_encipherment |
|
||||
key_agreement | key_cert_sign | crl_sign | encipher_only | decipher_only
|
||||
ExtendedKeyUsages server_auth | client_auth | code_signing | email_protection | ocsp_signing |
|
||||
time_stamping | any_purpose
|
||||
ExtendedKeyUsages client_auth | server_auth | code_signing | email_protection | ocsp_signing |
|
||||
time_stamping
|
||||
SubjectAttribute common_name | organization | country | state | locality |
|
||||
organizational_unit | domain_component
|
||||
SanType dns_name | ip_address | email | uri | upn
|
||||
organizational_unit
|
||||
SanType dns_name | ip_address | email | uri
|
||||
#>
|
||||
|
||||
$Configuration = [Ordered]@{
|
||||
@@ -197,46 +199,80 @@ Set-StrictMode -Version Latest
|
||||
|
||||
CertificatePolicies = @(
|
||||
<#
|
||||
Subject and SubjectAltNames are deliberately left out below. A constraint is only sent when
|
||||
it carries values, and omitting these leaves the common name and SAN list unconstrained,
|
||||
which is what fleet enrollment needs: every machine presents its own name.
|
||||
Every attribute a request carries must have a policy entry, or issuance is refused with
|
||||
"not allowed by template policy (no subject policies defined)". An entry of Allowed = @('*')
|
||||
is therefore not decoration: it is what keeps the common name and SAN list open, which is
|
||||
what fleet enrollment needs, since every machine presents its own name. Narrow a value by
|
||||
replacing the wildcard, for example Allowed = @('*.contoso.com').
|
||||
|
||||
To constrain them, add entries carrying values, for example:
|
||||
Subject = @( [Ordered]@{ Type = 'organization'; Allowed = @('Contoso') } )
|
||||
SubjectAltNames = @( [Ordered]@{ Type = 'dns_name'; Allowed = @('*.contoso.com') } )
|
||||
SignatureAlgorithms is likewise mandatory. A policy that omits it rejects every request with
|
||||
"Signature algorithm ... not defined in template".
|
||||
#>
|
||||
[Ordered]@{
|
||||
Name = 'mecm-server-client-auth'
|
||||
Description = 'SCCM/MECM site systems and clients. RSA, server and client authentication.'
|
||||
MaxValidity = '365d'
|
||||
KeyAlgorithms = @('RSA_2048', 'RSA_3072', 'RSA_4096')
|
||||
KeyUsages = [Ordered]@{ Required = @('digital_signature', 'key_encipherment') }
|
||||
ExtendedKeyUsages = [Ordered]@{ Required = @('server_auth', 'client_auth') }
|
||||
Subject = @()
|
||||
SubjectAltNames = @()
|
||||
Name = 'mecm-server-client-auth'
|
||||
Description = 'SCCM/MECM site systems and clients. RSA, server and client authentication.'
|
||||
MaxValidity = '365d'
|
||||
KeyAlgorithms = @('RSA_2048', 'RSA_3072', 'RSA_4096')
|
||||
SignatureAlgorithms = @('RSA-SHA256', 'RSA-SHA384', 'RSA-SHA512')
|
||||
KeyUsages = [Ordered]@{ Required = @('digital_signature', 'key_encipherment') }
|
||||
ExtendedKeyUsages = [Ordered]@{ Required = @('server_auth', 'client_auth') }
|
||||
Subject = @(
|
||||
[Ordered]@{ Type = 'common_name'; Allowed = @('*') }
|
||||
[Ordered]@{ Type = 'organization'; Allowed = @('*') }
|
||||
[Ordered]@{ Type = 'organizational_unit'; Allowed = @('*') }
|
||||
[Ordered]@{ Type = 'country'; Allowed = @('*') }
|
||||
[Ordered]@{ Type = 'state'; Allowed = @('*') }
|
||||
[Ordered]@{ Type = 'locality'; Allowed = @('*') }
|
||||
)
|
||||
SubjectAltNames = @(
|
||||
[Ordered]@{ Type = 'dns_name'; Allowed = @('*') }
|
||||
[Ordered]@{ Type = 'ip_address'; Allowed = @('*') }
|
||||
)
|
||||
}
|
||||
[Ordered]@{
|
||||
Name = 'ec-server-client-auth'
|
||||
Description = 'General workload certificates. ECDSA P-384, server and client authentication.'
|
||||
MaxValidity = '90d'
|
||||
KeyAlgorithms = @('EC_secp384r1')
|
||||
KeyUsages = [Ordered]@{ Required = @('digital_signature') }
|
||||
ExtendedKeyUsages = [Ordered]@{ Required = @('server_auth', 'client_auth') }
|
||||
Subject = @()
|
||||
SubjectAltNames = @()
|
||||
Name = 'ec-server-client-auth'
|
||||
Description = 'General workload certificates. ECDSA P-384, server and client authentication.'
|
||||
MaxValidity = '90d'
|
||||
KeyAlgorithms = @('EC_secp384r1')
|
||||
SignatureAlgorithms = @('ECDSA-SHA384')
|
||||
KeyUsages = [Ordered]@{ Required = @('digital_signature') }
|
||||
ExtendedKeyUsages = [Ordered]@{ Required = @('server_auth', 'client_auth') }
|
||||
Subject = @(
|
||||
[Ordered]@{ Type = 'common_name'; Allowed = @('*') }
|
||||
[Ordered]@{ Type = 'organization'; Allowed = @('*') }
|
||||
[Ordered]@{ Type = 'organizational_unit'; Allowed = @('*') }
|
||||
[Ordered]@{ Type = 'country'; Allowed = @('*') }
|
||||
[Ordered]@{ Type = 'state'; Allowed = @('*') }
|
||||
[Ordered]@{ Type = 'locality'; Allowed = @('*') }
|
||||
)
|
||||
SubjectAltNames = @(
|
||||
[Ordered]@{ Type = 'dns_name'; Allowed = @('*') }
|
||||
[Ordered]@{ Type = 'ip_address'; Allowed = @('*') }
|
||||
)
|
||||
}
|
||||
[Ordered]@{
|
||||
Name = 'ec-code-signing'
|
||||
Description = 'Code signing. ECDSA P-384, code signing only.'
|
||||
MaxValidity = '365d'
|
||||
KeyAlgorithms = @('EC_secp384r1')
|
||||
KeyUsages = [Ordered]@{ Required = @('digital_signature') }
|
||||
ExtendedKeyUsages = [Ordered]@{ Required = @('code_signing'); Denied = @('server_auth', 'client_auth') }
|
||||
Subject = @()
|
||||
SubjectAltNames = @()
|
||||
Name = 'ec-code-signing'
|
||||
Description = 'Code signing. ECDSA P-384, code signing only.'
|
||||
MaxValidity = '365d'
|
||||
KeyAlgorithms = @('EC_secp384r1')
|
||||
SignatureAlgorithms = @('ECDSA-SHA384')
|
||||
KeyUsages = [Ordered]@{ Required = @('digital_signature') }
|
||||
ExtendedKeyUsages = [Ordered]@{ Required = @('code_signing'); Denied = @('server_auth', 'client_auth') }
|
||||
Subject = @(
|
||||
[Ordered]@{ Type = 'common_name'; Allowed = @('*') }
|
||||
[Ordered]@{ Type = 'organization'; Allowed = @('*') }
|
||||
[Ordered]@{ Type = 'organizational_unit'; Allowed = @('*') }
|
||||
[Ordered]@{ Type = 'country'; Allowed = @('*') }
|
||||
)
|
||||
SubjectAltNames = @()
|
||||
}
|
||||
)
|
||||
|
||||
<#
|
||||
Defaults fill in what a requester leaves out. They are required whenever the policy marks a
|
||||
constraint Required: Request-InfisicalCertificate does not send key usages of its own, so
|
||||
without a default the request arrives empty and is refused with "Missing required key usages".
|
||||
#>
|
||||
CertificateProfiles = @(
|
||||
[Ordered]@{
|
||||
Slug = 'mecm-server-client-auth'
|
||||
@@ -245,6 +281,12 @@ Set-StrictMode -Version Latest
|
||||
CertificatePolicy = 'mecm-server-client-auth'
|
||||
AutoRenew = $True
|
||||
RenewBeforeDays = 14
|
||||
Defaults = [Ordered]@{
|
||||
KeyAlgorithm = 'RSA_2048'
|
||||
SignatureAlgorithm = 'RSA-SHA256'
|
||||
KeyUsages = @('digital_signature', 'key_encipherment')
|
||||
ExtendedKeyUsages = @('server_auth', 'client_auth')
|
||||
}
|
||||
}
|
||||
[Ordered]@{
|
||||
Slug = 'ec-server-client-auth'
|
||||
@@ -253,6 +295,12 @@ Set-StrictMode -Version Latest
|
||||
CertificatePolicy = 'ec-server-client-auth'
|
||||
AutoRenew = $True
|
||||
RenewBeforeDays = 14
|
||||
Defaults = [Ordered]@{
|
||||
KeyAlgorithm = 'EC_secp384r1'
|
||||
SignatureAlgorithm = 'ECDSA-SHA384'
|
||||
KeyUsages = @('digital_signature')
|
||||
ExtendedKeyUsages = @('server_auth', 'client_auth')
|
||||
}
|
||||
}
|
||||
[Ordered]@{
|
||||
Slug = 'ec-code-signing'
|
||||
@@ -261,6 +309,12 @@ Set-StrictMode -Version Latest
|
||||
CertificatePolicy = 'ec-code-signing'
|
||||
AutoRenew = $False
|
||||
RenewBeforeDays = $Null
|
||||
Defaults = [Ordered]@{
|
||||
KeyAlgorithm = 'EC_secp384r1'
|
||||
SignatureAlgorithm = 'ECDSA-SHA384'
|
||||
KeyUsages = @('digital_signature')
|
||||
ExtendedKeyUsages = @('code_signing')
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -282,6 +336,62 @@ Set-StrictMode -Version Latest
|
||||
Write-Host ("{0} {1}" -f $prefix, $Message)
|
||||
}
|
||||
|
||||
function Get-ApiProperty {
|
||||
<#
|
||||
Reads a property that may not be present. Set-StrictMode makes an absent property a terminating
|
||||
error, and response shapes vary across Infisical versions and route namespaces, so every read of an
|
||||
API response goes through here rather than risking a crash on a field the server did not send.
|
||||
#>
|
||||
param($InputObject, [Parameter(Mandatory = $True)][String]$Name)
|
||||
|
||||
if ($Null -eq $InputObject) { return $Null }
|
||||
|
||||
$property = $InputObject.PSObject.Properties[$Name]
|
||||
if ($Null -eq $property) { return $Null }
|
||||
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Get-ApiCollection {
|
||||
<#
|
||||
Returns a list from a response, as an array. Infisical is not consistent about wrapping: the
|
||||
secrets and pki routes wrap a list in a named property, while the cert-manager routes return a
|
||||
bare JSON array. Named properties are tried first, then the response itself if it is already a
|
||||
list, so one call site handles either shape.
|
||||
#>
|
||||
param($InputObject, [Parameter(Mandatory = $True)][String[]]$Name)
|
||||
|
||||
foreach ($candidate in $Name) {
|
||||
$value = Get-ApiProperty -InputObject $InputObject -Name $candidate
|
||||
if ($Null -ne $value) { return @($value) }
|
||||
}
|
||||
|
||||
# A bare array carries no named property to find, so fall through to the response itself. Strings are
|
||||
# excluded because they are enumerable but are never a list of resources.
|
||||
if ($Null -ne $InputObject -and $InputObject -isnot [String] -and $InputObject -is [System.Collections.IEnumerable]) {
|
||||
return @($InputObject)
|
||||
}
|
||||
|
||||
return @()
|
||||
}
|
||||
|
||||
function Get-ApiObject {
|
||||
<#
|
||||
Returns a single resource from a response, handling the same wrapped-or-bare split as
|
||||
Get-ApiCollection. An unwrapped response is recognised by carrying an id of its own.
|
||||
#>
|
||||
param($InputObject, [Parameter(Mandatory = $True)][String[]]$Name)
|
||||
|
||||
foreach ($candidate in $Name) {
|
||||
$value = Get-ApiProperty -InputObject $InputObject -Name $candidate
|
||||
if ($Null -ne $value) { return $value }
|
||||
}
|
||||
|
||||
if ($Null -ne (Get-ApiProperty -InputObject $InputObject -Name 'id')) { return $InputObject }
|
||||
|
||||
return $Null
|
||||
}
|
||||
|
||||
function ConvertFrom-SecureStringToPlainText {
|
||||
param([System.Security.SecureString]$Value)
|
||||
$pointer = [System.Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode($Value)
|
||||
@@ -381,11 +491,11 @@ Set-StrictMode -Version Latest
|
||||
}
|
||||
|
||||
$response = Invoke-InfisicalApi -Method 'POST' -Path '/api/v1/auth/universal-auth/login' -Body $body -NoAuth
|
||||
if ($Null -eq $response -or [String]::IsNullOrWhiteSpace($response.accessToken)) {
|
||||
if ($Null -eq $response -or [String]::IsNullOrWhiteSpace((Get-ApiProperty -InputObject $response -Name 'accessToken'))) {
|
||||
throw 'Authentication succeeded but returned no access token.'
|
||||
}
|
||||
|
||||
$Script:BearerToken = $response.accessToken
|
||||
$Script:BearerToken = Get-ApiProperty -InputObject $response -Name 'accessToken'
|
||||
Write-Step 'Authenticated with universal auth.' -Status 'Sub'
|
||||
}
|
||||
|
||||
@@ -394,7 +504,7 @@ Set-StrictMode -Version Latest
|
||||
|
||||
# The singular v1 route is the listing; /api/v2/organizations mounts only /:organizationId/* sub-routes.
|
||||
$response = Invoke-InfisicalApi -Method 'GET' -Path '/api/v1/organization'
|
||||
$organizations = @($response.organizations)
|
||||
$organizations = Get-ApiCollection -InputObject $response -Name 'organizations'
|
||||
if ($organizations.Count -eq 0) { throw 'No organizations are visible to this identity; pass -OrganizationId.' }
|
||||
if ($organizations.Count -gt 1) {
|
||||
$names = ($organizations | ForEach-Object { "$($_.name) ($($_.id))" }) -join ', '
|
||||
@@ -421,7 +531,7 @@ Set-StrictMode -Version Latest
|
||||
Write-Step 'Project'
|
||||
|
||||
$response = Invoke-InfisicalApi -Method 'GET' -Path '/api/v1/projects'
|
||||
$all = @($response.projects) + @($response.workspaces) | Where-Object { $Null -ne $_ }
|
||||
$all = Get-ApiCollection -InputObject $response -Name 'projects','workspaces'
|
||||
$certManagerProjects = @($all | Where-Object { $_.type -eq 'cert-manager' })
|
||||
|
||||
if ($certManagerProjects.Count -gt 0) {
|
||||
@@ -452,7 +562,7 @@ Set-StrictMode -Version Latest
|
||||
}
|
||||
|
||||
$created = Invoke-InfisicalApi -Method 'POST' -Path '/api/v2/workspace' -Body $body
|
||||
$project = $created.project
|
||||
$project = Get-ApiObject -InputObject $created -Name 'project','workspace'
|
||||
Write-Step "project '$($project.name)' ($($project.id))" -Status 'Created'
|
||||
return $project
|
||||
}
|
||||
@@ -479,7 +589,7 @@ Set-StrictMode -Version Latest
|
||||
$existing = $Null
|
||||
if (-not [String]::IsNullOrWhiteSpace($ProjectId)) {
|
||||
$response = Invoke-InfisicalApi -Method 'GET' -Path "/api/v1/cert-manager/applications?projectId=$ProjectId"
|
||||
$existing = @($response.applications) | Where-Object { $_.name -eq $Configuration.Application.Name } | Select-Object -First 1
|
||||
$existing = (Get-ApiCollection -InputObject $response -Name 'applications') | Where-Object { $_.name -eq $Configuration.Application.Name } | Select-Object -First 1
|
||||
}
|
||||
|
||||
if ($Null -ne $existing) {
|
||||
@@ -502,7 +612,7 @@ Set-StrictMode -Version Latest
|
||||
if ($profileIds.Count -gt 0) { $body.profileIds = $profileIds }
|
||||
|
||||
$created = Invoke-InfisicalApi -Method 'POST' -Path '/api/v1/cert-manager/applications' -Body $body
|
||||
$application = $created.application
|
||||
$application = Get-ApiObject -InputObject $created -Name 'application'
|
||||
Write-Step "application '$($application.name)' ($($application.id)) with $($profileIds.Count) profile(s)" -Status 'Created'
|
||||
return $application
|
||||
}
|
||||
@@ -513,7 +623,13 @@ Set-StrictMode -Version Latest
|
||||
if (@($ProfileIds).Count -eq 0) { return }
|
||||
|
||||
$response = Invoke-InfisicalApi -Method 'GET' -Path "/api/v1/cert-manager/applications/$($Application.id)/profiles?projectId=$ProjectId"
|
||||
$attached = @($response.profiles | ForEach-Object { $_.id })
|
||||
# This route returns application-to-profile join rows, which carry the profile under profileId rather
|
||||
# than as an object with an id of its own.
|
||||
$attached = @((Get-ApiCollection -InputObject $response -Name 'profiles') | ForEach-Object {
|
||||
$attachedId = Get-ApiProperty -InputObject $_ -Name 'profileId'
|
||||
if ($Null -eq $attachedId) { $attachedId = Get-ApiProperty -InputObject $_ -Name 'id' }
|
||||
$attachedId
|
||||
} | Where-Object { $Null -ne $_ })
|
||||
$missing = @($ProfileIds | Where-Object { $attached -notcontains $_ })
|
||||
|
||||
if ($missing.Count -eq 0) {
|
||||
@@ -539,7 +655,7 @@ Set-StrictMode -Version Latest
|
||||
$existingByName = @{}
|
||||
if (-not [String]::IsNullOrWhiteSpace($ProjectId)) {
|
||||
$response = Invoke-InfisicalApi -Method 'GET' -Path "/api/v1/cert-manager/ca/internal?projectId=$ProjectId"
|
||||
foreach ($ca in @($response.certificateAuthorities)) { $existingByName[$ca.name] = $ca }
|
||||
foreach ($ca in (Get-ApiCollection -InputObject $response -Name 'certificateAuthorities','cas')) { $existingByName[$ca.name] = $ca }
|
||||
}
|
||||
|
||||
foreach ($definition in $Configuration.CertificateAuthorities) {
|
||||
@@ -558,7 +674,7 @@ Set-StrictMode -Version Latest
|
||||
|
||||
$notAfter = [DateTime]::UtcNow.AddYears($definition.ValidityYears).ToString('yyyy-MM-ddTHH:mm:ss.fffZ')
|
||||
|
||||
$configuration = [Ordered]@{
|
||||
$caConfiguration = [Ordered]@{
|
||||
type = $definition.Type
|
||||
commonName = $definition.CommonName
|
||||
organization = $Configuration.Organization.Name
|
||||
@@ -572,19 +688,19 @@ Set-StrictMode -Version Latest
|
||||
# Infisical only self-signs on creation for a root, and only when it is given an expiry. A
|
||||
# subordinate is created pending and signed in the step below.
|
||||
if ($definition.Type -eq 'root') {
|
||||
$configuration.notAfter = $notAfter
|
||||
$configuration.maxPathLength = $definition.MaxPathLength
|
||||
$caConfiguration.notAfter = $notAfter
|
||||
$caConfiguration.maxPathLength = $definition.MaxPathLength
|
||||
}
|
||||
|
||||
$body = @{
|
||||
projectId = $ProjectId
|
||||
name = $definition.Name
|
||||
status = 'active'
|
||||
configuration = $configuration
|
||||
configuration = $caConfiguration
|
||||
}
|
||||
|
||||
$created = Invoke-InfisicalApi -Method 'POST' -Path '/api/v1/cert-manager/ca/internal' -Body $body
|
||||
$ca = $created.certificateAuthority
|
||||
$ca = Get-ApiObject -InputObject $created -Name 'certificateAuthority','ca'
|
||||
$resolved[$definition.Name] = $ca
|
||||
Write-Step "$($definition.Type) CA '$($definition.Name)' ($($ca.id))" -Status 'Created'
|
||||
|
||||
@@ -616,12 +732,12 @@ Set-StrictMode -Version Latest
|
||||
Write-Step "signing '$($Definition.Name)' with '$($Definition.Parent)'" -Status 'Sub'
|
||||
|
||||
$csrResponse = Invoke-InfisicalApi -Method 'GET' -Path "/api/v1/pki/ca/$($Subordinate.id)/csr"
|
||||
if ([String]::IsNullOrWhiteSpace($csrResponse.csr)) {
|
||||
if ([String]::IsNullOrWhiteSpace((Get-ApiProperty -InputObject $csrResponse -Name 'csr'))) {
|
||||
throw "CA '$($Definition.Name)' returned no CSR to sign."
|
||||
}
|
||||
|
||||
$signBody = @{
|
||||
csr = $csrResponse.csr
|
||||
csr = Get-ApiProperty -InputObject $csrResponse -Name 'csr'
|
||||
notAfter = $NotAfter
|
||||
maxPathLength = $Definition.MaxPathLength
|
||||
}
|
||||
@@ -629,8 +745,8 @@ Set-StrictMode -Version Latest
|
||||
$signed = Invoke-InfisicalApi -Method 'POST' -Path "/api/v1/pki/ca/$($parent.id)/sign-intermediate" -Body $signBody
|
||||
|
||||
$importBody = @{
|
||||
certificate = $signed.certificate
|
||||
certificateChain = $signed.certificateChain
|
||||
certificate = Get-ApiProperty -InputObject $signed -Name 'certificate'
|
||||
certificateChain = Get-ApiProperty -InputObject $signed -Name 'certificateChain'
|
||||
}
|
||||
|
||||
$Null = Invoke-InfisicalApi -Method 'POST' -Path "/api/v1/pki/ca/$($Subordinate.id)/import-certificate" -Body $importBody
|
||||
@@ -646,7 +762,7 @@ Set-StrictMode -Version Latest
|
||||
$existingByName = @{}
|
||||
if (-not [String]::IsNullOrWhiteSpace($ProjectId)) {
|
||||
$response = Invoke-InfisicalApi -Method 'GET' -Path "/api/v1/cert-manager/certificate-policies?projectId=$ProjectId"
|
||||
foreach ($policy in @($response.certificatePolicies)) { $existingByName[$policy.name] = $policy }
|
||||
foreach ($policy in (Get-ApiCollection -InputObject $response -Name 'certificatePolicies','policies')) { $existingByName[$policy.name] = $policy }
|
||||
}
|
||||
|
||||
foreach ($definition in $Configuration.CertificatePolicies) {
|
||||
@@ -668,7 +784,10 @@ Set-StrictMode -Version Latest
|
||||
name = $definition.Name
|
||||
description = $definition.Description
|
||||
validity = @{ max = $definition.MaxValidity }
|
||||
algorithms = @{ keyAlgorithm = @($definition.KeyAlgorithms) }
|
||||
algorithms = @{
|
||||
keyAlgorithm = @($definition.KeyAlgorithms)
|
||||
signature = @($definition.SignatureAlgorithms)
|
||||
}
|
||||
}
|
||||
|
||||
$subject = @(ConvertTo-PolicyConstraintList -Definitions $definition.Subject)
|
||||
@@ -684,7 +803,7 @@ Set-StrictMode -Version Latest
|
||||
if ($Null -ne $extendedKeyUsages) { $body.extendedKeyUsages = $extendedKeyUsages }
|
||||
|
||||
$created = Invoke-InfisicalApi -Method 'POST' -Path '/api/v1/cert-manager/certificate-policies' -Body $body
|
||||
$policy = $created.certificatePolicy
|
||||
$policy = Get-ApiObject -InputObject $created -Name 'certificatePolicy'
|
||||
$resolved[$definition.Name] = $policy
|
||||
Write-Step "policy '$($definition.Name)' ($($policy.id))" -Status 'Created'
|
||||
}
|
||||
@@ -713,6 +832,32 @@ Set-StrictMode -Version Latest
|
||||
return $result
|
||||
}
|
||||
|
||||
function ConvertTo-ProfileDefaults {
|
||||
<#
|
||||
Profile defaults are flat: scalars for the algorithms and plain string arrays for the usages,
|
||||
unlike the allowed/required/denied objects a policy takes. Keys are camelCased to match the API.
|
||||
#>
|
||||
param($Definition)
|
||||
|
||||
if ($Null -eq $Definition) { return $Null }
|
||||
|
||||
$result = [Ordered]@{}
|
||||
foreach ($key in @('KeyAlgorithm', 'SignatureAlgorithm')) {
|
||||
if (-not $Definition.Contains($key)) { continue }
|
||||
if ([String]::IsNullOrWhiteSpace($Definition[$key])) { continue }
|
||||
$result[$key.Substring(0, 1).ToLowerInvariant() + $key.Substring(1)] = $Definition[$key]
|
||||
}
|
||||
foreach ($key in @('KeyUsages', 'ExtendedKeyUsages')) {
|
||||
if (-not $Definition.Contains($key)) { continue }
|
||||
$values = @($Definition[$key])
|
||||
if ($values.Count -eq 0) { continue }
|
||||
$result[$key.Substring(0, 1).ToLowerInvariant() + $key.Substring(1)] = $values
|
||||
}
|
||||
|
||||
if ($result.Count -eq 0) { return $Null }
|
||||
return $result
|
||||
}
|
||||
|
||||
function ConvertTo-PolicyConstraintList {
|
||||
param($Definitions)
|
||||
|
||||
@@ -738,7 +883,7 @@ Set-StrictMode -Version Latest
|
||||
$existingBySlug = @{}
|
||||
if (-not [String]::IsNullOrWhiteSpace($ProjectId)) {
|
||||
$response = Invoke-InfisicalApi -Method 'GET' -Path "/api/v1/cert-manager/certificate-profiles?projectId=$ProjectId"
|
||||
foreach ($profile in @($response.certificateProfiles)) { $existingBySlug[$profile.slug] = $profile }
|
||||
foreach ($existingProfile in (Get-ApiCollection -InputObject $response -Name 'certificateProfiles','profiles')) { $existingBySlug[$existingProfile.slug] = $existingProfile }
|
||||
}
|
||||
|
||||
foreach ($definition in $Configuration.CertificateProfiles) {
|
||||
@@ -774,10 +919,13 @@ Set-StrictMode -Version Latest
|
||||
apiConfig = $apiConfig
|
||||
}
|
||||
|
||||
$defaults = ConvertTo-ProfileDefaults -Definition $definition.Defaults
|
||||
if ($Null -ne $defaults) { $body.defaults = $defaults }
|
||||
|
||||
$created = Invoke-InfisicalApi -Method 'POST' -Path '/api/v1/cert-manager/certificate-profiles' -Body $body
|
||||
$profile = $created.certificateProfile
|
||||
$resolved[$definition.Slug] = $profile
|
||||
Write-Step "profile '$($definition.Slug)' ($($profile.id))" -Status 'Created'
|
||||
$createdProfile = Get-ApiObject -InputObject $created -Name 'certificateProfile'
|
||||
$resolved[$definition.Slug] = $createdProfile
|
||||
Write-Step "profile '$($definition.Slug)' ($($createdProfile.id))" -Status 'Created'
|
||||
}
|
||||
|
||||
return $resolved
|
||||
|
||||
Reference in New Issue
Block a user