2026-10-24 22:49:48 -07:00
|
|
|
# The relay: one small CVM in Tencent Cloud that terminates TLS for the .cn
|
|
|
|
|
# domain and forwards over WireGuard to the homelab ingress.
|
|
|
|
|
#
|
|
|
|
|
# Why a relay and not Cloudflare: mainland clients reach a Tencent edge far
|
|
|
|
|
# faster than a Cloudflare edge, and Cloudflare's China network is an
|
|
|
|
|
# Enterprise product. The homelab keeps zero inbound exposure because the
|
|
|
|
|
# tunnel is dialled outbound from the cluster.
|
|
|
|
|
#
|
|
|
|
|
# ICP FILING: a mainland resource serving HTTP/HTTPS for a domain requires an
|
|
|
|
|
# ICP filing naming that domain. Without it this instance accepts the
|
|
|
|
|
# connection and the mainland network resets the HTTP layer. Put the relay in a
|
|
|
|
|
# non-mainland region (ap-hongkong, ap-singapore) when no filing exists.
|
|
|
|
|
|
|
|
|
|
resource "tencentcloud_vpc" "edge" {
|
|
|
|
|
name = "${replace(var.china_domain, ".", "_")}_edge"
|
2026-10-25 18:03:55 -07:00
|
|
|
cidr_block = var.edge_vpc_cidr
|
2026-10-24 22:49:48 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resource "tencentcloud_subnet" "edge" {
|
|
|
|
|
name = "${replace(var.china_domain, ".", "_")}_edge"
|
|
|
|
|
vpc_id = tencentcloud_vpc.edge.id
|
|
|
|
|
availability_zone = var.tencent_availability_zone
|
2026-10-25 18:03:55 -07:00
|
|
|
cidr_block = var.edge_subnet_cidr
|
2026-10-24 22:49:48 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resource "tencentcloud_key_pair" "edge" {
|
|
|
|
|
key_name = "${replace(var.china_domain, ".", "_")}_edge"
|
|
|
|
|
public_key = var.relay_ssh_public_key
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resource "tencentcloud_security_group" "edge" {
|
|
|
|
|
name = "${replace(var.china_domain, ".", "_")}_edge"
|
|
|
|
|
description = "TLS + WireGuard for the ${var.china_domain} relay; SSH from the operator only"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resource "tencentcloud_security_group_rule" "ssh" {
|
|
|
|
|
security_group_id = tencentcloud_security_group.edge.id
|
|
|
|
|
type = "ingress"
|
|
|
|
|
ip_protocol = "tcp"
|
|
|
|
|
port_range = "22"
|
|
|
|
|
cidr_ip = var.relay_admin_cidr
|
|
|
|
|
policy = "accept"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resource "tencentcloud_security_group_rule" "tls" {
|
|
|
|
|
security_group_id = tencentcloud_security_group.edge.id
|
|
|
|
|
type = "ingress"
|
|
|
|
|
ip_protocol = "tcp"
|
|
|
|
|
port_range = "80,443"
|
|
|
|
|
cidr_ip = "0.0.0.0/0"
|
|
|
|
|
policy = "accept"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resource "tencentcloud_security_group_rule" "wireguard" {
|
|
|
|
|
security_group_id = tencentcloud_security_group.edge.id
|
|
|
|
|
type = "ingress"
|
|
|
|
|
ip_protocol = "udp"
|
|
|
|
|
port_range = tostring(var.wireguard_port)
|
2026-10-25 18:48:15 -07:00
|
|
|
# The homelab dials outbound and has no static public address, so this is
|
|
|
|
|
# 0.0.0.0/0 by default. WireGuard authenticates every packet by public key,
|
|
|
|
|
# and an unauthenticated packet is dropped before it reaches the tunnel.
|
|
|
|
|
# Narrow this to the homelab egress CIDR when that address is stable.
|
|
|
|
|
cidr_ip = var.wireguard_allowed_cidr
|
|
|
|
|
policy = "accept"
|
2026-10-24 22:49:48 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# cloud-init: WireGuard listener, forwarding sysctls, and Caddy for TLS.
|
|
|
|
|
# The relay never holds application credentials; it forwards bytes.
|
2026-10-25 18:03:55 -07:00
|
|
|
#
|
|
|
|
|
# NOTE ON THE PRIVATE KEY: user_data is visible in the instance metadata and in
|
|
|
|
|
# Terraform state. That is a known limitation of bootstrapping WireGuard this
|
|
|
|
|
# way. The argument is wrapped in sensitive() to keep it out of plan/apply
|
|
|
|
|
# output, but anyone with console or state access can read it. Rotate the key
|
|
|
|
|
# if the state or the instance metadata is ever exposed.
|
2026-10-24 22:49:48 -07:00
|
|
|
locals {
|
2026-10-25 18:03:55 -07:00
|
|
|
china_fqdns = [for host in var.china_hostnames : "${host}.${var.china_domain}"]
|
|
|
|
|
|
|
|
|
|
caddyfile = templatefile("${path.module}/templates/Caddyfile.tpl", {
|
|
|
|
|
acme_email = var.acme_email
|
|
|
|
|
hosts = join(", ", local.china_fqdns)
|
|
|
|
|
ingress_upstream = var.ingress_upstream
|
|
|
|
|
})
|
|
|
|
|
|
2026-10-24 22:49:48 -07:00
|
|
|
relay_user_data = <<-EOT
|
|
|
|
|
#cloud-config
|
|
|
|
|
package_update: true
|
2026-10-25 18:03:55 -07:00
|
|
|
package_upgrade: false
|
|
|
|
|
manage_etc_hosts: true
|
|
|
|
|
timezone: Asia/Shanghai
|
2026-10-24 22:49:48 -07:00
|
|
|
packages: [wireguard, wireguard-tools, caddy]
|
|
|
|
|
write_files:
|
|
|
|
|
- path: /etc/wireguard/wg0.conf
|
|
|
|
|
permissions: "0600"
|
|
|
|
|
content: |
|
|
|
|
|
[Interface]
|
|
|
|
|
Address = ${var.wireguard_relay_address}
|
|
|
|
|
ListenPort = ${var.wireguard_port}
|
|
|
|
|
PrivateKey = ${var.wireguard_relay_private_key}
|
2026-10-25 18:03:55 -07:00
|
|
|
# Masquerade forwarded traffic so the homelab peer only has to accept
|
|
|
|
|
# packets sourced from the relay's tunnel address. Without this the
|
|
|
|
|
# homelab peer's AllowedIPs would drop the original client source IP.
|
|
|
|
|
PostUp = iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE
|
|
|
|
|
PostDown = iptables -t nat -D POSTROUTING -o wg0 -j MASQUERADE
|
2026-10-24 22:49:48 -07:00
|
|
|
[Peer]
|
|
|
|
|
PublicKey = ${var.wireguard_homelab_public_key}
|
2026-10-25 18:03:55 -07:00
|
|
|
AllowedIPs = ${var.wireguard_homelab_allowed_ips}
|
2026-10-24 22:49:48 -07:00
|
|
|
PersistentKeepalive = 25
|
|
|
|
|
- path: /etc/sysctl.d/99-wireguard.conf
|
|
|
|
|
content: |
|
|
|
|
|
net.ipv4.ip_forward = 1
|
2026-10-25 18:03:55 -07:00
|
|
|
- path: /etc/caddy/Caddyfile
|
|
|
|
|
permissions: "0644"
|
|
|
|
|
content: ${jsonencode(local.caddyfile)}
|
2026-10-24 22:49:48 -07:00
|
|
|
runcmd:
|
|
|
|
|
- sysctl --system
|
|
|
|
|
- systemctl enable --now wg-quick@wg0
|
2026-10-25 18:03:55 -07:00
|
|
|
- caddy validate --config /etc/caddy/Caddyfile
|
2026-10-24 22:49:48 -07:00
|
|
|
- systemctl enable --now caddy
|
2026-10-25 18:03:55 -07:00
|
|
|
- systemctl restart caddy
|
2026-10-24 22:49:48 -07:00
|
|
|
EOT
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resource "tencentcloud_instance" "relay" {
|
|
|
|
|
instance_name = "${replace(var.china_domain, ".", "_")}_edge"
|
|
|
|
|
availability_zone = var.tencent_availability_zone
|
|
|
|
|
image_id = var.relay_image_id
|
|
|
|
|
instance_type = var.relay_instance_type
|
|
|
|
|
|
|
|
|
|
vpc_id = tencentcloud_vpc.edge.id
|
|
|
|
|
subnet_id = tencentcloud_subnet.edge.id
|
|
|
|
|
security_groups = [tencentcloud_security_group.edge.id]
|
|
|
|
|
key_ids = [tencentcloud_key_pair.edge.id]
|
|
|
|
|
allocate_public_ip = true
|
|
|
|
|
|
|
|
|
|
# Outbound only needs a few Mbps; inbound TLS is the same order.
|
2026-10-25 18:03:55 -07:00
|
|
|
internet_max_bandwidth_out = var.relay_internet_max_bandwidth_out
|
|
|
|
|
system_disk_type = var.relay_system_disk_type
|
|
|
|
|
system_disk_size = var.relay_system_disk_size
|
2026-10-24 22:49:48 -07:00
|
|
|
|
2026-10-25 18:03:55 -07:00
|
|
|
# The private key is embedded in this bootstrap script. sensitive() keeps it
|
|
|
|
|
# out of CLI output; it does not remove it from state or instance metadata.
|
|
|
|
|
user_data = sensitive(local.relay_user_data)
|
2026-10-24 22:49:48 -07:00
|
|
|
|
|
|
|
|
tags = {
|
|
|
|
|
role = "china-edge"
|
|
|
|
|
domain = var.china_domain
|
|
|
|
|
}
|
2026-10-25 18:03:55 -07:00
|
|
|
|
|
|
|
|
# Accidental `terraform destroy` would take the only China-facing edge down.
|
|
|
|
|
# Remove this block deliberately when a replacement path exists.
|
|
|
|
|
lifecycle {
|
|
|
|
|
prevent_destroy = true
|
|
|
|
|
}
|
2026-10-24 22:49:48 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# DNS for the .cn domain lives at DNSPod (Tencent). Each subdomain resolves to
|
|
|
|
|
# the relay's public address, and the relay proxies to the homelab over the
|
|
|
|
|
# tunnel. The "中国境内" line is the default line; no separate line records.
|
|
|
|
|
resource "tencentcloud_dnspod_record" "china" {
|
|
|
|
|
for_each = toset(var.china_hostnames)
|
|
|
|
|
|
|
|
|
|
domain = var.china_domain
|
|
|
|
|
sub_domain = each.value
|
|
|
|
|
record_type = "A"
|
2026-10-25 18:03:55 -07:00
|
|
|
record_line = var.china_dns_record_line
|
2026-10-24 22:49:48 -07:00
|
|
|
value = tencentcloud_instance.relay.public_ip
|
2026-10-25 18:03:55 -07:00
|
|
|
ttl = var.china_dns_ttl
|
2026-10-24 22:49:48 -07:00
|
|
|
remark = "China edge relay; origin is the homelab ingress over WireGuard"
|
|
|
|
|
}
|