boma/docs/decisions/002-security.md
sjat 4ee1b66e23 Source vault password from Vaultwarden via rbw; nest vault structure
Master vault password is fetched from Vaultwarden via the rbw agent
(scripts/vault-pass-client.sh, wired as vault_password_file) instead of a
plaintext .vault_pass. Vault secrets use a nested vault.<service>.<key> map.
Encrypted vault.yml files are excluded from lint. Includes the host rename in
Makefile and STATUS.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 18:16:35 +02:00

3.1 KiB

ADR-002 — Security baseline

Context

Every managed host must reach a defined security baseline before any services are deployed. This baseline is applied by the base role and is non-negotiable — it runs first, on every host, every time.

The goal is a principled, maintainable baseline appropriate for a homelab with some public-facing services — not a compliance exercise.

Baseline components

Access & authentication

  • SSH key authentication only — password auth disabled
  • Root login disabled — PermitRootLogin no
  • Dedicated ansible user with locked-down sudo (NOPASSWD for automation)
  • No shared user accounts — per-person SSH keys in group_vars/all/vars.yml

Firewall

  • nftables (native on Debian 13, replaces iptables)
  • Default policy: deny inbound, allow established/related, allow loopback
  • Rules managed entirely by Ansible — never edited manually on hosts
  • Port definitions live in group_vars/ so rules stay in sync with deployed services
  • Docker's own iptables rules are disabled — nftables manages all filtering

Note on Docker + nftables: Docker historically bypassed iptables-based firewalls. This is addressed by setting "iptables": false in Docker daemon config and managing all rules via nftables explicitly. See docs/decisions/004-docker-model.md.

Intrusion deterrence

  • fail2ban monitoring SSH (and optionally reverse proxy logs)
  • Configured to ban after 5 failed attempts, 1-hour ban

Updates

  • unattended-upgrades enabled for security patches only
  • Full system upgrades triggered deliberately via Ansible (make deploy PLAYBOOK=upgrade)
  • No automatic reboots — reboots are a conscious operational decision

Minimal attack surface

  • No unnecessary packages installed
  • Docker daemon TCP socket disabled — Unix socket only
  • No open ports beyond those explicitly defined in firewall rules

Audit trail

  • auditd installed and running with a baseline ruleset
  • Logs shipped to a central location if a log aggregation service is available

Secrets management

  • Ansible Vault for all secrets (API keys, passwords, certificates), structured as a nested vault.<service>.<key> map (ADR-003)
  • The master vault password lives in Vaultwarden and is fetched on demand by scripts/vault-pass-client.sh (wired as vault_password_file) through the rbw agent — never written to a plaintext file on disk. Unlock once per session with rbw unlock; nothing decryptable sits at rest in the repo or working tree
  • See docs/runbooks/rotate-secrets.md for rbw setup and rotation

What this baseline does not include

  • Full CIS benchmark hardening — adds complexity for marginal gain at this scale
  • SELinux / AppArmor — not applied by default, revisit if threat model changes
  • Intrusion detection (IDS) — out of scope for now

Decision

This baseline was chosen to be:

  • Effective against the realistic threat model (exposed services, shared repo)
  • Maintainable by a small team without security expertise overhead
  • Automated — no manual steps should be needed to reach baseline state