railiance-infra/docs/verification.md
codex 4d9e77c968
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Close RAIL-HO-WP-0009 declared-state gaps; leave live 6443 prune gated
Make the k3s API tunnel-only (ADR-005), stop declaring Flannel VXLAN
open to Anywhere, tag the base role so firewall can be scoped, and
schedule the Goss declared-vs-live check. CoulombCore sets ufw_manage
false so a converge cannot enable UFW there. T02 still needs operator
approval for make converge-firewall HOST=Railiance01.
2026-08-15 15:41:59 +02:00

89 lines
3.5 KiB
Markdown

# 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 the single source of truth for the target state
of every managed node. 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_sources` is empty, and
`k3s_api_revoked_sources` is pruned on a firewall-tagged converge. Flannel
VXLAN (8472/udp) is omitted while the cluster is single-node; peer
addresses go in `flannel_vxlan_allowed_sources` when a second node appears.
Hosts with `ufw_manage: false` (CoulombCore) are not rewritten. 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 the desired state of a node, update this file first. Then
update the Ansible role **and** the Goss tests to match.
## Running verification
```bash
make verify
```
This runs `ansible/playbooks/verify.yaml` against all hosts. The playbook:
1. Downloads the Goss binary (pinned version) to `/usr/local/bin/goss`
2. Copies `goss/baseline.yaml` to `/etc/goss/baseline.yaml` on each host
3. Runs `goss validate --format tap`
4. Fails the play (non-zero exit) if any assertion fails
5. Fetches the TAP report to `reports/goss-<host>-<timestamp>.tap`
6. Auto-commits the report to git
**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:
```bash
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.
## 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
1. Add the desired state to `spec/server-baseline.yaml`
2. Add the Ansible task to `ansible/roles/base/tasks/main.yml`
3. Add the Goss assertion to `goss/baseline.yaml.j2`
4. Run `make converge-firewall` and `make verify-host HOST=…` to confirm
An hourly on-host timer (`railiance-goss-baseline.timer`) reruns the last
rendered baseline. See `docs/conformance-loop.md`.
## Reports
TAP reports are committed to `reports/` after each `make verify` run.
They are machine-readable and suitable for CI pipelines. A cleanup policy
for old reports is tracked as extension point EP `78ef4879`.