# ============================================================================= # Firewalls Hetzner Cloud (interfaces publiques uniquement) # ============================================================================= # Politique : tout est ferme par defaut. On n'ouvre que le strict necessaire, # et jamais vers 0.0.0.0/0 sauf pour le trafic web derriere Cloudflare. locals { # Rangs d'IP Cloudflare. A rafraichir avec : # curl -s https://www.cloudflare.com/ips-v4 # curl -s https://www.cloudflare.com/ips-v6 # Verifiez cette liste a chaque `terraform apply` (cf. scripts/refresh-cloudflare-ips.sh). cloudflare_ipv4 = [ "173.245.48.0/20", "103.21.244.0/22", "103.22.200.0/22", "103.31.4.0/22", "141.101.64.0/18", "108.162.192.0/18", "190.93.240.0/20", "188.114.96.0/20", "197.234.240.0/22", "198.41.128.0/17", "162.158.0.0/15", "104.16.0.0/13", "104.24.0.0/14", "172.64.0.0/13", "131.0.72.0/22", ] cloudflare_ipv6 = [ "2400:cb00::/32", "2606:4700::/32", "2803:f800::/32", "2405:b500::/32", "2405:8100::/32", "2a06:98c0::/29", "2c0f:f248::/32", ] http_sources_v4 = var.restrict_http_to_cloudflare ? local.cloudflare_ipv4 : ["0.0.0.0/0"] http_sources_v6 = var.restrict_http_to_cloudflare ? local.cloudflare_ipv6 : ["::/0"] http_sources = concat(local.http_sources_v4, local.http_sources_v6) } # --- Noeud applicatif (k3s server) ------------------------------------------ resource "hcloud_firewall" "app" { name = "${var.project_name}-fw-app" # SSH : uniquement depuis les IPs d'administration. rule { direction = "in" protocol = "tcp" port = "22" source_ips = var.admin_ip_allowlist description = "SSH administrateur" } # API Kubernetes : uniquement depuis les IPs d'administration. # Les runners GitHub Actions n'ont pas d'IP fixe : le deploiement passe donc # par SSH + kubectl local sur le serveur, pas par 6443 expose. # Cf. .github/workflows/cd-main.yml. rule { direction = "in" protocol = "tcp" port = "6443" source_ips = var.admin_ip_allowlist description = "API k3s (kubectl administrateur)" } rule { direction = "in" protocol = "tcp" port = "80" source_ips = local.http_sources description = "HTTP (redirection 301 + ACME HTTP-01 de secours)" } rule { direction = "in" protocol = "tcp" port = "443" source_ips = local.http_sources description = "HTTPS via Cloudflare" } # ICMP : utile pour le diagnostic reseau, sans risque notable. rule { direction = "in" protocol = "icmp" source_ips = ["0.0.0.0/0", "::/0"] description = "ICMP (ping / MTU discovery)" } labels = { project = var.project_name managed = "terraform" } } # --- Ouverture temporaire pour la CI/CD ------------------------------------- # Les runners GitHub Actions n'ont pas d'IP fixe : impossible de les inscrire # une fois pour toutes dans une liste blanche, et ouvrir SSH au monde entier # n'est pas une option. # # Ce firewall est attache a app-01 et reste VIDE en regime nominal. Le workflow # cd-main.yml y injecte l'IP du runner juste avant le deploiement, puis le vide # systematiquement (etape `if: always()`). La fenetre d'exposition dure le temps # du deploiement, pour une seule IP, sur le seul port 22. # # `ignore_changes = [rule]` est indispensable : sans lui, le prochain # `terraform apply` supprimerait une regle posee par la CI en cours d'execution. resource "hcloud_firewall" "cicd" { name = "${var.project_name}-fw-cicd" # Aucune regle : l'etat au repos est "ferme". labels = { project = var.project_name managed = "terraform" purpose = "cicd-temporaire" } lifecycle { ignore_changes = [rule] } } # --- Noeud de donnees -------------------------------------------------------- # Aucun port applicatif expose publiquement. PostgreSQL et Redis n'ecoutent que # sur l'IP privee (cf. conf/postgresql.conf et conf/redis.conf) et sont en plus # filtres par nftables. Ce firewall ne laisse passer que le SSH d'administration. resource "hcloud_firewall" "db" { name = "${var.project_name}-fw-db" rule { direction = "in" protocol = "tcp" port = "22" source_ips = var.admin_ip_allowlist description = "SSH administrateur" } rule { direction = "in" protocol = "icmp" source_ips = ["0.0.0.0/0", "::/0"] description = "ICMP (ping / MTU discovery)" } labels = { project = var.project_name managed = "terraform" } }