1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.privacyidea;
7
8 uwsgi = pkgs.uwsgi.override { plugins = [ "python3" ]; };
9 python = uwsgi.python3;
10 penv = python.withPackages (ps: [ ps.privacyidea ]);
11 logCfg = pkgs.writeText "privacyidea-log.cfg" ''
12 [formatters]
13 keys=detail
14
15 [handlers]
16 keys=stream
17
18 [formatter_detail]
19 class=privacyidea.lib.log.SecureFormatter
20 format=[%(asctime)s][%(process)d][%(thread)d][%(levelname)s][%(name)s:%(lineno)d] %(message)s
21
22 [handler_stream]
23 class=StreamHandler
24 level=NOTSET
25 formatter=detail
26 args=(sys.stdout,)
27
28 [loggers]
29 keys=root,privacyidea
30
31 [logger_privacyidea]
32 handlers=stream
33 qualname=privacyidea
34 level=INFO
35
36 [logger_root]
37 handlers=stream
38 level=ERROR
39 '';
40
41 piCfgFile = pkgs.writeText "privacyidea.cfg" ''
42 SUPERUSER_REALM = [ '${concatStringsSep "', '" cfg.superuserRealm}' ]
43 SQLALCHEMY_DATABASE_URI = 'postgresql:///privacyidea'
44 SECRET_KEY = '${cfg.secretKey}'
45 PI_PEPPER = '${cfg.pepper}'
46 PI_ENCFILE = '${cfg.encFile}'
47 PI_AUDIT_KEY_PRIVATE = '${cfg.auditKeyPrivate}'
48 PI_AUDIT_KEY_PUBLIC = '${cfg.auditKeyPublic}'
49 PI_LOGCONFIG = '${logCfg}'
50 ${cfg.extraConfig}
51 '';
52
53in
54
55{
56 options = {
57 services.privacyidea = {
58 enable = mkEnableOption "PrivacyIDEA";
59
60 environmentFile = mkOption {
61 type = types.nullOr types.path;
62 default = null;
63 example = "/root/privacyidea.env";
64 description = ''
65 File to load as environment file. Environment variables
66 from this file will be interpolated into the config file
67 using <package>envsubst</package> which is helpful for specifying
68 secrets:
69 <programlisting>
70 { <xref linkend="opt-services.privacyidea.secretKey" /> = "$SECRET"; }
71 </programlisting>
72
73 The environment-file can now specify the actual secret key:
74 <programlisting>
75 SECRET=veryverytopsecret
76 </programlisting>
77 '';
78 };
79
80 stateDir = mkOption {
81 type = types.str;
82 default = "/var/lib/privacyidea";
83 description = ''
84 Directory where all PrivacyIDEA files will be placed by default.
85 '';
86 };
87
88 superuserRealm = mkOption {
89 type = types.listOf types.str;
90 default = [ "super" "administrators" ];
91 description = ''
92 The realm where users are allowed to login as administrators.
93 '';
94 };
95
96 secretKey = mkOption {
97 type = types.str;
98 example = "t0p s3cr3t";
99 description = ''
100 This is used to encrypt the auth_token.
101 '';
102 };
103
104 pepper = mkOption {
105 type = types.str;
106 example = "Never know...";
107 description = ''
108 This is used to encrypt the admin passwords.
109 '';
110 };
111
112 encFile = mkOption {
113 type = types.str;
114 default = "${cfg.stateDir}/enckey";
115 description = ''
116 This is used to encrypt the token data and token passwords
117 '';
118 };
119
120 auditKeyPrivate = mkOption {
121 type = types.str;
122 default = "${cfg.stateDir}/private.pem";
123 description = ''
124 Private Key for signing the audit log.
125 '';
126 };
127
128 auditKeyPublic = mkOption {
129 type = types.str;
130 default = "${cfg.stateDir}/public.pem";
131 description = ''
132 Public key for checking signatures of the audit log.
133 '';
134 };
135
136 adminPasswordFile = mkOption {
137 type = types.path;
138 description = "File containing password for the admin user";
139 };
140
141 adminEmail = mkOption {
142 type = types.str;
143 example = "admin@example.com";
144 description = "Mail address for the admin user";
145 };
146
147 extraConfig = mkOption {
148 type = types.lines;
149 default = "";
150 description = ''
151 Extra configuration options for pi.cfg.
152 '';
153 };
154
155 user = mkOption {
156 type = types.str;
157 default = "privacyidea";
158 description = "User account under which PrivacyIDEA runs.";
159 };
160
161 group = mkOption {
162 type = types.str;
163 default = "privacyidea";
164 description = "Group account under which PrivacyIDEA runs.";
165 };
166
167 ldap-proxy = {
168 enable = mkEnableOption "PrivacyIDEA LDAP Proxy";
169
170 configFile = mkOption {
171 type = types.path;
172 default = "";
173 description = ''
174 Path to PrivacyIDEA LDAP Proxy configuration (proxy.ini).
175 '';
176 };
177
178 user = mkOption {
179 type = types.str;
180 default = "pi-ldap-proxy";
181 description = "User account under which PrivacyIDEA LDAP proxy runs.";
182 };
183
184 group = mkOption {
185 type = types.str;
186 default = "pi-ldap-proxy";
187 description = "Group account under which PrivacyIDEA LDAP proxy runs.";
188 };
189 };
190 };
191 };
192
193 config = mkMerge [
194
195 (mkIf cfg.enable {
196
197 environment.systemPackages = [ python.pkgs.privacyidea ];
198
199 services.postgresql.enable = mkDefault true;
200
201 systemd.services.privacyidea = let
202 piuwsgi = pkgs.writeText "uwsgi.json" (builtins.toJSON {
203 uwsgi = {
204 plugins = [ "python3" ];
205 pythonpath = "${penv}/${uwsgi.python3.sitePackages}";
206 socket = "/run/privacyidea/socket";
207 uid = cfg.user;
208 gid = cfg.group;
209 chmod-socket = 770;
210 chown-socket = "${cfg.user}:nginx";
211 chdir = cfg.stateDir;
212 wsgi-file = "${penv}/etc/privacyidea/privacyideaapp.wsgi";
213 processes = 4;
214 harakiri = 60;
215 reload-mercy = 8;
216 stats = "/run/privacyidea/stats.socket";
217 max-requests = 2000;
218 limit-as = 1024;
219 reload-on-as = 512;
220 reload-on-rss = 256;
221 no-orphans = true;
222 vacuum = true;
223 };
224 });
225 in {
226 wantedBy = [ "multi-user.target" ];
227 after = [ "postgresql.service" ];
228 path = with pkgs; [ openssl ];
229 environment.PRIVACYIDEA_CONFIGFILE = "${cfg.stateDir}/privacyidea.cfg";
230 preStart = let
231 pi-manage = "${pkgs.sudo}/bin/sudo -u privacyidea -HE ${penv}/bin/pi-manage";
232 pgsu = config.services.postgresql.superUser;
233 psql = config.services.postgresql.package;
234 in ''
235 mkdir -p ${cfg.stateDir} /run/privacyidea
236 chown ${cfg.user}:${cfg.group} -R ${cfg.stateDir} /run/privacyidea
237 umask 077
238 ${lib.getBin pkgs.envsubst}/bin/envsubst -o ${cfg.stateDir}/privacyidea.cfg \
239 -i "${piCfgFile}"
240 chown ${cfg.user}:${cfg.group} ${cfg.stateDir}/privacyidea.cfg
241 if ! test -e "${cfg.stateDir}/db-created"; then
242 ${pkgs.sudo}/bin/sudo -u ${pgsu} ${psql}/bin/createuser --no-superuser --no-createdb --no-createrole ${cfg.user}
243 ${pkgs.sudo}/bin/sudo -u ${pgsu} ${psql}/bin/createdb --owner ${cfg.user} privacyidea
244 ${pi-manage} create_enckey
245 ${pi-manage} create_audit_keys
246 ${pi-manage} createdb
247 ${pi-manage} admin add admin -e ${cfg.adminEmail} -p "$(cat ${cfg.adminPasswordFile})"
248 ${pi-manage} db stamp head -d ${penv}/lib/privacyidea/migrations
249 touch "${cfg.stateDir}/db-created"
250 chmod g+r "${cfg.stateDir}/enckey" "${cfg.stateDir}/private.pem"
251 fi
252 ${pi-manage} db upgrade -d ${penv}/lib/privacyidea/migrations
253 '';
254 serviceConfig = {
255 Type = "notify";
256 ExecStart = "${uwsgi}/bin/uwsgi --json ${piuwsgi}";
257 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
258 EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
259 ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
260 NotifyAccess = "main";
261 KillSignal = "SIGQUIT";
262 };
263 };
264
265 users.users.privacyidea = mkIf (cfg.user == "privacyidea") {
266 group = cfg.group;
267 isSystemUser = true;
268 };
269
270 users.groups.privacyidea = mkIf (cfg.group == "privacyidea") {};
271 })
272
273 (mkIf cfg.ldap-proxy.enable {
274
275 systemd.services.privacyidea-ldap-proxy = let
276 ldap-proxy-env = pkgs.python2.withPackages (ps: [ ps.privacyidea-ldap-proxy ]);
277 in {
278 description = "privacyIDEA LDAP proxy";
279 wantedBy = [ "multi-user.target" ];
280 serviceConfig = {
281 User = cfg.ldap-proxy.user;
282 Group = cfg.ldap-proxy.group;
283 ExecStart = ''
284 ${ldap-proxy-env}/bin/twistd \
285 --nodaemon \
286 --pidfile= \
287 -u ${cfg.ldap-proxy.user} \
288 -g ${cfg.ldap-proxy.group} \
289 ldap-proxy \
290 -c ${cfg.ldap-proxy.configFile}
291 '';
292 Restart = "always";
293 };
294 };
295
296 users.users.pi-ldap-proxy = mkIf (cfg.ldap-proxy.user == "pi-ldap-proxy") {
297 group = cfg.ldap-proxy.group;
298 isSystemUser = true;
299 };
300
301 users.groups.pi-ldap-proxy = mkIf (cfg.ldap-proxy.group == "pi-ldap-proxy") {};
302 })
303 ];
304
305}