diff --git a/nixos/tests/networking/networkmanager.nix b/nixos/tests/networking/networkmanager.nix index 320aa86cd04d..55f65897e3cc 100644 --- a/nixos/tests/networking/networkmanager.nix +++ b/nixos/tests/networking/networkmanager.nix @@ -8,10 +8,34 @@ with import ../../lib/testing-python.nix { inherit system pkgs; }; let lib = pkgs.lib; + + # Generate EAP certificates on the fly (CA, server, and client certs) + eapCerts = pkgs.runCommand "eap-certs" { buildInputs = [ pkgs.openssl ]; } '' + mkdir -p $out + + # Create CA certificate + openssl req -x509 -newkey rsa:2048 -days 365000 -nodes \ + -keyout $out/ca.key -out $out/ca.cert \ + -subj "/CN=ExampleCA" + + # Create server certificate + openssl req -newkey rsa:2048 -nodes \ + -keyout $out/server.key -out server.csr \ + -subj "/CN=server.example.com" + openssl x509 -req -in server.csr -CA $out/ca.cert -CAkey $out/ca.key \ + -days 365000 -set_serial 100 -out $out/server.cert + + # Create client certificate + openssl req -newkey rsa:2048 -nodes \ + -keyout $out/client1.key -out client1.csr \ + -subj "/CN=client1.example.com" + openssl x509 -req -in client1.csr -CA $out/ca.cert -CAkey $out/ca.key \ + -days 365000 -set_serial 101 -out $out/client1.cert + ''; + # this is intended as a client test since you shouldn't use NetworkManager for a router or server # so using systemd-networkd for the router vm is fine in these tests. router = import ./router.nix { networkd = true; }; - qemu-common = import ../../lib/qemu-common.nix { inherit (pkgs) lib pkgs; }; clientConfig = extraConfig: lib.recursiveUpdate { @@ -180,6 +204,100 @@ let client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::23") ''; }; + eap = + let + toBase64Blob = + file: + "data:;base64," + + builtins.readFile ( + pkgs.runCommand "base64" { } '' + ${pkgs.coreutils}/bin/base64 -w0 ${file} > $out + '' + ); + in + { + name = "eap / 802.1x with secrets in blob encoding"; + nodes = { + router = import ./router-eap.nix { inherit eapCerts; }; + client = clientConfig { + networking.networkmanager.ensureProfiles.profiles.default = { + ipv4.method = "auto"; + "802-1x" = { + eap = "tls"; + identity = "client1.example.com"; + ca-cert = toBase64Blob "${eapCerts}/ca.cert"; + client-cert = toBase64Blob "${eapCerts}/client1.cert"; + private-key = toBase64Blob "${eapCerts}/client1.key"; + private-key-password-flags = "4"; + }; + }; + networking.wireless = { + # it is a bit unfortunate that wpa-supplicant is equated with + # `wireless` when it also works for wired connections + enable = lib.mkOverride 9 true; + driver = "wired"; + }; + }; + }; + + testScript = '' + start_all() + client.wait_for_unit("NetworkManager.service") + router.wait_for_unit("freeradius.service") + router.wait_for_unit("hostapd.service") + router.wait_until_succeeds("journalctl -b --unit freeradius.service | grep \"Sent Access-Accept\"") + router.wait_until_succeeds("journalctl -b --unit freeradius.service | grep \"TLS-Client-Cert-Common-Name := \\\"client1.example.com\\\"\"") + ''; + }; + eapFiles = { + name = "eap / 802.1x with secrets in stored in files"; + nodes = { + router = import ./router-eap.nix { inherit eapCerts; }; + client = clientConfig { + environment.etc = { + "wpa_supplicant/ca.cert" = { + source = "${eapCerts}/ca.cert"; + user = "wpa_supplicant"; + mode = "0400"; + }; + "wpa_supplicant/client1.cert" = { + source = "${eapCerts}/client1.cert"; + user = "wpa_supplicant"; + mode = "0400"; + }; + "wpa_supplicant/client1.key" = { + source = "${eapCerts}/client1.key"; + user = "wpa_supplicant"; + mode = "0400"; + }; + }; + networking.networkmanager.ensureProfiles.profiles.default = { + ipv4.method = "auto"; + "802-1x" = { + eap = "tls"; + identity = "client1.example.com"; + ca-cert = "/etc/wpa_supplicant/ca.cert"; + client-cert = "/etc/wpa_supplicant/client1.cert"; + private-key = "/etc/wpa_supplicant/client1.key"; + private-key-password-flags = "4"; + }; + }; + networking.wireless = { + enable = lib.mkOverride 9 true; + driver = "wired"; + }; + }; + }; + + testScript = '' + start_all() + client.wait_for_unit("NetworkManager.service") + router.wait_for_unit("freeradius.service") + router.wait_for_unit("hostapd.service") + router.wait_until_succeeds("journalctl -b --unit freeradius.service | grep \"Sent Access-Accept\"") + router.wait_until_succeeds("journalctl -b --unit freeradius.service | grep \"TLS-Client-Cert-Common-Name := \\\"client1.example.com\\\"\"") + ''; + }; }; in lib.mapAttrs (lib.const ( diff --git a/nixos/tests/networking/router-eap.nix b/nixos/tests/networking/router-eap.nix new file mode 100644 index 000000000000..fac1e5b700f2 --- /dev/null +++ b/nixos/tests/networking/router-eap.nix @@ -0,0 +1,146 @@ +{ eapCerts }: +{ config, pkgs, ... }: +let + radiusDir = + let + eapConfig = builtins.toFile "eap.conf" '' + eap { + default_eap_type = tls + timer_expire = 60 + ignore_unknown_eap_types = no + + tls { + # Path to CA certificate + ca_file = ''${certdir}/ca.cert + + # Path to server certificate and private key + certificate_file = ''${certdir}/server.cert + private_key_file = ''${certdir}/server.key + + # Enable mutual authentication + require_client_cert = yes + + # Cipher suite (example) + ciphers = "DEFAULT" + } + } + ''; + clientsConfig = builtins.toFile "client.conf" '' + client localhost { + ipaddr = 127.0.0.1 + secret = insecure + require_message_authenticator = no + } + ''; + + # sample users file, not used for eap-tls, only for e.g. eap-ttls + usersConfig = builtins.toFile "users.conf" '' + testuser Cleartext-Password := "supersecret" + ''; + + # this constructs the freeradius config directory + # it starts with the upstream config, then overwrites certain files + # and uses the generated certs from eapCerts + buildScript = pkgs.writeShellApplication { + name = "builder"; + + runtimeInputs = [ pkgs.coreutils ]; + + # https://www.shellcheck.net/wiki/SC2154 -- out is referenced but not assigned. + excludeShellChecks = [ "SC2154" ]; + + text = '' + cp --recursive ${pkgs.freeradius}/etc/* "$out" + chmod +w -R "$out" + cp --force ${eapCerts}/ca.cert "$out/certs/ca.cert" + cp --force ${eapCerts}/ca.key "$out/certs/ca.key" + cp --force ${eapCerts}/server.cert "$out/certs/server.cert" + cp --force ${eapCerts}/server.key "$out/certs/server.key" + cp --force ${eapConfig} "$out/mods-enabled/eap" + cp --force ${usersConfig} "$out/users" + cp --force ${clientsConfig} "$out/clients.conf" + ''; + }; + in + derivation { + name = "radius_dir"; + builder = "${pkgs.bash}/bin/bash"; + args = [ "${buildScript}/bin/builder" ]; + inherit (pkgs) system; + }; + inherit (pkgs) lib; + vlanIfs = lib.range 1 (lib.length config.virtualisation.vlans); +in +{ + virtualisation.vlans = [ + 1 + 2 + 3 + ]; + boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true; + networking = { + useDHCP = false; + useNetworkd = true; + firewall.checkReversePath = true; + firewall.allowedUDPPorts = [ 547 ]; + interfaces = lib.mkOverride 0 ( + lib.listToAttrs ( + lib.forEach vlanIfs ( + n: + lib.nameValuePair "eth${toString n}" { + ipv4.addresses = [ + { + address = "192.168.${toString n}.1"; + prefixLength = 24; + } + ]; + ipv6.addresses = [ + { + address = "fd00:1234:5678:${toString n}::1"; + prefixLength = 64; + } + ]; + } + ) + ) + ); + }; + + services.freeradius = { + enable = true; + configDir = radiusDir; + debug = true; + }; + + # upstream nixpkgs hostapd is focused on Wifi + systemd.services.hostapd = + let + hostapdConfig = builtins.toFile "hostapd.conf" '' + interface=eth1 + driver=wired + logger_stdout=-1 + logger_stdout_level=1 + debug=2 + dump_file=/tmp/hostapd.dump + ieee8021x=1 + eap_reauth_period=3600 + use_pae_group_addr=1 + ##### RADIUS configuration #################################################### + own_ip_addr=127.0.0.1 + nas_identifier=ap.example.com + auth_server_addr=127.0.0.1 + auth_server_port=1812 + auth_server_shared_secret=insecure + ''; + in + { + description = "IEEE 802.11 Host Access-Point Daemon"; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = "${pkgs.hostapd}/bin/hostapd ${hostapdConfig}"; + Restart = "always"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + RuntimeDirectory = "hostapd"; + }; + }; +}