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:
@@ -30,6 +30,15 @@
|
|||||||
greg = {
|
greg = {
|
||||||
home = true;
|
home = true;
|
||||||
gnome.enable = true;
|
gnome.enable = true;
|
||||||
|
nebula = {
|
||||||
|
enable = true;
|
||||||
|
unsafeRoutes = [
|
||||||
|
{
|
||||||
|
route = "10.42.0.0/16";
|
||||||
|
via = "10.157.0.2"; # genesis
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
podman.enable = true;
|
podman.enable = true;
|
||||||
print.enable = true;
|
print.enable = true;
|
||||||
tailscale = {
|
tailscale = {
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ in
|
|||||||
greg = {
|
greg = {
|
||||||
home = true;
|
home = true;
|
||||||
gnome.enable = false;
|
gnome.enable = false;
|
||||||
|
nebula = {
|
||||||
|
enable = true;
|
||||||
|
# genesis routes the home LAN (10.42.0.0/16) into the Nebula overlay.
|
||||||
|
# Sign genesis's cert with -subnets '10.42.0.0/16' (see secrets/nebula/README.md).
|
||||||
|
routesSubnet = "10.42.0.0/16";
|
||||||
|
};
|
||||||
proxies = {
|
proxies = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -49,6 +49,15 @@ in
|
|||||||
|
|
||||||
greg = {
|
greg = {
|
||||||
home = true;
|
home = true;
|
||||||
|
nebula = {
|
||||||
|
enable = true;
|
||||||
|
unsafeRoutes = [
|
||||||
|
{
|
||||||
|
route = "10.42.0.0/16";
|
||||||
|
via = "10.157.0.2"; # genesis
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
proxies = {
|
proxies = {
|
||||||
"jellyfin.home".target = "http://localhost:8096/";
|
"jellyfin.home".target = "http://localhost:8096/";
|
||||||
"jellyfin.thehellings.lan".target = "http://localhost:8096/";
|
"jellyfin.thehellings.lan".target = "http://localhost:8096/";
|
||||||
|
|||||||
@@ -42,6 +42,15 @@
|
|||||||
vip = metadata.hosts.${config.networking.hostName}.ip;
|
vip = metadata.hosts.${config.networking.hostName}.ip;
|
||||||
priority = 255;
|
priority = 255;
|
||||||
};
|
};
|
||||||
|
nebula = {
|
||||||
|
enable = true;
|
||||||
|
unsafeRoutes = [
|
||||||
|
{
|
||||||
|
route = "10.42.0.0/16";
|
||||||
|
via = "10.157.0.2"; # genesis
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
podman.enable = true;
|
podman.enable = true;
|
||||||
tailscale = {
|
tailscale = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|||||||
@@ -85,6 +85,15 @@ in
|
|||||||
vip = ip;
|
vip = ip;
|
||||||
priority = 254;
|
priority = 254;
|
||||||
};
|
};
|
||||||
|
nebula = {
|
||||||
|
enable = true;
|
||||||
|
unsafeRoutes = [
|
||||||
|
{
|
||||||
|
route = "10.42.0.0/16";
|
||||||
|
via = "10.157.0.2"; # genesis
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
tailscale = {
|
tailscale = {
|
||||||
enable = true;
|
enable = true;
|
||||||
tags = [ "home" ];
|
tags = [ "home" ];
|
||||||
|
|||||||
@@ -26,6 +26,10 @@
|
|||||||
greg = {
|
greg = {
|
||||||
home = false;
|
home = false;
|
||||||
linode.enable = true;
|
linode.enable = true;
|
||||||
|
nebula = {
|
||||||
|
enable = true;
|
||||||
|
isLighthouse = true;
|
||||||
|
};
|
||||||
proxies."immich.thehellings.com" = {
|
proxies."immich.thehellings.com" = {
|
||||||
genAliases = false;
|
genAliases = false;
|
||||||
target = "http://localhost:${builtins.toString config.services.immich-public-proxy.port}";
|
target = "http://localhost:${builtins.toString config.services.immich-public-proxy.port}";
|
||||||
|
|||||||
@@ -26,6 +26,15 @@
|
|||||||
vipInterface = "enp12s0";
|
vipInterface = "enp12s0";
|
||||||
priority = 253;
|
priority = 253;
|
||||||
};
|
};
|
||||||
|
nebula = {
|
||||||
|
enable = true;
|
||||||
|
unsafeRoutes = [
|
||||||
|
{
|
||||||
|
route = "10.42.0.0/16";
|
||||||
|
via = "10.157.0.2"; # genesis
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
remote-builder.enable = true;
|
remote-builder.enable = true;
|
||||||
runner = {
|
runner = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
./router.nix
|
./router.nix
|
||||||
./rpi4.nix
|
./rpi4.nix
|
||||||
./syncthing.nix
|
./syncthing.nix
|
||||||
|
./nebula.nix
|
||||||
./tailscale.nix
|
./tailscale.nix
|
||||||
./vmdev.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
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
+46
-10
@@ -16,21 +16,29 @@
|
|||||||
"ip": null,
|
"ip": null,
|
||||||
"pubkey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFxmnCj2E9DxcnefPW+n4yCuLShxqr0p024riogdeXA3",
|
"pubkey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFxmnCj2E9DxcnefPW+n4yCuLShxqr0p024riogdeXA3",
|
||||||
"ts": "100.70.99.91",
|
"ts": "100.70.99.91",
|
||||||
"system": "x86_64-linux"
|
"system": "x86_64-linux",
|
||||||
|
"nebulaIp": "10.157.0.7"
|
||||||
},
|
},
|
||||||
"genesis": {
|
"genesis": {
|
||||||
"ip": "10.42.1.5",
|
"ip": "10.42.1.5",
|
||||||
"pubkey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO/CGE2rqlL2Qr0TJkwQMsHFSDkhGWlrUlvfcvcylO1n",
|
"pubkey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO/CGE2rqlL2Qr0TJkwQMsHFSDkhGWlrUlvfcvcylO1n",
|
||||||
"ts": "100.96.198.104",
|
"ts": "100.96.198.104",
|
||||||
"system": "x86_64-linux",
|
"system": "x86_64-linux",
|
||||||
"tags": ["router", "server"]
|
"tags": [
|
||||||
|
"router",
|
||||||
|
"server"
|
||||||
|
],
|
||||||
|
"nebulaIp": "10.157.0.2"
|
||||||
},
|
},
|
||||||
"hosea": {
|
"hosea": {
|
||||||
"ip": "10.42.1.7",
|
"ip": "10.42.1.7",
|
||||||
"pubkey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKLIwkTTXA56sUlUjEulXXZRvZy5H4a5ZwgKWLlpkQDz",
|
"pubkey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKLIwkTTXA56sUlUjEulXXZRvZy5H4a5ZwgKWLlpkQDz",
|
||||||
"ts": "100.68.203.1",
|
"ts": "100.68.203.1",
|
||||||
"system": "x86_64-linux",
|
"system": "x86_64-linux",
|
||||||
"tags": ["server"]
|
"tags": [
|
||||||
|
"server"
|
||||||
|
],
|
||||||
|
"nebulaIp": "10.157.0.3"
|
||||||
},
|
},
|
||||||
"icdm-root": {
|
"icdm-root": {
|
||||||
"external": true,
|
"external": true,
|
||||||
@@ -42,8 +50,16 @@
|
|||||||
"pubkey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHleYKtfV4W1Z63Ysu9w5Rbglqlz4F92YcZoMkucoTNf",
|
"pubkey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHleYKtfV4W1Z63Ysu9w5Rbglqlz4F92YcZoMkucoTNf",
|
||||||
"ts": "100.84.183.79",
|
"ts": "100.84.183.79",
|
||||||
"system": "x86_64-linux",
|
"system": "x86_64-linux",
|
||||||
"systems": ["x86_64-linux", "aarch64-linux"],
|
"systems": [
|
||||||
"tags": ["builder", "kube", "server"]
|
"x86_64-linux",
|
||||||
|
"aarch64-linux"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"builder",
|
||||||
|
"kube",
|
||||||
|
"server"
|
||||||
|
],
|
||||||
|
"nebulaIp": "10.157.0.4"
|
||||||
},
|
},
|
||||||
"iso": {
|
"iso": {
|
||||||
"external": true,
|
"external": true,
|
||||||
@@ -59,8 +75,16 @@
|
|||||||
"pubkey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOjQjXq9WYU2Ki27BR9WwJ4ZruS/lJXbjC1b0Q42Adi0",
|
"pubkey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOjQjXq9WYU2Ki27BR9WwJ4ZruS/lJXbjC1b0Q42Adi0",
|
||||||
"ts": "100.102.186.39",
|
"ts": "100.102.186.39",
|
||||||
"system": "x86_64-linux",
|
"system": "x86_64-linux",
|
||||||
"systems": ["x86_64-linux", "aarch64-linux"],
|
"systems": [
|
||||||
"tags": ["builder", "kube", "server"]
|
"x86_64-linux",
|
||||||
|
"aarch64-linux"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"builder",
|
||||||
|
"kube",
|
||||||
|
"server"
|
||||||
|
],
|
||||||
|
"nebulaIp": "10.157.0.5"
|
||||||
},
|
},
|
||||||
"joel": {
|
"joel": {
|
||||||
"external": true,
|
"external": true,
|
||||||
@@ -72,7 +96,11 @@
|
|||||||
"pubkey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMv9Zud3kZOl86gtmkn+uj3D4kiXWDPtyUL02VVLNR4Q",
|
"pubkey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMv9Zud3kZOl86gtmkn+uj3D4kiXWDPtyUL02VVLNR4Q",
|
||||||
"ts": "100.109.86.8",
|
"ts": "100.109.86.8",
|
||||||
"system": "x86_64-linux",
|
"system": "x86_64-linux",
|
||||||
"tags": ["public", "server"]
|
"tags": [
|
||||||
|
"public",
|
||||||
|
"server"
|
||||||
|
],
|
||||||
|
"nebulaIp": "10.157.0.1"
|
||||||
},
|
},
|
||||||
"MacBook-Pro.local": {
|
"MacBook-Pro.local": {
|
||||||
"external": true,
|
"external": true,
|
||||||
@@ -108,8 +136,16 @@
|
|||||||
"pubkey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOos0zQePsa+T6Z2dsKbPOvEdrBQ8a6mx3s7pN6ysCI0",
|
"pubkey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOos0zQePsa+T6Z2dsKbPOvEdrBQ8a6mx3s7pN6ysCI0",
|
||||||
"ts": "100.90.74.19",
|
"ts": "100.90.74.19",
|
||||||
"system": "x86_64-linux",
|
"system": "x86_64-linux",
|
||||||
"systems": ["x86_64-linux", "aarch64-linux"],
|
"systems": [
|
||||||
"tags": ["builder", "kube", "server"]
|
"x86_64-linux",
|
||||||
|
"aarch64-linux"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"builder",
|
||||||
|
"kube",
|
||||||
|
"server"
|
||||||
|
],
|
||||||
|
"nebulaIp": "10.157.0.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Files that belong here (committed to repo):
|
||||||
|
# ca.crt — Nebula CA certificate (public)
|
||||||
|
# *.crt — per-host certificates (public)
|
||||||
|
# *.key.age — agenix-encrypted private keys (safe to commit)
|
||||||
|
#
|
||||||
|
# Files that must NEVER be committed:
|
||||||
|
# ca.key — CA private key (keep offline!)
|
||||||
|
# *.key — plaintext host private keys (encrypt with agenix first)
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
# Nebula PKI Bootstrap Guide
|
||||||
|
|
||||||
|
This directory holds the Nebula CA certificate, host certificates, and
|
||||||
|
agenix-encrypted private keys for the `nebula.thehellings.com` overlay network.
|
||||||
|
|
||||||
|
## CIDR
|
||||||
|
`10.157.0.0/16`
|
||||||
|
|
||||||
|
## Host IP Assignments
|
||||||
|
|
||||||
|
| Host | Nebula IP | Role |
|
||||||
|
|-----------|-------------|----------------------|
|
||||||
|
| linode | 10.157.0.1 | Lighthouse + relay |
|
||||||
|
| genesis | 10.157.0.2 | LAN router (unsafe) |
|
||||||
|
| hosea | 10.157.0.3 | Regular node |
|
||||||
|
| isaiah | 10.157.0.4 | Regular node |
|
||||||
|
| jeremiah | 10.157.0.5 | Regular node |
|
||||||
|
| zeke | 10.157.0.6 | Regular node |
|
||||||
|
| exodus | 10.157.0.7 | Regular node (laptop) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1 — Install nebula-cert
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix shell nixpkgs#nebula
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 2 — Create the CA
|
||||||
|
|
||||||
|
Run once; keep `ca.key` offline/safe (do NOT commit it):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nebula-cert ca -name "thehellings.com" -out-crt ca.crt -out-key ca.key
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit `ca.crt` (public) to the repo at `secrets/nebula/ca.crt`.
|
||||||
|
Store `ca.key` securely (password manager / offline).
|
||||||
|
|
||||||
|
## Step 3 — Sign host certificates
|
||||||
|
|
||||||
|
For most hosts (no subnet routing):
|
||||||
|
```bash
|
||||||
|
nebula-cert sign -ca-crt ca.crt -ca-key ca.key \
|
||||||
|
-name <hostname> \
|
||||||
|
-ip <nebulaIp>/16 \
|
||||||
|
-out-crt secrets/nebula/<hostname>.crt \
|
||||||
|
-out-key secrets/nebula/<hostname>.key
|
||||||
|
```
|
||||||
|
|
||||||
|
For **genesis** (routes the home LAN `10.42.0.0/16`), add `-subnets`:
|
||||||
|
```bash
|
||||||
|
nebula-cert sign -ca-crt ca.crt -ca-key ca.key \
|
||||||
|
-name genesis \
|
||||||
|
-ip 10.157.0.2/16 \
|
||||||
|
-subnets '10.42.0.0/16' \
|
||||||
|
-out-crt secrets/nebula/genesis.crt \
|
||||||
|
-out-key secrets/nebula/genesis.key
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit `*.crt` files (public) to the repo.
|
||||||
|
Do NOT commit raw `*.key` files — encrypt them first (Step 4).
|
||||||
|
|
||||||
|
## Step 4 — Encrypt private keys with agenix
|
||||||
|
|
||||||
|
From the repo root:
|
||||||
|
```bash
|
||||||
|
cd nixos
|
||||||
|
for host in linode genesis hosea isaiah jeremiah zeke exodus; do
|
||||||
|
agenix -e secrets/nebula/${host}.key.age < secrets/nebula/${host}.key
|
||||||
|
rm secrets/nebula/${host}.key # remove plaintext key
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
The `secrets/secrets.nix` file already declares the `.key.age` recipients.
|
||||||
|
|
||||||
|
## Step 5 — Configure linode's public DNS / firewall
|
||||||
|
|
||||||
|
- Add a DNS A record: `linode.nebula.thehellings.com` → linode's public IP
|
||||||
|
- Open UDP port `4242` in linode's firewall / Linode Cloud Firewall rules
|
||||||
|
|
||||||
|
## Step 6 — Deploy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
colmena apply --on linode # lighthouse first
|
||||||
|
colmena apply # rest of the fleet
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verifying
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From any host, ping another by Nebula IP
|
||||||
|
ping 10.157.0.2 # genesis
|
||||||
|
|
||||||
|
# From any host, reach home LAN via unsafe_routes
|
||||||
|
ping 10.42.1.1 # UDM Pro (via genesis)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- `ca.crt` is public and lives in the repo unencrypted.
|
||||||
|
- `*.crt` (host certs) are public and live in the repo unencrypted.
|
||||||
|
- `*.key.age` are agenix-encrypted private keys (in `secrets/nebula/`).
|
||||||
|
- The NixOS module (`modules/nixos/nebula.nix`) references these paths directly.
|
||||||
|
- The lighthouse (`linode`) has `isLighthouse = true` and `isRelay = true`.
|
||||||
|
- `genesis` has `routesSubnet = "10.42.0.0/16"` which enables IP forwarding
|
||||||
|
and nftables masquerade so Nebula peers can reach the home LAN.
|
||||||
|
- All other hosts have `unsafeRoutes` pointing to genesis (10.157.0.2) for
|
||||||
|
the 10.42.0.0/16 subnet.
|
||||||
@@ -137,4 +137,14 @@ in
|
|||||||
"compose/attic.env.age".publicKeys = everyone;
|
"compose/attic.env.age".publicKeys = everyone;
|
||||||
|
|
||||||
"grafana-api-token.age".publicKeys = everyone;
|
"grafana-api-token.age".publicKeys = everyone;
|
||||||
|
|
||||||
|
# Nebula mesh network — one private key per host, encrypted to that host's
|
||||||
|
# system key + all user keys so Greg can (re)encrypt them from any machine.
|
||||||
|
"nebula/genesis.key.age".publicKeys = everyone;
|
||||||
|
"nebula/hosea.key.age".publicKeys = everyone;
|
||||||
|
"nebula/isaiah.key.age".publicKeys = everyone;
|
||||||
|
"nebula/jeremiah.key.age".publicKeys = everyone;
|
||||||
|
"nebula/linode.key.age".publicKeys = everyone;
|
||||||
|
"nebula/zeke.key.age".publicKeys = everyone;
|
||||||
|
"nebula/exodus.key.age".publicKeys = everyone;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user