Unverified Commit 7e2992c0 authored by Robin Gloster's avatar Robin Gloster Committed by GitHub
Browse files

Merge pull request #264584 from Ma27/drop-privacyidea

privacyidea: remove
parents 16cfcd2e 5927d556
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -341,6 +341,8 @@

- `service.borgmatic.settings.location` and `services.borgmatic.configurations.<name>.location` are deprecated, please move your options out of sections to the global scope.

- `privacyidea` (and the corresponding `privacyidea-ldap-proxy`) has been removed from nixpkgs because it has severely outdated dependencies that became unmaintainable with nixpkgs' python package-set.

- `dagger` was removed because using a package called `dagger` and packaging it from source violates their trademark policy.

- `win-virtio` package was renamed to `virtio-win` to be consistent with the upstream package name.
+0 −1
Original line number Diff line number Diff line
@@ -1176,7 +1176,6 @@
  ./services/security/opensnitch.nix
  ./services/security/pass-secret-service.nix
  ./services/security/physlock.nix
  ./services/security/privacyidea.nix
  ./services/security/shibboleth-sp.nix
  ./services/security/sks.nix
  ./services/security/sshguard.nix
+0 −458
Original line number Diff line number Diff line
{ config, lib, options, pkgs, ... }:

with lib;

let
  cfg = config.services.privacyidea;
  opt = options.services.privacyidea;

  uwsgi = pkgs.uwsgi.override { plugins = [ "python3" ]; python3 = pkgs.python310; };
  python = uwsgi.python3;
  penv = python.withPackages (const [ pkgs.privacyidea ]);
  logCfg = pkgs.writeText "privacyidea-log.cfg" ''
    [formatters]
    keys=detail

    [handlers]
    keys=stream

    [formatter_detail]
    class=privacyidea.lib.log.SecureFormatter
    format=[%(asctime)s][%(process)d][%(thread)d][%(levelname)s][%(name)s:%(lineno)d] %(message)s

    [handler_stream]
    class=StreamHandler
    level=NOTSET
    formatter=detail
    args=(sys.stdout,)

    [loggers]
    keys=root,privacyidea

    [logger_privacyidea]
    handlers=stream
    qualname=privacyidea
    level=INFO

    [logger_root]
    handlers=stream
    level=ERROR
  '';

  piCfgFile = pkgs.writeText "privacyidea.cfg" ''
    SUPERUSER_REALM = [ '${concatStringsSep "', '" cfg.superuserRealm}' ]
    SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2:///privacyidea'
    SECRET_KEY = '${cfg.secretKey}'
    PI_PEPPER = '${cfg.pepper}'
    PI_ENCFILE = '${cfg.encFile}'
    PI_AUDIT_KEY_PRIVATE = '${cfg.auditKeyPrivate}'
    PI_AUDIT_KEY_PUBLIC = '${cfg.auditKeyPublic}'
    PI_LOGCONFIG = '${logCfg}'
    ${cfg.extraConfig}
  '';

  renderValue = x:
    if isList x then concatMapStringsSep "," (x: ''"${x}"'') x
    else if isString x && hasInfix "," x then ''"${x}"''
    else x;

  ldapProxyConfig = pkgs.writeText "ldap-proxy.ini"
    (generators.toINI {}
      (flip mapAttrs cfg.ldap-proxy.settings
        (const (mapAttrs (const renderValue)))));

  privacyidea-token-janitor = pkgs.writeShellScriptBin "privacyidea-token-janitor" ''
    exec -a privacyidea-token-janitor \
      /run/wrappers/bin/sudo -u ${cfg.user} \
      env PRIVACYIDEA_CONFIGFILE=${cfg.stateDir}/privacyidea.cfg \
      ${penv}/bin/privacyidea-token-janitor $@
  '';
in

{
  options = {
    services.privacyidea = {
      enable = mkEnableOption (lib.mdDoc "PrivacyIDEA");

      environmentFile = mkOption {
        type = types.nullOr types.path;
        default = null;
        example = "/root/privacyidea.env";
        description = lib.mdDoc ''
          File to load as environment file. Environment variables
          from this file will be interpolated into the config file
          using `envsubst` which is helpful for specifying
          secrets:
          ```
          { services.privacyidea.secretKey = "$SECRET"; }
          ```

          The environment-file can now specify the actual secret key:
          ```
          SECRET=veryverytopsecret
          ```
        '';
      };

      stateDir = mkOption {
        type = types.str;
        default = "/var/lib/privacyidea";
        description = lib.mdDoc ''
          Directory where all PrivacyIDEA files will be placed by default.
        '';
      };

      superuserRealm = mkOption {
        type = types.listOf types.str;
        default = [ "super" "administrators" ];
        description = lib.mdDoc ''
          The realm where users are allowed to login as administrators.
        '';
      };

      secretKey = mkOption {
        type = types.str;
        example = "t0p s3cr3t";
        description = lib.mdDoc ''
          This is used to encrypt the auth_token.
        '';
      };

      pepper = mkOption {
        type = types.str;
        example = "Never know...";
        description = lib.mdDoc ''
          This is used to encrypt the admin passwords.
        '';
      };

      encFile = mkOption {
        type = types.str;
        default = "${cfg.stateDir}/enckey";
        defaultText = literalExpression ''"''${config.${opt.stateDir}}/enckey"'';
        description = lib.mdDoc ''
          This is used to encrypt the token data and token passwords
        '';
      };

      auditKeyPrivate = mkOption {
        type = types.str;
        default = "${cfg.stateDir}/private.pem";
        defaultText = literalExpression ''"''${config.${opt.stateDir}}/private.pem"'';
        description = lib.mdDoc ''
          Private Key for signing the audit log.
        '';
      };

      auditKeyPublic = mkOption {
        type = types.str;
        default = "${cfg.stateDir}/public.pem";
        defaultText = literalExpression ''"''${config.${opt.stateDir}}/public.pem"'';
        description = lib.mdDoc ''
          Public key for checking signatures of the audit log.
        '';
      };

      adminPasswordFile = mkOption {
        type = types.path;
        description = lib.mdDoc "File containing password for the admin user";
      };

      adminEmail = mkOption {
        type = types.str;
        example = "admin@example.com";
        description = lib.mdDoc "Mail address for the admin user";
      };

      extraConfig = mkOption {
        type = types.lines;
        default = "";
        description = lib.mdDoc ''
          Extra configuration options for pi.cfg.
        '';
      };

      user = mkOption {
        type = types.str;
        default = "privacyidea";
        description = lib.mdDoc "User account under which PrivacyIDEA runs.";
      };

      group = mkOption {
        type = types.str;
        default = "privacyidea";
        description = lib.mdDoc "Group account under which PrivacyIDEA runs.";
      };

      tokenjanitor = {
        enable = mkEnableOption (lib.mdDoc "automatic runs of the token janitor");
        interval = mkOption {
          default = "quarterly";
          type = types.str;
          description = lib.mdDoc ''
            Interval in which the cleanup program is supposed to run.
            See {manpage}`systemd.time(7)` for further information.
          '';
        };
        action = mkOption {
          type = types.enum [ "delete" "mark" "disable" "unassign" ];
          description = lib.mdDoc ''
            Which action to take for matching tokens.
          '';
        };
        unassigned = mkOption {
          default = false;
          type = types.bool;
          description = lib.mdDoc ''
            Whether to search for **unassigned** tokens
            and apply [](#opt-services.privacyidea.tokenjanitor.action)
            onto them.
          '';
        };
        orphaned = mkOption {
          default = true;
          type = types.bool;
          description = lib.mdDoc ''
            Whether to search for **orphaned** tokens
            and apply [](#opt-services.privacyidea.tokenjanitor.action)
            onto them.
          '';
        };
      };

      ldap-proxy = {
        enable = mkEnableOption (lib.mdDoc "PrivacyIDEA LDAP Proxy");

        configFile = mkOption {
          type = types.nullOr types.path;
          default = null;
          description = lib.mdDoc ''
            Path to PrivacyIDEA LDAP Proxy configuration (proxy.ini).
          '';
        };

        user = mkOption {
          type = types.str;
          default = "pi-ldap-proxy";
          description = lib.mdDoc "User account under which PrivacyIDEA LDAP proxy runs.";
        };

        group = mkOption {
          type = types.str;
          default = "pi-ldap-proxy";
          description = lib.mdDoc "Group account under which PrivacyIDEA LDAP proxy runs.";
        };

        settings = mkOption {
          type = with types; attrsOf (attrsOf (oneOf [ str bool int (listOf str) ]));
          default = {};
          description = lib.mdDoc ''
            Attribute-set containing the settings for `privacyidea-ldap-proxy`.
            It's possible to pass secrets using env-vars as substitutes and
            use the option [](#opt-services.privacyidea.ldap-proxy.environmentFile)
            to inject them via `envsubst`.
          '';
        };

        environmentFile = mkOption {
          default = null;
          type = types.nullOr types.str;
          description = lib.mdDoc ''
            Environment file containing secrets to be substituted into
            [](#opt-services.privacyidea.ldap-proxy.settings).
          '';
        };
      };
    };
  };

  config = mkMerge [

    (mkIf cfg.enable {

      assertions = [
        {
          assertion = cfg.tokenjanitor.enable -> (cfg.tokenjanitor.orphaned || cfg.tokenjanitor.unassigned);
          message = ''
            privacyidea-token-janitor has no effect if neither orphaned nor unassigned tokens
            are to be searched.
          '';
        }
      ];

      environment.systemPackages = [ pkgs.privacyidea (hiPrio privacyidea-token-janitor) ];

      services.postgresql.enable = mkDefault true;

      systemd.services.privacyidea-tokenjanitor = mkIf cfg.tokenjanitor.enable {
        environment.PRIVACYIDEA_CONFIGFILE = "${cfg.stateDir}/privacyidea.cfg";
        path = [ penv ];
        serviceConfig = {
          CapabilityBoundingSet = [ "" ];
          ExecStart = "${pkgs.writeShellScript "pi-token-janitor" ''
            ${optionalString cfg.tokenjanitor.orphaned ''
              echo >&2 "Removing orphaned tokens..."
              privacyidea-token-janitor find \
                --orphaned true \
                --action ${cfg.tokenjanitor.action}
            ''}
            ${optionalString cfg.tokenjanitor.unassigned ''
              echo >&2 "Removing unassigned tokens..."
              privacyidea-token-janitor find \
                --assigned false \
                --action ${cfg.tokenjanitor.action}
            ''}
          ''}";
          Group = cfg.group;
          LockPersonality = true;
          MemoryDenyWriteExecute = true;
          ProtectHome = true;
          ProtectHostname = true;
          ProtectKernelLogs = true;
          ProtectKernelModules = true;
          ProtectKernelTunables = true;
          ProtectSystem = "strict";
          ReadWritePaths = cfg.stateDir;
          Type = "oneshot";
          User = cfg.user;
          WorkingDirectory = cfg.stateDir;
        };
      };
      systemd.timers.privacyidea-tokenjanitor = mkIf cfg.tokenjanitor.enable {
        wantedBy = [ "timers.target" ];
        timerConfig.OnCalendar = cfg.tokenjanitor.interval;
        timerConfig.Persistent = true;
      };

      systemd.services.privacyidea = let
        piuwsgi = pkgs.writeText "uwsgi.json" (builtins.toJSON {
          uwsgi = {
            buffer-size = 8192;
            plugins = [ "python3" ];
            pythonpath = "${penv}/${uwsgi.python3.sitePackages}";
            socket = "/run/privacyidea/socket";
            uid = cfg.user;
            gid = cfg.group;
            chmod-socket = 770;
            chown-socket = "${cfg.user}:nginx";
            chdir = cfg.stateDir;
            wsgi-file = "${penv}/etc/privacyidea/privacyideaapp.wsgi";
            processes = 4;
            harakiri = 60;
            reload-mercy = 8;
            stats = "/run/privacyidea/stats.socket";
            max-requests = 2000;
            limit-as = 1024;
            reload-on-as = 512;
            reload-on-rss = 256;
            no-orphans = true;
            vacuum = true;
          };
        });
      in {
        wantedBy = [ "multi-user.target" ];
        after = [ "postgresql.service" ];
        path = with pkgs; [ openssl ];
        environment.PRIVACYIDEA_CONFIGFILE = "${cfg.stateDir}/privacyidea.cfg";
        preStart = let
          pi-manage = "${config.security.sudo.package}/bin/sudo -u privacyidea -HE ${penv}/bin/pi-manage";
          pgsu = config.services.postgresql.superUser;
          psql = config.services.postgresql.package;
        in ''
          mkdir -p ${cfg.stateDir} /run/privacyidea
          chown ${cfg.user}:${cfg.group} -R ${cfg.stateDir} /run/privacyidea
          umask 077
          ${lib.getBin pkgs.envsubst}/bin/envsubst -o ${cfg.stateDir}/privacyidea.cfg \
                                                   -i "${piCfgFile}"
          chown ${cfg.user}:${cfg.group} ${cfg.stateDir}/privacyidea.cfg
          if ! test -e "${cfg.stateDir}/db-created"; then
            ${config.security.sudo.package}/bin/sudo -u ${pgsu} ${psql}/bin/createuser --no-superuser --no-createdb --no-createrole ${cfg.user}
            ${config.security.sudo.package}/bin/sudo -u ${pgsu} ${psql}/bin/createdb --owner ${cfg.user} privacyidea
            ${pi-manage} create_enckey
            ${pi-manage} create_audit_keys
            ${pi-manage} createdb
            ${pi-manage} admin add admin -e ${cfg.adminEmail} -p "$(cat ${cfg.adminPasswordFile})"
            ${pi-manage} db stamp head -d ${penv}/lib/privacyidea/migrations
            touch "${cfg.stateDir}/db-created"
            chmod g+r "${cfg.stateDir}/enckey" "${cfg.stateDir}/private.pem"
          fi
          ${pi-manage} db upgrade -d ${penv}/lib/privacyidea/migrations
        '';
        serviceConfig = {
          Type = "notify";
          ExecStart = "${uwsgi}/bin/uwsgi --json ${piuwsgi}";
          ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
          EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
          ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
          NotifyAccess = "main";
          KillSignal = "SIGQUIT";
        };
      };

      users.users.privacyidea = mkIf (cfg.user == "privacyidea") {
        group = cfg.group;
        isSystemUser = true;
      };

      users.groups.privacyidea = mkIf (cfg.group == "privacyidea") {};
    })

    (mkIf cfg.ldap-proxy.enable {

      assertions = [
        { assertion = let
            xor = a: b: a && !b || !a && b;
          in xor (cfg.ldap-proxy.settings == {}) (cfg.ldap-proxy.configFile == null);
          message = "configFile & settings are mutually exclusive for services.privacyidea.ldap-proxy!";
        }
      ];

      warnings = mkIf (cfg.ldap-proxy.configFile != null) [
        "Using services.privacyidea.ldap-proxy.configFile is deprecated! Use the RFC42-style settings option instead!"
      ];

      systemd.services.privacyidea-ldap-proxy = let
        ldap-proxy-env = pkgs.python3.withPackages (ps: [ ps.privacyidea-ldap-proxy ]);
      in {
        description = "privacyIDEA LDAP proxy";
        wantedBy = [ "multi-user.target" ];
        serviceConfig = {
          User = cfg.ldap-proxy.user;
          Group = cfg.ldap-proxy.group;
          StateDirectory = "privacyidea-ldap-proxy";
          EnvironmentFile = mkIf (cfg.ldap-proxy.environmentFile != null)
            [ cfg.ldap-proxy.environmentFile ];
          ExecStartPre =
            "${pkgs.writeShellScript "substitute-secrets-ldap-proxy" ''
              umask 0077
              ${pkgs.envsubst}/bin/envsubst \
                -i ${ldapProxyConfig} \
                -o $STATE_DIRECTORY/ldap-proxy.ini
            ''}";
          ExecStart = let
            configPath = if cfg.ldap-proxy.settings != {}
              then "%S/privacyidea-ldap-proxy/ldap-proxy.ini"
              else cfg.ldap-proxy.configFile;
          in ''
            ${ldap-proxy-env}/bin/twistd \
              --nodaemon \
              --pidfile= \
              -u ${cfg.ldap-proxy.user} \
              -g ${cfg.ldap-proxy.group} \
              ldap-proxy \
              -c ${configPath}
          '';
          Restart = "always";
        };
      };

      users.users.pi-ldap-proxy = mkIf (cfg.ldap-proxy.user == "pi-ldap-proxy") {
        group = cfg.ldap-proxy.group;
        isSystemUser = true;
      };

      users.groups.pi-ldap-proxy = mkIf (cfg.ldap-proxy.group == "pi-ldap-proxy") {};
    })
  ];

}
+0 −1
Original line number Diff line number Diff line
@@ -685,7 +685,6 @@ in {
  predictable-interface-names = handleTest ./predictable-interface-names.nix {};
  printing-socket = handleTest ./printing.nix { socket = true; };
  printing-service = handleTest ./printing.nix { socket = false; };
  privacyidea = handleTest ./privacyidea.nix {};
  privoxy = handleTest ./privoxy.nix {};
  prometheus = handleTest ./prometheus.nix {};
  prometheus-exporters = handleTest ./prometheus-exporters.nix {};

nixos/tests/privacyidea.nix

deleted100644 → 0
+0 −43
Original line number Diff line number Diff line
# Miscellaneous small tests that don't warrant their own VM run.

import ./make-test-python.nix ({ pkgs, ...} : rec {
  name = "privacyidea";
  meta = with pkgs.lib.maintainers; {
    maintainers = [ ];
  };

  nodes.machine = { ... }: {
    virtualisation.cores = 2;

    services.privacyidea = {
      enable = true;
      secretKey = "$SECRET_KEY";
      pepper = "$PEPPER";
      adminPasswordFile = pkgs.writeText "admin-password" "testing";
      adminEmail = "root@localhost";

      # Don't try this at home!
      environmentFile = pkgs.writeText "pi-secrets.env" ''
        SECRET_KEY=testing
        PEPPER=testing
      '';
    };
    services.nginx = {
      enable = true;
      virtualHosts."_".locations."/".extraConfig = ''
        uwsgi_pass unix:/run/privacyidea/socket;
      '';
    };
  };

  testScript = ''
    machine.start()
    machine.wait_for_unit("multi-user.target")
    machine.succeed("curl --fail http://localhost | grep privacyIDEA")
    machine.succeed("grep \"SECRET_KEY = 'testing'\" /var/lib/privacyidea/privacyidea.cfg")
    machine.succeed("grep \"PI_PEPPER = 'testing'\" /var/lib/privacyidea/privacyidea.cfg")
    machine.succeed(
        "curl --fail http://localhost/auth -F username=admin -F password=testing | grep token"
    )
  '';
})
Loading