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.
141 lines
5.9 KiB
Markdown
141 lines
5.9 KiB
Markdown
# 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: false` on 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:
|
|
|
|
- `*:6443` k3s-server (the API this workplan closes)
|
|
- `92.205.62.239:2379` / `:2380` etcd on the public address — S2 concern
|
|
- `*:10250` kubelet — S2 concern
|
|
- `0.0.0.0:18081` Python reverse proxy to `127.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:18081` issue-core proxy is for, and whether it stays.
|