diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 38a6410..be69d4c 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -969,7 +969,7 @@ custody-gated and the siblings were undeclared. **LOGGED, NOT ACTIONED (hard rule 1)** -- the `chmod` and the retain-or-delete question are mutations outside this step. **STILL OUTSTANDING (not done, not silently dropped):** ruling 1's TIER 2 `Pn` (tier 1 - is wired -- see above); ruling 4's SEC-009 demotion; tier 3 + is wired -- see above); tier 3 (VALIDITY); `--render`'s source-field derivation; and the `cardinality` field remains largely inert with a one-token S5 bypass (`per-DC` -> `singleton`). diff --git a/docs/changelog-20260726-d137-tier1.md b/docs/changelog-20260726-d137-tier1.md index 135a525..8cae794 100644 --- a/docs/changelog-20260726-d137-tier1.md +++ b/docs/changelog-20260726-d137-tier1.md @@ -506,3 +506,42 @@ **Revert:** drop the P5 block from `scripts/preflight.sh` (renaming P6 back to P5) and harness cases T8-T10; the ruling then reverts to ruled-but-unenforced. + +## Item 17 -- D-137 ruling 4 EXECUTED: SEC-009 demoted to a pointer + +Ruling 4 was already RULED ("D-137 is the authority"); its stated trigger -- tier 2 built -- +had passed, so this is execution, not a decision. No operator exchange needed. + +**Moved.** The standing consolidation rule left `docs/security-ledger.md` for +`docs/design-decisions.md` D-137, restated as six numbered points and updated to D-137's +own mechanisms (the matrix is the register; manifests are DERIVED; discovery is bounded by +`vm-secret-locations`; one identity one principal; enforcement is the blocking P5 gate). +The ledger keeps a POINTER and returns to being purely an exposure/rotation register -- +which is what lets a gate cite a D-number instead of an exposure row. + +**Kept as history, deliberately.** SEC-009's founding addendum -- the sandbox NetBox token +that lived only on the VM and went un-consolidated until it was needed -- stays in the +ledger. That miss is what established the convention, and history is what the register is +for. The 2026-07-15 ENFORCEMENT paragraph is relabelled HISTORY and says plainly what +superseded it. + +**Carried forward WITH CORRECTIONS, not verbatim.** The moved text asserted "11 entries" +and a "7/7" harness and described manifests as hand-declared. All three were stale by +2026-07-26; they are not reproduced. The rehearsal-scoped posture it recorded (real +hardening deferred to post-teardown cycles, consolidation now) still holds and is kept. + +**Callers repointed in the same commit** -- a demotion that leaves callers citing the moved +text is not done: +- `runbooks/dc-dc-phase3-maas-enlist-deploy.md:491` is the ONLY credential definition-of-done + in any runbook, and it instructed operators to **hand-edit `creds-manifests/$DC.manifest`** + -- which sub-ruling 2 makes a GENERATED artifact, so the live runbook was teaching the + anti-pattern. It now directs a `creds-matrix.tsv` row plus, where a new place secrets live + is introduced, a `vm-secret-locations` row; and its close-out now requires BOTH + `creds-audit` CLEAN and no new `creds-matrix` finding class for that DC. +- `docs/vr1-office1-as-built.md` restated the convention; now a pointer. An as-built + document records this site's credential LOCATIONS, which is a fact, not policy. + +Gauntlet ALL GREEN (80), repo-lint 0-fail. + +**Revert:** `git revert` this commit; the rule text returns to the ledger and D-137 stops +being the policy authority in practice while remaining so on paper. diff --git a/docs/design-decisions.md b/docs/design-decisions.md index 7b13317..d06c63e 100644 --- a/docs/design-decisions.md +++ b/docs/design-decisions.md @@ -5240,3 +5240,34 @@ caller-supplied `--pending-stage`, which do not belong in an unattended gate -- that remains an open operator decision. Status and the live consequence live in `docs/CURRENT-STATE.md` (GA-R1). + +**CREDENTIAL-LIFECYCLE POLICY (moved here 2026-07-26 by sub-ruling 4; supersedes the +SEC-009 convention block, which is now a pointer).** This is the standing rule; the +security ledger is an exposure/rotation register and no longer states it. + +1. **Consolidation.** ALL sensitive files and environment/config files for a site live in + a single `~/-creds/` folder on the jumphost: folder 0700, files 0600, public key + halves 0644. No loose credential files in `~`. Every site follows this from day one -- + never accumulate loose `*.env` and consolidate later. +2. **At mint time, not later.** A credential is consolidated and registered AT THE MOMENT + IT IS MINTED. "Later" is when it is lost. This is a data-loss/continuity control as + much as a security one: scattered credentials disappear between sessions. +3. **The register is the expected state.** `creds-matrix.tsv` declares what MUST exist; + `creds-manifests/*.manifest` are DERIVED from it (sub-ruling 2), so a manifest is no + longer the place to declare anything by hand. +4. **Discovery is bounded absolutely** by `creds-manifests/vm-secret-locations` + (sub-ruling 3). A location that is not declared is not audited, and an unaudited + location is how SEC-022 happened. **Adding a location row is definition-of-done for + any new mint site.** +5. **One identity serves one principal type** (sub-ruling 5), enforced by the matrix + `principal` column. +6. **Enforcement is blocking, not advisory** (sub-ruling 1): tier 1 runs as + `scripts/preflight.sh` P5 and fails the deploy. Advisory was rejected as the posture + that demonstrably failed -- `creds-audit` read CLEAN while four region-VM secrets sat + undeclared. + +CARRIED FORWARD WITH CORRECTIONS, not verbatim: the moved text's specific counts +("11 entries", "7/7") and its description of manifests as hand-declared were stale by +2026-07-26 and are not reproduced. The rehearsal-scoped posture it recorded still holds -- +real credential hardening (rotation, revocation, non-shared tokens) remains deferred to +post-teardown cycles per SEC-009's original rationale, while consolidation happens NOW. diff --git a/docs/security-ledger.md b/docs/security-ledger.md index 1f1c117..13d8ccd 100644 --- a/docs/security-ledger.md +++ b/docs/security-ledger.md @@ -43,15 +43,16 @@ | SEC-024 | 2026-07-26 | **`opentofu/terraform.tfstate.backup` is mode 0664 (group- and world-readable) and carries the Office1 MAAS API key in PLAINTEXT.** DOCFIX-175 records that `maas_api_key` reaches the OpenTofu state file in clear; the live `terraform.tfstate` is correctly 0600, but its automatically-written `.backup` sibling is 0664. MEASURED `stat -c '%n %a'` this session on the jumphost clone. FOUND BY the D-137 credential matrix on its first run after the committee fixes: the world-readable check had been gated to `custody=consolidated` rows (43 of 77 rows unchecked), and the tfstate siblings were not declared locations at all -- so neither the mode nor the file was visible to any control. The two `terraform.tfstate.pre-*` snapshots are the same class and carry the same key. | measured 2026-07-26; `creds-matrix.py --tier2` E2 WORLD-READABLE; DOCFIX-175; capture `docs/audit/d137-tier2-postcommittee-20260726.txt` | operator | **OPEN -- logged, NOT actioned (hard rule 1).** Remediation is a `chmod` on the jumphost plus a decision about whether the historical snapshots should be retained at all; both are mutations and neither is in this step's scope. Rotation of the underlying key remains the SEC-018/-019 obligation. Custody off-repo per D-069. | -**STANDING CONVENTION (SEC-009, 2026-07-15): per-site credential/env consolidation.** ALL sensitive -files AND environment/config files for a site live in a single `~/-creds/` folder on vcloud, -mode 0700, files 0600 (public keys 0644). No loose env files in `~`. Sites: `~/vr1-office1-creds/` -(exists); when DC deployment starts, `~/vr1-dc0-creds/` and `~/vr1-dc1-creds/` follow the SAME -pattern from day one -- do not accumulate loose `vr1-dc0*.env` in `~` and consolidate later. This is -a DATA-LOSS / continuity control as much as a security one: scattered creds get lost between -sessions and after compaction. Rationale is looser-security-but-strict-hygiene: this is a closed -in-house test, so real credential hardening (rotation, revocation, non-shared tokens) is deferred to -post-teardown redeploy cycles (test2/3/4+), but consolidation happens NOW so nothing is lost. +**CREDENTIAL-LIFECYCLE POLICY LIVES IN D-137 -- this register does not carry the rule.** +DEMOTED 2026-07-26 per D-137 sub-ruling 4 ("D-137 is the authority"): the standing +consolidation convention that used to be stated here has MOVED to +`docs/design-decisions.md` D-137, which is now the credential-lifecycle policy authority. +This ledger returns to being purely an EXPOSURE/ROTATION register: one row per exposure, +with its disposition. Do not re-state policy here -- a gate may now cite a D-number +instead of an exposure row, which was the point of the ruling. + +SEC-009's FOUNDING HISTORY stays below, deliberately: the NetBox-token miss is what +established the convention, and history is exactly what this register is for. **ADDENDUM (2026-07-15): the sandbox NetBox token was MISSED by the original consolidation.** It lived only at `/root/netbox-secrets/api.token` on `office1-netbox`, never in `~/vr1-office1-creds/` -- the @@ -64,16 +65,12 @@ copy on the jumphost, mirroring how `vr1-netbox.env` holds the upstream token. See `docs/archive/changelogs/changelog-20260715-fidelity-check-scope-correctness.md`. -**ENFORCEMENT (2026-07-15) -- so the miss cannot recur, esp. for DC0/DC1.** The convention was never -ENFORCED; a VM-minted secret had no forcing function to get consolidated and no check to catch a miss. -Now built (`docs/archive/changelogs/changelog-20260715-creds-audit.md`): -- **`creds-manifests/.manifest`** -- declares EXACTLY the secrets a site must hold, each with its - mode and PROVENANCE (`local`, or `:` for VM-minted secrets -- the class that was missed). - `creds-manifests/vr1-office1.manifest` seeded (11 entries) and the live folder audits CLEAN. -- **`scripts/creds-audit.sh `** -- verifies the live `~/-creds/` against the manifest - (presence, 0700 folder / declared file modes, non-empty), flags UNDECLARED files in the folder, and - scans the home root for SPRAWL (loose `*.env`/`*.token`/`*authkey*` outside any `*-creds/`). Reads NO - secret values -- metadata only. Harness `tests/creds-audit/` (7/7). -- **Standing rule:** run `bash scripts/creds-audit.sh vr1-dcN` at the CLOSE of each DC standup, and - consolidate+manifest every secret AT MINT TIME (not "later" -- later is when it is lost). Wired into - the `dc-dc-phase3` runbook. +**ENFORCEMENT -- HISTORY (2026-07-15).** The convention was never ENFORCED; a VM-minted +secret had no forcing function to get consolidated and no check to catch a miss. The +2026-07-15 build introduced `creds-manifests/.manifest` and `scripts/creds-audit.sh` +(`docs/archive/changelogs/changelog-20260715-creds-audit.md`). That machinery is now +SUPERSEDED in its policy role by D-137: manifests are DERIVED from `creds-matrix.tsv` +rather than hand-declared, discovery is bounded by `creds-manifests/vm-secret-locations`, +and enforcement is a blocking preflight gate rather than a run-it-and-remember habit. +The current rule text is in D-137; this paragraph is kept as the record of what existed +before it. diff --git a/docs/vr1-office1-as-built.md b/docs/vr1-office1-as-built.md index fbd4677..0c0ab52 100644 --- a/docs/vr1-office1-as-built.md +++ b/docs/vr1-office1-as-built.md @@ -133,10 +133,10 @@ | VR1 OpenTofu env | `~/vr1-office1-creds/vr1-stage1.env` (vcloud) | `TF_VAR_*` incl. `TF_VAR_maas_api_key`. Consolidated + chmod 600 2026-07-15 (was loose + 0664, SEC-009). TF_VAR pool-path names updated to D-119 (`vr1_dc0`/`vr1_dc1`). | | Tailscale auth key (self-hosted tailnet) | `~/vr1-office1-creds/tailscale-authkey.txt` (vcloud, `0600`) | Operator-supplied, 48 chars. Consumed by PATH, never printed. The copy on the node was SHREDDED after `tailscale up`. Rotate/revoke on the control server at v1 close. | -**Credential consolidation (SEC-009, standing convention):** ALL sensitive + env/config files for a -site live in one `~/-creds/` folder (0700; files 0600; public keys 0644) -- no loose env files -in `~`. When DC deployment starts, `~/vr1-dc0-creds/` and `~/vr1-dc1-creds/` follow the same pattern -from day one. See `docs/security-ledger.md` SEC-009. +**Credential consolidation -- POLICY POINTER.** The standing rule moved to +`docs/design-decisions.md` D-137 on 2026-07-26 (sub-ruling 4); it is not restated here. +This document records this site's as-built credential LOCATIONS (the rows above), which +is an as-built fact, not policy. **NetBox API token format (4.6):** the wire form is `nbt_.`. The API's `token` field alone is NOT usable -- present it raw and you get `403 Invalid v1 token`. See diff --git a/runbooks/dc-dc-phase3-maas-enlist-deploy.md b/runbooks/dc-dc-phase3-maas-enlist-deploy.md index 9d598a1..5da791d 100644 --- a/runbooks/dc-dc-phase3-maas-enlist-deploy.md +++ b/runbooks/dc-dc-phase3-maas-enlist-deploy.md @@ -491,12 +491,20 @@ > **CREDS-CONSOLIDATION CLOSE-OUT (SEC-009 enforcement -- non-negotiable).** Standing up a DC MINTS > secrets on its VMs and on the jumphost -- MAAS API key, per-DC NetBox token (if any), node/edge SSH > keys, OPNsense root creds, tailscale authkey, Juju/Vault material. **The moment each is minted, -> consolidate it into `~/$DC-creds/` (0700; files 0600) and add it to `creds-manifests/$DC.manifest` -> with its provenance (`local` or `<vm>:<path>`).** Do NOT defer this -- "later" is when secrets get -> lost (and after a compaction they are gone). This is the exact class that bit us: office1's sandbox +> consolidate it into `~/$DC-creds/` (0700; files 0600), add a row to `creds-matrix.tsv`, and -- +> if it introduces a new place secrets live -- add a row to `creds-manifests/vm-secret-locations`.** +> UPDATED 2026-07-26 (D-137): do NOT hand-edit `creds-manifests/$DC.manifest`. Manifests are now +> DERIVED from the matrix (sub-ruling 2), and an undeclared LOCATION is never audited at all +> (sub-ruling 3) -- which is how SEC-022 happened. Policy text: `docs/design-decisions.md` D-137. +> +> Do NOT defer this -- "later" is when secrets get lost (and after a compaction they are gone). This is the exact class that bit us: office1's sandbox > NetBox token, minted on the VM at `/root/netbox-secrets/`, went un-consolidated until it was needed -> (SEC-009 addendum). Before declaring this DC done, `bash scripts/creds-audit.sh $DC` MUST report -> **CLEAN** (folder perms, every manifest entry present, no undeclared files, no home-root sprawl). +> (SEC-009 addendum). Before declaring this DC done, BOTH must hold: +> `bash scripts/creds-audit.sh $DC` reports **CLEAN** (folder perms, every manifest entry +> present, no undeclared files, no home-root sprawl), AND +> `python3 scripts/creds-matrix.py` shows no NEW finding class for this DC -- it is the +> blocking preflight gate P5, so a DC standup that adds an unregistered credential will stop +> the next deploy rather than surface later. ---