infra/roles/services/templates/traefik/dynamic/routes.yml.j2
jack fccbd1a45a
Some checks failed
CI/CD / syntax-check (push) Successful in 42s
CI/CD / deploy (push) Failing after 52s
feat: Cloudflare DNS-01 ACME + Docker hardening + sysctl
Cloudflare DNS-01 ACME:
- Switch Traefik cert resolver from httpChallenge to dnsChallenge
  using Cloudflare provider (resolvers: 1.1.1.1, 1.0.0.1)
- Add CLOUDFLARE_DNS_API_TOKEN env to Traefik container
- Add CF_ZONE_ID + cloudflare_dns_api_token to all/main.yml
- Store API token in Ansible Vault

Docker daemon hardening:
- Add log-driver: json-file with max-size 10m / max-file 3
  (prevents disk fill from unbounded container logs)
- Add live-restore: true (containers survive Docker daemon restart)

Kernel hardening (sysctl):
- New roles/base/tasks/sysctl.yml via ansible.posix.sysctl
- IP spoofing protection (rp_filter)
- Disable ICMP redirects and broadcast pings
- SYN flood protection (syncookies, backlog)
- Disable IPv6 (not used)
- Restrict kernel pointers and dmesg to root
- Disable SysRq, suid core dumps

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 04:06:46 +07:00

206 lines
5.4 KiB
Django/Jinja

# Traefik dynamic routing config — generated by Ansible
# Do not edit manually; re-run ansible-playbook deploy.yml
http:
routers:
traefik-dashboard:
rule: "Host(`{{ domain_traefik }}`)"
entrypoints: [websecure]
tls:
certresolver: letsencrypt
service: api@internal
middlewares: [authelia@docker, rate-limit-strict]
vaultwarden:
rule: "Host(`{{ domain_vault }}`)"
entrypoints: [websecure]
tls:
certresolver: letsencrypt
service: vaultwarden
middlewares: [rate-limit-default]
forgejo:
rule: "Host(`{{ domain_git }}`)"
entrypoints: [websecure]
tls:
certresolver: letsencrypt
service: forgejo
middlewares: [rate-limit-default]
plane-api:
rule: "Host(`{{ domain_plane }}`) && (PathPrefix(`/api/`) || PathPrefix(`/auth/`))"
entrypoints: [websecure]
tls:
certresolver: letsencrypt
service: plane-api
middlewares: [rate-limit-api]
plane:
rule: "Host(`{{ domain_plane }}`)"
entrypoints: [websecure]
tls:
certresolver: letsencrypt
service: plane-web
middlewares: [rate-limit-default]
plane-godmode:
rule: "Host(`{{ domain_plane }}`) && PathPrefix(`/god-mode/`)"
entrypoints: [websecure]
tls:
certresolver: letsencrypt
service: plane-admin
middlewares: [authelia@docker, rate-limit-strict]
priority: 10
plane-spaces:
rule: "Host(`{{ domain_plane }}`) && PathPrefix(`/spaces/`)"
entrypoints: [websecure]
tls:
certresolver: letsencrypt
service: plane-space
middlewares: [rate-limit-default]
priority: 10
syncthing:
rule: "Host(`{{ domain_sync }}`)"
entrypoints: [websecure]
tls:
certresolver: letsencrypt
service: syncthing
middlewares: [authelia@docker, rate-limit-strict]
grafana:
rule: "Host(`{{ domain_dashboard }}`)"
entrypoints: [websecure]
tls:
certresolver: letsencrypt
service: grafana
middlewares: [rate-limit-default]
authelia:
rule: "Host(`{{ domain_auth }}`)"
entrypoints: [websecure]
tls:
certresolver: letsencrypt
service: authelia
middlewares: [rate-limit-strict]
uptime-kuma:
rule: "Host(`{{ domain_status }}`)"
entrypoints: [websecure]
tls:
certresolver: letsencrypt
service: uptime-kuma
middlewares: [rate-limit-default]
services:
vaultwarden:
loadBalancer:
servers:
- url: "http://vaultwarden:80"
forgejo:
loadBalancer:
servers:
- url: "http://forgejo:3000"
plane-api:
loadBalancer:
servers:
- url: "http://plane-api:8000"
plane-web:
loadBalancer:
servers:
- url: "http://plane-web:3000"
plane-admin:
loadBalancer:
servers:
- url: "http://plane-admin:80"
plane-space:
loadBalancer:
servers:
- url: "http://plane-space:3000"
syncthing:
loadBalancer:
servers:
- url: "http://syncthing:8384"
grafana:
loadBalancer:
servers:
- url: "http://grafana:3000"
authelia:
loadBalancer:
servers:
- url: "http://authelia:9091"
uptime-kuma:
loadBalancer:
servers:
- url: "http://uptime-kuma:3001"
middlewares:
# ── Security Headers (applied globally via entrypoint) ─────────────────
security-headers:
headers:
stsSeconds: 31536000
stsIncludeSubdomains: true
stsPreload: true
forceSTSHeader: true
frameDeny: true
contentTypeNosniff: true
browserXssFilter: true
referrerPolicy: "strict-origin-when-cross-origin"
permissionsPolicy: "camera=(), microphone=(), geolocation=(), payment=()"
customResponseHeaders:
X-Robots-Tag: "noindex, nofollow"
Server: ""
# ── Rate Limiting ──────────────────────────────────────────────────────
# Default: 100 req/s burst 50 — general web traffic
rate-limit-default:
rateLimit:
average: 100
burst: 50
period: 1s
# API: 30 req/s burst 20 — API endpoints
rate-limit-api:
rateLimit:
average: 30
burst: 20
period: 1s
# Strict: 10 req/s burst 5 — admin/login interfaces
rate-limit-strict:
rateLimit:
average: 10
burst: 5
period: 1s
# ── Auth (legacy basic auth kept for direct fallback) ─────────────────
traefik-auth:
basicAuth:
users:
- "{{ traefik_dashboard_htpasswd }}"
syncthing-auth:
basicAuth:
users:
- "{{ syncthing_basic_auth_htpasswd }}"
# ── Authelia ForwardAuth ───────────────────────────────────────────────
authelia:
forwardAuth:
address: "http://authelia:9091/api/verify?rd=https://{{ domain_auth }}"
trustForwardHeader: true
authResponseHeaders:
- Remote-User
- Remote-Groups
- Remote-Email
- Remote-Name