KeiSeiKit-1.0/skills/vm-provision/phase-5-verify.md
Parfii-bot 0be354a920 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

3.9 KiB

Phase 5 — Verification Hard Gate (ssh-check + firewall-diff)

Goal: fail-closed verification. Phase 6 refuses to run unless BOTH ssh-check AND firewall-diff exit 0. Verify criterion: SSH_CHECK_OK = true AND FW_DIFF_OK = true.


5.a — Pull config artefacts from the VM

scp "${ADMIN_USER}@${VM_IP}:/etc/ssh/sshd_config"            <run-dir>/sshd_config
ssh "${ADMIN_USER}@${VM_IP}" "sudo tar -C /etc/ssh -cf - sshd_config.d" \
  | tar -C <run-dir>/ -xf -
ssh "${ADMIN_USER}@${VM_IP}" "sudo ufw status numbered"      > <run-dir>/ufw-status.txt

The ufw status requires sudo on most distros — the admin user has it via NOPASSWD:ALL from harden-base.sh. If sudo requires TTY, prefix sudo -n and surface the failure.

All captured files are READ ONLY, for ssh-check / firewall-diff to parse. We NEVER push config back from the workstation.


5.b — Run ssh-check

_primitives/_rust/ssh-check/target/release/ssh-check \
  --config  <run-dir>/sshd_config \
  --drop-in <run-dir>/sshd_config.d \
  --allow-user "${ADMIN_USER}" \
  --json > <run-dir>/ssh-check.json
SSH_EXIT=$?

Exit 0 → SSH_CHECK_OK=true. Exit 2 → SSH_CHECK_OK=false and <run-dir>/ssh-check.json lists the violating directives with file:line precision. Exit 1 → usage/parse error; surface the stderr and loop back to Phase 4.


5.c — Run firewall-diff

_primitives/_rust/firewall-diff/target/release/firewall-diff \
  --intent <run-dir>/firewall-intent.yaml \
  --status-file <run-dir>/ufw-status.txt \
  --json > <run-dir>/firewall-diff.json
FW_EXIT=$?

Exit 0 → FW_DIFF_OK=true. Exit 2 → the JSON lists missing (in intent, not live) and extra (in live, not intent) rules; default_mismatches flags a non-deny inbound policy.


5.d — Decision tree

ssh-check firewall-diff Action
0 0 Proceed to Phase 6.
2 0 Loop to 4.a with the sshd_config.d fix + re-ship harden-base.sh.
0 2 Ask user: apply the missing/extra deltas via ufw commands, or update firewall-intent.yaml (the intent was wrong). ONE AskUserQuestion.
2 2 Both failed — show both JSON reports; recommend a single fresh harden-base.sh re-run first (common-mode fix), then re-verify.
1 1 Workstation issue (missing binary, bad path) — NOT a VM problem. Rebuild the Rust primitives (cargo build --release in _primitives/_rust/).

5.e — The AskUserQuestion

Exactly ONE AskUserQuestion, gated on the decision tree above:

Verification results: ssh-check=<PASS|FAIL>, firewall-diff=<PASS|FAIL>. Pick one:

  • Proceed (only shown when both PASS) → Phase 6.
  • Fix and retry → loop to Phase 4 (or to 5.c if intent YAML is wrong).
  • Ignore and proceedBLOCKED. The hard-gate invariant refuses this path per SKILL.md. You can abort, but you cannot bypass.

5.f — Verify criterion

  • ssh-check exit 0.
  • firewall-diff exit 0.
  • <run-dir>/ssh-check.json and <run-dir>/firewall-diff.json saved.

Emit: Phase 5 done: hard-gate PASSED. Artefacts in <run-dir>/.

Proceed to Phase 6.


5.g — Non-obvious pitfalls

  • sshd_config.d drop-in not loaded. Debian 12's default /etc/ssh/sshd_config includes the .d directory via an Include directive. We don't follow Include on purpose (security — includes can escape the intended tree). Pass --drop-in explicitly.
  • ufw status shows IPv6 rules as duplicates. Intent is IPv4-only by default; firewall-diff's normalisation treats (v6) rules with same port/proto as "expected" and does not flag them. If you need strict v6-only rules, open a separate intent file.
  • MaxAuthTries at 6 or 10 (Debian default). harden-base.sh sets 3. If a previous manual edit raised it and we re-ran without rewriting, ssh-check will FAIL maxauthtries. Fix: re-run harden-base.sh.