feat: add Nebula mesh network overlay
buildbot/nix-eval Build done.
buildbot/nix-build gitea:greg/nixos#checks.x86_64-linux.nixos-exodus Build done.
buildbot/nix-build gitea:greg/nixos#checks.x86_64-linux.nixos-isaiah Build done.
buildbot/nix-build gitea:greg/nixos#checks.x86_64-linux.nixos-genesis Build done.
buildbot/nix-build gitea:greg/nixos#checks.x86_64-linux.nixos-linode Build done.
buildbot/nix-build gitea:greg/nixos#checks.x86_64-linux.nixos-jeremiah Build done.
buildbot/nix-build gitea:greg/nixos#checks.x86_64-linux.nixos-zeke Build done.
buildbot/nix-build gitea:greg/nixos#checks.x86_64-linux.nixos-hosea Build done.
buildbot/nix-build Build done.
buildbot/nix-eval Build done.
buildbot/nix-build gitea:greg/nixos#checks.x86_64-linux.nixos-exodus Build done.
buildbot/nix-build gitea:greg/nixos#checks.x86_64-linux.nixos-isaiah Build done.
buildbot/nix-build gitea:greg/nixos#checks.x86_64-linux.nixos-genesis Build done.
buildbot/nix-build gitea:greg/nixos#checks.x86_64-linux.nixos-linode Build done.
buildbot/nix-build gitea:greg/nixos#checks.x86_64-linux.nixos-jeremiah Build done.
buildbot/nix-build gitea:greg/nixos#checks.x86_64-linux.nixos-zeke Build done.
buildbot/nix-build gitea:greg/nixos#checks.x86_64-linux.nixos-hosea Build done.
buildbot/nix-build Build done.
Introduces a greg.nebula NixOS module and enables it across all managed
hosts for the nebula.thehellings.com overlay (CIDR: 10.157.0.0/16).
Architecture:
- linode: lighthouse + relay (public internet, UDP 4242)
- genesis: regular node + unsafe_routes router for 10.42.0.0/16 (home LAN)
- hosea, isaiah, jeremiah, zeke, exodus: regular nodes with unsafe_routes
pointing to genesis to reach the home LAN
Changes:
- modules/nixos/nebula.nix: new greg.nebula module
- isLighthouse / isRelay options
- unsafeRoutes option (tun.unsafe_routes)
- routesSubnet option: enables IP forwarding + nftables masquerade NAT
on the gateway host (genesis) so Nebula peers reach 10.42.0.0/16
- agenix secret reference per-host (secrets/nebula/<name>.key.age)
- opens UDP/4242 in the firewall
- modules/nixos/default.nix: import nebula.nix
- hosts/unstable/linode/default.nix: greg.nebula.isLighthouse = true
- hosts/unstable/genesis/default.nix: greg.nebula.routesSubnet = "10.42.0.0/16"
- hosts/unstable/{hosea,isaiah,jeremiah,zeke,exodus}/default.nix:
greg.nebula.enable = true with unsafeRoutes via genesis
- network.json: add nebulaIp field for each managed host
- secrets/secrets.nix: declare nebula/<host>.key.age entries
- secrets/nebula/README.md: full PKI bootstrap guide (CA, certs, agenix)
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
./router.nix
|
||||
./rpi4.nix
|
||||
./syncthing.nix
|
||||
./nebula.nix
|
||||
./tailscale.nix
|
||||
./vmdev.nix
|
||||
];
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
metadata,
|
||||
...
|
||||
}:
|
||||
|
||||
# Nebula overlay mesh network module (greg namespace)
|
||||
#
|
||||
# This module configures a Nebula node for the nebula.thehellings.com overlay.
|
||||
# CIDR: 10.157.0.0/16
|
||||
# Lighthouse: linode (public internet, acts as relay too)
|
||||
#
|
||||
# Each host requires:
|
||||
# secrets/nebula/<hostname>.key.age - encrypted private key
|
||||
# secrets/nebula/<hostname>.crt - certificate (public, unencrypted in repo)
|
||||
# secrets/nebula/ca.crt - CA certificate (public, unencrypted in repo)
|
||||
#
|
||||
# The nebula IP for each host must be set in network.json under
|
||||
# hosts.<name>.nebulaIp (e.g. "10.157.0.1")
|
||||
#
|
||||
# Lighthouse address is the public IP/DNS of linode. Update the
|
||||
# `lighthouseAddrs` option below or override per-host if it changes.
|
||||
|
||||
let
|
||||
cfg = config.greg.nebula;
|
||||
nebulaDomain = "nebula.thehellings.com";
|
||||
# Linode's public address — used by all non-lighthouse hosts to reach it.
|
||||
# Override with greg.nebula.lighthouseAddr if the public IP ever changes.
|
||||
defaultLighthouseAddr = "linode.${nebulaDomain}";
|
||||
in
|
||||
{
|
||||
options.greg.nebula = {
|
||||
enable = lib.mkEnableOption "Nebula overlay mesh network";
|
||||
|
||||
isLighthouse = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether this host is a Nebula lighthouse/relay node";
|
||||
};
|
||||
|
||||
isRelay = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = cfg.isLighthouse;
|
||||
description = "Whether this host acts as a relay (am_relay). Defaults to true when isLighthouse is true.";
|
||||
};
|
||||
|
||||
nebulaIp = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "This host's Nebula overlay IP (e.g. 10.157.0.1)";
|
||||
default =
|
||||
let
|
||||
hostData = metadata.hosts.${config.networking.hostName} or { };
|
||||
in
|
||||
hostData.nebulaIp or (throw "greg.nebula.nebulaIp must be set for host ${config.networking.hostName}");
|
||||
};
|
||||
|
||||
lighthouseAddr = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = defaultLighthouseAddr;
|
||||
description = "Public address (host:port) used to reach the lighthouse from non-lighthouse hosts";
|
||||
};
|
||||
|
||||
lighthouseNebulaIp = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default =
|
||||
let
|
||||
linodeData = metadata.hosts.linode or { };
|
||||
in
|
||||
linodeData.nebulaIp or "10.157.0.1";
|
||||
description = "Nebula overlay IP of the lighthouse host";
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 4242;
|
||||
description = "UDP port Nebula listens on";
|
||||
};
|
||||
|
||||
# unsafe_routes: allow non-Nebula subnets to be routed through this host.
|
||||
# Used on genesis to expose 10.42.0.0/16 (the home LAN) to the overlay.
|
||||
unsafeRoutes = lib.mkOption {
|
||||
type = lib.types.listOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
route = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "CIDR to route through this host (e.g. 10.42.0.0/16)";
|
||||
};
|
||||
via = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Nebula overlay IP of the host that provides the route";
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
default = [ ];
|
||||
description = "List of unsafe_routes to configure on this host (for reaching non-Nebula subnets)";
|
||||
};
|
||||
|
||||
# Whether this host IS the router for an unsafe subnet
|
||||
# (enables IP forwarding + nftables masquerade for the home LAN)
|
||||
routesSubnet = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
When set, this host will route traffic from the Nebula overlay
|
||||
to this subnet. Enables IP forwarding and nftables masquerade.
|
||||
Example: "10.42.0.0/16"
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
# agenix: decrypt this host's Nebula private key at boot
|
||||
age.secrets."nebula-${config.networking.hostName}-key" = {
|
||||
file = ../../secrets/nebula/${config.networking.hostName}.key.age;
|
||||
# nebula service runs as root, key owned by root is fine
|
||||
mode = "0400";
|
||||
};
|
||||
|
||||
services.nebula.networks.${nebulaDomain} = {
|
||||
enable = true;
|
||||
|
||||
# CA certificate (public — lives unencrypted in the repo)
|
||||
ca = ../../secrets/nebula/ca.crt;
|
||||
|
||||
# Host certificate (public — lives unencrypted in the repo)
|
||||
cert = ../../secrets/nebula/${config.networking.hostName}.crt;
|
||||
|
||||
# Private key (agenix-decrypted at runtime)
|
||||
key = config.age.secrets."nebula-${config.networking.hostName}-key".path;
|
||||
|
||||
# Static host map: tell every node where the lighthouse lives
|
||||
staticHostMap = {
|
||||
"${cfg.lighthouseNebulaIp}" = [ "${cfg.lighthouseAddr}:${toString cfg.port}" ];
|
||||
};
|
||||
|
||||
isLighthouse = cfg.isLighthouse;
|
||||
isRelay = cfg.isRelay;
|
||||
|
||||
listen = {
|
||||
host = "0.0.0.0";
|
||||
port = cfg.port;
|
||||
};
|
||||
|
||||
lighthouses = lib.optionals (!cfg.isLighthouse) [ cfg.lighthouseNebulaIp ];
|
||||
|
||||
relays = lib.optionals (!cfg.isLighthouse && !cfg.isRelay) [ cfg.lighthouseNebulaIp ];
|
||||
|
||||
tun = {
|
||||
# Interface name: nebula.<domain-first-label>
|
||||
dev = "nebula0";
|
||||
# unsafe_routes for hosts that need access to a non-Nebula subnet
|
||||
unsafeRoutes = cfg.unsafeRoutes;
|
||||
};
|
||||
|
||||
# Firewall: permissive defaults — tighten per-host as desired
|
||||
firewall = {
|
||||
outbound = [
|
||||
{
|
||||
port = "any";
|
||||
proto = "any";
|
||||
host = "any";
|
||||
}
|
||||
];
|
||||
inbound =
|
||||
[
|
||||
# Allow ICMP (ping) from any Nebula peer
|
||||
{
|
||||
port = "any";
|
||||
proto = "icmp";
|
||||
host = "any";
|
||||
}
|
||||
# Allow all traffic from within the Nebula overlay
|
||||
{
|
||||
port = "any";
|
||||
proto = "any";
|
||||
host = "any";
|
||||
}
|
||||
]
|
||||
# When routing an unsafe subnet, allow inbound traffic destined
|
||||
# for that subnet from any Nebula peer (local_cidr scopes it)
|
||||
++ lib.optionals (cfg.routesSubnet != null) [
|
||||
{
|
||||
port = "any";
|
||||
proto = "any";
|
||||
host = "any";
|
||||
local_cidr = cfg.routesSubnet;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
# Open the Nebula UDP port in the firewall
|
||||
networking.firewall.allowedUDPPorts = [ cfg.port ];
|
||||
|
||||
# When this host routes traffic to a non-Nebula subnet, enable IP
|
||||
# forwarding and add nftables masquerade rules (see unsafe_routes guide).
|
||||
boot.kernel.sysctl = lib.mkIf (cfg.routesSubnet != null) {
|
||||
"net.ipv4.ip_forward" = lib.mkDefault "1";
|
||||
};
|
||||
|
||||
networking.nftables.tables = lib.mkIf (cfg.routesSubnet != null) {
|
||||
nebula_routing = {
|
||||
family = "ip";
|
||||
content = ''
|
||||
chain postrouting {
|
||||
type nat hook postrouting priority srcnat; policy accept;
|
||||
ip saddr 10.157.0.0/16 ip daddr ${cfg.routesSubnet} counter masquerade
|
||||
}
|
||||
|
||||
chain forward {
|
||||
type filter hook forward priority filter; policy accept;
|
||||
ct state related,established counter accept
|
||||
iifname "nebula0" ip saddr 10.157.0.0/16 ip daddr ${cfg.routesSubnet} counter accept
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user