Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02994-7685-7940-bf34-3555b8256018
4.8 KiB
Server Verification
railiance-infra ships a declarative baseline spec and a Goss test suite that
asserts every managed node matches it. This replaces manual spot-checks with
a reproducible, CI-friendly pass/fail verdict.
The spec
spec/server-baseline.yaml is executable through
scripts/baseline_contract.py. The dynamic inventory resolves its selected
profile into the variables consumed by both Ansible and Goss. It covers:
- Firewall — UFW active, default deny inbound, required ports allowed
(SSH 22/tcp; HostEurope Nydus 2224/tcp). The k3s API (6443/tcp) is
tunnel-only (ADR-005):
k3s_api_allowed_sourcesis empty, andk3s_api_revoked_sourcesis pruned on a firewall-tagged converge. Flannel VXLAN (8472/udp) is omitted while the cluster is single-node; peer addresses go inflannel_vxlan_allowed_sourceswhen a second node appears.CoulombCoreselects an explicit external-firewall profile and verifies its replacement INPUT default-drop control without rewriting it. A host with an empty 6443 allowlist is recoverable over SSH. - SSH daemon — root login disabled, password auth disabled, pubkey auth enabled
- Services — ufw, fail2ban, ssh.socket enabled and running
- Packages — ufw, fail2ban, git, curl, vim, htop (age and sops installed as binaries)
- Users — admin user with bash shell and passwordless sudo
- Security — fail2ban sshd jail active, HISTCONTROL=ignorespace in /etc/profile.d/
When you change governed desired state, update this file. The consumers use the
same resolved variables; make validate-baseline fails if either consumer is
disconnected from the contract.
Running verification
make verify
This runs ansible/playbooks/verify.yaml against all hosts. The playbook:
- Reads the installed Goss binary and
/etc/goss/baseline.yamlmetadata. - Renders the selected profile on the controller and requires its SHA-256 digest to match the installed baseline exactly.
- Runs the fixed
goss validate --format tapargument vector. - Fails the play if the surface is missing/stale or any assertion fails.
- Writes TAP evidence only on the controller under
reports/.
scripts/handoff_contract.py statically rejects remote modules other than
stat, assert, and the exact Goss command. Controller writes must be
delegated to localhost and remain under reports/. It also pins every Goss
command assertion and both profile firewall probes to their reviewed read-only
set. make verify therefore does not install packages, rewrite files, reload
systemd, or change services on a managed host.
All assertions passed → exit 0
One or more assertions FAILED → exit non-zero, TAP report in reports/
After convergence
The standard workflow after converging a new or updated node:
make converge # bring the node to the desired state
make verify # assert it got there
Run make status for a quick human-readable summary; run make verify when
you need a structured, automatable check.
If verification fails because the installed surface is stale, review the rendered changes and use the explicit mutating interface for only the intended host:
make verify-refresh-host \
HOST=Railiance01 \
APPROVE_VERIFY_REFRESH=REFRESH-GOSS-Railiance01
Refreshing every host requires the distinct approval value
APPROVE_VERIFY_REFRESH=REFRESH-GOSS-ALL. Both refresh targets install or
update Goss, its baseline, wrapper, service, and timer before running checks.
Goss test file
goss/baseline.yaml.j2 is rendered per host from the same inventory lists
that converge UFW. The mapping is:
| spec section | Goss resource |
|---|---|
firewall |
command: ufw status stdout patterns |
ssh |
file: /etc/ssh/sshd_config.d/10-hardening.conf contains |
services |
service: blocks |
packages |
package: blocks |
users |
user: block + command: grep NOPASSWD |
security.histcontrol |
command: grep -r HISTCONTROL /etc/profile.d/ |
security.fail2ban_jails |
command: fail2ban-client status sshd |
age, sops (binary installs) |
command: test -x /usr/local/bin/{age,sops} |
Adding new assertions
- Add the desired state to
spec/server-baseline.yaml. - If it introduces a new control kind, teach both consumers that kind.
- Run
make validate-baselineand the unit tests. - Run the applicable reviewed convergence/refresh command, then use
make verify-host HOST=…as the read-only acceptance check.
An hourly on-host timer (railiance-goss-baseline.timer) reruns the last
rendered baseline. See docs/conformance-loop.md.
Reports
TAP reports are transient under ignored reports/. They are machine-readable
and suitable for CI pipelines. Promote deliberately retained evidence under
docs/evidence/ after reviewing it for safe metadata. A cleanup policy for old
reports is tracked as extension point EP 78ef4879.