diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index ae11b19..bc79fda 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -2606,6 +2606,34 @@ - Preflight's overall verdict is still FAIL, which is **P5 working as designed** on the pre-existing red register (SEC-021, the SEC-020 conflation, the power-key asymmetry). P7 itself PASSES on the headend. + - **THE ESTATE'S NAME NOW LIVES IN ONE FILE: `scripts/lib-identity.sh`, 2026-07-30** + (operator: "The specific server and region names do not matter as they change during every + deployment"; then "Yes, take option 2"). `CLOUD_NAME` + `CLOUD_DOMAIN`, no side effects, + env-overridable. With `` and `` already derived from the site token, these were + the last typed identity in the shell surface -- **a rebuild that renames the estate now + edits ONE file** and the certs, zones, SANs and the P7 gate follow. Consumers: + `octavia-pki.sh derive_zone()`, phase-01 1.0-GEN.c, dc-dc-phase6 Step 0. The tofu side was + ALREADY parameterised (`opentofu/variables.tf domain_suffix`), so it was not touched. + - **NOT in `lib-net.sh`, deliberately:** sourcing that bare populates a full flat plane/VIP + namespace (its own header says so), and a certificate checker needs two strings, not a + network namespace -- the R9 hazard in miniature. Sourced from `SCRIPT_DIR` (sibling), and + FAILS CLOSED (REFUSE 3) if absent. + - **Harness 48/48 -> 51/51.** T45 proves the centralisation is REAL by overriding both + values and requiring the zone to follow (`acme.dc0.vr1.example.test`); T46 pins the + fail-closed path; **T47 catches SHELL-vs-OPENTOFU drift** -- HCL cannot source a shell + file, so two copies of one fact exist and nothing else would notice them parting. + T47 was **proven able to fail** by injecting a drifted value, then restored. + - **WITHDRAWN, and recorded because I proposed it first:** retiring the `vr0-dc0` selector. + Measured, the ambiguity it would remove was ALREADY removed by D-119's region-qualification + (the hazard was the bare `dc0`, rejected loudly today); `lib-net.sh:110` documents that arm + as a NO-OP over the file's flat defaults; and the footprint is 43 files, mostly the separate + VR0 phase-NN track the VR1 runbooks cite as validated precedent. Medium cost, near-zero + benefit. **VR0 is confirmed NOT this deployment** (operator, 2026-07-30) but its selector + arm is inert and stays. + - **THE FULL NAMING SURFACE OF A REBUILD is now: `scripts/lib-identity.sh` (2 constants) plus + the site allowlists in `octavia-pki.sh` / `preflight.sh` / `lib-net.sh` / `lib-hosts.sh`.** + The allowlists stay explicit token sets deliberately -- one that accepts anything is how a + typo'd DC name becomes a wrong-target write. - The certs remain INERT by design -- `os-public-hostname` is still set nowhere (R5 refused setting it at Stage 5 as a D-019 repeat). The names are now correct IN ADVANCE of D-106's Stage-7 work arming them. diff --git a/docs/changelog-20260730-octavia-reissue-tool.md b/docs/changelog-20260730-octavia-reissue-tool.md index 9e48c0f..b2db6c4 100644 --- a/docs/changelog-20260730-octavia-reissue-tool.md +++ b/docs/changelog-20260730-octavia-reissue-tool.md @@ -355,3 +355,85 @@ red on real pre-existing defects (SEC-021's declaration gaps, the SEC-020 identity conflation, the per-DC power-key asymmetry). P7 does not change that verdict on the headend -- it passes there. Going green is remediation, gated per row. + +--- + +# PART 5 (same session) -- `lib-identity.sh`: the estate's name in ONE place + +Operator: "The specific server and region names do not matter as they change during every +deployment and hardening of the deployment workflow." Then, after scoping: "Yes, take option 2." + +## 15. What I recommended AGAINST, and why the measurement changed it + +I first proposed retiring the `vr0-dc0` selector as "removing the last cross-deployment name +and the ambiguity class outright". **Measured, that was wrong on both halves and I withdrew +it:** D-119 already closed the ambiguity by region-qualifying (the hazard was the BARE `dc0`, +which is rejected loudly today); `lib-net.sh:110` documents its `vr0-dc0` arm as a NO-OP over +the file's flat defaults, so it injects nothing; and the footprint is **43 files**, most of them +the separate VR0 phase-NN track that the VR1 dc-dc runbooks cite as validated precedent. +Medium cost, near-zero benefit. Logged, not executed. + +## 16. `scripts/lib-identity.sh` -- NEW + +Two constants, no side effects, env-overridable: + + CLOUD_NAME="${CLOUD_NAME:-omega}" + CLOUD_DOMAIN="${CLOUD_DOMAIN:-cloud.neumatrix.local}" + +After the earlier parts of this session the naming chain is +`....`, where `` and `` are already +DERIVED from the site token. These two were the ONLY typed identity left in the shell surface. +**A rebuild that renames the estate now edits one file** and the certificates, DNS zones, SANs +and the P7 gate all follow. + +**DELIBERATELY NOT IN `lib-net.sh`**, and this was the real design decision. Sourcing +`lib-net.sh` bare POPULATES a full flat plane/VIP namespace -- its own header says so +("continues to populate PLANE_CIDRS ... VR0/DC0's real, measured values"). A certificate +checker needs two strings, not a network namespace, and pulling that in would hand +`octavia-pki.sh` -- which sources NOTHING today -- a pile of VR0-shaped defaults it never asked +for. That is the R9 hazard in miniature. + +**Sourced from `SCRIPT_DIR`, not `REPO_ROOT`:** it is a sibling script, and `REPO_ROOT` is +harness-overridable to a fixture with no `scripts/` of its own. **FAILS CLOSED** (REFUSE 3) if +absent -- a certificate gate that invents the estate's identity is worse than none. + +Consumers updated: `scripts/octavia-pki.sh` `derive_zone()`, `phase-01` 1.0-GEN.c, and +`dc-dc-phase6` Step 0. The tofu side was ALREADY parameterised (`opentofu/variables.tf` +`domain_suffix`, passed explicitly into `modules/dc-planes`), so nothing there changed. + +## 17. Harness 48/48 -> 51/51 + +- **T45 -- the centralisation must be REAL, not decorative.** Overrides both constants and + requires the derived zone to follow BOTH: `acme.dc0.vr1.example.test`. A constant nothing can + vary is indistinguishable from a literal, which is the shape this repo keeps shipping. +- **T46** -- a missing `lib-identity.sh` REFUSES (exit 3) rather than guessing. +- **T47 -- SHELL vs OPENTOFU DRIFT.** HCL cannot source a shell file, so `opentofu/variables.tf` + keeps its own `domain_suffix` default. Two copies of one fact is a drift generator: if they + part, the libvirt plane domains and the certificate zones describe DIFFERENT estates and + nothing else would notice. Same standing shape as D-137 ruling 2's render-drift check. + **Proven capable of failing:** injecting `drifted.example` into `variables.tf` turned the + harness red naming both values; restored, green. + +## 18. Two small corrections made along the way + +- `repo-lint` L6 flagged `. "$REPO/scripts/..."` as a bare invocation. L6's own docstring + blesses a `bash|source|python3` prefix, and sourcing needs no exec bit -- so the RUNBOOK moved + to the documented `source` form rather than the gate being changed for style. +- `${REPO:?}` is now guarded at the FIRST use in GEN.c (my source line), not only at `:538` + below it -- an unset `$REPO` would otherwise try `/scripts/lib-identity.sh` and fail with a + path error instead of naming the actual mistake. + +## Verification (part 5) + + bash tests/octavia-pki/run-tests.sh -> 51/51 PASS (T47 proven able to fail) + bash -n (GEN.c extracted) -> 0 + bash scripts/run-tests-all.sh -> GAUNTLET: ALL GREEN (89) + bash scripts/repo-lint.sh -> 0 fail + +## What is STILL a typed name, deliberately + +The site allowlists (`vr1-dc0|vr1-dc1`) stay explicit token sets in `octavia-pki.sh`, +`preflight.sh`, `lib-net.sh` and `lib-hosts.sh`. An allowlist that accepts anything is how a +typo'd DC name becomes a wrong-target write -- which is exactly what those selectors refuse +today. A rebuild edits those four token sets plus `lib-identity.sh`; that is the whole naming +surface. diff --git a/runbooks/dc-dc-phase6-designate-cos-magnum.md b/runbooks/dc-dc-phase6-designate-cos-magnum.md index 857bc81..cfa6f80 100644 --- a/runbooks/dc-dc-phase6-designate-cos-magnum.md +++ b/runbooks/dc-dc-phase6-designate-cos-magnum.md @@ -137,7 +137,10 @@ # expansions octavia-pki.sh uses; verified for vr1-dc0, vr1-dc1 and vr0-dc0. DC_REGION="${DC%%-*}" # vr1-dc0 -> vr1 (D-008 ) DC_DNS_LABEL="${DC#*-}" # vr1-dc0 -> dc0 (D-008 , 0-indexed per D-117) -DC_ZONE="omega.${DC_DNS_LABEL}.${DC_REGION}.cloud.neumatrix.local" +# label + base domain from the ONE place they live: a rebuild that renames the +# estate edits scripts/lib-identity.sh only (2026-07-30). +source "$REPO/scripts/lib-identity.sh" || { echo "FAIL: cannot read scripts/lib-identity.sh"; exit 1; } +DC_ZONE="${CLOUD_NAME}.${DC_DNS_LABEL}.${DC_REGION}.${CLOUD_DOMAIN}" echo "$DC_ZONE" # EXPECT omega.dc0.vr1.cloud.neumatrix.local (vr1-dc0) juju controllers # confirm DC_CONTROLLER is listed and reachable diff --git a/runbooks/phase-01-bundle-deploy.md b/runbooks/phase-01-bundle-deploy.md index 5cb1e48..be6c1f9 100644 --- a/runbooks/phase-01-bundle-deploy.md +++ b/runbooks/phase-01-bundle-deploy.md @@ -468,7 +468,14 @@ # (`scripts/octavia-pki.sh`), so the runbook and the gate cannot disagree: DC_REGION="${DC%%-*}" # vr1-dc0 -> vr1 (D-008 ) DC_DNS_LABEL="${DC#*-}" # vr1-dc0 -> dc0 (D-008 , 0-indexed per D-117) - DC_ZONE="omega.${DC_DNS_LABEL}.${DC_REGION}.cloud.neumatrix.local" + # The label and base domain come from the ONE place they live (2026-07-30), so a + # rebuild that renames the estate edits scripts/lib-identity.sh and nothing else. + # ${REPO:?} guarded HERE, not only at :538 below: this is now the FIRST use of $REPO in + # this step, and an unset one would try to source "/scripts/lib-identity.sh" and fail with + # a path error instead of naming the actual mistake. + source "${REPO:?REPO not set -- run 1.0-GEN.0 first}/scripts/lib-identity.sh" \ + || { echo "FAIL: cannot read \$REPO/scripts/lib-identity.sh -- stop"; exit 1; } + DC_ZONE="${CLOUD_NAME}.${DC_DNS_LABEL}.${DC_REGION}.${CLOUD_DOMAIN}" echo "controller cert zone: $DC_ZONE" # CONFIRM before the sign # ONE printf PER LINE, APPENDING. NO HEREDOC (F8 fixed at source, 2026-07-30). # The heredoc this replaces had its body and terminator at column 1 inside an indented diff --git a/scripts/lib-identity.sh b/scripts/lib-identity.sh new file mode 100644 index 0000000..d0d63e4 --- /dev/null +++ b/scripts/lib-identity.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# scripts/lib-identity.sh -- the cloud's NAME and DNS base, in ONE place. +# +# WHY THIS FILE EXISTS (2026-07-30, operator-directed). This is a rehearsal estate that is +# torn down and rebuilt repeatedly, and the region and DC names change every time. Operator, +# verbatim: "The specific server and region names do not matter as they change during every +# deployment and hardening of the deployment workflow." A naming surface that has to be +# hand-edited in several files on every rebuild is a defect generator -- F9 is the proof: the +# Octavia controller certificates on BOTH DCs carried a PREVIOUS deployment's region for weeks +# because the identity was TYPED into a runbook heredoc instead of derived. +# +# WHAT IS DERIVED vs WHAT LIVES HERE. After the 2026-07-30 work the naming chain is: +# +# .... (D-008 shape) +# | | | | +# | | | +-- CLOUD_DOMAIN, below +# | | +------------ DERIVED: ${SITE%%-*} (vr1-dc0 -> vr1) +# | +------------------ DERIVED: ${SITE#*-} (vr1-dc0 -> dc0) +# +------------------------- CLOUD_NAME, below +# +# So region and DC follow the site token automatically and need no edit on a rebuild; these +# two constants are the ONLY typed identity left in the shell surface. Change them here and +# the certificates, DNS zones, SANs and the P7 preflight gate all follow. +# +# DELIBERATELY NOT IN lib-net.sh. Sourcing lib-net.sh bare POPULATES a full set of flat +# plane/VIP defaults (its own header: "sourcing this file with no further action continues to +# populate PLANE_CIDRS ... VR0/DC0's real, measured values"). A certificate checker needs two +# strings, not a network namespace, and pulling that namespace in would hand it a pile of +# values it never asked for -- the R9 hazard in miniature. This file has NO side effects +# beyond the two assignments, so it is safe to source from anywhere. +# +# ENV-OVERRIDABLE, and that is load-bearing rather than a convenience: it is what lets the +# harness prove the centralisation is REAL by changing the value and watching the derived +# zone change with it. A constant nothing can vary is indistinguishable from a literal. +# +# NOT the site allowlists. `vr1-dc0|vr1-dc1` stays an explicit token set in the selectors -- +# an allowlist that accepts anything is how a typo'd DC name becomes a wrong-target write, +# which is exactly what preflight.sh and lib-net.sh refuse today. +# +# KEEP IN STEP WITH OPENTOFU. `opentofu/variables.tf` carries its own `domain_suffix` default +# because HCL cannot source a shell file. The two MUST agree; the octavia-pki harness asserts +# it, so a change here that forgets the tofu side turns the gauntlet red instead of silently +# splitting the estate's DNS in two. +# +# ASCII + LF. No side effects. Safe under `set -u`. + +CLOUD_NAME="${CLOUD_NAME:-omega}" +CLOUD_DOMAIN="${CLOUD_DOMAIN:-cloud.neumatrix.local}" diff --git a/scripts/octavia-pki.sh b/scripts/octavia-pki.sh index 51e12db..99f29de 100755 --- a/scripts/octavia-pki.sh +++ b/scripts/octavia-pki.sh @@ -89,6 +89,21 @@ REPO_ROOT="${OCTAVIA_PKI_REPO:-$(cd -- "$SCRIPT_DIR/.." && pwd)}" PKI_HOME="${OCTAVIA_PKI_HOME:-$HOME}" +# THE CLOUD'S NAME AND DNS BASE, from the one place they live (2026-07-30). +# Sourced from SCRIPT_DIR, not REPO_ROOT: it is a SIBLING script, and REPO_ROOT is +# harness-overridable to a fixture that has no scripts/ of its own. lib-identity.sh has no +# side effects beyond two assignments -- deliberately NOT lib-net.sh, whose bare source +# populates a full flat plane/VIP namespace this checker never asked for (the R9 hazard). +# FAIL CLOSED: without these two values every derived zone would be silently wrong, and a +# certificate gate that invents its own idea of the estate's identity is worse than none. +if [ -r "$SCRIPT_DIR/lib-identity.sh" ]; then + . "$SCRIPT_DIR/lib-identity.sh" +else + echo "REFUSE: scripts/lib-identity.sh not readable -- it carries the cloud name and DNS" + echo " base every zone here is derived from. Refusing to guess the estate's identity." + exit 3 +fi + NFAIL=0; NREFUSE=0; NOK=0 ok() { echo " ok $1"; NOK=$((NOK+1)); } nfail() { echo " FAIL $1"; NFAIL=$((NFAIL+1)); } @@ -201,12 +216,15 @@ # why the label question is settled). Sets REGION, DC_DNS, EXP_ZONE. # REGION = ${SITE%%-*} vr1-dc0 -> vr1 (D-008 ) # DC_DNS = ${SITE#*-} vr1-dc0 -> dc0 (D-008 , 0-indexed per D-117) -# zone = omega...cloud.neumatrix.local -# `omega` is D-008's label, carried unchanged by D-106's VR1 instantiation. +# zone = ${CLOUD_NAME}...${CLOUD_DOMAIN} +# The label and the base domain are the ONLY typed identity left in this chain, and +# they now live in scripts/lib-identity.sh (2026-07-30) rather than here. This estate is torn +# down and rebuilt repeatedly and the names change every time, so an identity hand-edited in +# several files is a defect generator -- F9 is the proof. derive_zone() { REGION="${SITE%%-*}" DC_DNS="${SITE#*-}" - EXP_ZONE="omega.${DC_DNS}.${REGION}.cloud.neumatrix.local" + EXP_ZONE="${CLOUD_NAME}.${DC_DNS}.${REGION}.${CLOUD_DOMAIN}" } # The subject CN of a certificate, or "" if unreadable. `-nameopt multiline` is used rather diff --git a/tests/octavia-pki/run-tests.sh b/tests/octavia-pki/run-tests.sh index be3dc85..0c4ec26 100755 --- a/tests/octavia-pki/run-tests.sh +++ b/tests/octavia-pki/run-tests.sh @@ -835,6 +835,49 @@ fi fi +# =================================================================================== +# T45..T47 -- lib-identity.sh: the cloud name and DNS base live in ONE place (2026-07-30) +# +# Operator direction: this estate is torn down and rebuilt repeatedly and the names change +# every time, so identity must not be typed into several files. Region and DC are already +# derived from the site token; these cases pin the remaining two constants. + +# T45 -- THE CENTRALISATION MUST BE REAL, NOT DECORATIVE. A constant that nothing can vary is +# indistinguishable from a literal, and this repo has shipped exactly that before. Override +# both values and require the derived zone to follow BOTH of them. +E=$(mkfix identityvar vr1-dc0 "VR1 DC0" full "omega.dc0.vr1.cloud.neumatrix.local") +OUT="$(OCTAVIA_PKI_REPO="$E/repo" OCTAVIA_PKI_HOME="$E/home" OCTAVIA_PKI_THIS_HOST=fixturehost \ + CLOUD_NAME=acme CLOUD_DOMAIN=example.test bash "$CHECK" verify vr1-dc0 2>&1)" +if printf '%s' "$OUT" | grep -q "acme.dc0.vr1.example.test"; then + ok "T45 CLOUD_NAME/CLOUD_DOMAIN really drive the derived zone (acme.dc0.vr1.example.test)" +else + no "T45 the derived zone must follow lib-identity.sh, not a literal" + printf '%s\n' "$OUT" | grep -i 'zone' | head -3 | sed 's/^/ /' +fi + +# T46 -- FAIL CLOSED on a missing identity file. Without it every derived zone would be +# silently wrong, and a certificate gate that invents the estate's identity is worse than +# none. REFUSE (3), not FAIL: we could not look up what the names are. +CP="$TMPROOT/nolib"; mkdir -p "$CP" +cp "$CHECK" "$CP/octavia-pki.sh" # copied WITHOUT its sibling lib-identity.sh +RC=0; OUT="$(bash "$CP/octavia-pki.sh" verify vr1-dc0 2>&1)" || RC=$? +[ "$RC" = 3 ] && printf '%s' "$OUT" | grep -q 'lib-identity.sh not readable' \ + && ok "T46 a missing lib-identity.sh REFUSES (exit 3) rather than guessing the estate's identity" \ + || { no "T46 missing identity file must refuse (rc=$RC)"; printf '%s\n' "$OUT" | head -3 | sed 's/^/ /'; } + +# T47 -- SHELL AND OPENTOFU MUST AGREE. HCL cannot source a shell file, so opentofu carries +# its own `domain_suffix` default. Two copies of one fact is a drift generator: if they part, +# the libvirt plane domains and the certificate zones describe different estates and NOTHING +# else would notice. This is the same standing shape as D-137 ruling 2's render-drift check. +SH_DOMAIN="$(CLOUD_DOMAIN= bash -c '. '"$REPO"'/scripts/lib-identity.sh; echo "$CLOUD_DOMAIN"')" +TF_DOMAIN="$(awk '/variable "domain_suffix"/,/^}/' "$REPO/opentofu/variables.tf" \ + | sed -n 's/.*default *= *"\([^"]*\)".*/\1/p' | head -1)" +if [ -n "$SH_DOMAIN" ] && [ "$SH_DOMAIN" = "$TF_DOMAIN" ]; then + ok "T47 lib-identity.sh and opentofu/variables.tf agree on the base domain ('$SH_DOMAIN')" +else + no "T47 base-domain DRIFT: lib-identity.sh='$SH_DOMAIN' opentofu/variables.tf='$TF_DOMAIN' -- the certificate zones and the libvirt plane domains would describe different estates" +fi + echo if [ "$F" = 0 ]; then echo "octavia-pki: $P/$P PASS"; exit 0; fi echo "octavia-pki: FAILURES: $F (passed $P)"; exit 1