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.
114 lines
3.5 KiB
Django/Jinja
114 lines
3.5 KiB
Django/Jinja
# Goss baseline assertions for railiance managed nodes.
|
|
# Derived from spec/server-baseline.yaml — keep in sync.
|
|
# Run: goss -g /etc/goss/baseline.yaml validate
|
|
#
|
|
# THIS IS A TEMPLATE. It is rendered per host from inventory variables, so the
|
|
# firewall assertions below are generated from the SAME declaration that
|
|
# converges the host (k3s_api_allowed_sources, flannel_vxlan_allowed_sources,
|
|
# ufw_extra_allowed in group_vars). That is deliberate: a hand-written
|
|
# assertion drifts from the declaration it is meant to check, which is exactly
|
|
# how RAIL-HO-WP-0009 happened.
|
|
|
|
package:
|
|
ufw:
|
|
installed: true
|
|
fail2ban:
|
|
installed: true
|
|
git:
|
|
installed: true
|
|
curl:
|
|
installed: true
|
|
vim:
|
|
installed: true
|
|
htop:
|
|
installed: true
|
|
|
|
# age and sops are binary installs, not apt packages — checked via command below
|
|
|
|
service:
|
|
ufw:
|
|
enabled: true
|
|
running: true
|
|
fail2ban:
|
|
enabled: true
|
|
running: true
|
|
# Ubuntu 24.04 uses socket activation: ssh.service is disabled by design,
|
|
# ssh.socket keeps it running. Assert the socket is enabled.
|
|
ssh.socket:
|
|
enabled: true
|
|
running: true
|
|
|
|
file:
|
|
/etc/ssh/sshd_config.d/10-hardening.conf:
|
|
exists: true
|
|
contents:
|
|
- "PermitRootLogin no"
|
|
- "PasswordAuthentication no"
|
|
- "PubkeyAuthentication yes"
|
|
|
|
user:
|
|
tegwick:
|
|
exists: true
|
|
# sudo access is via /etc/sudoers.d/tegwick (NOPASSWD), not group membership
|
|
shell: /bin/bash
|
|
|
|
command:
|
|
"ufw status":
|
|
exit-status: 0
|
|
stdout:
|
|
- "Status: active"
|
|
- /OpenSSH.*ALLOW/
|
|
{% for src in k3s_api_allowed_sources | default([]) %}
|
|
- '/6443\/tcp\s+ALLOW\s+{{ src.address | regex_escape }}/'
|
|
{% endfor %}
|
|
{% for src in flannel_vxlan_allowed_sources | default([]) %}
|
|
- '/8472\/udp\s+ALLOW\s+{{ src.address | regex_escape }}/'
|
|
{% endfor %}
|
|
{% for rule in ufw_extra_allowed | default([]) %}
|
|
- '/{{ rule.port }}\/{{ rule.proto | default("tcp") }}\s+ALLOW/'
|
|
{% endfor %}
|
|
|
|
# Exact allowlist size: extra hand grants must fail, not only missing ones.
|
|
# The previous assertion matched /6443\/tcp.*ALLOW/, which passes identically
|
|
# whether the API is restricted to one operator address or open to the entire
|
|
# internet — it asserted that the port was allowed, not from whom.
|
|
"ufw status | grep -Ec '6443/tcp[[:space:]]+ALLOW[[:space:]]+Anywhere' || true":
|
|
exit-status: 0
|
|
stdout:
|
|
- "0"
|
|
"ufw status | grep -E '6443/tcp[[:space:]]+ALLOW' | grep -vc 'Anywhere' || true":
|
|
exit-status: 0
|
|
stdout:
|
|
- "{{ k3s_api_allowed_sources | default([]) | length }}"
|
|
"ufw status | grep -Ec '8472/udp[[:space:]]+ALLOW[[:space:]]+Anywhere' || true":
|
|
exit-status: 0
|
|
stdout:
|
|
- "0"
|
|
"ufw status | grep -E '8472/udp[[:space:]]+ALLOW' | grep -vc 'Anywhere' || true":
|
|
exit-status: 0
|
|
stdout:
|
|
- "{{ flannel_vxlan_allowed_sources | default([]) | length }}"
|
|
|
|
{% for src in k3s_api_revoked_sources | default([]) %}
|
|
# Revoked operator source must not retain access: {{ src.comment | default('') }}
|
|
"ufw status | grep -Ec '6443/tcp[[:space:]]+ALLOW[[:space:]]+{{ src.address }}' || true":
|
|
exit-status: 0
|
|
stdout:
|
|
- "0"
|
|
{% endfor %}
|
|
"grep NOPASSWD /etc/sudoers.d/tegwick":
|
|
exit-status: 0
|
|
stdout:
|
|
- "NOPASSWD"
|
|
"grep -r HISTCONTROL /etc/profile.d/":
|
|
exit-status: 0
|
|
stdout:
|
|
- "ignorespace"
|
|
"fail2ban-client status sshd":
|
|
exit-status: 0
|
|
stdout:
|
|
- "Status for the jail: sshd"
|
|
"test -x /usr/local/bin/age":
|
|
exit-status: 0
|
|
"test -x /usr/local/bin/sops":
|
|
exit-status: 0
|