344f432640
control runs a oneshot on each deploy that creates each firewall if missing and replaces its rules via the Hetzner API set_rules action, using a Read/Write token stored as a clan secret. Public SSH is not exposed; admin access rides the ZeroTier mesh, with emergency-access as the console fallback.
47 lines
1.1 KiB
Nix
47 lines
1.1 KiB
Nix
let
|
|
world = [ "0.0.0.0/0" "::/0" ];
|
|
|
|
zerotier = {
|
|
direction = "in";
|
|
protocol = "udp";
|
|
port = "9993";
|
|
source_ips = world;
|
|
description = "ZeroTier";
|
|
};
|
|
|
|
ping = {
|
|
direction = "in";
|
|
protocol = "icmp";
|
|
source_ips = world;
|
|
description = "ICMP (ping / PMTUD)";
|
|
};
|
|
|
|
dnsRules = [
|
|
{ direction = "in"; protocol = "udp"; port = "53"; source_ips = world; description = "DNS (UDP)"; }
|
|
{ direction = "in"; protocol = "tcp"; port = "53"; source_ips = world; description = "DNS (TCP)"; }
|
|
zerotier
|
|
ping
|
|
];
|
|
in
|
|
{
|
|
imports = [
|
|
../../modules/hetzner-firewall.nix
|
|
];
|
|
|
|
time.timeZone = "Etc/GMT-3"; # UTC+3 (fixed offset, no DST)
|
|
services.timesyncd.enable = true;
|
|
|
|
# Public Hetzner Cloud firewalls, kept in sync from this config on every
|
|
# deploy. Admin SSH is intentionally not exposed publicly: it rides the
|
|
# ZeroTier mesh (inside UDP 9993), with emergency-access as the console
|
|
# fallback if the mesh is ever unreachable.
|
|
cnx.hetznerFirewall = {
|
|
enable = true;
|
|
firewalls = {
|
|
"clan-control" = [ zerotier ping ];
|
|
"clan-ns1" = dnsRules;
|
|
"clan-ns2" = dnsRules;
|
|
};
|
|
};
|
|
}
|