77 lines
3.0 KiB
Nix
77 lines
3.0 KiB
Nix
# Public reverse proxy with TLS termination for web01. Caddy fronts internal
|
|
# services and forwards to them over the ZeroTier mesh, never the public net.
|
|
# The cert is a single wildcard (*.cnx.network) obtained via ACME DNS-01, so
|
|
# adding a vhost needs no new issuance. Public ports: 443 for the proxy and 80
|
|
# only for Caddy's HTTP->HTTPS redirect (issuance never uses inbound HTTP).
|
|
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
let
|
|
mesh = import ./mesh-hosts.nix {
|
|
dir = config.clan.core.settings.directory;
|
|
inherit lib;
|
|
};
|
|
hosts = import ./hosts.nix;
|
|
certName = "cnx.network";
|
|
in
|
|
{
|
|
imports = [ ./dns/acme-web01-secret.nix ];
|
|
|
|
# Render the shared acme_web01 TSIG secret into a lego rfc2136 env file. lego
|
|
# (via security.acme below) uses it to write _acme-challenge.cnx.network TXT
|
|
# records on ns1, which authorizes the acme_web01 key for exactly that owner.
|
|
clan.core.vars.generators.dns-acme-web01-rfc2136 = {
|
|
files."rfc2136.env".secret = true; # root-owned; systemd reads it as root
|
|
dependencies = [ "dns-acme-web01-secret" ];
|
|
script = ''
|
|
printf 'RFC2136_NAMESERVER=${hosts.ns1.ipv4}:53\nRFC2136_TSIG_ALGORITHM=hmac-sha256.\nRFC2136_TSIG_KEY=acme_web01\nRFC2136_TSIG_SECRET=%s\n' \
|
|
"$(cat "$in"/dns-acme-web01-secret/secret)" > "$out"/rfc2136.env
|
|
'';
|
|
};
|
|
|
|
security.acme = {
|
|
acceptTerms = true;
|
|
defaults.email = "postmaster@cnx.email";
|
|
# One wildcard cert for every vhost this proxy serves, via DNS-01 (so issuance
|
|
# never depends on inbound HTTP). Port 80 is open only for Caddy's
|
|
# HTTP->HTTPS redirect, not for ACME.
|
|
certs.${certName} = {
|
|
domain = "*.cnx.network";
|
|
extraDomainNames = [ "cnx.network" ];
|
|
dnsProvider = "rfc2136";
|
|
environmentFile = config.clan.core.vars.generators.dns-acme-web01-rfc2136.files."rfc2136.env".path;
|
|
# ns1 is the only nameserver that accepts the acme_web01 UPDATE; check
|
|
# propagation against it directly rather than a public resolver.
|
|
dnsResolver = "${hosts.ns1.ipv4}:53";
|
|
# Caddy reads the cert from explicit file paths (tls directive below), so it
|
|
# won't notice a renewal on its own — reload it whenever the cert changes.
|
|
reloadServices = [ "caddy.service" ];
|
|
};
|
|
};
|
|
|
|
# The lego-issued cert is owned group=acme; Caddy needs to read the key.
|
|
users.users.caddy.extraGroups = [ "acme" ];
|
|
|
|
# Reverse proxy. The explicit `tls cert key` points Caddy at the wildcard cert
|
|
# and disables its automatic ACME, so no extra issuance happens. Backends are
|
|
# dialed over the mesh by their ZeroTier address (mesh.hosts.<name>).
|
|
services.caddy = {
|
|
enable = true;
|
|
virtualHosts."grafana.cnx.network".extraConfig = ''
|
|
tls /var/lib/acme/${certName}/cert.pem /var/lib/acme/${certName}/key.pem
|
|
reverse_proxy http://[${mesh.hosts.control}]:3000
|
|
'';
|
|
};
|
|
|
|
# 443 serves the proxy; 80 only carries Caddy's automatic HTTP->HTTPS redirect
|
|
# (the Hetzner cloud firewall also scopes these in
|
|
# modules/hetzner-firewall-rules.nix). Admin still rides the mesh.
|
|
networking.firewall.allowedTCPPorts = [
|
|
80
|
|
443
|
|
];
|
|
}
|