4.0 KiB
4.0 KiB
Ansible — dev_blog production host
Provisions the AlmaLinux 10 instance created by infra/ (Vultr) so it can
run the Podman-managed dev-blog-app + dev-blog-caddy stack with:
- Automatic security updates via
dnf-automatic(upgrade_type=security,apply_updates=yes,reboot=never) plus a separateauto-reboot.timerthat fires daily at 02:00 America/Chicago and reboots only whendnf needs-restarting -rsays one is pending. - nftables as the host firewall (allow
22/80/443/tcp+443/udp, rate-limited new SSH,policy dropeverywhere else; firewalld masked). - fail2ban with the
nftables-multiportbanaction, watching the SSH journal and the Caddy / Coraza logs. - Podman + runtime deps (
crun,netavark,aardvark-dns,slirp4netns,fuse-overlayfs,passt,container-selinux,skopeo,buildah) and the Quadlet units from../quadlet/(with theImage=lines rewritten to point at GHCR). - GHCR auth via the
ghCLI: a PAT (ghcr_pat) is used to authenticate bothghandpodmanagainstghcr.io, and the app/caddy images are pulled before the Quadlet units start.
Coraza → fail2ban → nftables wiring
[ Caddy + Coraza container ]
│ writes to /var/log/caddy (bind-mounted from host)
▼
/var/log/caddy/access.log (Caddy JSON access log – status=403 lines)
/var/log/caddy/coraza-audit.log (Coraza serial audit log – any --A-- entry)
│ tailed by
▼
[ fail2ban jail: caddy-coraza ]
│ banaction = nftables-multiport
▼
[ nftables table f2b-table / set addr-set-caddy-coraza ] → packets dropped
Three repo files were updated to enable that wiring:
quadlet/dev-blog-caddy.container— addsVolume=/var/log/caddy:/var/log/caddy:Z.caddy/Caddyfile— sends access logs to/var/log/caddy/access.log(JSON).caddy/coraza.conf— enablesSecAuditLog(serial) at/var/log/caddy/coraza-audit.log, only on relevant 4xx/5xx.
The fail2ban filter (roles/fail2ban/files/caddy-coraza.filter) matches
either "remote_ip":"…","status":403 lines from the access log or
--xxxx-A-- headers in the audit log. Either pattern produces a hit on
<HOST>, so once a client crosses maxretry within findtime, fail2ban
drops it into the f2b-table nft set for bantime.
Usage
cd ansible
cp inventory.yml.example inventory.yml # set ansible_host = your Vultr IP
# Quick connectivity / fact gathering:
ansible -m ping blog
# Full provision:
ansible-playbook site.yml
# Just one role:
ansible-playbook site.yml --tags fail2ban # (add `tags:` to roles if needed)
Tunables (group_vars/all.yml)
| Var | Default | Notes |
|---|---|---|
ssh_port |
22 |
|
http_port / https_port |
80 / 443 |
|
caddy_log_dir |
/var/log/caddy |
host path bind-mounted into Caddy |
dnf_automatic_apply_updates |
true |
|
dnf_automatic_upgrade_type |
security |
default for full updates |
f2b_findtime / f2b_bantime / f2b_maxretry |
10m / 1h / 5 |
Caddy/Coraza jail thresholds |
ghcr_owner / ghcr_repo |
OWNER / dev_blog |
GHCR namespace |
image_tag |
latest |
Tag to pull (CI sets to commit SHA) |
Notes
- Don't enable both
firewalldand thenftablesservice — this playbook masksfirewalld. Podman'snetavarkwrites to its own nft tables and doesn't conflict. - The
synchronizetask usesansible.posix.synchronize. Installansible-galaxy collection install ansible.posixonce on the controller. - After first run, verify with:
systemctl status dnf-automatic.timer nftables fail2bannft list rulesetfail2ban-client status caddy-coraza