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:
2026-07-31 22:02:24 -04:00
parent 74d22a941c
commit 3015138484
4 changed files with 213 additions and 61 deletions
+4
View File
@@ -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._
+2 -2
View File
@@ -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