Refine Hetzner firewall rules and update server types

Overhaul and expand firewall definitions for both `prod` and `test` environments to enable comprehensive inter-subnet communication.

This includes implementing explicit rules supporting:
- Docker Swarm overlay networks between application and database subnets.
- High-availability database clusters (PostgreSQL replication, MongoDB replica sets, Patroni, etcd).
- Internal access for various infrastructure services (Vault, Redis, RabbitMQ, APISIX, Prometheus, Grafana).

All firewall rule descriptions are standardized in English for improved clarity and consistency.

Additionally, update default `server_type_swarm` and `server_type_db` variables to the recommended `CPX` series for both environments. An initial generated Ansible inventory for the test environment is also added.
This commit is contained in:
Murat ÖZDEMİR 2026-05-11 14:54:46 +03:00
parent b115a4cbdf
commit 03ad812512
10 changed files with 409 additions and 224 deletions

View File

@ -0,0 +1,14 @@
"all":
"children":
"db":
"hosts":
"iklim-db-01":
"ansible_host": "167.235.194.61"
"ansible_user": "root"
"private_ip": "10.10.20.11"
"swarm":
"hosts":
"iklim-app-01":
"ansible_host": "167.235.205.93"
"ansible_user": "root"
"private_ip": "10.10.10.11"

View File

@ -1,141 +1,172 @@
# Swarm node firewall public HTTP/HTTPS + private infra services
resource "hcloud_firewall" "swarm" { resource "hcloud_firewall" "swarm" {
name = "${local.name_prefix}-firewall-app" name = "${local.name_prefix}-firewall-app"
# SSH admin CIDRs only
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "22" port = "22"
source_ips = var.admin_allowed_cidrs source_ips = var.admin_allowed_cidrs
description = "SSH — admin CIDRs only"
} }
# HTTP public
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "80" port = "80"
source_ips = ["0.0.0.0/0", "::/0"] source_ips = ["0.0.0.0/0", "::/0"]
description = "HTTP public"
} }
# HTTPS public
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "443" port = "443"
source_ips = ["0.0.0.0/0", "::/0"] source_ips = ["0.0.0.0/0", "::/0"]
description = "HTTPS public"
} }
# Docker Swarm control plane
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "2377" port = "2377"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "Docker Swarm control plane"
} }
# Docker Swarm node discovery (TCP)
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "7946" port = "7946"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "Docker Swarm node discovery (TCP)"
} }
# Docker Swarm node discovery (UDP)
rule { rule {
direction = "in" direction = "in"
protocol = "udp" protocol = "udp"
port = "7946" port = "7946"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "Docker Swarm node discovery (UDP)"
} }
# Docker Swarm VXLAN overlay
rule { rule {
direction = "in" direction = "in"
protocol = "udp" protocol = "udp"
port = "4789" port = "4789"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "Docker Swarm VXLAN overlay"
}
rule {
direction = "in"
protocol = "tcp"
port = "2377"
source_ips = [local.db_subnet_cidr]
description = "Docker Swarm control plane from DB subnet"
}
rule {
direction = "in"
protocol = "tcp"
port = "7946"
source_ips = [local.db_subnet_cidr]
description = "Docker Swarm node discovery (TCP) from DB subnet"
}
rule {
direction = "in"
protocol = "udp"
port = "7946"
source_ips = [local.db_subnet_cidr]
description = "Docker Swarm node discovery (UDP) from DB subnet"
}
rule {
direction = "in"
protocol = "udp"
port = "4789"
source_ips = [local.db_subnet_cidr]
description = "Docker Swarm VXLAN overlay from DB subnet"
} }
# Vault API private only, never public
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "8200" port = "8200"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "Vault API — private only, never public"
} }
# Redis
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "6379" port = "6379"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "Redis"
} }
# RabbitMQ AMQP
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "5672" port = "5672"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "RabbitMQ AMQP"
} }
# RabbitMQ STOMP
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "61613" port = "61613"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "RabbitMQ STOMP"
} }
# RabbitMQ Web STOMP
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "15674" port = "15674"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "RabbitMQ Web STOMP"
} }
# RabbitMQ Management private only, SWAG arkasindan 443 uzerinden
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "15672" port = "15672"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "RabbitMQ Management — private, via SWAG on 443"
} }
# APISIX Dashboard private only, SWAG arkasindan 443 uzerinden (IP kisitli)
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "9000" port = "9000"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "APISIX Dashboard — private, via SWAG on 443 (IP restricted)"
} }
# APISIX Admin API sadece Docker overlay icinden Dashboard erisir
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "9180" port = "9180"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "APISIX Admin API — internal only, accessed by Dashboard via Docker overlay"
} }
# Prometheus private only, Grafana Docker overlay uzerinden erisir
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "9090" port = "9090"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "Prometheus — private, accessed by Grafana via Docker overlay"
} }
# Grafana private only, SWAG arkasindan 443 uzerinden (IP kisitli)
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "3000" port = "3000"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "Grafana — private, via SWAG on 443 (IP restricted)"
} }
labels = { labels = {
@ -144,48 +175,103 @@ resource "hcloud_firewall" "swarm" {
} }
} }
# DB node firewall SSH + DB ports from app/swarm subnet only
resource "hcloud_firewall" "db" { resource "hcloud_firewall" "db" {
name = "${local.name_prefix}-firewall-db" name = "${local.name_prefix}-firewall-db"
# SSH admin CIDRs only
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "22" port = "22"
source_ips = var.admin_allowed_cidrs source_ips = var.admin_allowed_cidrs
description = "SSH — admin CIDRs only"
} }
# PostgreSQL from app/swarm subnet
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "5432" port = "5432"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "PostgreSQL from app subnet"
} }
# PostgreSQL replication within DB subnet
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "5432" port = "5432"
source_ips = [local.db_subnet_cidr] source_ips = [local.db_subnet_cidr]
description = "PostgreSQL replication within DB subnet"
} }
# MongoDB from app/swarm subnet
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "27017" port = "27017"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "MongoDB from app subnet"
} }
# MongoDB replica set internal traffic
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "27017" port = "27017"
source_ips = [local.db_subnet_cidr] source_ips = [local.db_subnet_cidr]
description = "MongoDB replica set internal traffic"
}
rule {
direction = "in"
protocol = "tcp"
port = "2377"
source_ips = [local.app_subnet_cidr]
description = "Docker Swarm control plane from app subnet"
}
rule {
direction = "in"
protocol = "tcp"
port = "7946"
source_ips = [local.app_subnet_cidr]
description = "Docker Swarm node discovery (TCP) from app subnet"
}
rule {
direction = "in"
protocol = "udp"
port = "7946"
source_ips = [local.app_subnet_cidr]
description = "Docker Swarm node discovery (UDP) from app subnet"
}
rule {
direction = "in"
protocol = "udp"
port = "4789"
source_ips = [local.app_subnet_cidr]
description = "Docker Swarm VXLAN overlay from app subnet"
}
rule {
direction = "in"
protocol = "tcp"
port = "2379"
source_ips = [local.db_subnet_cidr]
description = "etcd client port within DB subnet"
}
rule {
direction = "in"
protocol = "tcp"
port = "2380"
source_ips = [local.db_subnet_cidr]
description = "etcd peer port within DB subnet"
}
rule {
direction = "in"
protocol = "tcp"
port = "8008"
source_ips = [local.db_subnet_cidr]
description = "Patroni REST API within DB subnet"
} }
labels = { labels = {

View File

@ -2,7 +2,7 @@
hcloud_token = "YOUR_HETZNER_PROD_PROJECT_API_TOKEN" hcloud_token = "YOUR_HETZNER_PROD_PROJECT_API_TOKEN"
location = "fsn1" location = "fsn1"
image = "rocky-10" image = "rocky-10"
server_type_swarm = "cx42" server_type_swarm = "cpx42"
server_type_db = "cx52" server_type_db = "cpx32"
admin_ssh_public_key_path = "~/.ssh/id_ed25519.pub" admin_ssh_public_key_path = "~/.ssh/id_ed25519.pub"
admin_allowed_cidrs = ["1.2.3.4/32", "5.6.7.8/32"] admin_allowed_cidrs = ["1.2.3.4/32", "5.6.7.8/32"]

View File

@ -18,13 +18,13 @@ variable "image" {
variable "server_type_swarm" { variable "server_type_swarm" {
type = string type = string
default = "cx42" default = "cpx42"
description = "Hetzner server type for Swarm nodes" description = "Hetzner server type for Swarm nodes"
} }
variable "server_type_db" { variable "server_type_db" {
type = string type = string
default = "cx52" default = "cpx32"
description = "Hetzner server type for DB nodes" description = "Hetzner server type for DB nodes"
} }

View File

@ -0,0 +1,23 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hetznercloud/hcloud" {
version = "1.62.0"
constraints = "~> 1.49"
hashes = [
"h1:Y9/c7aSVFnOvkvx+E33cNqHsFOu7S4vijxorU9FXmxs=",
"zh:2077f1655b6a7e26ae6d8ce3b5f35a6a65728416deb16dbd5165115da7534f37",
"zh:2234db7b84efa489b8e5f29f756cfed4a5bab760985f62d38c4b9ed2b3d6b4b6",
"zh:4abee7212fd15bcbf22b156ff18933f3975f2b1153fd3e93a1cacf31b9d35137",
"zh:5d7a63a8d4c73babea715c0c7a5dc04a08b5076e2f1f59855bf61f2393017bf0",
"zh:5ef15b4367c139b18167b2169421cb1f760d485db42f05ef292bd63eadcfa802",
"zh:62b432d918815812ea35ceca252d0ea833a8e1dbbc72c6b2d410369d7b8b0d85",
"zh:63fd3d3803a86447f9a1c0c49bffe704168fbc907ea3688cfd847e1dd012e9ff",
"zh:6a84f7125dad475f939afb58a1f0ec089e835d1b30ca64f467d85565a89f7508",
"zh:834c2ddcaa986323ecb7aa2baa3fd7b1888c2aec249f296822e53a4bc46be66e",
"zh:887e503de3720894eb756bcfc67b3d8ceb68564f9f8bb2a115d7398f0b5990b7",
"zh:976216f9aa89a466a1d97ae776c4df2edbac4a9ab29ff9850884060d15024570",
"zh:c3d7fc02e0fdf1bbee3a07c9171281a59d79ad9df2ec04342a81f3875709171c",
"zh:e0165f404357f2c1f89524165f38c88f643fb518483adfbf7817a6033a83f4d8",
]
}

View File

@ -1,141 +1,172 @@
# Swarm node firewall public HTTP/HTTPS + private infra services
resource "hcloud_firewall" "swarm" { resource "hcloud_firewall" "swarm" {
name = "${local.name_prefix}-firewall-app" name = "${local.name_prefix}-firewall-app"
# SSH admin CIDRs only
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "22" port = "22"
source_ips = var.admin_allowed_cidrs source_ips = var.admin_allowed_cidrs
description = "SSH — admin CIDRs only"
} }
# HTTP public
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "80" port = "80"
source_ips = ["0.0.0.0/0", "::/0"] source_ips = ["0.0.0.0/0", "::/0"]
description = "HTTP public"
} }
# HTTPS public
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "443" port = "443"
source_ips = ["0.0.0.0/0", "::/0"] source_ips = ["0.0.0.0/0", "::/0"]
description = "HTTPS public"
} }
# Docker Swarm control plane
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "2377" port = "2377"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "Docker Swarm control plane"
} }
# Docker Swarm node discovery (TCP)
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "7946" port = "7946"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "Docker Swarm node discovery (TCP)"
} }
# Docker Swarm node discovery (UDP)
rule { rule {
direction = "in" direction = "in"
protocol = "udp" protocol = "udp"
port = "7946" port = "7946"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "Docker Swarm node discovery (UDP)"
} }
# Docker Swarm VXLAN overlay
rule { rule {
direction = "in" direction = "in"
protocol = "udp" protocol = "udp"
port = "4789" port = "4789"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "Docker Swarm VXLAN overlay"
}
rule {
direction = "in"
protocol = "tcp"
port = "2377"
source_ips = [local.db_subnet_cidr]
description = "Docker Swarm control plane from DB subnet"
}
rule {
direction = "in"
protocol = "tcp"
port = "7946"
source_ips = [local.db_subnet_cidr]
description = "Docker Swarm node discovery (TCP) from DB subnet"
}
rule {
direction = "in"
protocol = "udp"
port = "7946"
source_ips = [local.db_subnet_cidr]
description = "Docker Swarm node discovery (UDP) from DB subnet"
}
rule {
direction = "in"
protocol = "udp"
port = "4789"
source_ips = [local.db_subnet_cidr]
description = "Docker Swarm VXLAN overlay from DB subnet"
} }
# Vault API private only, never public
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "8200" port = "8200"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "Vault API — private only, never public"
} }
# Redis
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "6379" port = "6379"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "Redis"
} }
# RabbitMQ AMQP
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "5672" port = "5672"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "RabbitMQ AMQP"
} }
# RabbitMQ STOMP
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "61613" port = "61613"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "RabbitMQ STOMP"
} }
# RabbitMQ Web STOMP
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "15674" port = "15674"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "RabbitMQ Web STOMP"
} }
# RabbitMQ Management private only, SWAG arkasindan 443 uzerinden erisim
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "15672" port = "15672"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "RabbitMQ Management — private, via SWAG on 443"
} }
# APISIX Dashboard private only, SWAG arkasindan 443 uzerinden (IP kisitli)
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "9000" port = "9000"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "APISIX Dashboard — private, via SWAG on 443 (IP restricted)"
} }
# APISIX Admin API sadece Docker overlay icinden Dashboard erisir, insan erisimi gerekmez
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "9180" port = "9180"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "APISIX Admin API — internal only, accessed by Dashboard via Docker overlay"
} }
# Prometheus private only, SWAG arkasindan 443 uzerinden erisim
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "9090" port = "9090"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "Prometheus — private, via SWAG on 443"
} }
# Grafana private only, SWAG arkasindan 443 uzerinden erisim
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "3000" port = "3000"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "Grafana — private, via SWAG on 443"
} }
labels = { labels = {
@ -144,32 +175,63 @@ resource "hcloud_firewall" "swarm" {
} }
} }
# DB node firewall SSH + DB ports from app/swarm subnet only
resource "hcloud_firewall" "db" { resource "hcloud_firewall" "db" {
name = "${local.name_prefix}-firewall-db" name = "${local.name_prefix}-firewall-db"
# SSH admin CIDRs only
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "22" port = "22"
source_ips = var.admin_allowed_cidrs source_ips = var.admin_allowed_cidrs
description = "SSH — admin CIDRs only"
} }
# PostgreSQL from app/swarm subnet
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "5432" port = "5432"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "PostgreSQL from app subnet"
} }
# MongoDB from app/swarm subnet
rule { rule {
direction = "in" direction = "in"
protocol = "tcp" protocol = "tcp"
port = "27017" port = "27017"
source_ips = [local.app_subnet_cidr] source_ips = [local.app_subnet_cidr]
description = "MongoDB from app subnet"
}
rule {
direction = "in"
protocol = "tcp"
port = "2377"
source_ips = [local.app_subnet_cidr]
description = "Docker Swarm control plane from app subnet"
}
rule {
direction = "in"
protocol = "tcp"
port = "7946"
source_ips = [local.app_subnet_cidr]
description = "Docker Swarm node discovery (TCP) from app subnet"
}
rule {
direction = "in"
protocol = "udp"
port = "7946"
source_ips = [local.app_subnet_cidr]
description = "Docker Swarm node discovery (UDP) from app subnet"
}
rule {
direction = "in"
protocol = "udp"
port = "4789"
source_ips = [local.app_subnet_cidr]
description = "Docker Swarm VXLAN overlay from app subnet"
} }
labels = { labels = {

View File

@ -2,7 +2,7 @@
hcloud_token = "YOUR_HETZNER_TEST_PROJECT_API_TOKEN" hcloud_token = "YOUR_HETZNER_TEST_PROJECT_API_TOKEN"
location = "fsn1" location = "fsn1"
image = "rocky-10" image = "rocky-10"
server_type_swarm = "cx32" server_type_swarm = "cpx42"
server_type_db = "cx42" server_type_db = "cpx42"
admin_ssh_public_key_path = "~/.ssh/id_ed25519.pub" admin_ssh_public_key_path = "~/.ssh/id_ed25519.pub"
admin_allowed_cidrs = ["1.2.3.4/32", "5.6.7.8/32"] admin_allowed_cidrs = ["1.2.3.4/32", "5.6.7.8/32"]

View File

@ -18,13 +18,13 @@ variable "image" {
variable "server_type_swarm" { variable "server_type_swarm" {
type = string type = string
default = "cx32" default = "cpx42"
description = "Hetzner server type for the Swarm node" description = "Hetzner server type for the Swarm node"
} }
variable "server_type_db" { variable "server_type_db" {
type = string type = string
default = "cx42" default = "cpx42"
description = "Hetzner server type for the DB node" description = "Hetzner server type for the DB node"
} }

BIN
test-app-graphs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

BIN
test-db-graphs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB