boma/terraform/modules/hetzner_vm/main.tf
sjat b0511179cb feat(tf/offsite): retire askari's WAN :22 (mesh-only SSH)
The Hetzner Cloud Firewall SSH rule is now conditional on a non-empty
ssh_admin_cidrs (default []); askari sets it empty so the WAN :22 rule is
removed on the next apply. SSH is reached over wt0; break-glass is the Hetzner
console. Apply is the live cutover (Task 5). Mesh-hardening 1/3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 20:51:24 +02:00

79 lines
2.1 KiB
HCL

# cloud-init: create the unprivileged `ansible` user with ubongo's key + sudo.
# (Mirrors the proxmox_vm module's user_account; Hetzner has no structured field.)
locals {
# Indentation matches the closing EOT (2 spaces) so `<<-` strips to column 0 —
# cloud-config requires `#cloud-config` as the first line with no leading space.
user_data = <<-EOT
#cloud-config
users:
- name: ansible
groups: [sudo]
sudo: "ALL=(ALL) NOPASSWD:ALL"
shell: /bin/bash
ssh_authorized_keys:
- ${var.ansible_ssh_pubkey}
package_update: true
packages:
- python3
EOT
}
resource "hcloud_ssh_key" "ansible" {
name = "${var.name}-ansible"
public_key = var.ansible_ssh_pubkey
}
resource "hcloud_firewall" "this" {
name = "${var.name}-fw"
# SSH from the control node only — and only when admin CIDRs are set. An empty
# ssh_admin_cidrs removes the WAN :22 rule entirely (mesh-only SSH; reach the host over
# wt0, break-glass = Hetzner console). Mesh-hardening 1/3.
dynamic "rule" {
for_each = length(var.ssh_admin_cidrs) > 0 ? [1] : []
content {
direction = "in"
protocol = "tcp"
port = "22"
source_ips = var.ssh_admin_cidrs
}
}
# Public web (Caddy 80/443) + NetBird STUN/TURN (3478/udp) — only when public_web
# (ADR-024, M4). Host nftables stays catalog-driven (ADR-020).
dynamic "rule" {
for_each = var.public_web ? ["80", "443"] : []
content {
direction = "in"
protocol = "tcp"
port = rule.value
source_ips = ["0.0.0.0/0", "::/0"]
}
}
dynamic "rule" {
for_each = var.public_web ? ["3478"] : []
content {
direction = "in"
protocol = "udp"
port = rule.value
source_ips = ["0.0.0.0/0", "::/0"]
}
}
}
resource "hcloud_server" "this" {
name = var.name
server_type = var.server_type
location = var.location
image = var.image
ssh_keys = [hcloud_ssh_key.ansible.id]
user_data = local.user_data
firewall_ids = [hcloud_firewall.this.id]
labels = var.labels
public_net {
ipv4_enabled = true
ipv6_enabled = true
}
}