From e3c44368c84bf2a50ccabc9fb8fe1d0d3ffc42db Mon Sep 17 00:00:00 2001 From: Keith Jackson Date: Mon, 31 Aug 2026 22:29:39 -0500 Subject: [PATCH] docs(prod): correct sudo posture to vaulted become; record status-surface ruling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two operator corrections: 1. PROD DOES NOT GET NOPASSWD SUDO. My initial checklist recommended mirroring dev's passwordless sudo onto prod — rejected, correctly. Dev's NOPASSWD is a convenience that must not propagate to production. Prod instead keeps a real sudo boundary and drives Ansible `become` with an ansible-vault-encrypted `ansible_become_password` in inventory/group_vars/k3s_prod/vault.yml, mirroring the EXISTING hypervisor and proxmox_hosts groups. The decrypting passphrase stays outside the repo at ~/.config/home-k3s/ansible-vault-pass (ansible.cfg :: vault_password_file), so plays still run unattended with no plaintext secret in git. 2. THE TASK BOARD IS NOT THE STATUS SURFACE — THE REPOSITORY IS. Operator ruling closes the escalation raised in the previous commit. Requirement/spec status is to be DERIVED from repo state (what is authored, reconciled, and covered by a green requirement-tagged test), not from a hand-maintained board. The spec board is left as a historical seed; no agent edits it and no SPEC_PIN churn is spent on it. ci/spec-immutability.sh keeps its full strictness — no path exception was added, since weakening an R-TEST-06 gate for a bookkeeping file would be a defect, not a fix. A repo-derived spec status report becomes the artifact of record alongside the coverage report and this log. Serves: R-ENV-03 (prod provisioning posture), R-SEC-01 (no plaintext secrets), R-TEST-06. Co-Authored-By: Claude Opus 5 (1M context) --- DECISIONS-LOG.md | 49 ++++++++++++++++++++------------------ infra/PROD-PROVISIONING.md | 24 +++++++++++++------ 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/DECISIONS-LOG.md b/DECISIONS-LOG.md index c70e513..4cb2ea9 100644 --- a/DECISIONS-LOG.md +++ b/DECISIONS-LOG.md @@ -1304,8 +1304,14 @@ starved etcd WAL fsync). 2. **`clusters/prod` never sets `GATEWAY_VIP`** → it would inherit the base default `10.73.0.240`, an address on the **retired libvirt network**. `cluster-vars`' own comment flags this as owed "when hardware is built". -3. **No NOPASSWD sudo for `k3s` on node 1** — `sudo -n` demands a password, so every Ansible - playbook fails at its first `become`. Dev nodes have it; prod must match. Operator action. +3. **Sudo on node 1 requires a password** — every Ansible playbook fails at its first `become`. + **Operator ruling: prod does NOT get NOPASSWD sudo** (my initial recommendation to mirror dev was + rejected, correctly — dev's passwordless sudo is a convenience that should not propagate to + prod). Instead prod uses Ansible `become` with an **ansible-vault-encrypted** + `ansible_become_password` in `inventory/group_vars/k3s_prod/vault.yml`, mirroring the existing + `hypervisor` and `proxmox_hosts` groups; the decrypting passphrase stays outside the repo at + `~/.config/home-k3s/ansible-vault-pass`. Plays still run unattended, no plaintext secret in git, + and prod keeps a real sudo boundary. 4. **`node_baseline` device defaults are virtio** (`/dev/vdb`, `/dev/vdc`) and do not exist on bare metal; the role makes a filesystem on a *block device*, so a single-disk node needs either a second drive, `/dev/sda5`, or a repartition. Prod `group_vars` must override. @@ -1313,30 +1319,27 @@ starved etcd WAL fsync). containerd pulls and CoreDNS forwarding on dev. The fix is **stranded unmerged** on `fix/node-dns-resilient-upstream`. -### ESCALATION — the task board is structurally unmaintainable +### RESOLVED BY OPERATOR — status comes from the repository, not the task board -`AGENTS.md` §11 and `coordination-protocol.md` §1 designate `agents/task-board.md` as *the one -surface agents may edit* — claim items, update status, raise `OQ-*`. But `ci/spec-immutability.sh` -fails the merge gate on **any** dirt in the spec repo, with **no path exception** for the board, and -committing the board there moves spec `HEAD` off `ci/SPEC_PIN` — which also fails. So editing the -board as instructed **blocks every merge**. +I raised an escalation here: `AGENTS.md` §11 designates `agents/task-board.md` as the one surface +agents may edit, yet `ci/spec-immutability.sh` fails the merge gate on **any** spec-repo dirt with no +path exception, and committing the board there moves spec `HEAD` off `ci/SPEC_PIN` — which also +fails. Editing the board as instructed blocks every merge. That is why the board still reads +all-`todo` after six delivered phases. -This fully explains why the board still reads all-`todo` after six delivered phases, and why -`OQ-BKP` is still marked open although Velero + CNPG Barman → S3 landed on `main`. It is not -neglect — the gate made the board unusable, and coordination state migrated here instead. +**Operator ruling (2026-08-31): the task board is not the status surface. The repository is.** +Status of each requirement/spec is to be *derived from the state of the repo* — what is authored, +what is reconciled, what has a green requirement-tagged test — not from a hand-maintained board. -I did **not** work around it: adding a path exception would weaken an R-TEST-06 gate, which the -protocol forbids ("never weaken a test to get green"). Options for the operator/architect: - -- **(a) Preferred** — the human commits board updates in the spec repo, and the impl repo adopts each - via a reviewed `ci/SPEC_PIN` bump. That is exactly the flow `SPEC_PIN`'s own comment describes, and - it keeps both the board truthful and the gate honest. Cost: a pin bump per board update. -- **(b)** Amend `spec-immutability.sh` to exempt `agents/task-board.md` only. Lower friction, - but it narrows an immutability gate — a **human** decision, not an agent's. -- **(c)** Formally declare `DECISIONS-LOG.md` the board of record and retire the spec board. Matches - practice, but contradicts the spec as written, so it needs a spec amendment. - -Until this is resolved, Phase-7 coordination state lives in this log. +Consequences, adopted: +- The spec board is left alone. It is a historical seed, not a live surface. No agent edits it, and + no `SPEC_PIN` churn is spent keeping it current. +- `ci/spec-immutability.sh` keeps its full strictness — **no path exception was added**. Weakening + an R-TEST-06 gate to make a bookkeeping file writable would have been a defect, not a fix. +- The **repo-derived spec status report** becomes the artifact of record, alongside the existing + requirement-coverage report and this log. Coverage already answers "does a tagged test exist"; + the status view must also answer "is this requirement actually *implemented and reconciled* in + the repo as it stands". ### Next steps diff --git a/infra/PROD-PROVISIONING.md b/infra/PROD-PROVISIONING.md index 342cb9a..bca74c7 100644 --- a/infra/PROD-PROVISIONING.md +++ b/infra/PROD-PROVISIONING.md @@ -111,14 +111,24 @@ Consequences to settle before imaging nodes 2 and 3: ## 5. Access (per node) — **blocks all Ansible** - [ ] **`k3s` user with the `home_network` SSH key.** Working on node 1. -- [ ] **NOPASSWD sudo.** *Not configured on node 1* — `sudo -n` demands a password, so every - playbook fails at its first `become` task. Dev nodes have this; prod must match: - ```bash - # on the node, as a user who can already sudo: - echo 'k3s ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/90-k3s - sudo chmod 0440 /etc/sudoers.d/90-k3s - sudo visudo -c +- [ ] **Password-protected sudo, driven by Ansible `become` + a vaulted password.** + **Prod does NOT use NOPASSWD sudo** (operator decision, 2026-08-31). Dev nodes do; prod + deliberately keeps the sudo password in place and supplies it to Ansible as an + **ansible-vault-encrypted** var, exactly like the existing `hypervisor` and `proxmox_hosts` + groups: ``` + inventory/group_vars/k3s_prod/ + README.md how to (re)create the vault — committed + vault.yml ansible_become_password: "..." — committed, ENCRYPTED + ``` + The passphrase that decrypts it lives **outside the repo** at + `~/.config/home-k3s/ansible-vault-pass` (`ansible.cfg :: vault_password_file`), so plays still + run unattended without a plaintext secret ever touching git. + ```bash + ansible-vault create inventory/group_vars/k3s_prod/vault.yml # ansible_become_password: ... + ``` + Prod nodes must therefore set `ansible_become: true` in their group_vars and must **not** + inherit any passwordless-sudo assumption from `group_vars/all.yml`. - [ ] **Drive health baseline** (§2), needs root: ```bash sudo apt-get update && sudo apt-get install -y smartmontools