From dc0f732d0fe5cd6732947b94bdc35b0762368262 Mon Sep 17 00:00:00 2001 From: Sergei Poljanski Date: Thu, 12 Nov 2026 15:52:29 +0400 Subject: [PATCH] terraform: match the servers; open only ports in use Checked against the Hetzner project: - Both servers have daily backups and delete and rebuild protection switched on, while the code said backups = false and set no protection. An apply would have turned all three off and discarded the existing backups. Record them as they are. - The firewalls had drifted both ways. Live, asxpio-ops also accepts XMPP ports and tcp/5000, and asxpio-services accepts 80, 443, XMPP, TURN, Valheim and Factorio ports; none of those services run. The code in turn missed tcp/25566, where the second Minecraft server listens. Describe the firewalls as they should be: internal networks, plus the public ports of services that run. Public SSH to asxpio-services goes too; sshd and iptables already refuse it from outside the VPN. This is not applied. The next plan will show the stale ports being closed and nothing else. Assisted-by: Claude:fable-5-2 --- terraform/README.md | 6 +- terraform/firewall.tf | 315 ++++++++---------------------------------- terraform/servers.tf | 8 +- 3 files changed, 68 insertions(+), 261 deletions(-) diff --git a/terraform/README.md b/terraform/README.md index c8163d0..326ec63 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -4,9 +4,11 @@ Hetzner Cloud infrastructure managed via Terraform. ## Servers -- **asxpio-ops** (2 vCPU, 4GB) - Traefik, Forgejo, app stacks +- **asxpio-ops** (CX23: 2 vCPU, 4GB) - Traefik, Forgejo, app stacks - **asxpio-services** (CX43: 8 vCPU, 16GB) - CI runner, game servers, SillyTavern +Both have Hetzner daily backups plus delete and rebuild protection. + Both servers carry `prevent_destroy` and ignore changes to `user_data`, `image` and `ssh_keys`: cloud-init only runs at first boot, and hcloud would otherwise replace a running server when those change. Edits to @@ -37,7 +39,7 @@ day-to-day configuration is in `../ansible`. | Item | Monthly | |------|---------| -| ops | €3.71 | +| ops CX23 | €3.71 | | services CX43 | €11.15 | | IPv4 (2x) | €1.24 | | S3 (1TB) | €6.19 | diff --git a/terraform/firewall.tf b/terraform/firewall.tf index c1ef557..8f87f8b 100644 --- a/terraform/firewall.tf +++ b/terraform/firewall.tf @@ -1,278 +1,79 @@ +# Only ports of services that actually run are open to the internet. +# Everything else is reachable from the private network and WireGuard. + +locals { + internal_networks = ["10.10.0.0/20", "10.20.0.0/24"] # VPC, WireGuard + anywhere = ["0.0.0.0/0", "::/0"] + + ops_public = [ + { protocol = "tcp", port = "22" }, # git over SSH (Forgejo); shell users are limited by sshd + { protocol = "tcp", port = "80" }, # Traefik + { protocol = "tcp", port = "443" }, # Traefik + { protocol = "udp", port = "60101" }, # WireGuard + ] + + services_public = [ + { protocol = "tcp", port = "25565-25566" }, # Minecraft (Paper, Fossils) + { protocol = "udp", port = "19132" }, # Minecraft Bedrock (Geyser) + ] +} + resource "hcloud_firewall" "ops_fw" { name = "asxpio-ops-fw" - # VPC - rule { - direction = "in" - protocol = "tcp" - port = "any" - source_ips = [ - "10.10.0.0/20" - ] + dynamic "rule" { + for_each = ["tcp", "udp"] + content { + direction = "in" + protocol = rule.value + port = "any" + source_ips = local.internal_networks + } } rule { - direction = "in" - protocol = "udp" - port = "any" - source_ips = [ - "10.10.0.0/20" - ] + direction = "in" + protocol = "icmp" + source_ips = local.internal_networks } - rule { - direction = "in" - protocol = "icmp" - source_ips = [ - "10.10.0.0/20" - ] - } - - # WireGuard - rule { - direction = "in" - protocol = "tcp" - port = "any" - source_ips = [ - "10.20.0.0/24" - ] - } - - rule { - direction = "in" - protocol = "udp" - port = "any" - source_ips = [ - "10.20.0.0/24" - ] - } - - rule { - direction = "in" - protocol = "icmp" - source_ips = [ - "10.20.0.0/24" - ] - } - - # SSH TODO: Remove - rule { - direction = "in" - protocol = "tcp" - port = "22" - source_ips = [ - "0.0.0.0/0", - "::/0" - ] - } - - 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" - ] - } - - # WireGuard VPN - rule { - direction = "in" - protocol = "udp" - port = "60101" - source_ips = [ - "0.0.0.0/0", - "::/0" - ] + dynamic "rule" { + for_each = local.ops_public + content { + direction = "in" + protocol = rule.value.protocol + port = rule.value.port + source_ips = local.anywhere + } } } resource "hcloud_firewall" "services_fw" { name = "asxpio-services-fw" - # VPC - rule { - direction = "in" - protocol = "tcp" - port = "any" - source_ips = [ - "10.10.0.0/20" - ] + dynamic "rule" { + for_each = ["tcp", "udp"] + content { + direction = "in" + protocol = rule.value + port = "any" + source_ips = local.internal_networks + } } rule { - direction = "in" - protocol = "udp" - port = "any" - source_ips = [ - "10.10.0.0/20" - ] + direction = "in" + protocol = "icmp" + source_ips = local.internal_networks } - rule { - direction = "in" - protocol = "icmp" - source_ips = [ - "10.10.0.0/20" - ] - } - - # WireGuard - rule { - direction = "in" - protocol = "tcp" - port = "any" - source_ips = [ - "10.20.0.0/24" - ] - } - - rule { - direction = "in" - protocol = "udp" - port = "any" - source_ips = [ - "10.20.0.0/24" - ] - } - - rule { - direction = "in" - protocol = "icmp" - source_ips = [ - "10.20.0.0/24" - ] - } - - # SSH TODO:Remove - rule { - direction = "in" - protocol = "tcp" - port = "22" - source_ips = [ - "0.0.0.0/0", - "::/0" - ] - } - - # Minecraft Server - rule { - direction = "in" - protocol = "tcp" - port = "25565" - source_ips = [ - "0.0.0.0/0", - "::/0" - ] - } - - # Minecraft Geyser - rule { - direction = "in" - protocol = "udp" - port = "19132" - source_ips = [ - "0.0.0.0/0", - "::/0" - ] - } - - # Valheim Server (UDP port range) - rule { - direction = "in" - protocol = "udp" - port = "2456-2458" - source_ips = [ - "0.0.0.0/0", - "::/0" - ] - } - - # Factorio Server - rule { - direction = "in" - protocol = "udp" - port = "34197" - source_ips = [ - "0.0.0.0/0", - "::/0" - ] - } - - # Prosody XMPP - Client connections - rule { - direction = "in" - protocol = "tcp" - port = "5222" - source_ips = [ - "0.0.0.0/0", - "::/0" - ] - } - - # Prosody XMPP - Server-to-server - rule { - direction = "in" - protocol = "tcp" - port = "5269" - source_ips = [ - "0.0.0.0/0", - "::/0" - ] - } - - # Prosody XMPP - HTTP/BOSH - rule { - direction = "in" - protocol = "tcp" - port = "5280" - source_ips = [ - "0.0.0.0/0", - "::/0" - ] - } - - # Prosody XMPP - HTTPS - rule { - direction = "in" - protocol = "tcp" - port = "5281" - source_ips = [ - "0.0.0.0/0", - "::/0" - ] - } - - # TURN/STUN Server (UDP) - rule { - direction = "in" - protocol = "udp" - port = "3478" - source_ips = [ - "0.0.0.0/0", - "::/0" - ] - } - - # TURN/STUN Server (TCP) - rule { - direction = "in" - protocol = "tcp" - port = "3478" - source_ips = [ - "0.0.0.0/0", - "::/0" - ] + dynamic "rule" { + for_each = local.services_public + content { + direction = "in" + protocol = rule.value.protocol + port = rule.value.port + source_ips = local.anywhere + } } } diff --git a/terraform/servers.tf b/terraform/servers.tf index 1d48fd9..286d320 100644 --- a/terraform/servers.tf +++ b/terraform/servers.tf @@ -10,7 +10,9 @@ resource "hcloud_server" "ops" { hcloud_ssh_key.deploy.id ] - backups = false + backups = true + delete_protection = true + rebuild_protection = true public_net { ipv4_enabled = true @@ -54,7 +56,9 @@ resource "hcloud_server" "services" { hcloud_ssh_key.deploy.id ] - backups = false + backups = true + delete_protection = true + rebuild_protection = true public_net { ipv4_enabled = true