diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index c47aee9..5f117f5 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -224,8 +224,12 @@ for this diagnosis and its ranked live hypotheses. REMAINING IN G10: netem only (step E, NOT started -- target is the dc0<->dc1 mesh = **virbr5 on vcloud**, - measured; `modules/netem-link` assumes PASSWORDLESS SUDO which vcloud's - operator account lacks). Session changelog item 18 (2026-07-20). + re-measured 2026-07-21). The sudo blocker is RESOLVED in mechanism: + operator-ruled scoped NOPASSWD 2026-07-21, fragment SHIPPED + (`scripts/sudoers.d/netem-tc`, harness 9 cases, gauntlet 75 ALL + GREEN); INSTALL on vcloud is operator-only and PENDING, then the + step-E placeholder netem run itself (gated). Session changelog + 2026-07-21 item 7. - The grounding audit is COMPLETE and EXITED (2026-07-19): Phases 1-6 all closed (charter `148dcef`; rulings `docs/audit/ga-rulings.md`; the Phase-5 sweep ran as six operator-gated batches in one session; exit diff --git a/docs/changelog-20260721-close-and-delivery.md b/docs/changelog-20260721-close-and-delivery.md index 2ce20b4..9ccf6bc 100644 --- a/docs/changelog-20260721-close-and-delivery.md +++ b/docs/changelog-20260721-close-and-delivery.md @@ -120,3 +120,21 @@ - LP draft authored: docs/audit/lp-draft-20260721-maas-agent-resolver.md (operator to file; sanitization warning re SEC-014 material included). - **Revert:** git revert this commit (docs only). + +## 7. Step-E netem sudo mechanism (operator-ruled) + sudoers fragment + +- RULING (this session, OPS -- netem is VR-only, no D-number per GA-R3): + question = sudo mechanism for modules/netem-link on vcloud; operator + selection = "Scoped NOPASSWD for tc (Recommended)". +- Shipped scripts/sudoers.d/netem-tc: exactly the module's two verbs + (qdisc replace ... netem * / qdisc del ... root) per MEASURED mesh + bridge (virbr5=dc0-dc1 [the step-E target], virbr7=dc0-office1, + virbr3=dc1-office1; virsh net-dumpxml this session). Bridge names are + auto-assigned/drifting (mesh-link does not pin them) -- fragment + documents the fail-closed property + mandatory pre-apply re-measure. +- NEW tests/netem-sudoers harness (9 cases incl. exact-shape rule match, + no-broadening, visudo -cf). Gauntlet 74 -> 75 ALL GREEN. +- Install is operator-only (root; visudo-checked): command in the + fragment header. NOT installed at time of this entry. +- **Revert:** git rm fragment + harness; if installed: + sudo rm /etc/sudoers.d/netem-tc && sudo visudo -c. diff --git a/scripts/sudoers.d/netem-tc b/scripts/sudoers.d/netem-tc new file mode 100644 index 0000000..9eb79a3 --- /dev/null +++ b/scripts/sudoers.d/netem-tc @@ -0,0 +1,33 @@ +# /etc/sudoers.d/netem-tc -- scoped NOPASSWD for modules/netem-link (step E). +# Operator-ruled 2026-07-21 ("Scoped NOPASSWD for tc (Recommended)"): the +# netem-link module runs `ssh vcloud 'sudo tc qdisc ...'` and the operator +# account has no passwordless sudo; this grants EXACTLY the two verbs the +# module issues, per measured mesh bridge, nothing else. +# +# INSTALL (operator, on vcloud -- visudo-checked, never edited in place): +# sudo install -m 0440 -o root -g root scripts/sudoers.d/netem-tc /etc/sudoers.d/netem-tc +# sudo visudo -c +# +# BRIDGE NAMES ARE MEASURED, NOT STABLE IDENTITY (libvirt auto-assigns +# virbrN; modules/mesh-link does not pin bridge names). Measured 2026-07-21 +# on vcloud (virsh net-dumpxml): +# virbr5 = mesh-vr1-dc0-vr1-dc1 (the step-E netem target) +# virbr7 = mesh-vr1-dc0-office1 +# virbr3 = mesh-vr1-dc1-office1 +# RE-MEASURE before every netem apply (the step-E runbook step does); if a +# mesh network is ever recreated, virbrN can change and this file MUST be +# re-issued from a fresh measurement -- a stale name here fails CLOSED +# (sudo denies), it cannot misapply to the wrong bridge silently unless the +# kernel reuses the name for another libvirt net, which the pre-apply +# re-measure exists to catch. +# +# Roosevelt: netem is VR-only (real fiber has real latency); this file has +# no Roosevelt analog and retires with VR1 (OPS-tagged, no D-number -- +# GA-R3 doubt resolves down). + +jessea123 ALL=(root) NOPASSWD: /usr/sbin/tc qdisc replace dev virbr5 root netem * +jessea123 ALL=(root) NOPASSWD: /usr/sbin/tc qdisc del dev virbr5 root +jessea123 ALL=(root) NOPASSWD: /usr/sbin/tc qdisc replace dev virbr7 root netem * +jessea123 ALL=(root) NOPASSWD: /usr/sbin/tc qdisc del dev virbr7 root +jessea123 ALL=(root) NOPASSWD: /usr/sbin/tc qdisc replace dev virbr3 root netem * +jessea123 ALL=(root) NOPASSWD: /usr/sbin/tc qdisc del dev virbr3 root diff --git a/tests/netem-sudoers/run-tests.sh b/tests/netem-sudoers/run-tests.sh new file mode 100755 index 0000000..62dbc39 --- /dev/null +++ b/tests/netem-sudoers/run-tests.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# tests/netem-sudoers/run-tests.sh -- guard for scripts/sudoers.d/netem-tc +# (step-E scoped NOPASSWD, operator-ruled 2026-07-21). Static confinement +# assertions: only tc, only the two ruled verbs, only measured mesh bridges, +# no broadening (ALL, shells, wildcards outside netem args). visudo syntax +# check when available. Exit: 0 all pass | 1 any failed. ASCII + LF. +set -uo pipefail +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +FRAG="$(cd "$HERE/../.." && pwd)/scripts/sudoers.d/netem-tc" +PASS=0; FAIL=0 +ok(){ echo " PASS $1"; PASS=$((PASS+1)); } +no(){ echo " FAIL $1"; FAIL=$((FAIL+1)); } + +[ -f "$FRAG" ] && ok "T1 fragment present" || { no "T1 fragment present"; echo; echo "netem-sudoers: $PASS passed, 1 failed"; exit 1; } + +RULES="$(grep -v '^#' "$FRAG" | grep -v '^[[:space:]]*$')" + +# T2: every rule line is a NOPASSWD tc rule for the operator user -- nothing else +BAD="$(grep -vcE '^jessea123 ALL=\(root\) NOPASSWD: /usr/sbin/tc qdisc (replace|del) dev virbr[0-9]+ root( netem \*)?$' <<<"$RULES")" +[ "$BAD" -eq 0 ] && ok "T2 every rule matches the ruled tc shape exactly" \ + || no "T2 every rule matches the ruled tc shape exactly ($BAD stray line(s))" + +# T3: exactly 6 rules -- replace+del per measured mesh bridge (5,7,3) +N="$(wc -l <<<"$RULES")" +[ "$N" -eq 6 ] && ok "T3 exactly 6 rules (2 verbs x 3 mesh bridges)" \ + || no "T3 exactly 6 rules (found $N)" +for br in virbr5 virbr7 virbr3; do + grep -q "replace dev $br root netem" <<<"$RULES" && grep -q "del dev $br root\$" <<<"$RULES" \ + && ok "T4 $br has replace+del pair" || no "T4 $br has replace+del pair" +done + +# T5: no broadening tokens anywhere in active rules +grep -qE 'ALL[[:space:]]*$|/bin/|/usr/bin/|sh |bash |env ' <<<"$RULES" \ + && no "T5 no shells / blanket ALL commands" \ + || ok "T5 no shells / blanket ALL commands" + +# T6: the drifting-ID caveat is documented (re-measure discipline) +grep -q 'RE-MEASURE' "$FRAG" && ok "T6 re-measure caveat documented" \ + || no "T6 re-measure caveat documented" + +# T7: visudo syntax check when available (unprivileged -cf; skip cleanly) +if command -v visudo >/dev/null 2>&1; then + if visudo -cf "$FRAG" >/dev/null 2>&1; then ok "T7 visudo -cf clean"; else no "T7 visudo -cf clean"; fi +else + echo " SKIP T7 visudo not available" +fi + +echo; echo "netem-sudoers: $PASS passed, $FAIL failed" +[ "$FAIL" -eq 0 ]