terraform: manage DNS for asxp.io and czsk.it
The zones were edited by hand in the console, and it showed: dozens of records for servers that no longer existed, a CNAME and a TXT record missing their trailing dot, and a 24 h default TTL that made today's XMPP move linger in resolver caches. Now both zones live in the asxpio project, so describe them here, adopted with import blocks (the first apply imported 87 and changed nothing but protection). Our servers' records reference hcloud_server instead of literal IPs. Every record set is change-protected except those of the test XMPP servers, and both zones are delete-protected. Assisted-by: Claude:opus-5-5
This commit is contained in:
parent
718524ffff
commit
32836d5d53
2 changed files with 172 additions and 33 deletions
|
|
@ -27,6 +27,18 @@ day-to-day configuration is in `../ansible`.
|
||||||
- asxpio-backups-repl (fsn1)
|
- asxpio-backups-repl (fsn1)
|
||||||
|
|
||||||
|
|
||||||
|
## DNS
|
||||||
|
|
||||||
|
`dns.tf` holds the zones asxp.io and czsk.it (Hetzner DNS, this project)
|
||||||
|
and every record set except SOA and NS. Our servers' records reference
|
||||||
|
`hcloud_server`, so moving a service is one edit.
|
||||||
|
|
||||||
|
All record sets are change-protected except the test XMPP servers'
|
||||||
|
(`prosody`, `ejabberd`, `anotherim`); both zones are delete-protected.
|
||||||
|
To change a protected record set, apply twice: first with the set
|
||||||
|
excluded from protection, then with the new value. Hand edits in the
|
||||||
|
console show up as drift in `terraform plan`.
|
||||||
|
|
||||||
## Network
|
## Network
|
||||||
|
|
||||||
- VPC: 10.10.0.0/20
|
- VPC: 10.10.0.0/20
|
||||||
|
|
|
||||||
189
terraform/dns.tf
189
terraform/dns.tf
|
|
@ -1,34 +1,161 @@
|
||||||
# Hetzner Cloud DNS Management
|
# DNS for asxp.io and czsk.it (Hetzner DNS, project asxpio). Every record
|
||||||
# =================================
|
# set of both zones is here except SOA and NS, which Hetzner manages.
|
||||||
#
|
#
|
||||||
# DNS zones and records are now managed by Terraform.
|
# Keys are "<name>/<type>"; "@" is the apex. Records of our own servers
|
||||||
#
|
# point at the server resources, so moving a service is one edit here.
|
||||||
# Existing Zones:
|
# Every record set has Hetzner's change protection except those of the
|
||||||
# - asxp.io (ID: 61604) - 34 records
|
# test servers in local.unprotected: a protected set cannot be changed or
|
||||||
# - czsk.it (ID: 61606) - 7 records
|
# deleted until protection is lifted, so a change to one is two applies
|
||||||
# - childfr.ee (ID: 61605) - 13 records
|
# (set protection off for it, then edit). Both zones are delete-protected.
|
||||||
#
|
|
||||||
# To manage DNS records:
|
|
||||||
# Use the 'hcloud_record' resource in Terraform.
|
|
||||||
#
|
|
||||||
# Key records to update during migration:
|
|
||||||
# - asxp.io: @, git, builds, upload, onitama, proxy, xmpp, turn, photos
|
|
||||||
# - czsk.it: @
|
|
||||||
# - childfr.ee: @
|
|
||||||
|
|
||||||
#resource "hcloud_zone" "czsk_it" {
|
locals {
|
||||||
# name = "czsk.it"
|
ip = {
|
||||||
# mode = "primary"
|
ops_v4 = hcloud_server.ops.ipv4_address
|
||||||
# labels = {
|
ops_v6 = hcloud_server.ops.ipv6_address
|
||||||
# managed_by = "terraform"
|
services_v4 = hcloud_server.services.ipv4_address
|
||||||
# }
|
services_v6 = hcloud_server.services.ipv6_address
|
||||||
#}
|
}
|
||||||
|
|
||||||
#resource "hcloud_zone_rrset" "apex_a" {
|
zones = {
|
||||||
# zone = hcloud_zone.czsk_it.name
|
"asxp.io" = { ttl = 3600, delete_protection = true }
|
||||||
# name = "test"
|
"czsk.it" = { ttl = 86400, delete_protection = true }
|
||||||
# type = "A"
|
}
|
||||||
# records = [
|
|
||||||
# { value = "127.0.0.1", comment = "test" },
|
# Test XMPP servers (prosody, ejabberd, anotherim) and their subdomains
|
||||||
# ]
|
unprotected = "(^|\\.)(prosody|ejabberd|anotherim)$"
|
||||||
#}
|
|
||||||
|
dns = {
|
||||||
|
"asxp.io" = {
|
||||||
|
"@/A" = { records = [local.ip.ops_v4] }
|
||||||
|
"anotherim/A" = { records = ["178.105.241.125"] }
|
||||||
|
"blog/A" = { records = [local.ip.ops_v4], ttl = 86400 }
|
||||||
|
"conference/A" = { records = [local.ip.services_v4], ttl = 300 }
|
||||||
|
"conference.anotherim/A" = { records = ["178.105.241.125"] }
|
||||||
|
"conference.ejabberd/A" = { records = ["178.105.240.144"] }
|
||||||
|
"conference.prosody/A" = { records = ["178.105.250.162"] }
|
||||||
|
"conference.xmpp/A" = { records = [local.ip.services_v4], ttl = 300 }
|
||||||
|
"ejabberd/A" = { records = ["178.105.240.144"] }
|
||||||
|
"mc/A" = { records = [local.ip.services_v4] }
|
||||||
|
"prosody/A" = { records = ["178.105.250.162"] }
|
||||||
|
"proxy.anotherim/A" = { records = ["178.105.241.125"] }
|
||||||
|
"proxy.ejabberd/A" = { records = ["178.105.240.144"] }
|
||||||
|
"proxy.prosody/A" = { records = ["178.105.250.162"] }
|
||||||
|
"pubsub.anotherim/A" = { records = ["178.105.241.125"] }
|
||||||
|
"pubsub.ejabberd/A" = { records = ["178.105.240.144"] }
|
||||||
|
"pubsub.prosody/A" = { records = ["178.105.250.162"] }
|
||||||
|
"s3/A" = { records = [local.ip.ops_v4] }
|
||||||
|
"traefik/A" = { records = [local.ip.ops_v4] }
|
||||||
|
"upload.ejabberd/A" = { records = ["178.105.240.144"] }
|
||||||
|
"upload.prosody/A" = { records = ["178.105.250.162"] }
|
||||||
|
"www/A" = { records = [local.ip.ops_v4] }
|
||||||
|
"xmpp/A" = { records = [local.ip.services_v4], ttl = 300 }
|
||||||
|
"@/AAAA" = { records = [local.ip.ops_v6] }
|
||||||
|
"anotherim/AAAA" = { records = ["2a01:4f8:c01e:27d9::1"] }
|
||||||
|
"blog/AAAA" = { records = [local.ip.ops_v6], ttl = 86400 }
|
||||||
|
"conference/AAAA" = { records = [local.ip.services_v6], ttl = 300 }
|
||||||
|
"conference.anotherim/AAAA" = { records = ["2a01:4f8:c01e:27d9::1"] }
|
||||||
|
"conference.ejabberd/AAAA" = { records = ["2a01:4f8:c01e:11b6::1"] }
|
||||||
|
"conference.prosody/AAAA" = { records = ["2a01:4f8:c01e:2891::1"] }
|
||||||
|
"conference.xmpp/AAAA" = { records = [local.ip.services_v6], ttl = 300 }
|
||||||
|
"ejabberd/AAAA" = { records = ["2a01:4f8:c01e:11b6::1"] }
|
||||||
|
"prosody/AAAA" = { records = ["2a01:4f8:c01e:2891::1"] }
|
||||||
|
"proxy.anotherim/AAAA" = { records = ["2a01:4f8:c01e:27d9::1"] }
|
||||||
|
"proxy.ejabberd/AAAA" = { records = ["2a01:4f8:c01e:11b6::1"] }
|
||||||
|
"proxy.prosody/AAAA" = { records = ["2a01:4f8:c01e:2891::1"] }
|
||||||
|
"pubsub.anotherim/AAAA" = { records = ["2a01:4f8:c01e:27d9::1"] }
|
||||||
|
"pubsub.ejabberd/AAAA" = { records = ["2a01:4f8:c01e:11b6::1"] }
|
||||||
|
"pubsub.prosody/AAAA" = { records = ["2a01:4f8:c01e:2891::1"] }
|
||||||
|
"s3/AAAA" = { records = [local.ip.ops_v6] }
|
||||||
|
"upload.ejabberd/AAAA" = { records = ["2a01:4f8:c01e:11b6::1"] }
|
||||||
|
"upload.prosody/AAAA" = { records = ["2a01:4f8:c01e:2891::1"] }
|
||||||
|
"www/AAAA" = { records = [local.ip.ops_v6] }
|
||||||
|
"xmpp/AAAA" = { records = [local.ip.services_v6], ttl = 300 }
|
||||||
|
"fm1._domainkey/CNAME" = { records = ["fm1.asxp.io.dkim.fmhosted.com."] }
|
||||||
|
"fm2._domainkey/CNAME" = { records = ["fm2.asxp.io.dkim.fmhosted.com."] }
|
||||||
|
"fm3._domainkey/CNAME" = { records = ["fm3.asxp.io.dkim.fmhosted.com."] }
|
||||||
|
"git/CNAME" = { records = ["git.czsk.it."], ttl = 86400 }
|
||||||
|
"mail/CNAME" = { records = ["mail.fastmail.com."] }
|
||||||
|
"@/MX" = { records = ["10 in1-smtp.messagingengine.com.", "20 in2-smtp.messagingengine.com."] }
|
||||||
|
"@/TXT" = { records = ["\"google-site-verification=H1sj22-DZ-4uW_Ri7z18RDKXo50bPwJXqIT1SyjthZg\"", "\"nfd=5777245\"", "\"v=spf1 include:spf.messagingengine.com ?all\""] }
|
||||||
|
"_discord/TXT" = { records = ["\"dh=8d39ced1e23afda4492219ee2456a81d209fecaa\""] }
|
||||||
|
"_dmarc/TXT" = { records = ["\"v=DMARC1;p=reject;rua=mailto:tr@asxp.io;ruf=mailto:tr@asxp.io;rf=afrf;pct=100\""] }
|
||||||
|
"mail._domainkey/TXT" = { records = ["\"k=rsa; t=s; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0X0brpvueSc7IiGZA2Qx zu12CWjeSN7/SquxrZM+afjU2If9BL5MHiE9iza/k6HgVnvc7exfMUdCHeLPUJJr WUIgCpYUR6ogJF0pe402zltYBExxjJFIMASAXuhTi0Gs4nc7fcfM56zSLckmJpO9 6jMrZPM7x3BxCeJt79j+4NLegClP8d5eRk1aByz9rujRYE\" \" rSJzaUcHuR47uEE5wz bSbY64hUadaSduBuD/OnAy0F3iqMSzVV5KNzDnAyWD4V7QcEWK7GAF9nuj3KhhS9 QLSHydPw/CCrhrXVjcprDDpfDG8dQw1JPfYPKs8h+Z44SGFw1ZxvqYIUmQ6mYrxv uwIDAQAB\""] }
|
||||||
|
"_stun._tcp/SRV" = { records = ["0 5 3478 xmpp.asxp.io."] }
|
||||||
|
"_stun._tcp.anotherim/SRV" = { records = ["0 5 3478 anotherim.asxp.io."] }
|
||||||
|
"_stun._tcp.ejabberd/SRV" = { records = ["0 5 3478 ejabberd.asxp.io."] }
|
||||||
|
"_stun._udp/SRV" = { records = ["0 5 3478 xmpp.asxp.io."] }
|
||||||
|
"_stun._udp.anotherim/SRV" = { records = ["0 5 3478 anotherim.asxp.io."] }
|
||||||
|
"_stun._udp.ejabberd/SRV" = { records = ["0 5 3478 ejabberd.asxp.io."] }
|
||||||
|
"_turn._tcp/SRV" = { records = ["0 5 3478 xmpp.asxp.io."] }
|
||||||
|
"_turn._tcp.anotherim/SRV" = { records = ["0 5 3478 anotherim.asxp.io."] }
|
||||||
|
"_turn._tcp.ejabberd/SRV" = { records = ["0 5 3478 ejabberd.asxp.io."] }
|
||||||
|
"_turn._udp/SRV" = { records = ["0 5 3478 xmpp.asxp.io."] }
|
||||||
|
"_turn._udp.anotherim/SRV" = { records = ["0 5 3478 anotherim.asxp.io."] }
|
||||||
|
"_turn._udp.ejabberd/SRV" = { records = ["0 5 3478 ejabberd.asxp.io."] }
|
||||||
|
"_xmpp-client._tcp/SRV" = { records = ["0 5 5222 xmpp.asxp.io."], ttl = 3600 }
|
||||||
|
"_xmpp-client._tcp.anotherim/SRV" = { records = ["0 5 5222 anotherim.asxp.io."] }
|
||||||
|
"_xmpp-client._tcp.ejabberd/SRV" = { records = ["0 5 5222 ejabberd.asxp.io."] }
|
||||||
|
"_xmpp-client._tcp.prosody/SRV" = { records = ["0 5 5222 prosody.asxp.io."] }
|
||||||
|
"_xmpp-server._tcp/SRV" = { records = ["0 5 5269 xmpp.asxp.io."], ttl = 3600 }
|
||||||
|
"_xmpp-server._tcp.anotherim/SRV" = { records = ["0 5 5269 anotherim.asxp.io."] }
|
||||||
|
"_xmpp-server._tcp.conference/SRV" = { records = ["0 5 5269 xmpp.asxp.io."], ttl = 3600 }
|
||||||
|
"_xmpp-server._tcp.conference.anotherim/SRV" = { records = ["0 5 5269 anotherim.asxp.io."] }
|
||||||
|
"_xmpp-server._tcp.conference.ejabberd/SRV" = { records = ["0 5 5269 ejabberd.asxp.io."] }
|
||||||
|
"_xmpp-server._tcp.conference.prosody/SRV" = { records = ["0 5 5269 prosody.asxp.io."] }
|
||||||
|
"_xmpp-server._tcp.ejabberd/SRV" = { records = ["0 5 5269 ejabberd.asxp.io."] }
|
||||||
|
"_xmpp-server._tcp.prosody/SRV" = { records = ["0 5 5269 prosody.asxp.io."] }
|
||||||
|
"_xmpps-client._tcp/SRV" = { records = ["0 5 5223 xmpp.asxp.io."], ttl = 3600 }
|
||||||
|
"_xmpps-client._tcp.anotherim/SRV" = { records = ["0 5 5223 anotherim.asxp.io."] }
|
||||||
|
"_xmpps-client._tcp.ejabberd/SRV" = { records = ["0 5 5223 ejabberd.asxp.io."] }
|
||||||
|
"_xmpps-client._tcp.prosody/SRV" = { records = ["0 5 5223 prosody.asxp.io."] }
|
||||||
|
},
|
||||||
|
"czsk.it" = {
|
||||||
|
"@/A" = { records = [local.ip.ops_v4] }
|
||||||
|
"www/A" = { records = [local.ip.ops_v4] }
|
||||||
|
"@/AAAA" = { records = [local.ip.ops_v6] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# "<zone>/<name>/<type>" => record set; the key is also the import ID
|
||||||
|
rrsets = merge([
|
||||||
|
for zone, sets in local.dns : {
|
||||||
|
for key, set in sets : "${zone}/${key}" => merge(set, {
|
||||||
|
zone = zone
|
||||||
|
name = split("/", key)[0]
|
||||||
|
type = split("/", key)[1]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "hcloud_zone" "zone" {
|
||||||
|
for_each = local.zones
|
||||||
|
name = each.key
|
||||||
|
mode = "primary"
|
||||||
|
ttl = each.value.ttl
|
||||||
|
delete_protection = each.value.delete_protection
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "hcloud_zone_rrset" "rr" {
|
||||||
|
for_each = local.rrsets
|
||||||
|
zone = hcloud_zone.zone[each.value.zone].name
|
||||||
|
name = each.value.name
|
||||||
|
type = each.value.type
|
||||||
|
ttl = try(each.value.ttl, null)
|
||||||
|
change_protection = !can(regex(local.unprotected, each.value.name))
|
||||||
|
records = [for value in each.value.records : { value = value }]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Adopted from the existing zones on 2026-11-12. Harmless once in state;
|
||||||
|
# remove after the first apply if you like.
|
||||||
|
import {
|
||||||
|
for_each = local.zones
|
||||||
|
to = hcloud_zone.zone[each.key]
|
||||||
|
id = each.key
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
for_each = local.rrsets
|
||||||
|
to = hcloud_zone_rrset.rr[each.key]
|
||||||
|
id = each.key
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue