diff --git a/flake-parts/hosts.nix b/flake-parts/hosts.nix index c950cba..3101dfc 100644 --- a/flake-parts/hosts.nix +++ b/flake-parts/hosts.nix @@ -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; diff --git a/flake-parts/tests.nix b/flake-parts/tests.nix new file mode 100644 index 0000000..221d65b --- /dev/null +++ b/flake-parts/tests.nix @@ -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; + }; +} diff --git a/justfile b/justfile index 20e4e8b..e0b05a0 100644 --- a/justfile +++ b/justfile @@ -10,6 +10,9 @@ fmt: nix fmt check: + nix flake check + +check-lite: nix flake check --no-build repair: diff --git a/modules/system/networking/default.nix b/modules/system/networking/default.nix index 5b790a4..24f8ac3 100644 --- a/modules/system/networking/default.nix +++ b/modules/system/networking/default.nix @@ -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 ( diff --git a/modules/system/networking/overlay.nix b/modules/system/networking/overlay.nix index 910f3aa..243e558 100644 --- a/modules/system/networking/overlay.nix +++ b/modules/system/networking/overlay.nix @@ -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); diff --git a/modules/system/networking/underlay.nix b/modules/system/networking/underlay.nix index 30e0b65..125cc75 100644 --- a/modules/system/networking/underlay.nix +++ b/modules/system/networking/underlay.nix @@ -40,7 +40,11 @@ in config = lib.mkMerge [ { - networking.useNetworkd = true; + networking = { + useNetworkd = true; + useDHCP = false; + }; + systemd.network = { enable = true; networks."10-${cfg.interface}" = { diff --git a/modules/system/services/dns.nix b/modules/system/services/dns.nix index f4c204f..f70e840 100644 --- a/modules/system/services/dns.nix +++ b/modules/system/services/dns.nix @@ -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: diff --git a/modules/system/services/nebula/default.nix b/modules/system/services/nebula/default.nix index 02cd028..d34e286 100644 --- a/modules/system/services/nebula/default.nix +++ b/modules/system/services/nebula/default.nix @@ -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 = { diff --git a/modules/system/services/sshd.nix b/modules/system/services/sshd.nix index 14f35c7..ca32281 100644 --- a/modules/system/services/sshd.nix +++ b/modules/system/services/sshd.nix @@ -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") diff --git a/modules/system/services/syncthing.nix b/modules/system/services/syncthing.nix index bdccc4c..72355c5 100644 --- a/modules/system/services/syncthing.nix +++ b/modules/system/services/syncthing.nix @@ -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 diff --git a/modules/system/web-services/gatus.nix b/modules/system/web-services/gatus.nix index aa8d3f4..c47da79 100644 --- a/modules/system/web-services/gatus.nix +++ b/modules/system/web-services/gatus.nix @@ -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) diff --git a/profiles/server.nix b/profiles/server.nix index f4b3dcf..08c9454 100644 --- a/profiles/server.nix +++ b/profiles/server.nix @@ -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}"; }; }; }; diff --git a/tests/infrastructure/default.nix b/tests/infrastructure/default.nix new file mode 100644 index 0000000..e325e25 --- /dev/null +++ b/tests/infrastructure/default.nix @@ -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'") + ''; +} diff --git a/tests/infrastructure/keys/ca.crt b/tests/infrastructure/keys/ca.crt new file mode 100644 index 0000000..5b6272b --- /dev/null +++ b/tests/infrastructure/keys/ca.crt @@ -0,0 +1,5 @@ +-----BEGIN NEBULA CERTIFICATE V2----- +MHygFoAEdGVzdIQB/4UEaY8shIYFASWHSoSCIM0af4sq7VnPAySG5h9fwiq/XHvD +a0Ssbk1+KVWFpR71g0DaZP8qR35Zut2z9i9D2bCDuagQNvvxCrkZ3JcF0gMvWu3u +uzKQMKzJSqipppgL/n3iQwwsBAoHYrx1XAY6zXgE +-----END NEBULA CERTIFICATE V2----- diff --git a/tests/infrastructure/keys/ca.key b/tests/infrastructure/keys/ca.key new file mode 100644 index 0000000..4a22aa1 --- /dev/null +++ b/tests/infrastructure/keys/ca.key @@ -0,0 +1,4 @@ +-----BEGIN NEBULA ED25519 PRIVATE KEY----- +8kwpb4GZIphJmamXx0ZrLm5TxPZ7G88L44mrdT2dQp3NGn+LKu1ZzwMkhuYfX8Iq +v1x7w2tErG5NfilVhaUe9Q== +-----END NEBULA ED25519 PRIVATE KEY----- diff --git a/tests/infrastructure/keys/client-ssh b/tests/infrastructure/keys/client-ssh new file mode 100644 index 0000000..125085e --- /dev/null +++ b/tests/infrastructure/keys/client-ssh @@ -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----- diff --git a/tests/infrastructure/keys/client-ssh.pub b/tests/infrastructure/keys/client-ssh.pub new file mode 100644 index 0000000..7cedc52 --- /dev/null +++ b/tests/infrastructure/keys/client-ssh.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICWAxmeZWndQUxKb/xD+uG07DeKgXualoFTK9cXCYGQx seb@laptop diff --git a/tests/infrastructure/keys/client.crt b/tests/infrastructure/keys/client.crt new file mode 100644 index 0000000..008e6f1 --- /dev/null +++ b/tests/infrastructure/keys/client.crt @@ -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----- diff --git a/tests/infrastructure/keys/client.key b/tests/infrastructure/keys/client.key new file mode 100644 index 0000000..978cb32 --- /dev/null +++ b/tests/infrastructure/keys/client.key @@ -0,0 +1,3 @@ +-----BEGIN NEBULA X25519 PRIVATE KEY----- +qURs9kzi3rsW8GLnOtzEV11M9TWs+0XSQxpEFN9Ab1Y= +-----END NEBULA X25519 PRIVATE KEY----- diff --git a/tests/infrastructure/keys/lighthouse.crt b/tests/infrastructure/keys/lighthouse.crt new file mode 100644 index 0000000..cae3a07 --- /dev/null +++ b/tests/infrastructure/keys/lighthouse.crt @@ -0,0 +1,6 @@ +-----BEGIN NEBULA CERTIFICATE V2----- +MIG0oE6ACmxpZ2h0aG91c2WhBwQFCgoKARijCAwGc2VydmVyhQRpjy1MhgUBJYdK +g4cgPHJHgTFNnzzjBXvHBEKAGVt2tf8XfoT7iYJEabSWJCCCIOfG1wz7tFj9GCvc +nth3Wm4oGYfK/iR7hbSXts8uAOwhg0DRuHJ6mGgi2deJIDz7aI6KmhMiWkdEnoxA +X8Eo5lZ4iIqyIiC8yAwYOMK1yHOVbfMplsUmhPgLw8Fu7wxSaiML +-----END NEBULA CERTIFICATE V2----- diff --git a/tests/infrastructure/keys/lighthouse.key b/tests/infrastructure/keys/lighthouse.key new file mode 100644 index 0000000..49c7fe2 --- /dev/null +++ b/tests/infrastructure/keys/lighthouse.key @@ -0,0 +1,3 @@ +-----BEGIN NEBULA X25519 PRIVATE KEY----- +fR7KPdR2nDOZtR/gEI+qwKQXI9JSAdi/j7PjYTAJShE= +-----END NEBULA X25519 PRIVATE KEY----- diff --git a/tests/infrastructure/keys/server-ssh b/tests/infrastructure/keys/server-ssh new file mode 100644 index 0000000..ced4abf --- /dev/null +++ b/tests/infrastructure/keys/server-ssh @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACBJ2fq1Q0oa5oZsEZuznAiux5ODES6fAvtqmOiiEBkxWgAAAJCyC2p+sgtq +fgAAAAtzc2gtZWQyNTUxOQAAACBJ2fq1Q0oa5oZsEZuznAiux5ODES6fAvtqmOiiEBkxWg +AAAED6j1Y/BoQsyvxtApUWipiCHCT1SiVyXf3NgmSsAjHAZknZ+rVDShrmhmwRm7OcCK7H +k4MRLp8C+2qY6KIQGTFaAAAACnNlYkBsYXB0b3ABAgM= +-----END OPENSSH PRIVATE KEY----- diff --git a/tests/infrastructure/keys/server-ssh.pub b/tests/infrastructure/keys/server-ssh.pub new file mode 100644 index 0000000..b591f07 --- /dev/null +++ b/tests/infrastructure/keys/server-ssh.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEnZ+rVDShrmhmwRm7OcCK7Hk4MRLp8C+2qY6KIQGTFa seb@laptop diff --git a/tests/infrastructure/keys/server.crt b/tests/infrastructure/keys/server.crt new file mode 100644 index 0000000..aad9f69 --- /dev/null +++ b/tests/infrastructure/keys/server.crt @@ -0,0 +1,6 @@ +-----BEGIN NEBULA CERTIFICATE V2----- +MIGwoEqABnNlcnZlcqEHBAUKCgoCGKMIDAZzZXJ2ZXKFBGmPLYyGBQElh0qDhyA8 +ckeBMU2fPOMFe8cEQoAZW3a1/xd+hPuJgkRptJYkIIIgWaZqtu8FVy/2REaZAVFo +BIOUaKrBSyrZuiLcBcFneR+DQOlv7S1H9Elhzl/8IhCCpiyamhkm4SL0eYV1N+S9 +lAsj3ga9dga/N5QqNZtWUs8RGgPzttNF8GOy0Evf10lZKwY= +-----END NEBULA CERTIFICATE V2----- diff --git a/tests/infrastructure/keys/server.key b/tests/infrastructure/keys/server.key new file mode 100644 index 0000000..517eb56 --- /dev/null +++ b/tests/infrastructure/keys/server.key @@ -0,0 +1,3 @@ +-----BEGIN NEBULA X25519 PRIVATE KEY----- +ug2E1t5D3hFNSwivf+sz0S9Xb4k44F0WM0lYyfw3X8I= +-----END NEBULA X25519 PRIVATE KEY-----