forked from mirrors/nixpkgs
Merge pull request #183717 from NetaliDev/mysql-auth
nixos: add mysql/mariadb user authentication module
This commit is contained in:
commit
9e8ea1b855
519
nixos/modules/config/mysql.nix
Normal file
519
nixos/modules/config/mysql.nix
Normal file
|
@ -0,0 +1,519 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.users.mysql;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
users.mysql = {
|
||||
enable = mkEnableOption "Authentication against a MySQL/MariaDB database";
|
||||
host = mkOption {
|
||||
type = types.str;
|
||||
example = "localhost";
|
||||
description = "The hostname of the MySQL/MariaDB server";
|
||||
};
|
||||
database = mkOption {
|
||||
type = types.str;
|
||||
example = "auth";
|
||||
description = "The name of the database containing the users";
|
||||
};
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
example = "nss-user";
|
||||
description = "The username to use when connecting to the database";
|
||||
};
|
||||
passwordFile = mkOption {
|
||||
type = types.path;
|
||||
example = "/run/secrets/mysql-auth-db-passwd";
|
||||
description = "The path to the file containing the password for the user";
|
||||
};
|
||||
pam = mkOption {
|
||||
description = "Settings for <literal>pam_mysql</literal>";
|
||||
type = types.submodule {
|
||||
options = {
|
||||
table = mkOption {
|
||||
type = types.str;
|
||||
example = "users";
|
||||
description = "The name of table that maps unique login names to the passwords.";
|
||||
};
|
||||
updateTable = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "users_updates";
|
||||
description = ''
|
||||
The name of the table used for password alteration. If not defined, the value
|
||||
of the <literal>table</literal> option will be used instead.
|
||||
'';
|
||||
};
|
||||
userColumn = mkOption {
|
||||
type = types.str;
|
||||
example = "username";
|
||||
description = "The name of the column that contains a unix login name.";
|
||||
};
|
||||
passwordColumn = mkOption {
|
||||
type = types.str;
|
||||
example = "password";
|
||||
description = "The name of the column that contains a (encrypted) password string.";
|
||||
};
|
||||
statusColumn = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "status";
|
||||
description = ''
|
||||
The name of the column or an SQL expression that indicates the status of
|
||||
the user. The status is expressed by the combination of two bitfields
|
||||
shown below:
|
||||
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>bit 0 (0x01)</literal>:
|
||||
if flagged, <literal>pam_mysql</literal> deems the account to be expired and
|
||||
returns <literal>PAM_ACCT_EXPIRED</literal>. That is, the account is supposed
|
||||
to no longer be available. Note this doesn't mean that <literal>pam_mysql</literal>
|
||||
rejects further authentication operations.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>bit 1 (0x02)</literal>:
|
||||
if flagged, <literal>pam_mysql</literal> deems the authentication token
|
||||
(password) to be expired and returns <literal>PAM_NEW_AUTHTOK_REQD</literal>.
|
||||
This ends up requiring that the user enter a new password.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
'';
|
||||
};
|
||||
passwordCrypt = mkOption {
|
||||
example = "2";
|
||||
type = types.enum [
|
||||
"0" "plain"
|
||||
"1" "Y"
|
||||
"2" "mysql"
|
||||
"3" "md5"
|
||||
"4" "sha1"
|
||||
"5" "drupal7"
|
||||
"6" "joomla15"
|
||||
"7" "ssha"
|
||||
"8" "sha512"
|
||||
"9" "sha256"
|
||||
];
|
||||
description = ''
|
||||
The method to encrypt the user's password:
|
||||
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>0</literal> (or <literal>"plain"</literal>):
|
||||
No encryption. Passwords are stored in plaintext. HIGHLY DISCOURAGED.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>1</literal> (or <literal>"Y"</literal>):
|
||||
Use crypt(3) function.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>2</literal> (or <literal>"mysql"</literal>):
|
||||
Use the MySQL PASSWORD() function. It is possible that the encryption function used
|
||||
by <literal>pam_mysql</literal> is different from that of the MySQL server, as
|
||||
<literal>pam_mysql</literal> uses the function defined in MySQL's C-client API
|
||||
instead of using PASSWORD() SQL function in the query.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>3</literal> (or <literal>"md5"</literal>):
|
||||
Use plain hex MD5.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>4</literal> (or <literal>"sha1"</literal>):
|
||||
Use plain hex SHA1.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>5</literal> (or <literal>"drupal7"</literal>):
|
||||
Use Drupal7 salted passwords.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>6</literal> (or <literal>"joomla15"</literal>):
|
||||
Use Joomla15 salted passwords.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>7</literal> (or <literal>"ssha"</literal>):
|
||||
Use ssha hashed passwords.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>8</literal> (or <literal>"sha512"</literal>):
|
||||
Use sha512 hashed passwords.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>9</literal> (or <literal>"sha256"</literal>):
|
||||
Use sha256 hashed passwords.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
'';
|
||||
};
|
||||
cryptDefault = mkOption {
|
||||
type = types.nullOr (types.enum [ "md5" "sha256" "sha512" "blowfish" ]);
|
||||
default = null;
|
||||
example = "blowfish";
|
||||
description = "The default encryption method to use for <literal>passwordCrypt = 1</literal>.";
|
||||
};
|
||||
where = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "host.name='web' AND user.active=1";
|
||||
description = "Additional criteria for the query.";
|
||||
};
|
||||
verbose = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
If enabled, produces logs with detailed messages that describes what
|
||||
<literal>pam_mysql</literal> is doing. May be useful for debugging.
|
||||
'';
|
||||
};
|
||||
disconnectEveryOperation = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
By default, <literal>pam_mysql</literal> keeps the connection to the MySQL
|
||||
database until the session is closed. If this option is set to true it
|
||||
disconnects every time the PAM operation has finished. This option may
|
||||
be useful in case the session lasts quite long.
|
||||
'';
|
||||
};
|
||||
logging = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enables logging of authentication attempts in the MySQL database.";
|
||||
};
|
||||
table = mkOption {
|
||||
type = types.str;
|
||||
example = "logs";
|
||||
description = "The name of the table to which logs are written.";
|
||||
};
|
||||
msgColumn = mkOption {
|
||||
type = types.str;
|
||||
example = "msg";
|
||||
description = ''
|
||||
The name of the column in the log table to which the description
|
||||
of the performed operation is stored.
|
||||
'';
|
||||
};
|
||||
userColumn = mkOption {
|
||||
type = types.str;
|
||||
example = "user";
|
||||
description = ''
|
||||
The name of the column in the log table to which the name of the
|
||||
user being authenticated is stored.
|
||||
'';
|
||||
};
|
||||
pidColumn = mkOption {
|
||||
type = types.str;
|
||||
example = "pid";
|
||||
description = ''
|
||||
The name of the column in the log table to which the pid of the
|
||||
process utilising the <literal>pam_mysql's</literal> authentication
|
||||
service is stored.
|
||||
'';
|
||||
};
|
||||
hostColumn = mkOption {
|
||||
type = types.str;
|
||||
example = "host";
|
||||
description = ''
|
||||
The name of the column in the log table to which the name of the user
|
||||
being authenticated is stored.
|
||||
'';
|
||||
};
|
||||
rHostColumn = mkOption {
|
||||
type = types.str;
|
||||
example = "rhost";
|
||||
description = ''
|
||||
The name of the column in the log table to which the name of the remote
|
||||
host that initiates the session is stored. The value is supposed to be
|
||||
set by the PAM-aware application with <literal>pam_set_item(PAM_RHOST)
|
||||
</literal>.
|
||||
'';
|
||||
};
|
||||
timeColumn = mkOption {
|
||||
type = types.str;
|
||||
example = "timestamp";
|
||||
description = ''
|
||||
The name of the column in the log table to which the timestamp of the
|
||||
log entry is stored.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
nss = mkOption {
|
||||
description = ''
|
||||
Settings for <literal>libnss-mysql</literal>.
|
||||
|
||||
All examples are from the <link xlink:href="https://github.com/saknopper/libnss-mysql/tree/master/sample/minimal">minimal example</link>
|
||||
of <literal>libnss-mysql</literal>, but they are modified with NixOS paths for bash.
|
||||
'';
|
||||
type = types.submodule {
|
||||
options = {
|
||||
getpwnam = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = literalExpression ''
|
||||
SELECT username,'x',uid,'5000','MySQL User', CONCAT('/home/',username),'/run/sw/current-system/bin/bash' \
|
||||
FROM users \
|
||||
WHERE username='%1$s' \
|
||||
LIMIT 1
|
||||
'';
|
||||
description = ''
|
||||
SQL query for the <link
|
||||
xlink:href="https://man7.org/linux/man-pages/man3/getpwnam.3.html">getpwnam</link>
|
||||
syscall.
|
||||
'';
|
||||
};
|
||||
getpwuid = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = literalExpression ''
|
||||
SELECT username,'x',uid,'5000','MySQL User', CONCAT('/home/',username),'/run/sw/current-system/bin/bash' \
|
||||
FROM users \
|
||||
WHERE uid='%1$u' \
|
||||
LIMIT 1
|
||||
'';
|
||||
description = ''
|
||||
SQL query for the <link
|
||||
xlink:href="https://man7.org/linux/man-pages/man3/getpwuid.3.html">getpwuid</link>
|
||||
syscall.
|
||||
'';
|
||||
};
|
||||
getspnam = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = literalExpression ''
|
||||
SELECT username,password,'1','0','99999','0','0','-1','0' \
|
||||
FROM users \
|
||||
WHERE username='%1$s' \
|
||||
LIMIT 1
|
||||
'';
|
||||
description = ''
|
||||
SQL query for the <link
|
||||
xlink:href="https://man7.org/linux/man-pages/man3/getspnam.3.html">getspnam</link>
|
||||
syscall.
|
||||
'';
|
||||
};
|
||||
getpwent = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = literalExpression ''
|
||||
SELECT username,'x',uid,'5000','MySQL User', CONCAT('/home/',username),'/run/sw/current-system/bin/bash' FROM users
|
||||
'';
|
||||
description = ''
|
||||
SQL query for the <link
|
||||
xlink:href="https://man7.org/linux/man-pages/man3/getpwent.3.html">getpwent</link>
|
||||
syscall.
|
||||
'';
|
||||
};
|
||||
getspent = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = literalExpression ''
|
||||
SELECT username,password,'1','0','99999','0','0','-1','0' FROM users
|
||||
'';
|
||||
description = ''
|
||||
SQL query for the <link
|
||||
xlink:href="https://man7.org/linux/man-pages/man3/getspent.3.html">getspent</link>
|
||||
syscall.
|
||||
'';
|
||||
};
|
||||
getgrnam = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = literalExpression ''
|
||||
SELECT name,password,gid FROM groups WHERE name='%1$s' LIMIT 1
|
||||
'';
|
||||
description = ''
|
||||
SQL query for the <link
|
||||
xlink:href="https://man7.org/linux/man-pages/man3/getgrnam.3.html">getgrnam</link>
|
||||
syscall.
|
||||
'';
|
||||
};
|
||||
getgrgid = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = literalExpression ''
|
||||
SELECT name,password,gid FROM groups WHERE gid='%1$u' LIMIT 1
|
||||
'';
|
||||
description = ''
|
||||
SQL query for the <link
|
||||
xlink:href="https://man7.org/linux/man-pages/man3/getgrgid.3.html">getgrgid</link>
|
||||
syscall.
|
||||
'';
|
||||
};
|
||||
getgrent = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = literalExpression ''
|
||||
SELECT name,password,gid FROM groups
|
||||
'';
|
||||
description = ''
|
||||
SQL query for the <link
|
||||
xlink:href="https://man7.org/linux/man-pages/man3/getgrent.3.html">getgrent</link>
|
||||
syscall.
|
||||
'';
|
||||
};
|
||||
memsbygid = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = literalExpression ''
|
||||
SELECT username FROM grouplist WHERE gid='%1$u'
|
||||
'';
|
||||
description = ''
|
||||
SQL query for the <link
|
||||
xlink:href="https://man7.org/linux/man-pages/man3/memsbygid.3.html">memsbygid</link>
|
||||
syscall.
|
||||
'';
|
||||
};
|
||||
gidsbymem = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = literalExpression ''
|
||||
SELECT gid FROM grouplist WHERE username='%1$s'
|
||||
'';
|
||||
description = ''
|
||||
SQL query for the <link
|
||||
xlink:href="https://man7.org/linux/man-pages/man3/gidsbymem.3.html">gidsbymem</link>
|
||||
syscall.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
system.nssModules = [ pkgs.libnss-mysql ];
|
||||
system.nssDatabases.shadow = [ "mysql" ];
|
||||
system.nssDatabases.group = [ "mysql" ];
|
||||
system.nssDatabases.passwd = [ "mysql" ];
|
||||
|
||||
environment.etc."security/pam_mysql.conf" = {
|
||||
user = "root";
|
||||
group = "root";
|
||||
mode = "0600";
|
||||
# password will be added from password file in activation script
|
||||
text = ''
|
||||
users.host=${cfg.host}
|
||||
users.db_user=${cfg.user}
|
||||
users.database=${cfg.database}
|
||||
users.table=${cfg.pam.table}
|
||||
users.user_column=${cfg.pam.userColumn}
|
||||
users.password_column=${cfg.pam.passwordColumn}
|
||||
users.password_crypt=${cfg.pam.passwordCrypt}
|
||||
users.disconnect_every_operation=${if cfg.pam.disconnectEveryOperation then "1" else "0"}
|
||||
verbose=${if cfg.pam.verbose then "1" else "0"}
|
||||
'' + optionalString (cfg.pam.cryptDefault != null) ''
|
||||
users.use_${cfg.pam.cryptDefault}=1
|
||||
'' + optionalString (cfg.pam.where != null) ''
|
||||
users.where_clause=${cfg.pam.where}
|
||||
'' + optionalString (cfg.pam.statusColumn != null) ''
|
||||
users.status_column=${cfg.pam.statusColumn}
|
||||
'' + optionalString (cfg.pam.updateTable != null) ''
|
||||
users.update_table=${cfg.pam.updateTable}
|
||||
'' + optionalString cfg.pam.logging.enable ''
|
||||
log.enabled=true
|
||||
log.table=${cfg.pam.logging.table}
|
||||
log.message_column=${cfg.pam.logging.msgColumn}
|
||||
log.pid_column=${cfg.pam.logging.pidColumn}
|
||||
log.user_column=${cfg.pam.logging.userColumn}
|
||||
log.host_column=${cfg.pam.logging.hostColumn}
|
||||
log.rhost_column=${cfg.pam.logging.rHostColumn}
|
||||
log.time_column=${cfg.pam.logging.timeColumn}
|
||||
'';
|
||||
};
|
||||
|
||||
environment.etc."libnss-mysql.cfg" = {
|
||||
mode = "0600";
|
||||
user = config.services.nscd.user;
|
||||
group = config.services.nscd.group;
|
||||
text = optionalString (cfg.nss.getpwnam != null) ''
|
||||
getpwnam ${cfg.nss.getpwnam}
|
||||
'' + optionalString (cfg.nss.getpwuid != null) ''
|
||||
getpwuid ${cfg.nss.getpwuid}
|
||||
'' + optionalString (cfg.nss.getspnam != null) ''
|
||||
getspnam ${cfg.nss.getspnam}
|
||||
'' + optionalString (cfg.nss.getpwent != null) ''
|
||||
getpwent ${cfg.nss.getpwent}
|
||||
'' + optionalString (cfg.nss.getspent != null) ''
|
||||
getspent ${cfg.nss.getspent}
|
||||
'' + optionalString (cfg.nss.getgrnam != null) ''
|
||||
getgrnam ${cfg.nss.getgrnam}
|
||||
'' + optionalString (cfg.nss.getgrgid != null) ''
|
||||
getgrgid ${cfg.nss.getgrgid}
|
||||
'' + optionalString (cfg.nss.getgrent != null) ''
|
||||
getgrent ${cfg.nss.getgrent}
|
||||
'' + optionalString (cfg.nss.memsbygid != null) ''
|
||||
memsbygid ${cfg.nss.memsbygid}
|
||||
'' + optionalString (cfg.nss.gidsbymem != null) ''
|
||||
gidsbymem ${cfg.nss.gidsbymem}
|
||||
'' + ''
|
||||
host ${cfg.host}
|
||||
database ${cfg.database}
|
||||
'';
|
||||
};
|
||||
|
||||
environment.etc."libnss-mysql-root.cfg" = {
|
||||
mode = "0600";
|
||||
user = config.services.nscd.user;
|
||||
group = config.services.nscd.group;
|
||||
# password will be added from password file in activation script
|
||||
text = ''
|
||||
username ${cfg.user}
|
||||
'';
|
||||
};
|
||||
|
||||
# Activation script to append the password from the password file
|
||||
# to the configuration files. It also fixes the owner of the
|
||||
# libnss-mysql-root.cfg because it is changed to root after the
|
||||
# password is appended.
|
||||
system.activationScripts.mysql-auth-passwords = ''
|
||||
if [[ -r ${cfg.passwordFile} ]]; then
|
||||
org_umask=$(umask)
|
||||
umask 0077
|
||||
|
||||
conf_nss="$(mktemp)"
|
||||
cp /etc/libnss-mysql-root.cfg $conf_nss
|
||||
printf 'password %s\n' "$(cat ${cfg.passwordFile})" >> $conf_nss
|
||||
mv -fT "$conf_nss" /etc/libnss-mysql-root.cfg
|
||||
chown ${config.services.nscd.user}:${config.services.nscd.group} /etc/libnss-mysql-root.cfg
|
||||
|
||||
conf_pam="$(mktemp)"
|
||||
cp /etc/security/pam_mysql.conf $conf_pam
|
||||
printf 'users.db_passwd=%s\n' "$(cat ${cfg.passwordFile})" >> $conf_pam
|
||||
mv -fT "$conf_pam" /etc/security/pam_mysql.conf
|
||||
|
||||
umask $org_umask
|
||||
fi
|
||||
'';
|
||||
};
|
||||
}
|
|
@ -22,6 +22,7 @@
|
|||
./config/ldap.nix
|
||||
./config/locale.nix
|
||||
./config/malloc.nix
|
||||
./config/mysql.nix
|
||||
./config/networking.nix
|
||||
./config/no-x-libs.nix
|
||||
./config/nsswitch.nix
|
||||
|
|
|
@ -142,6 +142,16 @@ let
|
|||
'';
|
||||
};
|
||||
|
||||
mysqlAuth = mkOption {
|
||||
default = config.users.mysql.enable;
|
||||
defaultText = literalExpression "config.users.mysql.enable";
|
||||
type = types.bool;
|
||||
description = ''
|
||||
If set, the <literal>pam_mysql</literal> module will be used to
|
||||
authenticate users against a MySQL/MariaDB database.
|
||||
'';
|
||||
};
|
||||
|
||||
fprintAuth = mkOption {
|
||||
default = config.services.fprintd.enable;
|
||||
defaultText = literalExpression "config.services.fprintd.enable";
|
||||
|
@ -441,11 +451,13 @@ let
|
|||
(
|
||||
''
|
||||
# Account management.
|
||||
account required pam_unix.so
|
||||
'' +
|
||||
optionalString use_ldap ''
|
||||
account sufficient ${pam_ldap}/lib/security/pam_ldap.so
|
||||
'' +
|
||||
optionalString cfg.mysqlAuth ''
|
||||
account sufficient ${pkgs.pam_mysql}/lib/security/pam_mysql.so config_file=/etc/security/pam_mysql.conf
|
||||
'' +
|
||||
optionalString (config.services.sssd.enable && cfg.sssdStrictAccess==false) ''
|
||||
account sufficient ${pkgs.sssd}/lib/security/pam_sss.so
|
||||
'' +
|
||||
|
@ -459,7 +471,11 @@ let
|
|||
account [success=ok ignore=ignore default=die] ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so
|
||||
account [success=ok default=ignore] ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_admin.so
|
||||
'' +
|
||||
# The required pam_unix.so module has to come after all the sufficient modules
|
||||
# because otherwise, the account lookup will fail if the user does not exist
|
||||
# locally, for example with MySQL- or LDAP-auth.
|
||||
''
|
||||
account required pam_unix.so
|
||||
|
||||
# Authentication management.
|
||||
'' +
|
||||
|
@ -475,6 +491,9 @@ let
|
|||
optionalString cfg.logFailures ''
|
||||
auth required pam_faillock.so
|
||||
'' +
|
||||
optionalString cfg.mysqlAuth ''
|
||||
auth sufficient ${pkgs.pam_mysql}/lib/security/pam_mysql.so config_file=/etc/security/pam_mysql.conf
|
||||
'' +
|
||||
optionalString (config.security.pam.enableSSHAgentAuth && cfg.sshAgentAuth) ''
|
||||
auth sufficient ${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so file=${lib.concatStringsSep ":" config.services.openssh.authorizedKeysFiles}
|
||||
'' +
|
||||
|
@ -572,6 +591,9 @@ let
|
|||
optionalString use_ldap ''
|
||||
password sufficient ${pam_ldap}/lib/security/pam_ldap.so
|
||||
'' +
|
||||
optionalString cfg.mysqlAuth ''
|
||||
password sufficient ${pkgs.pam_mysql}/lib/security/pam_mysql.so config_file=/etc/security/pam_mysql.conf
|
||||
'' +
|
||||
optionalString config.services.sssd.enable ''
|
||||
password sufficient ${pkgs.sssd}/lib/security/pam_sss.so use_authtok
|
||||
'' +
|
||||
|
@ -615,6 +637,9 @@ let
|
|||
optionalString use_ldap ''
|
||||
session optional ${pam_ldap}/lib/security/pam_ldap.so
|
||||
'' +
|
||||
optionalString cfg.mysqlAuth ''
|
||||
session optional ${pkgs.pam_mysql}/lib/security/pam_mysql.so config_file=/etc/security/pam_mysql.conf
|
||||
'' +
|
||||
optionalString config.services.sssd.enable ''
|
||||
session optional ${pkgs.sssd}/lib/security/pam_sss.so
|
||||
'' +
|
||||
|
@ -1236,6 +1261,9 @@ in
|
|||
optionalString (isEnabled (cfg: cfg.oathAuth)) ''
|
||||
"mr ${pkgs.oath-toolkit}/lib/security/pam_oath.so,
|
||||
'' +
|
||||
optionalString (isEnabled (cfg: cfg.mysqlAuth)) ''
|
||||
mr ${pkgs.pam_mysql}/lib/security/pam_mysql.so,
|
||||
'' +
|
||||
optionalString (isEnabled (cfg: cfg.yubicoAuth)) ''
|
||||
mr ${pkgs.yubico-pam}/lib/security/pam_yubico.so,
|
||||
'' +
|
||||
|
|
|
@ -27,6 +27,22 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "nscd";
|
||||
description = ''
|
||||
User account under which nscd runs.
|
||||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "nscd";
|
||||
description = ''
|
||||
User group under which nscd runs.
|
||||
'';
|
||||
};
|
||||
|
||||
config = mkOption {
|
||||
type = types.lines;
|
||||
default = builtins.readFile ./nscd.conf;
|
||||
|
@ -56,6 +72,13 @@ in
|
|||
config = mkIf cfg.enable {
|
||||
environment.etc."nscd.conf".text = cfg.config;
|
||||
|
||||
users.users.${cfg.user} = {
|
||||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
};
|
||||
|
||||
users.groups.${cfg.group} = {};
|
||||
|
||||
systemd.services.nscd =
|
||||
{ description = "Name Service Cache Daemon";
|
||||
|
||||
|
@ -69,18 +92,29 @@ in
|
|||
config.environment.etc.hosts.source
|
||||
config.environment.etc."nsswitch.conf".source
|
||||
config.environment.etc."nscd.conf".source
|
||||
] ++ optionals config.users.mysql.enable [
|
||||
config.environment.etc."libnss-mysql.cfg".source
|
||||
config.environment.etc."libnss-mysql-root.cfg".source
|
||||
];
|
||||
|
||||
# We use DynamicUser because in default configurations nscd doesn't
|
||||
# create any files that need to survive restarts. However, in some
|
||||
# configurations, nscd needs to be started as root; it will drop
|
||||
# privileges after all the NSS modules have read their configuration
|
||||
# files. So prefix the ExecStart command with "!" to prevent systemd
|
||||
# from dropping privileges early. See ExecStart in systemd.service(5).
|
||||
# In some configurations, nscd needs to be started as root; it will
|
||||
# drop privileges after all the NSS modules have read their
|
||||
# configuration files. So prefix the ExecStart command with "!" to
|
||||
# prevent systemd from dropping privileges early. See ExecStart in
|
||||
# systemd.service(5). We use a static user, because some NSS modules
|
||||
# sill want to read their configuration files after the privilege drop
|
||||
# and so users can set the owner of those files to the nscd user.
|
||||
serviceConfig =
|
||||
{ ExecStart = "!@${cfg.package}/bin/nscd nscd";
|
||||
Type = "forking";
|
||||
DynamicUser = true;
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
RemoveIPC = true;
|
||||
PrivateTmp = true;
|
||||
NoNewPrivileges = true;
|
||||
RestrictSUIDSGID = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = "read-only";
|
||||
RuntimeDirectory = "nscd";
|
||||
PIDFile = "/run/nscd/nscd.pid";
|
||||
Restart = "always";
|
||||
|
|
|
@ -41,6 +41,7 @@ in {
|
|||
apparmor = handleTest ./apparmor.nix {};
|
||||
atd = handleTest ./atd.nix {};
|
||||
atop = handleTest ./atop.nix {};
|
||||
auth-mysql = handleTest ./auth-mysql.nix {};
|
||||
avahi = handleTest ./avahi.nix {};
|
||||
avahi-with-resolved = handleTest ./avahi.nix { networkd = true; };
|
||||
babeld = handleTest ./babeld.nix {};
|
||||
|
|
177
nixos/tests/auth-mysql.nix
Normal file
177
nixos/tests/auth-mysql.nix
Normal file
|
@ -0,0 +1,177 @@
|
|||
import ./make-test-python.nix ({ pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
dbUser = "nixos_auth";
|
||||
dbPassword = "topsecret123";
|
||||
dbName = "auth";
|
||||
|
||||
mysqlUsername = "mysqltest";
|
||||
mysqlPassword = "topsecretmysqluserpassword123";
|
||||
mysqlGroup = "mysqlusers";
|
||||
|
||||
localUsername = "localtest";
|
||||
localPassword = "topsecretlocaluserpassword123";
|
||||
|
||||
mysqlInit = pkgs.writeText "mysqlInit" ''
|
||||
CREATE USER '${dbUser}'@'localhost' IDENTIFIED BY '${dbPassword}';
|
||||
CREATE DATABASE ${dbName};
|
||||
GRANT ALL PRIVILEGES ON ${dbName}.* TO '${dbUser}'@'localhost';
|
||||
FLUSH PRIVILEGES;
|
||||
|
||||
USE ${dbName};
|
||||
CREATE TABLE `groups` (
|
||||
rowid int(11) NOT NULL auto_increment,
|
||||
gid int(11) NOT NULL,
|
||||
name char(255) NOT NULL,
|
||||
PRIMARY KEY (rowid)
|
||||
);
|
||||
|
||||
CREATE TABLE `users` (
|
||||
name varchar(255) NOT NULL,
|
||||
uid int(11) NOT NULL auto_increment,
|
||||
gid int(11) NOT NULL,
|
||||
password varchar(255) NOT NULL,
|
||||
PRIMARY KEY (uid),
|
||||
UNIQUE (name)
|
||||
) AUTO_INCREMENT=5000;
|
||||
|
||||
INSERT INTO `users` (name, uid, gid, password) VALUES
|
||||
('${mysqlUsername}', 5000, 5000, SHA2('${mysqlPassword}', 256));
|
||||
INSERT INTO `groups` (name, gid) VALUES ('${mysqlGroup}', 5000);
|
||||
'';
|
||||
in
|
||||
{
|
||||
name = "auth-mysql";
|
||||
meta.maintainers = with lib.maintainers; [ netali ];
|
||||
|
||||
nodes.machine =
|
||||
{ ... }:
|
||||
{
|
||||
services.mysql = {
|
||||
enable = true;
|
||||
package = pkgs.mariadb;
|
||||
settings.mysqld.bind-address = "127.0.0.1";
|
||||
initialScript = mysqlInit;
|
||||
};
|
||||
|
||||
users.users.${localUsername} = {
|
||||
isNormalUser = true;
|
||||
password = localPassword;
|
||||
};
|
||||
|
||||
security.pam.services.login.makeHomeDir = true;
|
||||
|
||||
users.mysql = {
|
||||
enable = true;
|
||||
host = "127.0.0.1";
|
||||
user = dbUser;
|
||||
database = dbName;
|
||||
passwordFile = "${builtins.toFile "dbPassword" dbPassword}";
|
||||
pam = {
|
||||
table = "users";
|
||||
userColumn = "name";
|
||||
passwordColumn = "password";
|
||||
passwordCrypt = "sha256";
|
||||
disconnectEveryOperation = true;
|
||||
};
|
||||
nss = {
|
||||
getpwnam = ''
|
||||
SELECT name, 'x', uid, gid, name, CONCAT('/home/', name), "/run/current-system/sw/bin/bash" \
|
||||
FROM users \
|
||||
WHERE name='%1$s' \
|
||||
LIMIT 1
|
||||
'';
|
||||
getpwuid = ''
|
||||
SELECT name, 'x', uid, gid, name, CONCAT('/home/', name), "/run/current-system/sw/bin/bash" \
|
||||
FROM users \
|
||||
WHERE id=%1$u \
|
||||
LIMIT 1
|
||||
'';
|
||||
getspnam = ''
|
||||
SELECT name, password, 1, 0, 99999, 7, 0, -1, 0 \
|
||||
FROM users \
|
||||
WHERE name='%1$s' \
|
||||
LIMIT 1
|
||||
'';
|
||||
getpwent = ''
|
||||
SELECT name, 'x', uid, gid, name, CONCAT('/home/', name), "/run/current-system/sw/bin/bash" \
|
||||
FROM users
|
||||
'';
|
||||
getspent = ''
|
||||
SELECT name, password, 1, 0, 99999, 7, 0, -1, 0 \
|
||||
FROM users
|
||||
'';
|
||||
getgrnam = ''
|
||||
SELECT name, 'x', gid FROM groups WHERE name='%1$s' LIMIT 1
|
||||
'';
|
||||
getgrgid = ''
|
||||
SELECT name, 'x', gid FROM groups WHERE gid='%1$u' LIMIT 1
|
||||
'';
|
||||
getgrent = ''
|
||||
SELECT name, 'x', gid FROM groups
|
||||
'';
|
||||
memsbygid = ''
|
||||
SELECT name FROM users WHERE gid=%1$u
|
||||
'';
|
||||
gidsbymem = ''
|
||||
SELECT gid FROM users WHERE name='%1$s'
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
def switch_to_tty(tty_number):
|
||||
machine.fail(f"pgrep -f 'agetty.*tty{tty_number}'")
|
||||
machine.send_key(f"alt-f{tty_number}")
|
||||
machine.wait_until_succeeds(f"[ $(fgconsole) = {tty_number} ]")
|
||||
machine.wait_for_unit(f"getty@tty{tty_number}.service")
|
||||
machine.wait_until_succeeds(f"pgrep -f 'agetty.*tty{tty_number}'")
|
||||
|
||||
|
||||
def try_login(tty_number, username, password):
|
||||
machine.wait_until_tty_matches(tty_number, "login: ")
|
||||
machine.send_chars(f"{username}\n")
|
||||
machine.wait_until_tty_matches(tty_number, f"login: {username}")
|
||||
machine.wait_until_succeeds("pgrep login")
|
||||
machine.wait_until_tty_matches(tty_number, "Password: ")
|
||||
machine.send_chars(f"{password}\n")
|
||||
|
||||
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
machine.wait_for_unit("mysql.service")
|
||||
machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
|
||||
|
||||
with subtest("Local login"):
|
||||
switch_to_tty("2")
|
||||
try_login("2", "${localUsername}", "${localPassword}")
|
||||
|
||||
machine.wait_until_succeeds("pgrep -u ${localUsername} bash")
|
||||
machine.send_chars("id > local_id.txt\n")
|
||||
machine.wait_for_file("/home/${localUsername}/local_id.txt")
|
||||
machine.succeed("cat /home/${localUsername}/local_id.txt | grep 'uid=1000(${localUsername}) gid=100(users) groups=100(users)'")
|
||||
|
||||
with subtest("Local incorrect login"):
|
||||
switch_to_tty("3")
|
||||
try_login("3", "${localUsername}", "wrongpassword")
|
||||
|
||||
machine.wait_until_tty_matches("3", "Login incorrect")
|
||||
machine.wait_until_tty_matches("3", "login:")
|
||||
|
||||
with subtest("MySQL login"):
|
||||
switch_to_tty("4")
|
||||
try_login("4", "${mysqlUsername}", "${mysqlPassword}")
|
||||
|
||||
machine.wait_until_succeeds("pgrep -u ${mysqlUsername} bash")
|
||||
machine.send_chars("id > mysql_id.txt\n")
|
||||
machine.wait_for_file("/home/${mysqlUsername}/mysql_id.txt")
|
||||
machine.succeed("cat /home/${mysqlUsername}/mysql_id.txt | grep 'uid=5000(${mysqlUsername}) gid=5000(${mysqlGroup}) groups=5000(${mysqlGroup})'")
|
||||
|
||||
with subtest("MySQL incorrect login"):
|
||||
switch_to_tty("5")
|
||||
try_login("5", "${mysqlUsername}", "wrongpassword")
|
||||
|
||||
machine.wait_until_tty_matches("5", "Login incorrect")
|
||||
machine.wait_until_tty_matches("5", "login:")
|
||||
'';
|
||||
})
|
30
pkgs/os-specific/linux/libnss-mysql/default.nix
Normal file
30
pkgs/os-specific/linux/libnss-mysql/default.nix
Normal file
|
@ -0,0 +1,30 @@
|
|||
{ lib, stdenv, fetchFromGitHub, autoreconfHook, which, libmysqlclient }:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "libnss-mysql";
|
||||
version = "1.7.1";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "saknopper";
|
||||
repo = "libnss-mysql";
|
||||
rev = "v${version}";
|
||||
sha256 = "1fhsswa3h2nkhjkyjxxqnj07rlx6bmfvd8j521snimx2jba8h0d6";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ autoreconfHook which ];
|
||||
buildInputs = [ libmysqlclient ];
|
||||
|
||||
configureFlags = [ "--sysconfdir=/etc" ];
|
||||
installFlags = [ "sysconfdir=$(out)/etc" ];
|
||||
postInstall = ''
|
||||
rm -r $out/etc
|
||||
'';
|
||||
|
||||
meta = with lib; {
|
||||
description = "MySQL module for the Solaris Nameservice Switch (NSS)";
|
||||
homepage = "https://github.com/saknopper/libnss-mysql";
|
||||
license = licenses.gpl2Plus;
|
||||
platforms = platforms.linux;
|
||||
maintainers = with maintainers; [ netali ];
|
||||
};
|
||||
}
|
24
pkgs/os-specific/linux/pam_mysql/default.nix
Normal file
24
pkgs/os-specific/linux/pam_mysql/default.nix
Normal file
|
@ -0,0 +1,24 @@
|
|||
{ lib, stdenv, fetchFromGitHub, meson, ninja, pam, pkg-config, libmysqlclient, mariadb }:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "pam_mysql";
|
||||
version = "1.0.0-beta2";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "NigelCunningham";
|
||||
repo = "pam-MySQL";
|
||||
rev = version;
|
||||
sha256 = "07acf0hbhkd0kg49gnj4nb5ilnv3v4xx3dsggvzvjg8gi3cjmsap";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ meson pkg-config ninja ];
|
||||
buildInputs = [ pam libmysqlclient mariadb ];
|
||||
|
||||
meta = with lib; {
|
||||
description = "PAM authentication module against a MySQL database";
|
||||
homepage = "https://github.com/NigelCunningham/pam-MySQL";
|
||||
license = licenses.gpl2Plus;
|
||||
platforms = platforms.linux;
|
||||
maintainers = with maintainers; [ netali ];
|
||||
};
|
||||
}
|
|
@ -4144,6 +4144,8 @@ with pkgs;
|
|||
|
||||
libndtypes = callPackage ../development/libraries/libndtypes { };
|
||||
|
||||
libnss-mysql = callPackage ../os-specific/linux/libnss-mysql { };
|
||||
|
||||
libxnd = callPackage ../development/libraries/libxnd { };
|
||||
|
||||
lifeograph = callPackage ../applications/editors/lifeograph { };
|
||||
|
@ -24378,6 +24380,8 @@ with pkgs;
|
|||
|
||||
pam_mount = callPackage ../os-specific/linux/pam_mount { };
|
||||
|
||||
pam_mysql = callPackage ../os-specific/linux/pam_mysql { };
|
||||
|
||||
pam_p11 = callPackage ../os-specific/linux/pam_p11 { };
|
||||
|
||||
pam_pgsql = callPackage ../os-specific/linux/pam_pgsql { };
|
||||
|
|
Loading…
Reference in a new issue