Add mta1/mta2 DMZ mail gateways with WireGuard bootstrap
ci/woodpecker/pr/plan Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline failed

This commit is contained in:
2026-10-12 21:50:09 +11:00
parent f5921bf4c8
commit 0ee9dac5b0
8 changed files with 317 additions and 3 deletions
+50
View File
@@ -0,0 +1,50 @@
# This file is maintained automatically by "tofu init".
# Manual edits may be lost in future updates.
provider "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/binarylane" {
version = "0.14.0"
constraints = "0.14.0"
hashes = [
"h1:k72XOG+O2dyqOXoFYzdaOYzIBELekwNF1XRlCjZaSa0=",
"zh:d84c05d191a0c53c96b8fbac62f8ccd2d41678f008406dd656d29d2c10766b45",
]
}
provider "registry.opentofu.org/hashicorp/vault" {
version = "5.6.0"
constraints = "5.6.0"
hashes = [
"h1:afl/41BzHl93OACbk2EnGHF/xVgHtzmEREcdejASJcM=",
"zh:437c9f3920cc29d7a453aa5342270d55c618ca4979718c34641a49af1437d5a8",
"zh:5aeec1c3ed1710d5594277250ae4202c97bf9d8462a3c672c689e96bec3d7c8a",
"zh:71a61b7667e1016b5ed524b5842c65be1ab1661f258faf7a7cec9db4ea44abd8",
"zh:769a946cc4d99752dce8db0c4e509a201c0063f266dd387164b239854392d1b7",
"zh:82b0173dc9f65035b4ae6faaea497239455e5562d5d8c24a1f882e4451234dcf",
"zh:8f3373ef3dd1e026424c194d46842c0451ee79aacc67d1a1087d8d1ef8e55e95",
"zh:984d15ebd2fdaa55608efff7d98d59e414a1cb8c7b899b883fbbfbeb5849b70d",
"zh:9b4194ab7cf28d22098c51436dbfea878e413c418d6dbcfd3689aeb580993e21",
"zh:ef30a4958328b9fa3d58845ddebe8bd574a533170a28dc7b73121d52f565563b",
]
}
provider "registry.opentofu.org/ojford/wireguard" {
version = "0.4.1"
constraints = "0.4.1"
hashes = [
"h1:c09xyC2LPfUHgVG/nis4l1B51rBZOCJwfQIcn5RJ3/k=",
"zh:1c3e89cf19118fc07d7b04257251fc9897e722c16e0a0df7b07fcd261f8c12e7",
"zh:1cdcd10ba61520d7e21590ea4d96d188cefbce3dcdbf89b277696d6023ec7620",
"zh:26bde3cf4850ba8605c028c84bed6075d2175853a0744303f6051cc3b5c51dd1",
"zh:28c351481689762dcd653229aa5f847473f71568a8222fafbddda0c600377a46",
"zh:3bd7ab222f13286449342d35d6dd3dbe343f575e47d4c2e6f8f64149386070c7",
"zh:3d25381c96df52253e7767aae49f0bead427c79f34ecdfb38026335e03c12de2",
"zh:4017a40b50886d3f1f0e886f8eacd1924c5f03888c90862c55ba98bb376198b2",
"zh:561978966998a3703053ac990796d5a88c7c14afd77e623846941a66b6fc9c92",
"zh:5f8460880a5f9cc9f4cc7c7c3c41156b0dae3c2b56f29cc259b3dc75ad650fe1",
"zh:724592993a0cccd45e0290aba2a6b72f7bb668a4ae3b8d778bb5d379d4105863",
"zh:b4de1157176eaf1fcc624d812ae6134893d6c73f3d1d2325c94cd9e707678d7b",
"zh:c5530bc71beedba579be57ea580f4151ec87223f198147771a924debff2e255e",
"zh:d3c7863e5baaf05e76fa5db3d3f58ff94440408873e370770b6a0080981cec37",
"zh:ec0a7d3a540cfb92e15b9a7acc43446059a9254b5904b48c940564c70e2b8aa1",
]
}
+50
View File
@@ -0,0 +1,50 @@
# This file is maintained automatically by "tofu init".
# Manual edits may be lost in future updates.
provider "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/binarylane" {
version = "0.14.0"
constraints = "0.14.0"
hashes = [
"h1:k72XOG+O2dyqOXoFYzdaOYzIBELekwNF1XRlCjZaSa0=",
"zh:d84c05d191a0c53c96b8fbac62f8ccd2d41678f008406dd656d29d2c10766b45",
]
}
provider "registry.opentofu.org/hashicorp/vault" {
version = "5.6.0"
constraints = "5.6.0"
hashes = [
"h1:afl/41BzHl93OACbk2EnGHF/xVgHtzmEREcdejASJcM=",
"zh:437c9f3920cc29d7a453aa5342270d55c618ca4979718c34641a49af1437d5a8",
"zh:5aeec1c3ed1710d5594277250ae4202c97bf9d8462a3c672c689e96bec3d7c8a",
"zh:71a61b7667e1016b5ed524b5842c65be1ab1661f258faf7a7cec9db4ea44abd8",
"zh:769a946cc4d99752dce8db0c4e509a201c0063f266dd387164b239854392d1b7",
"zh:82b0173dc9f65035b4ae6faaea497239455e5562d5d8c24a1f882e4451234dcf",
"zh:8f3373ef3dd1e026424c194d46842c0451ee79aacc67d1a1087d8d1ef8e55e95",
"zh:984d15ebd2fdaa55608efff7d98d59e414a1cb8c7b899b883fbbfbeb5849b70d",
"zh:9b4194ab7cf28d22098c51436dbfea878e413c418d6dbcfd3689aeb580993e21",
"zh:ef30a4958328b9fa3d58845ddebe8bd574a533170a28dc7b73121d52f565563b",
]
}
provider "registry.opentofu.org/ojford/wireguard" {
version = "0.4.1"
constraints = "0.4.1"
hashes = [
"h1:c09xyC2LPfUHgVG/nis4l1B51rBZOCJwfQIcn5RJ3/k=",
"zh:1c3e89cf19118fc07d7b04257251fc9897e722c16e0a0df7b07fcd261f8c12e7",
"zh:1cdcd10ba61520d7e21590ea4d96d188cefbce3dcdbf89b277696d6023ec7620",
"zh:26bde3cf4850ba8605c028c84bed6075d2175853a0744303f6051cc3b5c51dd1",
"zh:28c351481689762dcd653229aa5f847473f71568a8222fafbddda0c600377a46",
"zh:3bd7ab222f13286449342d35d6dd3dbe343f575e47d4c2e6f8f64149386070c7",
"zh:3d25381c96df52253e7767aae49f0bead427c79f34ecdfb38026335e03c12de2",
"zh:4017a40b50886d3f1f0e886f8eacd1924c5f03888c90862c55ba98bb376198b2",
"zh:561978966998a3703053ac990796d5a88c7c14afd77e623846941a66b6fc9c92",
"zh:5f8460880a5f9cc9f4cc7c7c3c41156b0dae3c2b56f29cc259b3dc75ad650fe1",
"zh:724592993a0cccd45e0290aba2a6b72f7bb668a4ae3b8d778bb5d379d4105863",
"zh:b4de1157176eaf1fcc624d812ae6134893d6c73f3d1d2325c94cd9e707678d7b",
"zh:c5530bc71beedba579be57ea580f4151ec87223f198147771a924debff2e255e",
"zh:d3c7863e5baaf05e76fa5db3d3f58ff94440408873e370770b6a0080981cec37",
"zh:ec0a7d3a540cfb92e15b9a7acc43446059a9254b5904b48c940564c70e2b8aa1",
]
}
-3
View File
@@ -1,3 +0,0 @@
rule "terraform_unused_declarations" {
enabled = false
}
+37
View File
@@ -0,0 +1,37 @@
#cloud-config
hostname: ${hostname}
fqdn: ${fqdn}
prefer_fqdn_over_hostname: true
write_files:
- path: /etc/wireguard/wg0.conf
permissions: "0600"
content: |
[Interface]
Address = ${wg_address}/24
ListenPort = ${wg_port}
PrivateKey = ${wg_private_key}
[Peer]
PublicKey = ${hub_public_key}
Endpoint = ${hub_endpoint}
AllowedIPs = ${allowed_ips}
PersistentKeepalive = 25
- path: /etc/NetworkManager/conf.d/90-dns-none.conf
content: |
[main]
dns=none
runcmd:
- dnf install -y wireguard-tools
- systemctl enable --now wg-quick@wg0
- systemctl reload NetworkManager
- printf 'search main.unkin.net\nnameserver 198.18.200.7\n' > /etc/resolv.conf
- rpm -q puppet-agent || dnf install -y https://yum.puppet.com/puppet8-release-el-9.noarch.rpm
- dnf install -y puppet-agent
- /opt/puppetlabs/bin/puppet config set --section main certname ${fqdn}
- /opt/puppetlabs/bin/puppet config set --section main server puppet.k8s.syd1.au.unkin.net
- /opt/puppetlabs/bin/puppet config set --section main ca_server puppetca.k8s.syd1.au.unkin.net
- /opt/puppetlabs/bin/puppet config set --section main report_server puppet.k8s.syd1.au.unkin.net
- /opt/puppetlabs/bin/puppet config set --section main environment production
- systemctl enable --now puppet
+142
View File
@@ -0,0 +1,142 @@
locals {
# home gateway (prodnxsr0020) end of the mail DMZ tunnel
hub_endpoint = "103.216.191.185"
hub_port = 51821
hub_address = "10.10.13.1"
wg_port = 51820
# only these destinations route over wg0; everything else (incl. SMTP) stays public
wg_allowed_ips = [
"10.10.13.1/32", # prodnxsr0020 tunnel address
"10.10.12.200/32", # management ssh client (wireguard workstation)
"198.18.200.2/32", # puppetca.k8s.syd1.au.unkin.net
"198.18.200.3/32", # puppet.k8s.syd1.au.unkin.net
"198.18.200.4/32", # artifactapi.k8s.syd1.au.unkin.net (package repos)
"198.18.200.7/32", # bind-internal resolvers
"198.18.200.17/32", # rspamd milter :11332
"198.18.25.78/32", # vault.service.consul
"198.18.26.197/32", # vault.service.consul
"198.18.27.116/32", # vault.service.consul
"198.18.28.248/32", # vault.service.consul
"198.18.29.92/32", # vault.service.consul
]
vpcs = {
syd = "10.10.14.0/24"
mel = "10.10.15.0/24"
}
gateways = {
mta1 = { region = "syd", wg_address = "10.10.13.11" }
mta2 = { region = "mel", wg_address = "10.10.13.12" }
}
}
resource "binarylane_vpc" "dmz" {
for_each = local.vpcs
name = "dmz-${each.key}"
ip_range = each.value
}
resource "wireguard_asymmetric_key" "hub" {}
resource "wireguard_asymmetric_key" "gateway" {
for_each = local.gateways
}
resource "vault_kv_secret_v2" "hub" {
mount = "kv"
name = "service/wireguard/binarylane/prodnxsr0020"
data_json = jsonencode({
private_key = wireguard_asymmetric_key.hub.private_key
public_key = wireguard_asymmetric_key.hub.public_key
address = "${local.hub_address}/24"
listen_port = local.hub_port
})
}
resource "vault_kv_secret_v2" "gateway" {
for_each = local.gateways
mount = "kv"
name = "service/wireguard/binarylane/${each.key}"
data_json = jsonencode({
private_key = wireguard_asymmetric_key.gateway[each.key].private_key
public_key = wireguard_asymmetric_key.gateway[each.key].public_key
address = "${each.value.wg_address}/24"
listen_port = local.wg_port
})
}
resource "binarylane_server" "gateway" {
for_each = local.gateways
name = "${each.key}.edge.unkin.net"
region = each.value.region
image = "alma-9"
size = "std-min"
public_ipv4_count = 1
vpc_id = binarylane_vpc.dmz[each.value.region].id
port_blocking = false
backups = false
ipv6 = false
user_data = templatefile("${path.module}/cloud-init.yaml.tftpl", {
hostname = each.key
fqdn = "${each.key}.edge.unkin.net"
wg_address = each.value.wg_address
wg_port = local.wg_port
wg_private_key = wireguard_asymmetric_key.gateway[each.key].private_key
hub_public_key = wireguard_asymmetric_key.hub.public_key
hub_endpoint = "${local.hub_endpoint}:${local.hub_port}"
allowed_ips = join(", ", local.wg_allowed_ips)
})
}
resource "binarylane_server_reverse_name" "gateway" {
for_each = local.gateways
server_id = binarylane_server.gateway[each.key].id
ipv4_address = binarylane_server.gateway[each.key].public_ipv4_addresses[0]
reverse_name = "${each.key}.edge.unkin.net"
}
# SSH has no public rule: management reaches sshd through wg0, which this firewall never sees.
resource "binarylane_server_firewall_rules" "gateway" {
for_each = local.gateways
server_id = binarylane_server.gateway[each.key].id
firewall_rules = [
{
description = "mail and https"
protocol = "tcp"
source_addresses = ["0.0.0.0/0"]
destination_addresses = [binarylane_server.gateway[each.key].public_ipv4_addresses[0]]
destination_ports = ["25", "465", "587", "993", "443"]
action = "accept"
},
{
description = "wireguard from home gateway"
protocol = "udp"
source_addresses = ["${local.hub_endpoint}/32"]
destination_addresses = [binarylane_server.gateway[each.key].public_ipv4_addresses[0]]
destination_ports = [tostring(local.wg_port)]
action = "accept"
},
{
description = "icmp"
protocol = "icmp"
source_addresses = ["0.0.0.0/0"]
destination_addresses = [binarylane_server.gateway[each.key].public_ipv4_addresses[0]]
action = "accept"
},
{
description = "default deny"
protocol = "all"
source_addresses = ["0.0.0.0/0"]
destination_addresses = [binarylane_server.gateway[each.key].public_ipv4_addresses[0]]
action = "drop"
},
]
}
+15
View File
@@ -0,0 +1,15 @@
output "public_ipv4" {
description = "Public IPv4 per gateway."
value = { for k, v in binarylane_server.gateway : k => v.public_ipv4_addresses[0] }
}
output "wireguard" {
description = "Tunnel address and WireGuard public key per gateway, plus the prodnxsr0020 end."
value = {
hub = { address = local.hub_address, public_key = wireguard_asymmetric_key.hub.public_key, listen_port = local.hub_port }
gateways = { for k, v in local.gateways : k => {
address = v.wg_address
public_key = wireguard_asymmetric_key.gateway[k].public_key
} }
}
}
+8
View File
@@ -0,0 +1,8 @@
provider "binarylane" {
api_token = var.binarylane_token
}
# VAULT_ADDR / VAULT_TOKEN come from the Makefile; the k8s auth role token cannot mint child tokens.
provider "vault" {
skip_child_token = true
}
+15
View File
@@ -1,3 +1,18 @@
terraform {
required_version = ">= 1.10"
required_providers {
binarylane = {
source = "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/binarylane"
version = "0.14.0"
}
vault = {
source = "hashicorp/vault"
version = "5.6.0"
}
wireguard = {
source = "OJFord/wireguard"
version = "0.4.1"
}
}
}