3
0
Fork 0
forked from mirrors/nixpkgs
nixpkgs/nixos/modules/services/security/privacyidea.nix
pennae 2e751c0772 treewide: automatically md-convert option descriptions
the conversion procedure is simple:

 - find all things that look like options, ie calls to either `mkOption`
   or `lib.mkOption` that take an attrset. remember the attrset as the
   option
 - for all options, find a `description` attribute who's value is not a
   call to `mdDoc` or `lib.mdDoc`
 - textually convert the entire value of the attribute to MD with a few
   simple regexes (the set from mdize-module.sh)
 - if the change produced a change in the manual output, discard
 - if the change kept the manual unchanged, add some text to the
   description to make sure we've actually found an option. if the
   manual changes this time, keep the converted description

this procedure converts 80% of nixos options to markdown. around 2000
options remain to be inspected, but most of those fail the "does not
change the manual output check": currently the MD conversion process
does not faithfully convert docbook tags like <code> and <package>, so
any option using such tags will not be converted at all.
2022-07-30 15:16:34 +02:00

367 lines
12 KiB
Nix

{ config, lib, options, pkgs, ... }:
with lib;
let
cfg = config.services.privacyidea;
opt = options.services.privacyidea;
uwsgi = pkgs.uwsgi.override { plugins = [ "python3" ]; python3 = pkgs.python39; };
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:///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)))));
in
{
options = {
services.privacyidea = {
enable = mkEnableOption "PrivacyIDEA";
environmentFile = mkOption {
type = types.nullOr types.path;
default = null;
example = "/root/privacyidea.env";
description = ''
File to load as environment file. Environment variables
from this file will be interpolated into the config file
using <package>envsubst</package> which is helpful for specifying
secrets:
<programlisting>
{ <xref linkend="opt-services.privacyidea.secretKey" /> = "$SECRET"; }
</programlisting>
The environment-file can now specify the actual secret key:
<programlisting>
SECRET=veryverytopsecret
</programlisting>
'';
};
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.";
};
ldap-proxy = {
enable = mkEnableOption "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 = ''
Attribute-set containing the settings for <package>privacyidea-ldap-proxy</package>.
It's possible to pass secrets using env-vars as substitutes and
use the option <xref linkend="opt-services.privacyidea.ldap-proxy.environmentFile" />
to inject them via <package>envsubst</package>.
'';
};
environmentFile = mkOption {
default = null;
type = types.nullOr types.str;
description = ''
Environment file containing secrets to be substituted into
<xref linkend="opt-services.privacyidea.ldap-proxy.settings" />.
'';
};
};
};
};
config = mkMerge [
(mkIf cfg.enable {
environment.systemPackages = [ pkgs.privacyidea ];
services.postgresql.enable = mkDefault 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") {};
})
];
}