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
This commit is contained in:
Sergei Poljanski 2026-11-12 15:52:29 +04:00
commit dc0f732d0f
3 changed files with 68 additions and 261 deletions

View file

@ -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
}
}
}