Adds terraform/edge, a root module with two providers: - Cloudflare 5.28.1: CNAMEs to the existing tunnel, plus an opt-in resource that can take the tunnel ingress list over when manage_tunnel_config is true. It is off by default because that resource REPLACES the whole list, and the tunnel is remotely managed from the dashboard today. - Tencent Cloud 1.83.38: VPC, subnet, security group with three rules, key pair, one CVM relay with cloud-init (WireGuard listener, forwarding sysctls, Caddy), and DNSPod A records for the .cn hostnames. The design keeps the homelab inbound-free: the relay terminates TLS in China and forwards over a WireGuard tunnel the cluster dials outbound. Cloudflare keeps serving every existing hostname untouched. Verified: terraform init succeeds for both providers, fmt clean, validate reports 0 errors, and plan resolves to 10 creates with placeholder variables. Provider-side name validation caught a real bug during that run: key pair and security group names allow only letters, digits and underscore, so the derived names use underscores. Not applied. Credentials come from CLOUDFLARE_API_TOKEN, TENCENTCLOUD_SECRET_ID and TENCENTCLOUD_SECRET_KEY; the WireGuard private key is TF_VAR only. State is local and git-ignored like terraform/zitadel. Blocked on facts this module cannot invent: the real .cn domain, whether it has an ICP filing, the CVM image ID, the operator CIDR, and the homelab WireGuard public key.
131 lines
4.5 KiB
Terraform
131 lines
4.5 KiB
Terraform
# 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"
|
|
cidr_block = "10.66.0.0/16"
|
|
}
|
|
|
|
resource "tencentcloud_subnet" "edge" {
|
|
name = "${replace(var.china_domain, ".", "_")}_edge"
|
|
vpc_id = tencentcloud_vpc.edge.id
|
|
availability_zone = var.tencent_availability_zone
|
|
cidr_block = "10.66.1.0/24"
|
|
}
|
|
|
|
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)
|
|
cidr_ip = var.relay_admin_cidr
|
|
policy = "accept"
|
|
}
|
|
|
|
# cloud-init: WireGuard listener, forwarding sysctls, and Caddy for TLS.
|
|
# The relay never holds application credentials; it forwards bytes.
|
|
locals {
|
|
relay_user_data = <<-EOT
|
|
#cloud-config
|
|
package_update: true
|
|
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}
|
|
[Peer]
|
|
PublicKey = ${var.wireguard_homelab_public_key}
|
|
AllowedIPs = ${var.wireguard_homelab_address}
|
|
PersistentKeepalive = 25
|
|
- path: /etc/sysctl.d/99-wireguard.conf
|
|
content: |
|
|
net.ipv4.ip_forward = 1
|
|
runcmd:
|
|
- sysctl --system
|
|
- systemctl enable --now wg-quick@wg0
|
|
- systemctl enable --now caddy
|
|
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.
|
|
internet_max_bandwidth_out = 20
|
|
system_disk_type = "CLOUD_PREMIUM"
|
|
system_disk_size = 50
|
|
|
|
user_data = local.relay_user_data
|
|
|
|
tags = {
|
|
role = "china-edge"
|
|
domain = var.china_domain
|
|
}
|
|
}
|
|
|
|
# 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"
|
|
record_line = "默认"
|
|
value = tencentcloud_instance.relay.public_ip
|
|
ttl = 600
|
|
remark = "China edge relay; origin is the homelab ingress over WireGuard"
|
|
}
|