1 { config, pkgs, lib, ... }:
3 cfg = config.users.mysql;
6 meta.maintainers = [ lib.maintainers.netali ];
10 enable = lib.mkEnableOption "authentication against a MySQL/MariaDB database";
13 example = "localhost";
14 description = "The hostname of the MySQL/MariaDB server";
16 database = lib.mkOption {
19 description = "The name of the database containing the users";
24 description = "The username to use when connecting to the database";
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";
32 description = "Settings for `pam_mysql`";
33 type = lib.types.submodule {
35 table = lib.mkOption {
38 description = "The name of table that maps unique login names to the passwords.";
40 updateTable = lib.mkOption {
41 type = lib.types.nullOr lib.types.str;
43 example = "users_updates";
45 The name of the table used for password alteration. If not defined, the value
46 of the `table` option will be used instead.
49 userColumn = lib.mkOption {
52 description = "The name of the column that contains a unix login name.";
54 passwordColumn = lib.mkOption {
57 description = "The name of the column that contains a (encrypted) password string.";
59 statusColumn = lib.mkOption {
60 type = lib.types.nullOr lib.types.str;
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
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.
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.
79 passwordCrypt = lib.mkOption {
81 type = lib.types.enum [
94 The method to encrypt the user's password:
97 No encryption. Passwords are stored in plaintext. HIGHLY DISCOURAGED.
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.
109 - `5` (or `"drupal7"`):
110 Use Drupal7 salted passwords.
111 - `6` (or `"joomla15"`):
112 Use Joomla15 salted passwords.
114 Use ssha hashed passwords.
115 - `8` (or `"sha512"`):
116 Use sha512 hashed passwords.
117 - `9` (or `"sha256"`):
118 Use sha256 hashed passwords.
121 cryptDefault = lib.mkOption {
122 type = lib.types.nullOr (lib.types.enum [ "md5" "sha256" "sha512" "blowfish" ]);
124 example = "blowfish";
125 description = "The default encryption method to use for `passwordCrypt = 1`.";
127 where = lib.mkOption {
128 type = lib.types.nullOr lib.types.str;
130 example = "host.name='web' AND user.active=1";
131 description = "Additional criteria for the query.";
133 verbose = lib.mkOption {
134 type = lib.types.bool;
137 If enabled, produces logs with detailed messages that describes what
138 `pam_mysql` is doing. May be useful for debugging.
141 disconnectEveryOperation = lib.mkOption {
142 type = lib.types.bool;
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.
152 enable = lib.mkOption {
153 type = lib.types.bool;
155 description = "Enables logging of authentication attempts in the MySQL database.";
157 table = lib.mkOption {
158 type = lib.types.str;
160 description = "The name of the table to which logs are written.";
162 msgColumn = lib.mkOption {
163 type = lib.types.str;
166 The name of the column in the log table to which the description
167 of the performed operation is stored.
170 userColumn = lib.mkOption {
171 type = lib.types.str;
174 The name of the column in the log table to which the name of the
175 user being authenticated is stored.
178 pidColumn = lib.mkOption {
179 type = lib.types.str;
182 The name of the column in the log table to which the pid of the
183 process utilising the `pam_mysql` authentication
187 hostColumn = lib.mkOption {
188 type = lib.types.str;
191 The name of the column in the log table to which the name of the user
192 being authenticated is stored.
195 rHostColumn = lib.mkOption {
196 type = lib.types.str;
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)`.
204 timeColumn = lib.mkOption {
205 type = lib.types.str;
206 example = "timestamp";
208 The name of the column in the log table to which the timestamp of the
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.
223 type = lib.types.submodule {
225 getpwnam = lib.mkOption {
226 type = lib.types.nullOr lib.types.str;
228 example = lib.literalExpression ''
229 SELECT username,'x',uid,'5000','MySQL User', CONCAT('/home/',username),'/run/sw/current-system/bin/bash' \
231 WHERE username='%1$s' \
235 SQL query for the [getpwnam](https://man7.org/linux/man-pages/man3/getpwnam.3.html)
239 getpwuid = lib.mkOption {
240 type = lib.types.nullOr lib.types.str;
242 example = lib.literalExpression ''
243 SELECT username,'x',uid,'5000','MySQL User', CONCAT('/home/',username),'/run/sw/current-system/bin/bash' \
249 SQL query for the [getpwuid](https://man7.org/linux/man-pages/man3/getpwuid.3.html)
253 getspnam = lib.mkOption {
254 type = lib.types.nullOr lib.types.str;
256 example = lib.literalExpression ''
257 SELECT username,password,'1','0','99999','0','0','-1','0' \
259 WHERE username='%1$s' \
263 SQL query for the [getspnam](https://man7.org/linux/man-pages/man3/getspnam.3.html)
267 getpwent = lib.mkOption {
268 type = lib.types.nullOr lib.types.str;
270 example = lib.literalExpression ''
271 SELECT username,'x',uid,'5000','MySQL User', CONCAT('/home/',username),'/run/sw/current-system/bin/bash' FROM users
274 SQL query for the [getpwent](https://man7.org/linux/man-pages/man3/getpwent.3.html)
278 getspent = lib.mkOption {
279 type = lib.types.nullOr lib.types.str;
281 example = lib.literalExpression ''
282 SELECT username,password,'1','0','99999','0','0','-1','0' FROM users
285 SQL query for the [getspent](https://man7.org/linux/man-pages/man3/getspent.3.html)
289 getgrnam = lib.mkOption {
290 type = lib.types.nullOr lib.types.str;
292 example = lib.literalExpression ''
293 SELECT name,password,gid FROM groups WHERE name='%1$s' LIMIT 1
296 SQL query for the [getgrnam](https://man7.org/linux/man-pages/man3/getgrnam.3.html)
300 getgrgid = lib.mkOption {
301 type = lib.types.nullOr lib.types.str;
303 example = lib.literalExpression ''
304 SELECT name,password,gid FROM groups WHERE gid='%1$u' LIMIT 1
307 SQL query for the [getgrgid](https://man7.org/linux/man-pages/man3/getgrgid.3.html)
311 getgrent = lib.mkOption {
312 type = lib.types.nullOr lib.types.str;
314 example = lib.literalExpression ''
315 SELECT name,password,gid FROM groups
318 SQL query for the [getgrent](https://man7.org/linux/man-pages/man3/getgrent.3.html)
322 memsbygid = lib.mkOption {
323 type = lib.types.nullOr lib.types.str;
325 example = lib.literalExpression ''
326 SELECT username FROM grouplist WHERE gid='%1$u'
329 SQL query for the [memsbygid](https://man7.org/linux/man-pages/man3/memsbygid.3.html)
333 gidsbymem = lib.mkOption {
334 type = lib.types.nullOr lib.types.str;
336 example = lib.literalExpression ''
337 SELECT gid FROM grouplist WHERE username='%1$s'
340 SQL query for the [gidsbymem](https://man7.org/linux/man-pages/man3/gidsbymem.3.html)
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" = {
360 # password will be added from password file in systemd oneshot
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 ''
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}
391 environment.etc."libnss-mysql.cfg" = {
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}
417 database ${cfg.database}
421 environment.etc."libnss-mysql-root.cfg" = {
423 user = config.services.nscd.user;
424 group = config.services.nscd.group;
425 # password will be added from password file in systemd oneshot
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" ];
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
450 if [[ -r ${cfg.passwordFile} ]]; then
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
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