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.
5.9 KiB
RAIL-HO-WP-0009-T03 — declared-vs-live audit
Audited 2026-08-15 from the workstation against Railiance01
(92.205.62.239) and CoulombCore (92.205.130.254). SSH as tegwick.
This is a record of what was found; absence of drift is recorded too.
Method
Compared ansible/roles/base/tasks/main.yml, sibling bootstrap roles, and
spec/server-baseline.yaml to live ufw status, sshd -T, fail2ban,
sudoers, listening sockets, systemd user-slice, nproc, swappiness, and
timezone.
Matches declaration
| Surface | Railiance01 | CoulombCore |
|---|---|---|
sshd drop-in 10-hardening.conf |
present; root/password off, pubkey on | same |
sshd -T effective |
matches drop-in | matches drop-in |
fail2ban sshd jail |
active | active |
/etc/profile.d/histcontrol.sh |
ignorespace |
ignorespace |
sudoers tegwick NOPASSWD |
present | present |
| OpenSSH UFW | ALLOW Anywhere | UFW inactive (see below) |
Divergences
1. k3s API allowlist (Railiance01) — same defect class as T01
Live 6443 grants on 2026-08-15:
| From | Declared? |
|---|---|
89.244.90.236 |
was declared; now revoked by ADR-005 |
89.244.90.255 |
was declared; now revoked by ADR-005 |
89.244.90.248 |
never declared — third hand grant |
No blanket Anywhere rule. Stale grants from T02 (.246, .102) are gone.
.248 appeared after the 2026-08-12 session that was already fixing
allowlist drift. Goss as of T01 would not have failed: it asserted declared
sources present, not that the live set equalled the declared set.
Workstation egress on this audit was 85.132.220.102 — already on the
revoked list. That is the allowlist treadmill in one line.
2. Flannel VXLAN declaration weaker than needed (T06)
8472/udp listens on 0.0.0.0 on both hosts. Railiance01 has no UFW
rule for it (default deny incoming covers it). The role used to declare
8472/udp ALLOW with no source restriction, so a converge would have
opened VXLAN to the internet. Fixed: empty flannel_vxlan_allowed_sources,
blanket rule deleted if present.
3. Port 2224/tcp Anywhere — intended, now declared
Both hosts run nydus-ex-api on 0.0.0.0:2224. Railiance01 UFW allows it
Anywhere with comment nydus-ex-api dashboard agent. This is the HostEurope
Nydus agent (dashboard, root-password reset, backups); see
hosteurope/260308-dependency-nydus.md. Removing it would break the
provider. Declared as ufw_extra_allowed. Source-restricting it is not
possible without HostEurope's egress list.
4. CoulombCore: UFW inactive, but not "no firewall"
ufw status → Status: inactive. Live filter is iptables INPUT DROP
with a Plesk-era accept list: 22, 80, 443, 2224, mail, 3306, 5432, 53,
445/139, and tcp 49152-65535. 6443 and 8472 are not in that accept
list, so they are dropped even with UFW off.
Converging the base role would enable UFW, allow only SSH + (now empty) k3s
- nydus, and take down 80/443 and the rest of the accepted surface.
ufw_manage: falseon CoulombCore prevents that. Enabling UFW there is its own decision and needs a declared accept list first.
5. Resource limits and swap — applied on CoulombCore, never on Railiance01
| Railiance01 | CoulombCore | |
|---|---|---|
user-1000.slice MemoryMax |
absent | 1500M / swap 512M (Ansible) |
| PAM nproc | no limits.d files |
60-nproc-tegwick.conf |
| swappiness | 60 (kernel default) | 10 |
| timezone | Etc/UTC |
Etc/UTC |
The user-slice cap can OOM agent workloads (T02). Do not apply it to
Railiance01 without a separate decision. Role tags now isolate it
(resource_limits, swap, timezone).
inventory/group_vars/all.yaml sets timezone: Europe/Berlin but that file
is not loaded by the Ansible inventory script. The role default is UTC, which
matches live. Leave it; do not start loading that file as a side effect.
6. Inventory host_vars path bug
ansible/inventory_from_yaml.py loaded inventory/host_vars/<name>.yml
(does not exist). The real file is ansible/inventory/host_vars/CoulombCore.yml.
Ansible does not auto-load host_vars next to a script inventory, so
ufw_manage, swap, and resource-limit overrides would not reach the play.
Fixed: the script now reads ansible/inventory/host_vars/ first.
7. sshd restart was an unconditioned task
Restart sshd ran on every converge, which is why --check reported an
sshd restart among the 11 changes. It is now a handler notified only when
the hardening drop-in changes.
8. Undeclared listening sockets (not opened in UFW)
On Railiance01, not in this role, UFW default-deny so not currently public:
*:6443k3s-server (the API this workplan closes)92.205.62.239:2379/:2380etcd on the public address — S2 concern*:10250kubelet — S2 concern0.0.0.0:18081Python reverse proxy to127.0.0.1:18765(issue-core), started 2026-07-26. No UFW rule. Out of scope here; record only.
On CoulombCore, *:6443 and 0.0.0.0:8472 listen; iptables INPUT DROP
without an accept for those ports is what currently protects them, not UFW.
9. Routed policy
Railiance01 UFW default routed is deny. The role used to set routed
allow for Flannel. Routed allow is now applied only when
flannel_vxlan_allowed_sources is non-empty (multi-node). Single-node
converge will not flip it.
Decisions taken in this task
- Declare Nydus 2224; do not remove it.
- Do not enable UFW on CoulombCore (
ufw_manage: false). - Do not apply Railiance01 resource limits / swappiness / timezone as part of this workplan.
- Tag the base role and sibling bootstrap roles so firewall can be scoped.
- Fix the host_vars loader.
Still operator decisions (not this workplan)
- Enable UFW on CoulombCore, with a declared accept list that includes 80/443.
- Apply or drop the declared user-slice memory cap on Railiance01.
- Whether etcd/kubelet listening on the public address should be rebound
(
railiance-cluster). - What the
0.0.0.0:18081issue-core proxy is for, and whether it stays.