vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / config / mysql.nix
blob6b82603aa455d27f11f5d464b9af5d11afbb5d90
1 { config, pkgs, lib, ... }:
2 let
3   cfg = config.users.mysql;
4 in
6   meta.maintainers = [ lib.maintainers.netali ];
8   options = {
9     users.mysql = {
10       enable = lib.mkEnableOption "authentication against a MySQL/MariaDB database";
11       host = lib.mkOption {
12         type = lib.types.str;
13         example = "localhost";
14         description = "The hostname of the MySQL/MariaDB server";
15       };
16       database = lib.mkOption {
17         type = lib.types.str;
18         example = "auth";
19         description = "The name of the database containing the users";
20       };
21       user = lib.mkOption {
22         type = lib.types.str;
23         example = "nss-user";
24         description = "The username to use when connecting to the database";
25       };
26       passwordFile = lib.mkOption {
27         type = lib.types.path;
28         example = "/run/secrets/mysql-auth-db-passwd";
29         description = "The path to the file containing the password for the user";
30       };
31       pam = lib.mkOption {
32         description = "Settings for `pam_mysql`";
33         type = lib.types.submodule {
34           options = {
35             table = lib.mkOption {
36               type = lib.types.str;
37               example = "users";
38               description = "The name of table that maps unique login names to the passwords.";
39             };
40             updateTable = lib.mkOption {
41               type = lib.types.nullOr lib.types.str;
42               default = null;
43               example = "users_updates";
44               description = ''
45                 The name of the table used for password alteration. If not defined, the value
46                 of the `table` option will be used instead.
47               '';
48             };
49             userColumn = lib.mkOption {
50               type = lib.types.str;
51               example = "username";
52               description = "The name of the column that contains a unix login name.";
53             };
54             passwordColumn = lib.mkOption {
55               type = lib.types.str;
56               example = "password";
57               description = "The name of the column that contains a (encrypted) password string.";
58             };
59             statusColumn = lib.mkOption {
60               type = lib.types.nullOr lib.types.str;
61               default = null;
62               example = "status";
63               description = ''
64                 The name of the column or an SQL expression that indicates the status of
65                 the user. The status is expressed by the combination of two bitfields
66                 shown below:
68                 - `bit 0 (0x01)`:
69                    if flagged, `pam_mysql` deems the account to be expired and
70                    returns `PAM_ACCT_EXPIRED`. That is, the account is supposed
71                    to no longer be available. Note this doesn't mean that `pam_mysql`
72                    rejects further authentication operations.
73                 -  `bit 1 (0x02)`:
74                    if flagged, `pam_mysql` deems the authentication token
75                    (password) to be expired and returns `PAM_NEW_AUTHTOK_REQD`.
76                    This ends up requiring that the user enter a new password.
77               '';
78             };
79             passwordCrypt = lib.mkOption {
80               example = "2";
81               type = lib.types.enum [
82                 "0" "plain"
83                 "1" "Y"
84                 "2" "mysql"
85                 "3" "md5"
86                 "4" "sha1"
87                 "5" "drupal7"
88                 "6" "joomla15"
89                 "7" "ssha"
90                 "8" "sha512"
91                 "9" "sha256"
92               ];
93               description = ''
94                 The method to encrypt the user's password:
96                 - `0` (or `"plain"`):
97                   No encryption. Passwords are stored in plaintext. HIGHLY DISCOURAGED.
98                 - `1` (or `"Y"`):
99                   Use crypt(3) function.
100                 - `2` (or `"mysql"`):
101                   Use the MySQL PASSWORD() function. It is possible that the encryption function used
102                   by `pam_mysql` is different from that of the MySQL server, as
103                   `pam_mysql` uses the function defined in MySQL's C-client API
104                   instead of using PASSWORD() SQL function in the query.
105                 - `3` (or `"md5"`):
106                   Use plain hex MD5.
107                 - `4` (or `"sha1"`):
108                   Use plain hex SHA1.
109                 - `5` (or `"drupal7"`):
110                   Use Drupal7 salted passwords.
111                 - `6` (or `"joomla15"`):
112                   Use Joomla15 salted passwords.
113                 - `7` (or `"ssha"`):
114                   Use ssha hashed passwords.
115                 - `8` (or `"sha512"`):
116                   Use sha512 hashed passwords.
117                 - `9` (or `"sha256"`):
118                   Use sha256 hashed passwords.
119               '';
120             };
121             cryptDefault = lib.mkOption {
122               type = lib.types.nullOr (lib.types.enum [ "md5" "sha256" "sha512" "blowfish" ]);
123               default = null;
124               example = "blowfish";
125               description = "The default encryption method to use for `passwordCrypt = 1`.";
126             };
127             where = lib.mkOption {
128               type = lib.types.nullOr lib.types.str;
129               default = null;
130               example = "host.name='web' AND user.active=1";
131               description = "Additional criteria for the query.";
132             };
133             verbose = lib.mkOption {
134               type = lib.types.bool;
135               default = false;
136               description = ''
137                 If enabled, produces logs with detailed messages that describes what
138                 `pam_mysql` is doing. May be useful for debugging.
139               '';
140             };
141             disconnectEveryOperation = lib.mkOption {
142               type = lib.types.bool;
143               default = false;
144               description = ''
145                 By default, `pam_mysql` keeps the connection to the MySQL
146                 database until the session is closed. If this option is set to true it
147                 disconnects every time the PAM operation has finished. This option may
148                 be useful in case the session lasts quite long.
149               '';
150             };
151             logging = {
152               enable = lib.mkOption {
153                 type = lib.types.bool;
154                 default = false;
155                 description = "Enables logging of authentication attempts in the MySQL database.";
156               };
157               table = lib.mkOption {
158                 type = lib.types.str;
159                 example = "logs";
160                 description = "The name of the table to which logs are written.";
161               };
162               msgColumn = lib.mkOption {
163                 type = lib.types.str;
164                 example = "msg";
165                 description = ''
166                   The name of the column in the log table to which the description
167                   of the performed operation is stored.
168                 '';
169               };
170               userColumn = lib.mkOption {
171                 type = lib.types.str;
172                 example = "user";
173                 description = ''
174                   The name of the column in the log table to which the name of the
175                   user being authenticated is stored.
176                 '';
177               };
178               pidColumn = lib.mkOption {
179                 type = lib.types.str;
180                 example = "pid";
181                 description = ''
182                   The name of the column in the log table to which the pid of the
183                   process utilising the `pam_mysql` authentication
184                   service is stored.
185                 '';
186               };
187               hostColumn = lib.mkOption {
188                 type = lib.types.str;
189                 example = "host";
190                 description = ''
191                   The name of the column in the log table to which the name of the user
192                   being authenticated is stored.
193                 '';
194               };
195               rHostColumn = lib.mkOption {
196                 type = lib.types.str;
197                 example = "rhost";
198                 description = ''
199                   The name of the column in the log table to which the name of the remote
200                   host that initiates the session is stored. The value is supposed to be
201                   set by the PAM-aware application with `pam_set_item(PAM_RHOST)`.
202                 '';
203               };
204               timeColumn = lib.mkOption {
205                 type = lib.types.str;
206                 example = "timestamp";
207                 description = ''
208                   The name of the column in the log table to which the timestamp of the
209                   log entry is stored.
210                 '';
211               };
212             };
213           };
214         };
215       };
216       nss = lib.mkOption {
217         description = ''
218           Settings for `libnss-mysql`.
220           All examples are from the [minimal example](https://github.com/saknopper/libnss-mysql/tree/master/sample/minimal)
221           of `libnss-mysql`, but they are modified with NixOS paths for bash.
222         '';
223         type = lib.types.submodule {
224           options = {
225             getpwnam = lib.mkOption {
226               type = lib.types.nullOr lib.types.str;
227               default = null;
228               example = lib.literalExpression ''
229                 SELECT username,'x',uid,'5000','MySQL User', CONCAT('/home/',username),'/run/sw/current-system/bin/bash' \
230                 FROM users \
231                 WHERE username='%1$s' \
232                 LIMIT 1
233               '';
234               description = ''
235                 SQL query for the [getpwnam](https://man7.org/linux/man-pages/man3/getpwnam.3.html)
236                 syscall.
237               '';
238             };
239             getpwuid = lib.mkOption {
240               type = lib.types.nullOr lib.types.str;
241               default = null;
242               example = lib.literalExpression ''
243                 SELECT username,'x',uid,'5000','MySQL User', CONCAT('/home/',username),'/run/sw/current-system/bin/bash' \
244                 FROM users \
245                 WHERE uid='%1$u' \
246                 LIMIT 1
247               '';
248               description = ''
249                 SQL query for the [getpwuid](https://man7.org/linux/man-pages/man3/getpwuid.3.html)
250                 syscall.
251               '';
252             };
253             getspnam = lib.mkOption {
254               type = lib.types.nullOr lib.types.str;
255               default = null;
256               example = lib.literalExpression ''
257                 SELECT username,password,'1','0','99999','0','0','-1','0' \
258                 FROM users \
259                 WHERE username='%1$s' \
260                 LIMIT 1
261               '';
262               description = ''
263                 SQL query for the [getspnam](https://man7.org/linux/man-pages/man3/getspnam.3.html)
264                 syscall.
265               '';
266             };
267             getpwent = lib.mkOption {
268               type = lib.types.nullOr lib.types.str;
269               default = null;
270               example = lib.literalExpression ''
271                 SELECT username,'x',uid,'5000','MySQL User', CONCAT('/home/',username),'/run/sw/current-system/bin/bash' FROM users
272               '';
273               description = ''
274                 SQL query for the [getpwent](https://man7.org/linux/man-pages/man3/getpwent.3.html)
275                 syscall.
276               '';
277             };
278             getspent = lib.mkOption {
279               type = lib.types.nullOr lib.types.str;
280               default = null;
281               example = lib.literalExpression ''
282                 SELECT username,password,'1','0','99999','0','0','-1','0' FROM users
283               '';
284               description = ''
285                 SQL query for the [getspent](https://man7.org/linux/man-pages/man3/getspent.3.html)
286                 syscall.
287               '';
288             };
289             getgrnam = lib.mkOption {
290               type = lib.types.nullOr lib.types.str;
291               default = null;
292               example = lib.literalExpression ''
293                 SELECT name,password,gid FROM groups WHERE name='%1$s' LIMIT 1
294               '';
295               description = ''
296                 SQL query for the [getgrnam](https://man7.org/linux/man-pages/man3/getgrnam.3.html)
297                 syscall.
298               '';
299             };
300             getgrgid = lib.mkOption {
301               type = lib.types.nullOr lib.types.str;
302               default = null;
303               example = lib.literalExpression ''
304                 SELECT name,password,gid FROM groups WHERE gid='%1$u' LIMIT 1
305               '';
306               description = ''
307                 SQL query for the [getgrgid](https://man7.org/linux/man-pages/man3/getgrgid.3.html)
308                 syscall.
309               '';
310             };
311             getgrent = lib.mkOption {
312               type = lib.types.nullOr lib.types.str;
313               default = null;
314               example = lib.literalExpression ''
315                 SELECT name,password,gid FROM groups
316               '';
317               description = ''
318                 SQL query for the [getgrent](https://man7.org/linux/man-pages/man3/getgrent.3.html)
319                 syscall.
320               '';
321             };
322             memsbygid = lib.mkOption {
323               type = lib.types.nullOr lib.types.str;
324               default = null;
325               example = lib.literalExpression ''
326                 SELECT username FROM grouplist WHERE gid='%1$u'
327               '';
328               description = ''
329                 SQL query for the [memsbygid](https://man7.org/linux/man-pages/man3/memsbygid.3.html)
330                 syscall.
331               '';
332             };
333             gidsbymem = lib.mkOption {
334               type = lib.types.nullOr lib.types.str;
335               default = null;
336               example = lib.literalExpression ''
337                 SELECT gid FROM grouplist WHERE username='%1$s'
338               '';
339               description = ''
340                 SQL query for the [gidsbymem](https://man7.org/linux/man-pages/man3/gidsbymem.3.html)
341                 syscall.
342               '';
343             };
344           };
345         };
346       };
347     };
348   };
350   config = lib.mkIf cfg.enable {
351     system.nssModules = [ pkgs.libnss-mysql ];
352     system.nssDatabases.shadow = [ "mysql" ];
353     system.nssDatabases.group = [ "mysql" ];
354     system.nssDatabases.passwd = [ "mysql" ];
356     environment.etc."security/pam_mysql.conf" = {
357       user = "root";
358       group = "root";
359       mode = "0600";
360       # password will be added from password file in systemd oneshot
361       text = ''
362         users.host=${cfg.host}
363         users.db_user=${cfg.user}
364         users.database=${cfg.database}
365         users.table=${cfg.pam.table}
366         users.user_column=${cfg.pam.userColumn}
367         users.password_column=${cfg.pam.passwordColumn}
368         users.password_crypt=${cfg.pam.passwordCrypt}
369         users.disconnect_every_operation=${if cfg.pam.disconnectEveryOperation then "1" else "0"}
370         verbose=${if cfg.pam.verbose then "1" else "0"}
371       '' + lib.optionalString (cfg.pam.cryptDefault != null) ''
372         users.use_${cfg.pam.cryptDefault}=1
373       '' + lib.optionalString (cfg.pam.where != null) ''
374         users.where_clause=${cfg.pam.where}
375       '' + lib.optionalString (cfg.pam.statusColumn != null) ''
376         users.status_column=${cfg.pam.statusColumn}
377       '' + lib.optionalString (cfg.pam.updateTable != null) ''
378         users.update_table=${cfg.pam.updateTable}
379       '' + lib.optionalString cfg.pam.logging.enable ''
380         log.enabled=true
381         log.table=${cfg.pam.logging.table}
382         log.message_column=${cfg.pam.logging.msgColumn}
383         log.pid_column=${cfg.pam.logging.pidColumn}
384         log.user_column=${cfg.pam.logging.userColumn}
385         log.host_column=${cfg.pam.logging.hostColumn}
386         log.rhost_column=${cfg.pam.logging.rHostColumn}
387         log.time_column=${cfg.pam.logging.timeColumn}
388       '';
389     };
391     environment.etc."libnss-mysql.cfg" = {
392       mode = "0600";
393       user = config.services.nscd.user;
394       group = config.services.nscd.group;
395       text = lib.optionalString (cfg.nss.getpwnam != null) ''
396         getpwnam ${cfg.nss.getpwnam}
397       '' + lib.optionalString (cfg.nss.getpwuid != null) ''
398         getpwuid ${cfg.nss.getpwuid}
399       '' + lib.optionalString (cfg.nss.getspnam != null) ''
400         getspnam ${cfg.nss.getspnam}
401       '' + lib.optionalString (cfg.nss.getpwent != null) ''
402         getpwent ${cfg.nss.getpwent}
403       '' + lib.optionalString (cfg.nss.getspent != null) ''
404         getspent ${cfg.nss.getspent}
405       '' + lib.optionalString (cfg.nss.getgrnam != null) ''
406         getgrnam ${cfg.nss.getgrnam}
407       '' + lib.optionalString (cfg.nss.getgrgid != null) ''
408         getgrgid ${cfg.nss.getgrgid}
409       '' + lib.optionalString (cfg.nss.getgrent != null) ''
410         getgrent ${cfg.nss.getgrent}
411       '' + lib.optionalString (cfg.nss.memsbygid != null) ''
412         memsbygid ${cfg.nss.memsbygid}
413       '' + lib.optionalString (cfg.nss.gidsbymem != null) ''
414         gidsbymem ${cfg.nss.gidsbymem}
415       '' + ''
416         host ${cfg.host}
417         database ${cfg.database}
418       '';
419     };
421     environment.etc."libnss-mysql-root.cfg" = {
422       mode = "0600";
423       user = config.services.nscd.user;
424       group = config.services.nscd.group;
425       # password will be added from password file in systemd oneshot
426       text = ''
427         username ${cfg.user}
428       '';
429     };
431     systemd.services.mysql-auth-pw-init = {
432       description = "Adds the mysql password to the mysql auth config files";
434       before = [ "nscd.service" ];
435       wantedBy = [ "multi-user.target" ];
437       serviceConfig = {
438         Type = "oneshot";
439         User = "root";
440         Group = "root";
441       };
443       restartTriggers = [
444         config.environment.etc."security/pam_mysql.conf".source
445         config.environment.etc."libnss-mysql.cfg".source
446         config.environment.etc."libnss-mysql-root.cfg".source
447       ];
449       script = ''
450         if [[ -r ${cfg.passwordFile} ]]; then
451           umask 0077
452           conf_nss="$(mktemp)"
453           cp /etc/libnss-mysql-root.cfg $conf_nss
454           printf 'password %s\n' "$(cat ${cfg.passwordFile})" >> $conf_nss
455           mv -fT "$conf_nss" /etc/libnss-mysql-root.cfg
456           chown ${config.services.nscd.user}:${config.services.nscd.group} /etc/libnss-mysql-root.cfg
458           conf_pam="$(mktemp)"
459           cp /etc/security/pam_mysql.conf $conf_pam
460           printf 'users.db_passwd=%s\n' "$(cat ${cfg.passwordFile})" >> $conf_pam
461           mv -fT "$conf_pam" /etc/security/pam_mysql.conf
462         fi
463       '';
464     };
465   };