diff --git a/README.md b/README.md index f8022f4..c04816c 100644 --- a/README.md +++ b/README.md @@ -161,11 +161,9 @@ $ConnectInfisicalParameters = New-Object -TypeName 'System.Collections.Specializ $Connection = Connect-Infisical @ConnectInfisicalParameters -$Application = Get-InfisicalCertificateApplication | Where-Object {($_.Name -ieq '2pint')} +$Application = Get-InfisicalCertificateApplication | Where-Object {($_.Name -ieq 'platform')} -$CertificateProfile = Get-InfisicalCertificateProfile -ApplicationId ($Application.Id) -IncludeConfigs | - Where-Object {($_.EnrollmentType -ieq 'api') -and ($_.Slug -imatch 'server')} | - Select-Object -First 1 +$CertificateProfile = Get-InfisicalCertificateProfile -ApplicationId ($Application.Id) -IncludeConfigs | Where-Object {($_.EnrollmentType -ieq 'api') -and ($_.Slug -imatch 'server')} | Select-Object -First 1 $SanList = Get-InfisicalSANList @@ -188,10 +186,30 @@ $Null = Disconnect-Infisical -Verbose Four calls: find the application, pick its profile, gather SANs, request. No project lookup — `-ProjectId` resolves itself — and no CA lookup, because the profile already binds its issuing CA and `-InstallChain` installs the whole chain regardless. -Note `$CertificateProfile` rather than `$Profile`: `$Profile` is an automatic variable in PowerShell (the path to the current profile script), and assigning to it works but shadows something the host relies on. - `-StoreName`/`-StoreLocation` are omitted deliberately — see [Where certificates get installed](#where-certificates-get-installed). +## Renewing that certificate + +Run the same request again with `-AllowRenewal`. Nothing else changes — the parameters below are the ones built above, so this is the shape to put on a schedule: + +```powershell +$RequestInfisicalCertificateParameters.AllowRenewal = $True +$RequestInfisicalCertificateParameters.RenewalThresholdDays = 30 + +$Certificate = Request-InfisicalCertificate @RequestInfisicalCertificateParameters +``` + +The call is safe to repeat. Outside the renewal window it finds the installed certificate and returns it untouched; inside the window it issues a replacement and installs it: + +```text +VERBOSE: Reuse search for CN=WEB01 scoped to certificate profile 'a42f8446-...' returned 1 active certificate(s). +VERBOSE: Reusing existing certificate (Thumbprint=F480A920..., NotAfter=2026-10-28 19:36:49Z). +``` + +Reuse is matched on the issuing profile *and* the requested SAN set, so the same script issues a new certificate — rather than silently returning the old one — whenever the profile changes or a name is added to `-DnsName`. `-Force` issues unconditionally, ignoring both the renewal window and any existing certificate. + +Run it daily and it does nothing until the certificate is within 30 days of expiry, then rotates it. Nothing to gate it with, and no state to keep. + ### Project scoping Opening **Certificate Manager** in the Infisical console never asks which project to use. The project is in the URL — `/organizations/{orgId}/projects/cert-manager/{projectId}/applications` — but it is chosen for you, and everything below it is presented as **applications**. That is because Infisical's own resolver takes the single Certificate Manager project when an organization has exactly one: @@ -256,12 +274,12 @@ The two are different levels, which is worth keeping straight when reading outpu | **Project** | The Certificate Manager project itself. One per organization in most setups. | `Get-InfisicalProject -Type cert-manager` | | **Application** | A grouping of profiles, members, and certificates inside that project. | `Get-InfisicalCertificateApplication` | -So a project named `mecm` can contain applications named `2pint` and `microsoft-endpoint-configuration-manager`; listing projects returns only `mecm`, because the others are not projects. Every application carries the `ProjectId` it belongs to. +So a project named `pki` can contain applications named `platform` and `endpoint-management`; listing projects returns only `pki`, because the others are not projects. Every application carries the `ProjectId` it belongs to, which is why that field is real rather than vestigial. Profiles can be filtered to an application, matching how the console groups them: ```powershell -$Application = Get-InfisicalCertificateApplication | Where-Object {($_.Name -eq '2pint')} +$Application = Get-InfisicalCertificateApplication | Where-Object {($_.Name -ieq 'platform')} Get-InfisicalCertificateProfile -ApplicationId $Application.Id -IncludeConfigs Get-InfisicalCertificate -ApplicationId $Application.Id ``` @@ -269,32 +287,15 @@ Get-InfisicalCertificate -ApplicationId $Application.Id ### Example output ```text -Id : 00000000-0000-0000-0000-000000000000 -Name : Microsoft Endpoint Configuration Manager -Slug : mecm -Description : -OrganizationId : 11111111-1111-1111-1111-111111111111 -Type : cert-manager -AutoCapitalization : False -EnvironmentSlugs : {dev, staging, prod} -CreatedAtUtc : 3/12/2026 8:32:52 PM +00:00 -UpdatedAtUtc : 6/21/2026 7:00:28 PM +00:00 - -Name : root-ca -CommonName : Contoso Root Certificate Authority -Type : internal -Status : active -KeyAlgorithm : RSA_2048 -NotAfter : 03/25/2036 00:00:00 -Id : 22222222-2222-2222-2222-222222222222 - -Name : intermediate-ca -CommonName : Contoso Intermediate Certificate Authority -Type : internal -Status : active -KeyAlgorithm : RSA_2048 -NotAfter : 03/25/2031 00:00:00 -Id : 33333333-3333-3333-3333-333333333333 +Id : 11111111-1111-1111-1111-111111111111 +ProjectId : 00000000-0000-0000-0000-000000000000 +Name : platform +Description : +ProfileCount : 3 +MemberCount : 2 +CertificateCount : 0 +CreatedAtUtc : 7/30/2026 10:05:37 PM +00:00 +UpdatedAtUtc : 7/30/2026 10:05:37 PM +00:00 Id : 44444444-4444-4444-4444-444444444444 ProjectId : 00000000-0000-0000-0000-000000000000 @@ -321,6 +322,7 @@ WEB01.contoso.com 127.0.0.1 ::1 +VERBOSE: [...] - [Information] - [GetInfisicalCertificateApplicationCmdlet] - -ProjectId was not supplied; resolved the organization's only Certificate Manager project 'Platform PKI' (00000000-0000-0000-0000-000000000000). VERBOSE: [...] - [Information] - [PkiClient] - Attempting to search Infisical certificates. Please Wait... VERBOSE: [...] - [Verbose] - [HttpClient] - Attempting HTTP POST to https://infisical.contoso.com/api/v1/projects/00000000-0000-0000-0000-000000000000/certificates/search. Please Wait... VERBOSE: [...] - [Verbose] - [HttpClient] - HTTP POST completed with status 200. @@ -467,8 +469,8 @@ Request-InfisicalCertificate @RequestInfisicalCertificateParameters -WhatIf A certificate profile binds a CA to a certificate policy. The policy constrains the subject, key usages, and extended key usages with `allowed`/`required`/`denied` lists, so the common name still varies per request while staying inside guardrails. ```powershell -Get-InfisicalCertificateProfile -ProjectId ($Project.Id) | Format-Table Id, Name, CaId -Get-InfisicalCertificatePolicy -ProjectId ($Project.Id) | Format-Table Id, Name +Get-InfisicalCertificateProfile | Format-Table Id, Slug, CaId, EnrollmentType +Get-InfisicalCertificatePolicy | Format-Table Id, Name ``` Profile issuance is the only path that does **not** consult the CA's direct-issuance flag — the service short-circuits it: @@ -482,7 +484,7 @@ So a profile issues successfully against a CA whose `EnableDirectIssuance` is `F #### Discovering subscribers ```powershell -Get-InfisicalPkiSubscriber -ProjectId ($Project.Id) | +Get-InfisicalPkiSubscriber | Format-Table Name, CommonName, Status, Ttl, CaId ``` @@ -515,10 +517,10 @@ const ca = await certificateAuthorityDAL.create({ projectId, name: resolvedCaNam So **every CA created through the API or UI has direct issuance disabled**, and nothing can turn it on afterwards. Use a certificate profile, which ignores the flag entirely. ```powershell -Get-InfisicalCertificateAuthority -ProjectId ($Project.Id) -Kind Internal | +Get-InfisicalCertificateAuthority -Kind Internal | Format-Table Name, CommonName, Status, EnableDirectIssuance -$Ca = Get-InfisicalCertificateAuthority -ProjectId ($Project.Id) -Kind Internal | +$Ca = Get-InfisicalCertificateAuthority -Kind Internal | Where-Object {($_.EnableDirectIssuance -eq $True)} | Select-Object -First 1 @@ -574,7 +576,7 @@ A failing item does not abort the batch: ```powershell 'web01', 'does-not-exist', 'web02' | - ForEach-Object { Get-InfisicalPkiSubscriber -ProjectId $ProjectId -Name $_ -ErrorAction SilentlyContinue } + ForEach-Object { Get-InfisicalPkiSubscriber -Name $_ -ErrorAction SilentlyContinue } # emits web01 and web02; the failure is available in $Error ``` @@ -674,7 +676,7 @@ To add a route: ### Adding a new cmdlet -Cmdlets live in `src/PSInfisicalAPI/Cmdlets/` and derive from `InfisicalCmdletBase`, which exposes `HttpClient`, `Logger`, `ResolveProjectId`, and `ThrowTerminatingForException`. Follow the consolidated discovery pattern when the cmdlet supports both list and single-record retrieval: +Cmdlets live in `src/PSInfisicalAPI/Cmdlets/` and derive from `InfisicalCmdletBase`, which exposes `HttpClient`, `Logger`, `ResolveCertManagerProjectId`, and `ThrowTerminatingForException`. Follow the consolidated discovery pattern when the cmdlet supports both list and single-record retrieval: ```csharp [Cmdlet(VerbsCommon.Get, "InfisicalPkiSubscriber", DefaultParameterSetName = "List")] @@ -699,7 +701,7 @@ After adding (or removing) a cmdlet: ```powershell $Params = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase) - $Params.ProjectId = (Get-InfisicalProject | Select-Object -First 1).Id + $Params.ProjectId = (Get-InfisicalProject -Type cert-manager | Select-Object -First 1).Id $Result = Get-InfisicalPkiSubscriber @Params ``` 4. Add a `## Unreleased` entry to `CHANGELOG.md` describing the change (mark removals of public cmdlets or parameters as **BREAKING**).