{ config, lib, pkgs, ... }: let inherit (lib) mkEnableOption mkIf mkOption types mapAttrsToList optionalString zipListsWith ; settingsFormat = pkgs.formats.toml { }; pykanidm = pkgs.python3.pkgs.callPackage ./packages/pykanidm.nix { }; rlm_python = pkgs.callPackage ./packages/rlm_python.nix { inherit pykanidm; }; cfg = config.services.k-radius; in { options.services.k-radius = { enable = mkEnableOption "a freeradius service linked to kanidm."; settings = mkOption { inherit (settingsFormat) type; }; freeradius = mkOption { type = types.package; default = pkgs.freeradius.overrideAttrs (old: { buildInputs = (old.buildInputs or [ ]) ++ [ (pkgs.python3.withPackages (ps: [ ps.kanidm ])) ]; }); }; configDir = mkOption { type = types.path; default = "/var/lib/radius/raddb"; description = "The path of the freeradius server configuration directory."; }; authTokenFile = mkOption { type = types.path; description = "File to the auth token for the service account."; }; extra-mods = mkOption { type = types.attrsOf types.path; description = "Additional files to be linked in mods-enabled."; default = { }; }; extra-sites = mkOption { type = types.attrsOf types.path; description = "Additional files to be linked in sites-enabled."; default = { }; }; dictionary = mkOption { type = types.attrsOf ( types.enum [ "abinary" "date" "ipaddr" "integer" "string" ] ); description = "Declare additionnal attributes to be listed in the dictionary."; default = { }; }; radiusClients = mkOption { type = types.attrsOf ( types.submodule { options = { secret = mkOption { type = types.path; }; ipaddr = mkOption { type = types.str; }; }; } ); default = { }; description = "A mapping of clients and their authentication tokens."; }; certs = { ca = mkOption { type = types.str; description = "The signing CA of the RADIUS certificate."; }; dh = mkOption { type = types.str; description = "The output of `openssl dhparam -in ca.pem -out dh.pem 2048`."; }; cert = mkOption { type = types.str; description = "The certificate for the RADIUS server."; }; key = mkOption { type = types.str; description = "The signing key for the RADIUS certificate."; }; }; privateKeyPasswordFile = mkOption { type = types.path; }; checkConfiguration = mkOption { type = types.bool; description = "Check the configuration before starting the deamon. Usefull for debugging."; default = false; }; }; config = mkIf cfg.enable { users = { users.radius = { group = "radius"; description = "Radius daemon user"; isSystemUser = true; }; groups.radius = { }; }; services.k-radius.settings = { ca_path = cfg.certs.ca; radius_cert_path = cfg.certs.cert; radius_key_path = cfg.certs.key; radius_dh_path = cfg.certs.dh; radius_ca_path = cfg.certs.ca; }; systemd.services.radius = { description = "FreeRadius server"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; wants = [ "network.target" ]; startLimitIntervalSec = 20; startLimitBurst = 5; preStart = '' mkdir -p ${cfg.configDir} cp -R --no-preserve=mode ${cfg.freeradius}/etc/raddb/* ${cfg.configDir} cp -R --no-preserve=mode ${rlm_python}/etc/raddb/* ${cfg.configDir} chmod -R u+w ${cfg.configDir} # disable auth via methods kanidm doesn't support rm ${cfg.configDir}/mods-available/sql rm ${cfg.configDir}/mods-enabled/{passwd,totp} # enable the python and cache modules ln -nsf ${cfg.configDir}/mods-available/python3 ${cfg.configDir}/mods-enabled/python3 ln -nsf ${cfg.configDir}/sites-available/check-eap-tls ${cfg.configDir}/sites-enabled/check-eap-tls # write the clients configuration rm ${cfg.configDir}/clients.conf && touch ${cfg.configDir}/clients.conf ${builtins.concatStringsSep "\n" ( builtins.attrValues ( builtins.mapAttrs ( name: { secret, ipaddr }: '' cat <> ${cfg.configDir}/clients.conf client ${name} { ipaddr = ${ipaddr} secret = $(cat "${secret}") proto = * } EOF '' ) cfg.radiusClients ) )} # Copy the kanidm configuration cat < /var/lib/radius/kanidm.toml auth_token = "$(cat "${cfg.authTokenFile}")" EOF cat ${settingsFormat.generate "kanidm.toml" cfg.settings} >> /var/lib/radius/kanidm.toml chmod u+w /var/lib/radius/kanidm.toml # Copy the certificates to the correct directory rm -rf ${cfg.configDir}/certs && mkdir -p ${cfg.configDir}/certs cp ${cfg.certs.ca} ${cfg.configDir}/certs/ca.pem ${pkgs.openssl}/bin/openssl rehash ${cfg.configDir}/certs cp ${cfg.certs.dh} ${cfg.configDir}/certs/dh.pem cat ${cfg.certs.cert} ${cfg.certs.key} > ${cfg.configDir}/certs/server.pem # Write the password of the private_key in the eap module sed -i ${cfg.configDir}/mods-available/eap \ -e "s/whatever/$(cat "${cfg.privateKeyPasswordFile}")/" # Build the dictionary cat < ${cfg.configDir}/dictionary ${ let attrs = mapAttrsToList (name: type: { inherit name type; }) cfg.dictionary; idList = builtins.genList (id: 3000 + id) (builtins.length attrs); in builtins.concatStringsSep "\n" ( zipListsWith ({ name, type }: id: "ATTRIBUTE ${name} ${toString id} ${type}") attrs idList ) } EOF # Link extra-mods ${builtins.concatStringsSep "\n" ( mapAttrsToList (name: path: "ln -nsf ${path} ${cfg.configDir}/mods-enabled/${name}") cfg.extra-mods )} # Link extra-sites ${builtins.concatStringsSep "\n" ( mapAttrsToList ( name: path: "ln -nsf ${path} ${cfg.configDir}/sites-enabled/${name}" ) cfg.extra-sites )} # Check the configuration ${ optionalString (!cfg.checkConfiguration) "# " }${pkgs.freeradius}/bin/radiusd -C -d ${cfg.configDir} -l stdout ''; path = [ pkgs.openssl pkgs.gnused ]; serviceConfig = { ExecStart = "${cfg.freeradius}/bin/radiusd -X -f -d ${cfg.configDir} -l stdout"; ExecReload = [ "${cfg.freeradius}/bin/radiusd -C -d ${cfg.configDir} -l stdout" "${pkgs.coreutils}/bin/kill -HUP $MAINPID" ]; User = "radius"; Group = "radius"; DynamicUser = true; Restart = "on-failure"; RestartSec = 2; LogsDirectory = "radius"; StateDirectory = "radius"; RuntimeDirectory = "radius"; AmbientCapabilities = "CAP_NET_BIND_SERVICE"; Environment = [ "KANIDM_RLM_CONFIG=/var/lib/radius/kanidm.toml" "PYTHONPATH=${rlm_python.pythonPath}" ]; }; }; }; }