diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 1f6b979..fb96c21 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -811,7 +811,10 @@ UNBLOCKED, with the build spec at `docs/D-137-implementation-plan.md`** (Status line in design-decisions.md is the authority). Expect the first run to be RED by design: `admin` serves both a human and a service row, which is the defect the invariant names. Research capture: - `docs/audit/creds-creation-points-20260725.md`. + `docs/audit/creds-creation-points-20260725.md` -- its APPENDIX now carries the FULL + per-row inventory (55 MINT rows with file:line, host, destination, stage and + human/service classification), transcribed in-repo 2026-07-26 so the matrix SEED does + not depend on a session transcript. ## 9. Additional defect found while authoring (FIXED in sweep Batch 0.3, 2026-07-19 -- wrap-aware exclusion, GA-F15; history below) diff --git a/docs/audit/creds-creation-points-20260725.md b/docs/audit/creds-creation-points-20260725.md index 78d7a3f..33a2490 100644 --- a/docs/audit/creds-creation-points-20260725.md +++ b/docs/audit/creds-creation-points-20260725.md @@ -110,6 +110,105 @@ are the backlog the matrix exists to surface -- they should be admitted as `operator-terminal` and tracked as a debt to convert, not treated as parse errors. -Source: three read-only Explore agents, 2026-07-25. Full per-row tables (55 MINT rows -with file:line, host, destination, stage, and human/service classification) are in the -session transcript; the structural findings and all counts are reproduced above. +Source: three read-only Explore agents, 2026-07-25. The full per-row tables ARE NOW IN +THIS FILE (see APPENDIX below), transcribed 2026-07-26 before the originating session was +cleared -- they are the `creds-matrix.tsv` seed and must not depend on a transcript. + +--- + +# APPENDIX -- full per-row inventory (the matrix seed) + +Transcribed into the repo 2026-07-26 because the body above pointed at a session +transcript that was about to be cleared. These rows ARE the `creds-matrix.tsv` seed; +they are what `docs/D-137-implementation-plan.md` step 3 tells the build to read. +Classification: MINT = new secret material; MATERIALIZE/UPLOAD excluded (section E). + +## A. Scripted MINT points (repo code generates the secret) + +| # | file:line | Credential | For | Written to | Stage/step | Principal | +|---|---|---|---|---|---|---| +| A1 | `site-headend-install.sh:416-419` | `newpass()` primitive: `openssl rand -base64 30 \| tr -dc A-Za-z0-9 \| head -c 28`, 0600 | voffice1 | (helper) | Stage 2 | -- | +| A2 | `site-headend-install.sh:430` (`:151 SECRETS=/root/maas-secrets`) | PostgreSQL `maas` role password | region DB | `voffice1:/root/maas-secrets/db.pass` REMOTE | Stage 2 step 3 | service | +| A3 | `site-headend-install.sh:450,452` | MAAS superuser `admin` password (`--password` in ARGV) | region | `voffice1:/root/maas-secrets/admin.pass` REMOTE; copy consolidated 2026-07-25 | Stage 2 step 4 | **BOTH (conflated)** | +| A4 | `site-headend-install.sh:453` | MAAS API key for `admin` | region | `voffice1:/root/maas-secrets/admin.apikey` REMOTE, deliberately not consolidated | Stage 2 step 4 | service | +| A5 | `site-headend-install.sh:455` | MAAS CLI profile `admin` (`~/.maas.cli`) | region | region VM home | Stage 2 step 4 | service | +| A6 | `site-headend-install.sh:445-447` (doc `:25-29`) | **MAAS rack-enrollment secret** (side effect of `maas init region+rack`) | region -> DC racks | `voffice1:/var/snap/maas/common/maas/secret`; 0600 copy to rack via `--enroll-secret-file` | Stage 2 mint / Stage 3 consume | service | +| A7 | `site-headend-install.sh:485-486` | LXD trust password | voffice1 LXD | `voffice1:/root/maas-secrets/lxd-trust.pass` REMOTE | Stage 2 step 6 | service | +| A8 | `opnsense-mint-apikey.php:43` (write `:52-60`) | OPNsense REST API key+secret (vendor model) | any edge | `:/tmp/opnsense-apikey.$$.txt` 0600, then wiped | D-113(a2) | service | +| A9 | `opnsense-bootstrap-apikey.sh:89-90,114-120` (cleanup `:93`) | driver for A8; retrieves key to jumphost | edge -> jumphost | `~/vr1--creds/opnsense-api.txt` 0600 | Stage 3 replacement chain step 2 | service | +| A10 | `tenant-onboard.sh:39` `newpw()` | `secrets.token_urlsafe(24)` primitive | Keystone | (helper) | tenant onboard | -- | +| A11 | `tenant-onboard.sh:64-65` (write `:70-71`) | `-domain-admin` password | tenant domain | `~/tenant-/-domain-admin-cred.txt` 0600 | onboard stage1 | **human** | +| A12 | `tenant-onboard.sh:90-91` (write `:94-95`) | `-cluster` + `-svc` passwords | tenant project | `~/tenant-/*-cred.txt` 0600 | onboard stage2 | cluster=human-ish, svc=service | +| A13 | `tenant-onboard.sh:141` | Keystone application credential `-svc-cred` | Keystone | `~/tenant-/-svc-appcred.txt` 0600 | onboard stage3 (re-run guard `:125-139`) | service | +| A14 | `tenant-onboard.sh:167` | Nova keypair PRIVATE key `-key` | Nova | `~/tenant-/-key.pem` 0600 | onboard stage3 (guard `:154-166`) | service | +| A15 | `dc-dc-rbd-mirror.sh:88,156` | Ceph rbd-mirror pool peer bootstrap token | primary-DC Ceph | `/tmp/rbd-mirror-bootstrap-token.` 0600 ON THE UNIT | Stage 6 step 11.x | service | +| A16 | `dc-dc-radosgw-multisite.sh:172,178` | RGW multisite system user access-key+secret (radosgw-generated at realm create) | radosgw DC1 | stays in the RGW realm on the unit | Stage 6 step 4 | service | +| A17 | `phase-06-capi-stack.sh:91` (def `:78`) | cluster-admin kubeconfig (`sudo k8s config`) | capi-mgmt VM | `:~/.kube/config` 0600 REMOTE | phase-06 step 6.6a | service | +| A18 | `phase-06-capi-stack.sh:147-153` | CAPI/CAPO stack: per-cluster CA + bootstrap tokens at every cluster create | capi-mgmt | in-cluster k8s Secrets | phase-06 step 6.6d | service | +| A19 | `clientdocs/scripts/acceptance-run.sh:349` | throwaway Nova keypair `ci-accept-key` | tenant | client host; deleted `:129` | acceptance D2 | service (ephemeral) | +| A20 | `clientdocs/scripts/acceptance-run.sh:464` | Barbican secret `ci-accept-secret` | Barbican | Barbican; deleted `:470` | acceptance G1 | service | +| A21-A25 | `tenant-onboarding-v2-DRAFT.md:162,225-226,290,295,308-312,319-320` | **SAME credential set as A11-A14** -- the DRAFT runbook duplicates `tenant-onboard.sh`. Two creation LOCATIONS, one set. **Do not double-count.** | Keystone/Nova | `$HOME/-*` | onboard-v2 | mixed | + +## B. Runbook / prose MINT points (operator by hand; no script owns it) + +| # | file:line | Credential | Written to | Stage/step | Principal | +|---|---|---|---|---|---| +| B1 | `phase-02-vault-bringup.md:71` | **Vault `operator init`: 5 unseal shares + root token.** IRREVERSIBLE one-shot (`:14-15,63-64`) | the Vault init dir on the jumphost -- OUTSIDE any `*-creds/` | phase-02 step 2.1 | service (root of trust) | +| B2 | `phase-02-vault-bringup.md:127-130` | short-lived child Vault token (`-ttl=10m`), so root never hits juju logs | printed once | phase-02 step 2.3 | service | +| B3 | `phase-02-vault-bringup.md:142` | charm-vault credential (`authorize-charm token=`) | inside the charm | phase-02 step 2.3, DOCFIX-011 | service | +| B4 | `phase-02-vault-bringup.md:144` | **charm-pki-local root CA** (private key + cert) -- the whole cloud's TLS root | inside Vault | phase-02 step 2.3, DOCFIX-014 | service | +| B5-B12 | `phase-01-bundle-deploy.md:299-386` | **8 Octavia PKI artifacts**: issuing-CA passphrase (`:299-301`), issuing-CA key EC P-384 AES-256 (`:302-304`), issuing-CA cert (`:305-307`), controller-CA passphrase (`:321-323`), controller-CA key (`:324-326`), controller-CA cert (`:327-329`), controller key EC P-256 UNENCRYPTED (`:343-344`), signed `controller.bundle.pem` (`:375-386`) | the Octavia PKI dir on the jumphost -- OUTSIDE any `*-creds/` | phase-01 step 1.0-GEN.a/b/c | service | +| B13 | `phase-01-bundle-deploy.md:391-410` | `overlays/octavia-pki.yaml` -- base64 CA cert/key blobs **+ the issuing-CA passphrase in PLAINTEXT** | **INSIDE the repo clone** (gitignored) | phase-01 step 1.0-GEN.d | service | +| B14 | `dc-dc-phase4-juju-bundle-per-dc.md:128` | MAAS superuser `juju-` -- `createadmin` with NO `--password`, so **stored nowhere** | nowhere | Stage 5 step 2.0 | **BOTH (no custody)** | +| B15 | `dc-dc-phase4-juju-bundle-per-dc.md:136-139` | per-DC MAAS API key (`apikey --generate`, run FROM the jumphost) | `~/vr1--creds/maas-api-key.txt` 0600 (SEC-018/019) | Stage 5 step 2.0 | service | +| B16 | `dc-dc-phase4-juju-bundle-per-dc.md:141-142` | Juju cloud credential wrapping B15 | Juju client store on **voffice1** -- explicitly OFF-MANIFEST | Stage 5 step 2.0 | service | +| B17 | `dc-dc-phase4-juju-bundle-per-dc.md:151` | `juju bootstrap`: controller admin credential + controller CA/agent secrets, per DC (D-104) | controller + `~/.local/share/juju/` | Stage 5 step 2 | service | +| B18 | `dc-dc-phase1-office1-standup.md:464-471` | Office1 MAAS API key for OpenTofu (`TF_VAR_maas_api_key`); DOCFIX-175 warns it lands in `terraform.tfstate` PLAINTEXT | `~/vr1-office1-creds/vr1-stage1.env` 0600 | Stage 2 step 5 | service | +| B19 | `dc-dc-phase1-office1-standup.md:643-647` | NetBox API token | `office1-netbox:/root/netbox-secrets/api.token` REMOTE; copy in `vr1-netbox-sandbox.env` (the founding SEC-009 miss) | Stage 2 step 9 | service | +| B20 | implied `vr1-office1-as-built.md:130`, prose `:631-637` | NetBox `SECRET_KEY` (rotated off netbox-docker's PUBLIC default) + NetBox admin password | `office1-netbox:/root/netbox-secrets/` REMOTE | Stage 2 step 9 | SECRET_KEY=service, admin=**human** | +| B21 | `dc-dc-phase2-tofu-dc-substrate.md:371-395` (D-112(c)) | edge service SSH keypair + root access via serial-console payload | `~/vr1-dc-creds/vr1-dc-edge_ed25519` | Stage 3 replacement chain step 1 | service | +| B22 | `dc-dc-phase2-tofu-dc-substrate.md:770-778` (SEC-012/-016) | per-DC MAAS->libvirt power keypair, never cross-DC | private half in `voffice1:/var/snap/maas/current/root/.ssh/` (**per-snap-revision, re-assert after refresh**); dc1 copy on jumphost, **dc0 has none** (SEC-021) | Stage 3 DoD item 5 | service | +| B23 | `dc-dc-phase5-dr-failover-drill.md:230,261-263` | RGW multisite system key ("never invent it"; capture via `radosgw-admin user info`) | remote RGW realm | Stage 6 step 4 | service | +| B24 | `dc-dc-phase5-dr-failover-drill.md:294,411` | rbd-mirror peer bootstrap token (manual form of A15) | remote unit | Stage 6 step 11.5 | service | +| B25 | `phase-07-conductor-graft.md:89` / `phase-07-conductor-graft.sh:90` | Keystone `magnum` domain + `magnum_domain_admin` user + password (D-046, re-run after every redeploy) | Magnum charm config | phase-07 step 7.0 | service | +| B26 | `appendix-D-magnum-trust-model.md:18-25,77`; `tenant-acceptance.sh:54`; `phase-08-*.md:35,179` | per-cluster trustee user + Keystone trust + CAPO child app credential, minted at EVERY cluster create (D-039/D-046) | cloud only, no host file | phase-08 / every create | service | +| B27 | `changelog-20260725-maas-admin-recovery.md:24-29` | MAAS superuser **`operator`** -- 32-char base64, stdin never argv | `~/vr1-office1-creds/maas-operator-password` 0600 | ad-hoc 2026-07-25 (SEC-020c) | **human** (first human-only MAAS identity) | +| B28 | `clientdocs/self-service-guide.md:51-52`; `tenant-skill/references/day2-operations.md:147-148` | tenant end-user Keystone passwords (`--password-prompt`), client-run | client host | tenant day-2 | **human** | +| B29 | `clientdocs/ci-automation.md:63-70`; `ci-integration-guide.md:102-109` | client-minted app credential `pipeline-nightly` | client CI secret store | tenant CI | service | +| B30 | `dc-dc-phase3-maas-enlist-deploy.md:491-497` | *not a mint* -- the standing instruction enumerating what a DC standup mints and requiring immediate consolidation + a manifest row | `~/vr1--creds/` | Stage 4 close-out | -- | + +## D. Charm/platform-minted at bundle apply (no repo mint site) + +- **Keystone `admin` password** -- charm-minted; MATERIALIZED by + `phase-03-admin-openrc.sh:52` via `juju run keystone/leader get-admin-password` + + `extract_admin_password.py`. In matrix scope per the materialization ruling. +- Per-service DB / RabbitMQ / Ceph keyring credentials -- charm relation data, never + materialized. OUT of scope (56 apps, zero hardcoded secret options in `bundle.yaml`). +- Vault-issued per-service TLS certs -- cascade from B4; `phase-04-internal-cert-san-verify.sh` + verifies, does not mint. + +## E. MATERIALIZE / UPLOAD -- checked and EXCLUDED (not creation points) + +`phase-03-admin-openrc.sh:46-52,66-80` (writes `~/admin-openrc`, SEC-023); +`extract_admin_password.py`; `phase-06-kubeconfig-gate.sh:36,59-77`; +`phase-07-conductor-graft.sh:121-129` (base64 to the conductor unit, sha256-verified); +`phase-06-net-setup.sh:23,48` and `phase-06-incloud-mgmt-cluster.md:206` (UPLOAD of an +existing pubkey); `opentofu/main.tf:218-219,444-447,575-578` (cloud-init +`ssh_authorized_keys` via `file()`); `vr1-dc{0,1}-substrate/main.tf:21,18` +(`qemu+ssh?keyfile=` -- path only, D-126 boundary); `opnsense-api.sh:32,75` (consume); +`reenroll-hosts.sh:122-123` (`read -rsp` a libvirt password into MAAS power params -- +rotation warning `:22-25`); `vault-kv-inner-probe.sh:12-13,22` (consume); +`site-ssh-config.sh` / `site-forward.sh` / `site-baseleg.sh` (explicit no-key-material +boundaries); `netbox/*.py` (consume `NETBOX_TOKEN` from env only); `creds-audit.sh` +(metadata only); `.claude/hooks/guard-destructive.py:28-42`; `snapshot_scrub.py:10`. + +## G. Coverage boundary + +Searched beyond `scripts/`+`runbooks/`: `bundle.yaml`, `overlays/`, `clientdocs/` (incl. +`scripts/` and `tenant-skill/`), `policies/`, `skills/`, `.claude/`, `creds-manifests/`, +`_handoff-not-committed/`. `tests/` EXCLUDED as fixtures (`tests/tenant-onboard/run-tests.sh:21,86` +mints a fake CA/PEM; `test_logic.py` uses `NETBOX_TOKEN="fake-token"`) -- no real credential +originates there. NEVER opened, and excluded from content-printing greps: +`opentofu/terraform.tfstate*`, the 8 `*.tfplan`, `logs/`, `asbuilt/`, `docs/audit/`, +`netbox/draft/*.json`. `opentofu/README.md` + `variables.tf:6-10` confirm `maas_api_key` +reaches tfstate in plaintext (DOCFIX-175) -- a secret-at-rest location with no mint of its own.