Self-hosted Gitea on a single e2-small AlmaLinux 10 VM in us-east1, serving gitea.jasonmross.dev. Runtime is podman quadlets (systemd .container/.network/.volume units). Both images are built on Debian 13: Gitea from a GPG-verified release binary, and Caddy from an xcaddy build carrying the Google Cloud DNS provider (ACME DNS-01) and the Coraza WAF with the OWASP CRS embedded. Infrastructure is a Pulumi program in Go against a GCS state backend. Cloud Build handles CI: a push trigger for images, one for infra, and a weekly scheduled rebuild. Everything Cloud Build touches is 2nd gen. Notable design decisions, each documented where it lives: - Quadlets track a floating :prod tag. AutoUpdate=registry compares digests for a tag, so a digest-pinned image silently disables auto-updates. - Git transport and LFS bypass the WAF. With the bypass removed, a plain git push returns 403 -- packfiles trip CRS reliably. - gitea:wafMode drives both SecRuleEngine and whether the fail2ban jail acting on WAF verdicts exists. Banning on detections that were never blocks would turn a tuning false positive into an nftables ban. - fail2ban bans at the nftables prerouting hook. Published container ports are DNAT'd and never traverse INPUT, where the stock actions install their rules. - The DNS zone, backup bucket, and Gitea signing secrets are not Pulumi-owned, so pulumi destroy cannot take them with it. - The podman subnet is pinned because it is what Gitea's REVERSE_PROXY_TRUSTED_PROXIES names. Three update layers: dnf5-automatic for the OS, podman-auto-update with health-gated rollback for containers, and a weekly image rebuild that gives the second layer something to pull. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
49 lines
2.5 KiB
Plaintext
49 lines
2.5 KiB
Plaintext
# fail2ban action: drop banned sources at the nftables PREROUTING hook.
|
|
#
|
|
# Why this exists instead of the stock `nftables` action:
|
|
#
|
|
# Traffic to a published container port (443, 2222) is DNAT'd in prerouting and
|
|
# then traverses FORWARD -- it never reaches the INPUT hook. fail2ban's default
|
|
# actions install their rules on INPUT, so on a podman host they drop nothing
|
|
# at all while `fail2ban-client status` cheerfully reports active bans. It fails
|
|
# silently, which is the worst way for a security control to fail.
|
|
#
|
|
# Hooking at prerouting with priority -300 (raw) puts us ahead of both the DNAT
|
|
# that redirects the packet and netavark's own chains, so the drop applies to
|
|
# host-terminated and container-bound traffic alike.
|
|
#
|
|
# Verify with a real ban drill -- see docs/runbook.md.
|
|
|
|
[Definition]
|
|
|
|
actionstart = nft add table inet f2b-prerouting
|
|
nft add chain inet f2b-prerouting preroute '{ type filter hook prerouting priority -300 ; policy accept ; }'
|
|
nft add set inet f2b-prerouting f2b-<name>-v4 '{ type ipv4_addr ; flags interval ; }'
|
|
nft add set inet f2b-prerouting f2b-<name>-v6 '{ type ipv6_addr ; flags interval ; }'
|
|
nft add rule inet f2b-prerouting preroute ip saddr @f2b-<name>-v4 drop
|
|
nft add rule inet f2b-prerouting preroute ip6 saddr @f2b-<name>-v6 drop
|
|
|
|
# Flush rather than delete: the preroute rules still reference these sets, so a
|
|
# delete would be refused. Emptying them drops every ban, which is what stopping
|
|
# the jail means.
|
|
actionstop = nft flush set inet f2b-prerouting f2b-<name>-v4 2>/dev/null || true
|
|
nft flush set inet f2b-prerouting f2b-<name>-v6 2>/dev/null || true
|
|
|
|
# If something flushed our table out from under us, actionstart runs again.
|
|
actioncheck = nft list set inet f2b-prerouting f2b-<name>-v4 >/dev/null 2>&1
|
|
|
|
# Family is derived from the address itself rather than a fail2ban tag, so this
|
|
# stays correct across fail2ban versions that disagree about <family>.
|
|
actionban = case "<ip>" in \
|
|
*:*) nft add element inet f2b-prerouting f2b-<name>-v6 '{ <ip> }' ;; \
|
|
*) nft add element inet f2b-prerouting f2b-<name>-v4 '{ <ip> }' ;; \
|
|
esac
|
|
|
|
actionunban = case "<ip>" in \
|
|
*:*) nft delete element inet f2b-prerouting f2b-<name>-v6 '{ <ip> }' 2>/dev/null || true ;; \
|
|
*) nft delete element inet f2b-prerouting f2b-<name>-v4 '{ <ip> }' 2>/dev/null || true ;; \
|
|
esac
|
|
|
|
[Init]
|
|
name = default
|