6 Commits

Author SHA1 Message Date
gsadmin b27fe6f002 Drop a stale variable from the profile creation log line
The line still interpolated $enrollmentType, which went away when profile
creation stopped varying by enrollment method. Every run since had adopted
existing profiles, so the create path never executed and never surfaced it.
A run against an emptied project failed there immediately.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 22:34:27 -04:00
gsadmin c75cc145cb Enable enrollment protocols on the application, not by cloning profiles
The enrollment methods were not appearing in the UI because they were being set
in the wrong place. A profile's own enrollmentType is one value, but that is
only the base; what the UI lists under an application - "configure how this
application will issue certificates via API, EST, ACME, or SCEP" - is a
separate configuration on the application-to-profile link, and those are
independent. A single profile can answer all four at once.

That replaces the previous approach. The mecm-...-scep and ec-...-acme profiles
are gone; there are three profiles again, one per policy, and each declares an
Enrollment block naming the protocols it should answer. Applied with PUT to
/applications/{id}/profiles/{id}/enrollment/{api,scep,acme}, which is idempotent
by nature, so a re-run restates the same settings. The routes take PUT, not
POST, and a POST there returns 404.

SCEP now uses a dynamic challenge, so there is no shared secret to distribute
or rotate; each request collects a one-time password from the challenge
endpoint. -ScepChallengePassword is kept for anyone who sets ChallengeType to
static, and is otherwise unused.

Enabling a protocol is what makes Infisical mint its endpoint - a SCEP URL,
challenge URL and RA certificate, or an ACME directory URL - so the run reads
them back and prints them, and returns them under Enrollment. None of it can be
derived from the configuration alone.

Verified against a live instance: scepConfigured and acmeConfigured come back
true on the intended profiles, the dynamic challenge settings round-trip, and a
re-run restates without creating anything.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 22:28:35 -04:00
gsadmin c40160f789 Seed SCEP and ACME enrollment alongside API, and auto-renew everywhere
A profile carries exactly one enrollment method - Infisical rejects a mixed one
with "API enrollment type cannot have EST, ACME, or SCEP configuration" - so
reaching a policy over more than one method means a profile per method, sharing
the CA and policy. Added mecm-server-client-auth-scep and
ec-server-client-auth-acme next to the three API profiles.

SCEP for the RSA policy because that is how domain-joined Windows and mobile
clients enrol without a token, which is the NDES role SCCM/MECM would otherwise
need; RSA because SCEP client support for EC keys is patchy. ACME for the EC
server/client policy, for workloads that already speak it. Code signing stays
API only: SCEP and ACME prove control of a device or a DNS name, and neither
says anything about who may sign code.

A static SCEP profile needs a challenge password of at least eight characters.
Added -ScepChallengePassword, and when it is omitted the run generates one and
prints it once at the end, because Infisical will not hand it back afterwards.

ACME sets skipDnsOwnershipVerification. These are internal names from an
internal CA, and the DNS-01 challenge proves control of a public zone, so
leaving it enforced makes the profile unusable rather than safer. The comment
says so and says when to turn it off.

Auto-renew is now on for every API profile including code signing, at fourteen
days. It is an api-only setting; neither SCEP nor ACME has an equivalent,
because both have the client drive renewal itself.

Verified against a live instance: profiles create with their config ids
populated, a re-run creates nothing and does not reissue the challenge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 22:17:38 -04:00
gsadmin 3015138484 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>
2026-07-31 22:02:24 -04:00
gsadmin 74d22a941c Fix the organization listing endpoint and stop blaming the token for it
Seeding a real instance failed with "Route GET:/api/v2/organizations not found".
The route genuinely does not exist: /api/v2/organizations mounts only
/:organizationId/* sub-routes, and the listing lives on the singular
GET /api/v1/organization, which returns the same { organizations: [...] } shape.

The module had the same defect, so Get-InfisicalOrganization would have 404d on
any current server. ListOrganizations now prefers /api/v1/organization with the
v2 route retained as a fallback candidate, and the organization client invokes
with candidate fallback so the fallback can actually be reached. The endpoint
shape test expected the broken template and now expects the working one.

The script reported this as "The supplied -AccessToken was rejected ... it may
have expired", which sent the reader after a token that was seconds old and
perfectly valid. A 404 or a connection failure says nothing about the token, so
only a 401 or 403 now produces that message; anything else reports that the
instance could not be reached and states plainly that it is not a token problem.

The token probe also moves to /api/v1/projects, which is the next call the
script makes anyway, so a successful probe proves both the token and a route the
run actually depends on rather than one chosen only for validation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 21:23:33 -04:00
gsadmin f2a1492b66 Actually commit the seeding script, and let it authenticate with a token
The seeding script was never in the repository. .gitignore carries "scripts/"
for local helper scripts, core.ignorecase is true on Windows, so Scripts/
matched it and git add -A silently skipped the file. The commit that claimed to
add the script contained only its CHANGELOG and README entries; the script
itself existed on disk and nowhere else. It now lives in Tools/, which is not
ignored, and the ignore rule says why so this cannot swallow shipped tooling
again.

The script also gains an -AccessToken parameter set alongside
-ClientId/-ClientSecret, so a throwaway instance can be seeded with the bearer
token from a signed-in browser session rather than by first creating a machine
identity. The two are mutually exclusive parameter sets, so supplying both is
rejected at binding rather than silently preferring one.

A supplied token is proven with a single read before anything is created. An
expired browser token is the common case, and failing there is much better than
failing midway through building a CA hierarchy and leaving a half-seeded
project behind; the error says as much and suggests copying a fresh one.

Renaming the internal token variable was not cosmetic. The script held the
bearer token in $Script:AccessToken, the same name as the new parameter, so
PowerShell applied the parameter's [ValidateNotNull()] to every assignment and
both authentication paths failed with "the value $null is not a valid value for
the AccessToken variable" before reaching the network. It is now
$Script:BearerToken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 20:36:02 -04:00