diff --git a/creds-matrix.tsv b/creds-matrix.tsv index d007095..123cd25 100644 --- a/creds-matrix.tsv +++ b/creds-matrix.tsv @@ -108,6 +108,8 @@ octavia-controller-ca-cert per-DC vr1-dc0 headend controller-ca.cert.pem none service off-manifest-known stage5 runbook:runbooks/phase-01-bundle-deploy.md:414 SEC-023 n-outside-convention octavia-controller-key per-DC vr1-dc0 headend controller.key none service off-manifest-known stage5 runbook:runbooks/phase-01-bundle-deploy.md:429 SEC-023 n-outside-convention octavia-controller-bundle per-DC vr1-dc0 headend controller.bundle.pem none service off-manifest-known stage5 runbook:runbooks/phase-01-bundle-deploy.md:517 SEC-023 n-outside-convention +octavia-controller-cert per-DC vr1-dc0 headend controller.cert.pem none service off-manifest-known stage5 runbook:runbooks/phase-01-bundle-deploy.md:521 SEC-023 n-outside-convention +octavia-controller-ca-serial per-DC vr1-dc0 headend controller-ca.cert.srl none service off-manifest-known stage5 runbook:runbooks/phase-01-bundle-deploy.md:519 SEC-023 n-outside-convention octavia-pki-overlay per-DC vr1-dc0 headend vr1-dc0-octavia-pki.yaml none service off-manifest-known stage5 runbook:runbooks/phase-01-bundle-deploy.md:553 SEC-004 n-overlay-in-clone octavia-issuing-ca-passphrase per-DC vr1-dc1 headend passphrase.txt none service off-manifest-known stage5 runbook:runbooks/phase-01-bundle-deploy.md:383 SEC-023 n-outside-convention octavia-issuing-ca-key per-DC vr1-dc1 headend issuing-ca.key.enc none service off-manifest-known stage5 runbook:runbooks/phase-01-bundle-deploy.md:387 SEC-023 n-outside-convention @@ -117,6 +119,8 @@ octavia-controller-ca-cert per-DC vr1-dc1 headend controller-ca.cert.pem none service off-manifest-known stage5 runbook:runbooks/phase-01-bundle-deploy.md:414 SEC-023 n-outside-convention octavia-controller-key per-DC vr1-dc1 headend controller.key none service off-manifest-known stage5 runbook:runbooks/phase-01-bundle-deploy.md:429 SEC-023 n-outside-convention octavia-controller-bundle per-DC vr1-dc1 headend controller.bundle.pem none service off-manifest-known stage5 runbook:runbooks/phase-01-bundle-deploy.md:517 SEC-023 n-outside-convention +octavia-controller-cert per-DC vr1-dc1 headend controller.cert.pem none service off-manifest-known stage5 runbook:runbooks/phase-01-bundle-deploy.md:521 SEC-023 n-outside-convention +octavia-controller-ca-serial per-DC vr1-dc1 headend controller-ca.cert.srl none service off-manifest-known stage5 runbook:runbooks/phase-01-bundle-deploy.md:519 SEC-023 n-outside-convention octavia-pki-overlay per-DC vr1-dc1 headend vr1-dc1-octavia-pki.yaml none service off-manifest-known stage5 runbook:runbooks/phase-01-bundle-deploy.md:553 SEC-004 n-overlay-in-clone tenant-domain-admin per-tenant - jumphost -domain-admin-cred.txt gui human off-manifest-known tenant-onboard script:scripts/tenant-onboard.sh:64 SEC-023 n-tenant-dir tenant-cluster-user per-tenant - jumphost -cluster-cred.txt api human off-manifest-known tenant-onboard script:scripts/tenant-onboard.sh:90 SEC-023 n-tenant-dir diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 1103f9c..c3b78ce 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -2276,6 +2276,39 @@ root-level workspace copy is still ignored, and both per-DC overlays remain ignored. Same class as F4 -- a gitignore pattern whose real scope differed from its intent, this time in the direction that LOSES data rather than leaks it. +- **E3 RESOLVED 2026-07-30 by RULED disposition: declare the two artifacts that persist, DELETE + the two build intermediates.** Operator asked for the recommendation and reasoning, then + approved building it. The four undeclared per-DC outputs were separated by whether anything + will ever read them again: + **DECLARED** -- `controller.cert.pem`, because `octavia-pki.sh verify` reads it for the + subject, chain and SAN assertions and **a gate depending on an undeclared artifact is + incoherent**; and `controller-ca.cert.srl`, because it is CA issuance STATE, not residue -- + delete it and the next `-CAcreateserial` starts a fresh sequence, so the same CA can issue a + DUPLICATE serial. Reissuance is scheduled rather than hypothetical: the controller cert is + 2-year and F9's D-106 work will want new SANs. + **DELETED AT SOURCE** -- `controller.csr` (spent at signing) and `controller.cnf` (fully + derived from `$DC` plus the VIP overlay, so the repo already determines it, and `verify` now + asserts the SAN set on the CERT, which is where the F8 failure actually shows). + **WHY NOT SIMPLY DECLARE ALL FOUR**, which would have been lower-risk and less work: a + register that accumulates rows for artifacts that should not exist trains the reader to add a + row rather than ask whether the file belongs -- the register-as-rubber-stamp failure, the same + family as this project's false-green problems. Concretely, both intermediates are 664, so + declaring them would have produced FOUR MORE E2 findings for files nothing reads. + DELIVERED: 4 new register rows (2 ids x 2 DCs, `per-DC`, `headend`, `stage5`, mint-refs + anchored at the issuing and serial-creating lines) -- register now 95 rows, 22 octavia rows, + S1/S4/S7 clean and **no new findings** (still 7 on vcloud, the same pre-existing set); + generator gains `rm -f controller.csr controller.cnf` **plus the E2 fix at source** + (`chmod 600` on all three certs) so the NEXT DC is correct by construction rather than by a + remembered follow-up; `verify` A2 extended 9 -> 10 artifacts to assert the serial file, so the + register and the gate agree on what should exist. Harness **17/17** (was 15): T16 proves a + missing serial FAILS, and **T17 proves a workspace with the intermediates DELETED still + PASSES** -- without which a later A2 addition would have silently failed every clean + workspace. + **STILL OWED, operator-executed (the guard blocks both for the session, including the `chmod` + -- which is worth noting as fork-1 evidence, since it blocks an operation that strictly + IMPROVES posture):** the one-off `chmod 600` on the six existing certs, and removal of the four + existing intermediates. Until both run, live `verify` reads 23/3 and P5 keeps 12 of its 18 + findings. **F9 is next and remains untouched.** **F3 -- both `~/octavia-pki/` and `overlays/octavia-pki.yaml` are ABSENT here** (existence checked, no contents read). So this is generation FROM SCRATCH for both DCs: there is nothing to reuse, which retires the reuse-vs-regenerate choice diff --git a/runbooks/phase-01-bundle-deploy.md b/runbooks/phase-01-bundle-deploy.md index f2f490a..19ff9f9 100644 --- a/runbooks/phase-01-bundle-deploy.md +++ b/runbooks/phase-01-bundle-deploy.md @@ -524,6 +524,24 @@ openssl x509 -in controller.cert.pem -noout -dates cat controller.cert.pem controller.key > controller.bundle.pem chmod 600 controller.bundle.pem + # E2 FIX AT SOURCE (2026-07-29). The three certificates landed 664 on both DCs from the + # inherited umask, and `creds-matrix.py` E2 flagged them. A CA certificate that is + # group-WRITABLE is a SUBSTITUTABLE TRUST ANCHOR -- the exposure is integrity, not + # confidentiality, which is why "they're public certs" is not a defence. Nothing reads + # these files at runtime (Octavia receives the material via the overlay), so 600 costs + # nothing. Done here so the NEXT DC is correct by construction rather than by a + # remembered follow-up chmod. + chmod 600 controller.cert.pem ../issuing-ca/issuing-ca.cert.pem ../controller-ca/controller-ca.cert.pem + # E3 FIX AT SOURCE (2026-07-29). These two are BUILD INTERMEDIATES: the signing request + # is spent the moment the certificate is issued, and the openssl config is fully derived + # from $DC plus the VIP overlay, so the repo already determines it. Both were left behind + # on both DCs with no register row, which is what `creds-matrix.py` E3 reported. Removing + # them is preferred over declaring them: a register that accumulates rows for artifacts + # that should not exist trains the reader to add a row rather than ask whether the file + # belongs. RETAINED DELIBERATELY: `controller.cert.pem` (the record of what was issued, + # and what `octavia-pki.sh verify` reads) and `../controller-ca/controller-ca.cert.srl` + # (CA issuance STATE -- deleting it lets the same CA reissue a duplicate serial). + rm -f controller.csr controller.cnf } ) ``` diff --git a/scripts/octavia-pki.sh b/scripts/octavia-pki.sh index 57a1e04..8fb59e1 100755 --- a/scripts/octavia-pki.sh +++ b/scripts/octavia-pki.sh @@ -158,12 +158,18 @@ ok "A1 workspace present: ~/octavia-pki/$SITE" # The nine artifacts the generator is documented to produce. Absence is CONCLUSIVE. MISSING=() + # `controller-ca.cert.srl` is asserted because it is CA issuance STATE, not residue: if it + # is missing, the next `-CAcreateserial` starts a fresh sequence and the same CA can issue a + # DUPLICATE serial. The controller cert is 2-year and D-106's FQDN work will want new SANs + # (F9), so reissuance is scheduled, not hypothetical. Declared in creds-matrix.tsv alongside + # this assertion -- the register and the gate must agree on what should exist. for rel in issuing-ca/passphrase.txt issuing-ca/issuing-ca.key.enc issuing-ca/issuing-ca.cert.pem \ controller-ca/passphrase.txt controller-ca/controller-ca.key.enc controller-ca/controller-ca.cert.pem \ + controller-ca/controller-ca.cert.srl \ controller/controller.key controller/controller.cert.pem controller/controller.bundle.pem; do [ -f "$W/$rel" ] || MISSING+=("$rel") done - if [ "${#MISSING[@]}" -eq 0 ]; then ok "A2 all 9 expected artifacts present" + if [ "${#MISSING[@]}" -eq 0 ]; then ok "A2 all 10 expected artifacts present" else nfail "A2 missing ${#MISSING[@]} expected artifact(s): ${MISSING[*]}"; fi fi diff --git a/tests/octavia-pki/run-tests.sh b/tests/octavia-pki/run-tests.sh index 899395a..5a230ba 100755 --- a/tests/octavia-pki/run-tests.sh +++ b/tests/octavia-pki/run-tests.sh @@ -202,6 +202,25 @@ && ok "T13 an expanded/uppercase v6 SAN is normalised and matches (no false FAIL)" \ || { no "T13 v6 normalisation must match a correct cert"; printf '%s\n' "$OUT" | sed 's/^/ /'; } +# T16 -- the CA SERIAL file is issuance STATE, not residue. If it is missing, the next +# `-CAcreateserial` starts a fresh sequence and the same CA can issue a DUPLICATE serial. +# It is declared in creds-matrix.tsv, so the gate must assert it too -- the register and the +# gate have to agree on what should exist, or one of them is lying. +E=$(mkfix noserial vr1-dc0 "VR1 DC0" full) +rm -f "$E/home/octavia-pki/vr1-dc0/controller-ca/controller-ca.cert.srl"; run "$E" vr1-dc0 +[ "$RC" = 1 ] && printf '%s' "$OUT" | grep -q "controller-ca.cert.srl" \ + && ok "T16 a missing CA serial file FAILS -- duplicate-serial risk on the next reissue" \ + || { no "T16 must catch a missing CA serial (rc=$RC)"; printf '%s\n' "$OUT" | sed 's/^/ /'; } + +# T17 -- the build INTERMEDIATES must NOT be required. The generator now deletes them +# (E3 fix at source), so a workspace without them is CORRECT and must still PASS. Without +# this case, adding them to A2 later would silently make every clean workspace fail. +E=$(mkfix nointermediates vr1-dc0 "VR1 DC0" full) +rm -f "$E/home/octavia-pki/vr1-dc0/controller/controller.csr" \ + "$E/home/octavia-pki/vr1-dc0/controller/controller.cnf"; run "$E" vr1-dc0 +[ "$RC" = 0 ] && ok "T17 a workspace with the intermediates DELETED still PASSES (E3 fix at source)" \ + || { no "T17 deleted intermediates must not fail the gate (rc=$RC)"; printf '%s\n' "$OUT" | sed 's/^/ /'; } + # T14 -- args. A bare `dcN` has been a live defect class; it must be REJECTED, not guessed. RC=0; bash "$CHECK" verify dc0 >/dev/null 2>&1 || RC=$? [ "$RC" = 2 ] && ok "T14 a bare 'dc0' is REJECTED (exit 2), never coerced" || no "T14 bare site must exit 2 (rc=$RC)"