Compare commits

..

6 commits

25 changed files with 257 additions and 19 deletions

View file

@ -8,7 +8,10 @@ let
mkHost =
hostDir:
inputs.nixpkgs.lib.nixosSystem {
specialArgs = { inherit inputs self; };
specialArgs = {
inherit inputs self;
inherit (self) allHosts;
};
modules =
(lib.singleton {
networking.hostName = hostDir |> lib.baseNameOf |> lib.unsafeDiscardStringContext;

28
flake-parts/tests.nix Normal file
View file

@ -0,0 +1,28 @@
{ inputs, self, ... }:
{
perSystem =
{ pkgs, lib, ... }:
{
checks =
"${self}/tests"
|> builtins.readDir
|> lib.attrNames
|> lib.map (name: {
name = "${name}-test";
value = pkgs.testers.runNixOSTest (
{
name = "${name}-test";
}
// import "${self}/tests/${name}" {
inherit
inputs
self
pkgs
lib
;
}
);
})
|> lib.listToAttrs;
};
}

View file

@ -10,6 +10,9 @@ fmt:
nix fmt
check:
nix flake check
check-lite:
nix flake check --no-build
repair:

View file

@ -1,7 +1,7 @@
{
config,
self,
lib,
allHosts,
...
}:
let
@ -18,7 +18,7 @@ in
nodes = lib.mkOption {
type = lib.types.anything;
default =
self.allHosts
allHosts
|> lib.attrValues
|> lib.map (host: host.config.custom.networking)
|> lib.map (

View file

@ -1,7 +1,7 @@
{
config,
self,
lib,
allHosts,
...
}:
let
@ -27,6 +27,10 @@ in
type = lib.types.nonEmptyStr;
default = "";
};
fqdn = lib.mkOption {
type = lib.types.nonEmptyStr;
default = "${config.custom.networking.hostName}.${cfg.domain}";
};
address = lib.mkOption {
type = lib.types.nonEmptyStr;
@ -57,7 +61,7 @@ in
dnsServers = lib.mkOption {
type = lib.types.anything;
default =
self.allHosts
allHosts
|> lib.attrValues
|> lib.filter (host: host.config.custom.services.dns.enable)
|> lib.map (host: host.config.custom.networking.overlay.address);

View file

@ -40,7 +40,11 @@ in
config = lib.mkMerge [
{
networking.useNetworkd = true;
networking = {
useNetworkd = true;
useDHCP = false;
};
systemd.network = {
enable = true;
networks."10-${cfg.interface}" = {

View file

@ -2,6 +2,7 @@
config,
self,
lib,
allHosts,
...
}:
let
@ -23,11 +24,9 @@ in
local-zone = "\"${netCfg.overlay.domain}.\" static";
local-data =
let
nodeRecords =
netCfg.nodes
|> lib.map (node: "\"${node.hostName}.${node.overlay.domain}. A ${node.overlay.address}\"");
nodeRecords = netCfg.nodes |> lib.map (node: "\"${node.overlay.fqdn}. A ${node.overlay.address}\"");
serviceRecords =
self.allHosts
allHosts
|> lib.attrValues
|> lib.concatMap (
host:

View file

@ -27,6 +27,10 @@ in
++ lib.optional config.custom.services.syncthing.enable "syncthing";
};
caCertificatePath = lib.mkOption {
type = lib.types.path;
default = ./ca.crt;
};
publicKeyPath = lib.mkOption {
type = lib.types.path;
default = "${self}/hosts/${netCfg.hostName}/keys/nebula.pub";
@ -35,6 +39,10 @@ in
type = lib.types.path;
default = "${self}/hosts/${netCfg.hostName}/keys/nebula.crt";
};
privateKeyPath = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
};
};
config = lib.mkIf cfg.enable {
@ -50,14 +58,14 @@ in
systemdUnit = "nebula@mesh.service";
};
sops.secrets."nebula/host-key" = {
sops.secrets."nebula/host-key" = lib.mkIf (cfg.privateKeyPath == null) {
owner = config.users.users.nebula-mesh.name;
restartUnits = [ "nebula@mesh.service" ];
};
environment.etc = {
"nebula/ca.crt" = {
source = ./ca.crt;
source = cfg.caCertificatePath;
mode = "0440";
user = config.systemd.services."nebula@mesh".serviceConfig.User;
};
@ -73,7 +81,11 @@ in
ca = "/etc/nebula/ca.crt";
cert = "/etc/nebula/host.crt";
key = config.sops.secrets."nebula/host-key".path;
key =
if (cfg.privateKeyPath != null) then
cfg.privateKeyPath
else
config.sops.secrets."nebula/host-key".path;
tun.device = netCfg.overlay.interface;
listen = {

View file

@ -1,7 +1,7 @@
{
config,
self,
lib,
allHosts,
...
}:
let
@ -41,7 +41,7 @@ in
};
users.users.seb.openssh.authorizedKeys.keyFiles =
self.allHosts
allHosts
|> lib.attrValues
|> lib.filter (host: host.config.networking.hostName != netCfg.hostName)
|> lib.filter (host: host.config |> lib.hasAttr "home-manager")

View file

@ -2,6 +2,7 @@
config,
self,
lib,
allHosts,
...
}:
let
@ -87,7 +88,7 @@ in
settings =
let
hosts =
self.allHosts
allHosts
|> lib.filterAttrs (_: host: host.config.networking.hostName != config.networking.hostName)
|> lib.filterAttrs (_: host: host.config.custom.services.syncthing.enable);
in

View file

@ -1,7 +1,7 @@
{
config,
self,
lib,
allHosts,
...
}:
let
@ -173,7 +173,7 @@ in
web-services.gatus.endpoints =
let
defaultEndpoints =
self.allHosts
allHosts
|> lib.mapAttrs (
_: host:
host.config.custom.services.caddy.virtualHosts |> lib.attrValues |> lib.map (vHost: vHost.domain)

View file

@ -10,7 +10,7 @@
comin.enable = true;
alloy = {
enable = true;
domain = "alloy.${config.networking.hostName}.${config.custom.networking.overlay.domain}";
domain = "alloy.${config.custom.networking.overlay.fqdn}";
};
};
};

View file

@ -0,0 +1,132 @@
{
inputs,
self,
lib,
...
}:
{
defaults =
{ nodes, config, ... }:
{
imports = [ self.nixosModules.default ];
_module.args.allHosts = nodes |> lib.mapAttrs (_: node: { config = node; });
users = {
mutableUsers = false;
users.seb = {
isNormalUser = true;
password = "seb";
extraGroups = [ "wheel" ];
};
};
custom = {
networking.overlay.networkCidr = lib.mkForce "10.10.10.0/24";
services.nebula = {
caCertificatePath = ./keys/ca.crt;
certificatePath = ./keys/${config.networking.hostName}.crt;
privateKeyPath = ./keys/${config.networking.hostName}.key;
};
};
services.resolved.dnssec = lib.mkForce "false";
};
node.specialArgs = { inherit inputs self; };
nodes = {
lighthouse = {
custom = {
networking = {
overlay = {
address = "10.10.10.1";
isLighthouse = true;
role = "server";
};
underlay = {
interface = "eth1";
cidr = "192.168.0.1/16";
isPublic = true;
};
};
services.dns.enable = true;
};
};
server = {
custom = {
networking = {
overlay = {
address = "10.10.10.2";
role = "server";
};
underlay = {
interface = "eth1";
cidr = "192.168.0.2/16";
isPublic = true;
};
};
services.sshd.enable = true;
};
users.users.seb.openssh.authorizedKeys.keyFiles = [ ./keys/client-ssh.pub ];
environment.etc."ssh-key" = {
source = ./keys/server-ssh;
mode = "0600";
};
};
client = {
custom.networking = {
overlay = {
address = "10.10.10.3";
role = "client";
};
underlay = {
interface = "eth1";
cidr = "192.168.0.3/16";
};
};
users.users.seb.openssh.authorizedKeys.keyFiles = [ ./keys/server-ssh.pub ];
environment.etc."ssh-key" = {
source = ./keys/client-ssh;
mode = "0600";
};
};
};
testScript =
{ nodes, ... }:
let
lighthouseNetCfg = nodes.lighthouse.custom.networking.overlay;
serverNetCfg = nodes.server.custom.networking.overlay;
clientNetCfg = nodes.client.custom.networking.overlay;
sshOptions = "-i /etc/ssh-key -o BatchMode=yes -o ConnectTimeout=3 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null";
in
''
start_all()
lighthouse.wait_for_unit("${lighthouseNetCfg.systemdUnit}")
server.wait_for_unit("${serverNetCfg.systemdUnit}")
client.wait_for_unit("${clientNetCfg.systemdUnit}")
lighthouse.wait_for_unit("unbound.service")
server.wait_for_unit("sshd.service")
with subtest("Overlay connectivity between nodes"):
client.succeed("ping -c 1 ${serverNetCfg.address}")
server.succeed("ping -c 1 ${clientNetCfg.address}")
with subtest("DNS resolution of overlay hostnames"):
client.succeed("ping -c 1 ${serverNetCfg.fqdn}")
server.succeed("ping -c 1 ${clientNetCfg.fqdn}")
with subtest("SSH access restricted by role"):
client.succeed("ssh ${sshOptions} seb@${serverNetCfg.fqdn} 'echo Hello'")
server.fail("ssh ${sshOptions} seb@${clientNetCfg.fqdn} 'echo Hello'")
'';
}

View file

@ -0,0 +1,5 @@
-----BEGIN NEBULA CERTIFICATE V2-----
MHygFoAEdGVzdIQB/4UEaY8shIYFASWHSoSCIM0af4sq7VnPAySG5h9fwiq/XHvD
a0Ssbk1+KVWFpR71g0DaZP8qR35Zut2z9i9D2bCDuagQNvvxCrkZ3JcF0gMvWu3u
uzKQMKzJSqipppgL/n3iQwwsBAoHYrx1XAY6zXgE
-----END NEBULA CERTIFICATE V2-----

View file

@ -0,0 +1,4 @@
-----BEGIN NEBULA ED25519 PRIVATE KEY-----
8kwpb4GZIphJmamXx0ZrLm5TxPZ7G88L44mrdT2dQp3NGn+LKu1ZzwMkhuYfX8Iq
v1x7w2tErG5NfilVhaUe9Q==
-----END NEBULA ED25519 PRIVATE KEY-----

View file

@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACAlgMZnmVp3UFMSm/8Q/rhtOw3ioF7mpaBUyvXFwmBkMQAAAJCrUHOSq1Bz
kgAAAAtzc2gtZWQyNTUxOQAAACAlgMZnmVp3UFMSm/8Q/rhtOw3ioF7mpaBUyvXFwmBkMQ
AAAEB7OMxyFWm+GuvQA/GCdLPPXwkqC9rhPKdrLQU5PRt1fiWAxmeZWndQUxKb/xD+uG07
DeKgXualoFTK9cXCYGQxAAAACnNlYkBsYXB0b3ABAgM=
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICWAxmeZWndQUxKb/xD+uG07DeKgXualoFTK9cXCYGQx seb@laptop

View file

@ -0,0 +1,6 @@
-----BEGIN NEBULA CERTIFICATE V2-----
MIGwoEqABmNsaWVudKEHBAUKCgoDGKMIDAZjbGllbnSFBGmPLfCGBQElh0qDhyA8
ckeBMU2fPOMFe8cEQoAZW3a1/xd+hPuJgkRptJYkIIIg+h9fWh7oVaJEMJmmfCcC
zmFUQuPen59PiEE0+AKBbCyDQAIqxF7cIf5fL+z3zimUASA4hB5qFUCGEH+Er/Z6
vFXe0jHV4HYRBMaXgrM8JYnsGZgTtdyt+mlJR+uBWpH+pwg=
-----END NEBULA CERTIFICATE V2-----

View file

@ -0,0 +1,3 @@
-----BEGIN NEBULA X25519 PRIVATE KEY-----
qURs9kzi3rsW8GLnOtzEV11M9TWs+0XSQxpEFN9Ab1Y=
-----END NEBULA X25519 PRIVATE KEY-----

View file

@ -0,0 +1,6 @@
-----BEGIN NEBULA CERTIFICATE V2-----
MIG0oE6ACmxpZ2h0aG91c2WhBwQFCgoKARijCAwGc2VydmVyhQRpjy1MhgUBJYdK
g4cgPHJHgTFNnzzjBXvHBEKAGVt2tf8XfoT7iYJEabSWJCCCIOfG1wz7tFj9GCvc
nth3Wm4oGYfK/iR7hbSXts8uAOwhg0DRuHJ6mGgi2deJIDz7aI6KmhMiWkdEnoxA
X8Eo5lZ4iIqyIiC8yAwYOMK1yHOVbfMplsUmhPgLw8Fu7wxSaiML
-----END NEBULA CERTIFICATE V2-----

View file

@ -0,0 +1,3 @@
-----BEGIN NEBULA X25519 PRIVATE KEY-----
fR7KPdR2nDOZtR/gEI+qwKQXI9JSAdi/j7PjYTAJShE=
-----END NEBULA X25519 PRIVATE KEY-----

View file

@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBJ2fq1Q0oa5oZsEZuznAiux5ODES6fAvtqmOiiEBkxWgAAAJCyC2p+sgtq
fgAAAAtzc2gtZWQyNTUxOQAAACBJ2fq1Q0oa5oZsEZuznAiux5ODES6fAvtqmOiiEBkxWg
AAAED6j1Y/BoQsyvxtApUWipiCHCT1SiVyXf3NgmSsAjHAZknZ+rVDShrmhmwRm7OcCK7H
k4MRLp8C+2qY6KIQGTFaAAAACnNlYkBsYXB0b3ABAgM=
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEnZ+rVDShrmhmwRm7OcCK7Hk4MRLp8C+2qY6KIQGTFa seb@laptop

View file

@ -0,0 +1,6 @@
-----BEGIN NEBULA CERTIFICATE V2-----
MIGwoEqABnNlcnZlcqEHBAUKCgoCGKMIDAZzZXJ2ZXKFBGmPLYyGBQElh0qDhyA8
ckeBMU2fPOMFe8cEQoAZW3a1/xd+hPuJgkRptJYkIIIgWaZqtu8FVy/2REaZAVFo
BIOUaKrBSyrZuiLcBcFneR+DQOlv7S1H9Elhzl/8IhCCpiyamhkm4SL0eYV1N+S9
lAsj3ga9dga/N5QqNZtWUs8RGgPzttNF8GOy0Evf10lZKwY=
-----END NEBULA CERTIFICATE V2-----

View file

@ -0,0 +1,3 @@
-----BEGIN NEBULA X25519 PRIVATE KEY-----
ug2E1t5D3hFNSwivf+sz0S9Xb4k44F0WM0lYyfw3X8I=
-----END NEBULA X25519 PRIVATE KEY-----