Self-hosted platform for standing up and running software factories on AWS: complete DevSecOps environments with secure delivery pipelines, supply-chain controls, compliance evidence and multi-tenant governance — in minutes, not months.
Issuing the personal keys people need, and provisioning the three machine credentials an AWS deployment cannot run without.
registry-push is not optional on AWS — synth wires it into every build image unconditionally.pipeline-builder/{orgId}/…, so each org you onboard needs its own.audit tokens is the check that matters — it lists every stored credential, its expiry, and whether it is a key at all.This runbook is what an operator does on a fresh install. It assumes the platform is deployed and you can sign in.
For why an opaque key is the only shape in which revocation actually works, see Authentication → Access keys. The key is a secret the server never had, so it can only be issued, never derived.
pb_pat_… for a person, pb_sa_… for a service account — and shown to you exactly once.registry-push credential CodeBuild presents as Basic auth — required, because synth wires it into every build image unconditionally;reporting:ingest credential the events Lambda reads.pipeline-manager audit tokens — every stored credential, its expiry, and whether it is a key at all.| Credential | Issued by | Notes |
|---|---|---|
| Personal access key | POST /user/keys, pipeline-manager auth pat |
A person’s own; step-up gated, listed at Dashboard → API Tokens |
The reporting:ingest webhook token on the Incident Reporting page |
Settings → Incident reporting | A scoped key, not a person’s credential |
Stored platform credential (pipeline-manager infra store-token) |
POST /organization/:id/service-accounts/:accountId/keys |
A service-account key. See Machine credentials below |
| CodeBuild’s registry credential | the …/registry-push secret |
registry:push scope, no Roles |
The pipeline-events Lambda’s credential |
the …/reporting-ingest secret |
reporting:ingest scope, exchanged per batch |
| Browser sessions | sign-in | Not keys — ES256 user tokens |
| Service-to-service tokens | signServiceToken |
Not keys — minted per call, 5-minute life |
The AWS secret schema is { username, password, platformUrl, … }, and
password is the canonical field every consumer reads. Its value is an opaque
pb_sa_… key.
Each holder creates their own, either in the UI (Dashboard → API Tokens → Access keys → Create key, step-up gated) or from the CLI:
pipeline-manager auth pat --name ci-deploy
# or, to export it directly:
eval $(pipeline-manager auth pat --name ci --quiet)
Both open a browser to approve the sign-in and the step-up — there is no
password flag on the CLI any more. That is not a convenience: the step-up is
satisfied by the account’s real factors (passkey, authenticator code),
which live in the browser and not in a flag. --no-browser prints the
verification URL instead, for a headless box.
--name is required; --expires-days defaults to 90; --scope mints a
least-privilege key carrying that one capability and no Roles.
The printed pb_pat_… value is what PLATFORM_TOKEN and
Authorization: Bearer … now expect. Copy it immediately — only its hash
is stored, so it is never shown again.
Two refusals to expect rather than debug. Creating a key needs a step-up, and
a machine credential can never satisfy one, so a key cannot mint another
key (403 HUMAN_SESSION_REQUIRED). And in an org that sets administrative
actions require MFA, a session with no second factor is refused up front
(401 MFA_REQUIRED, details.reason: org_admin_policy) — the org’s policy
asking, not the route.
Automation never holds a person’s credential here. Every job that needs to
talk to platform gets an org service account holding a pb_sa_… key — one
account per job, with the narrowest scope that job needs — so no automation
inherits someone’s authority, outlives their employment, or makes the audit trail
read as if they had personally pushed every image:
| Secret | Service account | Roles | Key scope | Read by |
|---|---|---|---|---|
pipeline-builder/{orgId}/platform |
platform-automation |
org admin | none | CDK synth/deploy (--store-tokens), the plugin-lookup Lambda |
pipeline-builder/{orgId}/registry-push |
registry-push |
none | registry:push |
CodeBuild’s secretsManagerCredentials |
pipeline-builder/{orgId}/reporting-ingest |
reporting-ingest |
none | reporting:ingest |
the event-ingestion Lambda |
store-token creates the account if it is missing, issues the key, writes the
secret, and only then retires the key the previous run stored — so an
interrupted run never leaves an empty secret.
--scope takes exactly the two catalogued values above (registry:push,
reporting:ingest); each selects its own service account. With no --scope the
key is issued on the org’s single full-privilege platform-automation account.
--account <name> overrides the account a key lands on, and --dry-run shows
what would be provisioned without calling platform or writing to Secrets
Manager — worth doing once per AWS account before the real run.
Both writes are step-up gated, so store-token needs the operator’s password.
Put it in the environment rather than on the command line (it shows in shell
history and in provision plan output):
export PLATFORM_BASE_URL=https://<your-platform>
export PLATFORM_PASSWORD='…' # the operator's own password
export AWS_REGION=<region>
# 1. The org's full-privilege automation credential (synth/deploy, plugin lookup)
pipeline-manager infra store-token --schedule
# 2. The CI registry credential CodeBuild presents as Basic auth. REQUIRED:
# synth wires `.../registry-push` into every build image unconditionally.
pipeline-manager infra store-token --scope registry:push --schedule
# 3. The event-ingestion credential the Lambda reads
pipeline-manager infra store-token --scope reporting:ingest --schedule
# 4. Redeploy the ingestion Lambda so it runs the key-exchanging handler
pipeline-manager infra setup-events --scoped-ingest
Then synth and deploy every pipeline (`pipeline-manager pipeline synth –id