3
0
Fork 0
forked from mirrors/nixpkgs

Merge pull request #183717 from NetaliDev/mysql-auth

nixos: add mysql/mariadb user authentication module
This commit is contained in:
Maximilian Bosch 2022-08-16 20:30:16 +02:00 committed by GitHub
commit 9e8ea1b855
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 826 additions and 8 deletions

View 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
'';
};
}

View file

@ -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

View file

@ -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,
'' +

View file

@ -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";

View file

@ -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
View 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:")
'';
})

View 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 ];
};
}

View 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 ];
};
}

View file

@ -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 { };