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.
89.244.90.255 appeared in the live allowlist mid-session. Verified legitimate:
SSH pubkey auth as tegwick from that address on 2026-08-02, and UFW BLOCK
entries on 6443 immediately before it was granted. Now declared.
Both stale grants are deleted from the live host, so the security goal of T02 is
met and the live 6443 allowlist matches the declaration exactly.
Full convergence deliberately held: --check reports 11 changes, mostly unrelated
to the firewall, including MemoryMax=1500M on user-1000.slice which could OOM
running agent workloads, plus an sshd restart. That is a separate decision, and
the base role has no tags so convergence cannot be scoped to UFW alone. Records
both follow-ons.
The allowlist drifting again by hand during the session that was fixing
allowlist drift is now recorded as the strongest argument for T04, removing the
API from the public internet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The live host restricted 6443/tcp to specific operator addresses, added by hand,
while this role still declared the port open to Anywhere with no source
restriction. The declared config was weaker than reality: running the base role
would have REMOVED the restriction and exposed the Kubernetes API to the
internet. Security was tightened on the host and never fed back into the source
of truth.
Found 2026-08-11 while diagnosing lost cluster access, which turned out to be an
ISP lease rotation (89.244.90.246 -> .236) against a hand-maintained allowlist.
Changes:
- defaults: k3s_api_allowed_sources (empty = 6443 closed to all external
sources, the safe failure; SSH unaffected so the host stays recoverable) and
k3s_api_revoked_sources, so rotated addresses are pruned rather than left as
standing grants to whoever the ISP reassigns them to
- tasks: grant approved sources, then remove any blanket rule, then revoke
retired ones. Order matters - grants are added before the blanket rule is
deleted so convergence never opens a window with no API access
- group_vars/all.yaml: the current operator address, plus the two stale grants
(.246 rotated, 85.132.220.102 historic) marked for revocation
- docs/verification.md: state that 6443 is source-restricted rather than
listing it as a plainly allowed port
Not yet converged against the live host - the role change is committed but
running it is a production action needing operator approval.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add ops_bridge_pubkey to group_vars/all.yaml (public key only, safe to
commit) and inject it via ansible.posix.authorized_key in the base role,
immediately after SSH hardening. This ensures ops-bridge tunnel
connectivity is available as soon as SSH infrastructure is up on any
managed host — no manual key provisioning required for new nodes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC/V9fe5MGKdhTBz9KwEvC1NE+HjdoCtQocpGxP6Pko9
Generated 2026-03-27 via make custodian-keygen. Private key at workstation
only (~/.ssh/id_custodian_agent), never committed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Establishes a dedicated SSH keypair for the Custodian automation agent:
- ansible/roles/custodian_agent/: authorized_key task (tagged custodian_agent)
- ansible/inventory/group_vars/all.yaml: custodian_agent_user/pubkey vars
- ansible/playbooks/bootstrap.yaml: custodian_agent role added
- Makefile: provision-custodian-agent / provision-custodian-agent-host targets
Keypair generation: cd ~/the-custodian && make custodian-keygen
Then deploy: cd ~/railiance-infra && make provision-custodian-agent
The private key lives at ~/.ssh/id_custodian_agent — never committed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>