vcard4reseller/deploy/terraform/main.tf
Thomas Peterson 79e996ab03 Deployment: Caddy-Edge (TLS + On-Demand für Custom-Domains) + Hetzner DNS
- Caddy ersetzt den Hetzner-LB: terminiert TLS (Portal-Domain automatisch) und
  load-balanced per reverse_proxy über die App-Nodes. Für Custom-Domains (§11)
  On-Demand-TLS, autorisiert über GET /internal/tls-allowed.
- TlsCheckController + DomainRepository::findVerifiedByHostname: erlaubt Zertifikate
  nur für Portal-Domain oder verifizierte Domains (Schutz vor Cert-Flooding).
- Terraform: hcloud_load_balancer entfernt, Caddy-Server + Firewall (80/443) +
  cloud-init-caddy (Caddyfile templated mit Upstreams/Domain/ACME).
- Optional Hetzner DNS via API (manage_dns): A-Record Portal + Wildcard → Caddy.
- nginx.prod: /internal zu Symfony geroutet; APP_PORTAL_DOMAIN-Env.

Validiert: Caddyfile (caddy validate), Terraform (validate), /internal/tls-allowed (200/403/400).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 22:13:29 +02:00

175 lines
4.6 KiB
HCL

locals {
db_private_ip = "10.0.1.10"
caddy_private_ip = "10.0.1.5"
app_base_ip = 20 # App-Nodes: 10.0.1.20, .21, ...
database_url = "mysql://${var.db_user}:${var.db_password}@${local.db_private_ip}:3306/${var.db_name}?serverVersion=11.4.0-MariaDB&charset=utf8mb4"
# Caddy-Upstreams = private IPs der App-Nodes (:80)
app_upstreams = join(" ", [for i in range(var.app_count) : "10.0.1.${local.app_base_ip + i}:80"])
ask_upstream = "10.0.1.${local.app_base_ip}" # app-1 für die On-Demand-TLS-Abfrage
}
resource "hcloud_ssh_key" "admin" {
name = "vcard4-admin"
public_key = var.ssh_public_key
}
# --- Privates Netz ---
resource "hcloud_network" "net" {
name = "vcard4-net"
ip_range = "10.0.0.0/16"
}
resource "hcloud_network_subnet" "subnet" {
network_id = hcloud_network.net.id
type = "cloud"
network_zone = var.network_zone
ip_range = "10.0.1.0/24"
}
# --- Firewalls ---
resource "hcloud_firewall" "app" {
name = "vcard4-app-fw"
rule {
direction = "in"
protocol = "tcp"
port = "22"
source_ips = [var.admin_cidr]
}
rule {
direction = "in"
protocol = "tcp"
port = "80"
source_ips = ["10.0.0.0/16"] # nur aus dem privaten Netz (Load Balancer)
}
}
resource "hcloud_firewall" "db" {
name = "vcard4-db-fw"
rule {
direction = "in"
protocol = "tcp"
port = "22"
source_ips = [var.admin_cidr]
}
rule {
direction = "in"
protocol = "tcp"
port = "3306"
source_ips = ["10.0.0.0/16"] # nur App-Nodes
}
}
# --- DB-Node ---
resource "hcloud_server" "db" {
name = "vcard4-db"
server_type = var.db_server_type
image = "ubuntu-24.04"
location = var.location
ssh_keys = [hcloud_ssh_key.admin.id]
firewall_ids = [hcloud_firewall.db.id]
user_data = templatefile("${path.module}/cloud-init-db.yaml.tftpl", {
db_name = var.db_name
db_user = var.db_user
db_password = var.db_password
db_root_password = var.db_root_password
})
network {
network_id = hcloud_network.net.id
ip = local.db_private_ip
}
depends_on = [hcloud_network_subnet.subnet]
}
# --- App-Nodes (zustandslos) ---
resource "hcloud_server" "app" {
count = var.app_count
name = "vcard4-app-${count.index + 1}"
server_type = var.app_server_type
image = "ubuntu-24.04"
location = var.location
ssh_keys = [hcloud_ssh_key.admin.id]
firewall_ids = [hcloud_firewall.app.id]
user_data = templatefile("${path.module}/cloud-init-app.yaml.tftpl", {
repo_url = var.repo_url
repo_branch = var.repo_branch
run_migrations = count.index == 0 # Migrationen nur auf dem ersten Node
app_secret = var.app_secret
database_url = local.database_url
domain = var.domain
cors_allow_origin = "^https?://(www\\.)?${replace(var.domain, ".", "\\.")}$"
jwt_passphrase = var.jwt_passphrase
jwt_private_key = var.jwt_private_key
jwt_public_key = var.jwt_public_key
s3_endpoint = var.s3_endpoint
s3_region = var.s3_region
s3_bucket = var.s3_bucket
s3_key = var.s3_key
s3_secret = var.s3_secret
})
network {
network_id = hcloud_network.net.id
ip = "10.0.1.${local.app_base_ip + count.index}"
}
depends_on = [hcloud_network_subnet.subnet, hcloud_server.db]
}
# --- Caddy-Edge (TLS-Terminierung + Reverse-Proxy/Load-Balancing) ---
resource "hcloud_firewall" "caddy" {
name = "vcard4-caddy-fw"
rule {
direction = "in"
protocol = "tcp"
port = "22"
source_ips = [var.admin_cidr]
}
rule {
direction = "in"
protocol = "tcp"
port = "80"
source_ips = ["0.0.0.0/0", "::/0"]
}
rule {
direction = "in"
protocol = "tcp"
port = "443"
source_ips = ["0.0.0.0/0", "::/0"]
}
rule {
direction = "in"
protocol = "udp"
port = "443"
source_ips = ["0.0.0.0/0", "::/0"]
}
}
resource "hcloud_server" "caddy" {
name = "vcard4-caddy"
server_type = var.app_server_type
image = "ubuntu-24.04"
location = var.location
ssh_keys = [hcloud_ssh_key.admin.id]
firewall_ids = [hcloud_firewall.caddy.id]
user_data = templatefile("${path.module}/cloud-init-caddy.yaml.tftpl", {
acme_email = var.acme_email
domain = var.domain
app_upstreams = local.app_upstreams
ask_upstream = local.ask_upstream
})
network {
network_id = hcloud_network.net.id
ip = local.caddy_private_ip
}
depends_on = [hcloud_network_subnet.subnet, hcloud_server.app]
}