nixos/tests/networking: add eap tests (#484773)

This commit is contained in:
Michele Guerini Rocco
2026-02-06 21:04:46 +00:00
committed by GitHub
2 changed files with 265 additions and 1 deletions
+119 -1
View File
@@ -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 (
+146
View File
@@ -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";
};
};
}