{ config, lib, pkgs, ... }: let inherit (lib) mkDefault mkEnableOption mkIf mkOption types; yamlFormat = pkgs.formats.yaml { }; configFile = yamlFormat.generate "proxy_conf.yaml" cfg.proxyConf; cfg = config.services.satosa; mkYamlFiles = files: builtins.attrValues (builtins.mapAttrs (name: yamlFormat.generate "${name}.yaml") files); pyEnv = cfg.package.python.withPackages (ps: [ cfg.package ps.gunicorn ]); in { options.services.satosa = { enable = mkEnableOption "SATOSA, a SAML and OIDC proxy."; package = mkOption { type = types.package; default = import ./package { inherit pkgs; }; }; port = mkOption { type = types.port; default = 8080; }; host = mkOption { type = types.str; }; workers = mkOption { type = types.int; default = 1; }; configureNginx = mkOption { type = types.bool; default = true; }; proxyConf = mkOption { inherit (yamlFormat) type; default = { }; }; envFile = mkOption { type = with types; nullOr path; default = null; }; internalAttributes = mkOption { inherit (yamlFormat) type; default = { }; }; frontendModules = mkOption { type = types.attrsOf yamlFormat.type; default = { }; }; backendModules = mkOption { type = types.attrsOf yamlFormat.type; default = { }; }; microServices = mkOption { type = types.attrsOf yamlFormat.type; default = { }; }; }; config = mkIf cfg.enable { services.satosa.proxyConf = builtins.mapAttrs (_: mkDefault) { BASE = "https://${cfg.host}"; COOKIE_STATE_NAME = "satosa_state"; COOKIE_SECURE = true; COOKIE_HTTPONLY = true; COOKIE_SAMESITE = "None"; COOKIE_MAX_AGE = "1200"; CONTEXT_STATE_DELETE = true; INTERNAL_ATTRIBUTES = yamlFormat.generate "internal_attributes.yaml" { attributes = cfg.internalAttributes; }; BACKEND_MODULES = mkYamlFiles cfg.backendModules; FRONTEND_MODULES = mkYamlFiles cfg.frontendModules; MICRO_SERVICES = mkYamlFiles cfg.microServices; LOGGING = { version = 1; formatters.simple.format = "[%(asctime)s] [%(levelname)s] [%(name)s.%(funcName)s] %(message)s"; handlers.stdout = { class = "logging.StreamHandler"; stream = "ext://sys.stdout"; level = "DEBUG"; formatter = "simple"; }; loggers = { satosa.level = "DEBUG"; saml2.level = "DEBUG"; oidcendpoint.level = "DEBUG"; pyop.level = "DEBUG"; oic.level = "DEBUG"; root = { level = "DEBUG"; handlers = [ "stdout" ]; }; }; }; }; systemd.services = { satosa-metadata = { script = '' umask 077 # Generate a secret key/certificate if none are present mkdir -p ssl if [ ! -f "ssl/.created" ]; then ${pkgs.openssl}/bin/openssl req -x509 \ -newkey rsa:2048 \ -keyout ssl/key.pem \ -out ssl/cert.pem \ -sha256 \ -days 3650 \ -nodes \ -subj "/C=FR/ST=Île de France/L=Paris/O=DGNum/OU=./CN=saml-idp.dgnum.eu" \ && touch ssl/.created fi mkdir -p metadata ${cfg.package}/bin/satosa-saml-metadata \ --dir metadata \ --sign ${configFile} ssl/key.pem ssl/cert.pem ''; serviceConfig = { Type = "oneshot"; User = "satosa"; Group = "satosa"; DynamicUser = true; StateDirectory = "satosa"; WorkingDirectory = "/var/lib/satosa"; EnvironmentFile = lib.optional (cfg.envFile != null) cfg.envFile; }; }; satosa = { wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; wants = [ "satosa-metadata.service" ]; serviceConfig = { User = "satosa"; Group = "satosa"; DynamicUser = true; Type = "notify"; RuntimeDirectory = "satosa"; StateDirectory = "satosa"; WorkingDirectory = cfg.package; ExecStart = '' ${pyEnv}/bin/gunicorn \ -w ${builtins.toString cfg.workers} \ -b 127.0.0.1:${builtins.toString cfg.port} \ --pythonpath ${pyEnv}/${pkgs.python3.sitePackages} \ satosa.wsgi:app ''; ExecReload = "${pkgs.util-linux}/bin/kill -s HUP $MAINPID"; KillMode = "mixed"; TimeoutStopSec = "5"; EnvironmentFile = lib.optional (cfg.envFile != null) cfg.envFile; }; environment = { SATOSA_CONFIG = configFile; }; }; }; services.nginx = mkIf cfg.configureNginx { enable = true; virtualHosts.${cfg.host} = { locations."/".proxyPass = "http://127.0.0.1:${builtins.toString cfg.port}"; }; }; users.users.satosa = { isSystemUser = true; group = "satosa"; home = "/var/lib/satosa"; }; users.groups.satosa = { }; }; }