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>
3.1 KiB
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
ansibleuser 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": falsein Docker daemon config and managing all rules via nftables explicitly. Seedocs/decisions/004-docker-model.md.
Intrusion deterrence
fail2banmonitoring SSH (and optionally reverse proxy logs)- Configured to ban after 5 failed attempts, 1-hour ban
Updates
unattended-upgradesenabled 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
auditdinstalled 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 asvault_password_file) through therbwagent — never written to a plaintext file on disk. Unlock once per session withrbw unlock; nothing decryptable sits at rest in the repo or working tree - See
docs/runbooks/rotate-secrets.mdforrbwsetup 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