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