xpeditis2.0/infra/prod/terraform/firewall.tf
2026-09-07 21:40:50 +02:00

158 lines
4.5 KiB
HCL

# =============================================================================
# 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"
}
}