[MXS-2642] PAMAuth does not eliminate duplicate PAM services during authentication Created: 2019-08-19  Updated: 2020-08-25  Resolved: 2019-09-04

Status: Closed
Project: MariaDB MaxScale
Component/s: Authenticator
Affects Version/s: 2.3.11
Fix Version/s: 2.3.12, 2.4.1

Type: Bug Priority: Major
Reporter: Geoff Montee (Inactive) Assignee: Esa Korhonen
Resolution: Fixed Votes: 0
Labels: None

Issue Links:
Relates
relates to MXS-2544 PAMAuth doesn't check role permissions Closed
Sprint: MXS-SPRINT-89

 Description   

The PAMAuth authenticator is only supposed to try authenticating each user with a particular PAM service at most twice--once before reloading the users, and once after reloading the users. The second attempt only happens if the PAM service has changed since the first attempt. See here:

https://github.com/mariadb-corporation/MaxScale/blob/maxscale-2.3.11/server/modules/authenticator/PAM/PAMAuth/pam_client_session.cc#L427

However, it looks like it is actually possible for the PAMAuth authenticator to try a specific PAM service more times than that, because PAMAuth does not eliminate duplicate PAM services during authentication.

For example, let's say that we do the following:

INSTALL SONAME 'auth_pam';
 
CREATE USER 'alice'@'%' IDENTIFIED VIA pam USING 'mariadb';
GRANT SELECT ON *.* TO 'alice'@'%';
 
CREATE USER 'alice'@'192.168.1.%' IDENTIFIED VIA pam USING 'mariadb';
GRANT SELECT ON *.* TO 'alice'@'192.168.1.%';

Now we have two distinct user accounts named "alice", and they both use the "mariadb" PAM service.

The PAMAuth authenticator loads user accounts using the following query:

SELECT u.user, u.host, d.db, u.select_priv, u.authentication_string FROM 
mysql.user AS u LEFT JOIN mysql.db AS d ON (u.user = d.user AND u.host = d.host) WHERE 
(u.plugin = 'pam' AND (d.db IS NOT NULL OR u.select_priv = 'Y')) 
UNION 
SELECT u.user, u.host, t.db, u.select_priv, u.authentication_string FROM 
mysql.user AS u LEFT JOIN mysql.tables_priv AS t ON (u.user = t.user AND u.host = t.host) WHERE 
(u.plugin = 'pam' AND t.db IS NOT NULL AND u.select_priv = 'N') 
ORDER BY user;

See here:

https://github.com/mariadb-corporation/MaxScale/blob/maxscale-2.3.11/server/modules/authenticator/PAM/PAMAuth/pam_instance.cc#L219

The results for this specific scenario would be the following:

MariaDB [(none)]> SELECT u.user, u.host, d.db, u.select_priv, u.authentication_string FROM
    -> mysql.user AS u LEFT JOIN mysql.db AS d ON (u.user = d.user AND u.host = d.host) WHERE
    -> (u.plugin = 'pam' AND (d.db IS NOT NULL OR u.select_priv = 'Y'))
    -> UNION
    -> SELECT u.user, u.host, t.db, u.select_priv, u.authentication_string FROM
    -> mysql.user AS u LEFT JOIN mysql.tables_priv AS t ON (u.user = t.user AND u.host = t.host) WHERE
    -> (u.plugin = 'pam' AND t.db IS NOT NULL AND u.select_priv = 'N')
    -> ORDER BY user;
+-------+-------------+------+-------------+-----------------------+
| user  | host        | db   | select_priv | authentication_string |
+-------+-------------+------+-------------+-----------------------+
| alice | 192.168.1.% | NULL | Y           | mariadb               |
| alice | %           | NULL | Y           | mariadb               |
+-------+-------------+------+-------------+-----------------------+
2 rows in set (0.00 sec)

Let's say that the "alice" user tries to log in from a host with an IP address that is something like 192.168.1.10. In this case, the get_pam_user_services() function will tell PAMAuth to try the "mariadb" service twice--once for the 'alice'@'%' user account, and once for the 'alice'@'192.168.1.%' user account. See here:

https://github.com/mariadb-corporation/MaxScale/blob/maxscale-2.3.11/server/modules/authenticator/PAM/PAMAuth/pam_client_session.cc#L304

It's not really helpful to try authenticating with the same service multiple times. This can be especially problematic if the user's environment locks a user's account after so many failed authentication attempts. In environments like that, if MaxScale tries the same bad password multiple times for each login attempt, then that will cause the user account to be locked much sooner.

I think MaxScale should fix this by eliminating duplicate PAM services, so that it only tries each unique service once.

I see two potential ways to fix this that are both pretty easy:

1.) The SQLite query that fetches the PAM services in the get_pam_user_services() function does not currently eliminate duplicates. One way to fix this would be by adding the "DISINCT" keyword to the SQLite query, so that SQLite will remove duplicate PAM services from the results. e.g.:

    string services_query = string("SELECT DISTINCT authentication_string FROM ") + m_instance.m_tablename + " WHERE "
...

https://github.com/mariadb-corporation/MaxScale/blob/maxscale-2.3.11/server/modules/authenticator/PAM/PAMAuth/pam_client_session.cc#L307

2.) The get_pam_user_services() function currently uses a std::vector<std::string> to store the PAM services returned by the SQLite query. C++'s std::vector allows duplicate objects. Another way to fix this would be to switch from std::vector<std::string> to something like std::unordered_set<std::string>, which does not allow duplicates.

https://github.com/mariadb-corporation/MaxScale/blob/maxscale-2.3.11/server/modules/authenticator/PAM/PAMAuth/pam_client_session.hh#L29

https://github.com/mariadb-corporation/MaxScale/blob/maxscale-2.3.11/server/modules/authenticator/PAM/PAMAuth/pam_client_session.cc#L304

http://www.cplusplus.com/reference/unordered_set/unordered_set/



 Comments   
Comment by Geoff Montee (Inactive) [ 2019-08-22 ]

I've confirmed that MaxScale will try the same PAM service multiple times.

I reproduced this by creating the following user accounts:

CREATE USER 'alice'@'%' IDENTIFIED VIA pam USING 'mariadb';
GRANT SELECT ON *.* TO 'alice'@'%' IDENTIFIED VIA pam USING 'mariadb';
 
CREATE USER 'alice'@'172.30.0.%' IDENTIFIED VIA pam USING 'mariadb';
GRANT SELECT ON *.* TO 'alice'@'172.30.0.%' IDENTIFIED VIA pam USING 'mariadb';

And I used the following configuration file:

[C1N1]
type=server
address=172.30.0.105
port=3306
protocol=MariaDBBackend
authenticator=PAMBackendAuth
 
[C1N2]
type=server
address=172.30.0.96
port=3306
protocol=MariaDBBackend
authenticator=PAMBackendAuth
 
[C1N3]
type=server
address=172.30.0.126
port=3306
protocol=MariaDBBackend
authenticator=PAMBackendAuth
 
[Galera-Monitor]
type=monitor
module=galeramon
servers=C1N1,
        C1N2,
        C1N3
user=maxscale
password=password
monitor_interval=10000
 
[Splitter-Service-Listener]
type=listener
service=Splitter-Service
address=0.0.0.0
port=3306
protocol=MariaDBClient
authenticator=PAMAuth
 
[Splitter-Service]
type=service
router=readwritesplit
servers=C1N1,
        C1N2,
        C1N3
user=maxscale
password=password

And then I attempted to log into MaxScale using a bad password:

$ mysql -u alice -h 172.30.0.106 -P 3306
[mariadb] Password:
ERROR 1045 (28000): Access denied for user 'alice'@'172.30.0.106' (using password: YES)

The MaxScale error log clearly shows that authentication was tried twice, when it only should have been tried once in this case:

2019-08-22 14:19:21   info   : Found 2 valid PAM user entries for 'alice'@'172.30.0.106'.
2019-08-22 14:19:23   warning: [PAMAuth] Pam authentication for user 'alice' failed: 'Authentication failure'.
2019-08-22 14:19:25   warning: [PAMAuth] Pam authentication for user 'alice' failed: 'Authentication failure'.
2019-08-22 14:19:25   info   : Added normal PAM user 'alice'@'%' using service 'mariadb'.
2019-08-22 14:19:25   info   : Added normal PAM user 'alice'@'172.30.0.%' using service 'mariadb'.
2019-08-22 14:19:25   info   : Found 1 anonymous PAM user(s). Checking them for proxy grants.
2019-08-22 14:19:25   info   : Added anonymous PAM user ''@'%' with proxy grants using service 'mariadb'.
2019-08-22 14:19:25   info   : Found 2 valid PAM user entries for 'alice'@'172.30.0.106'.

And the syslog (i.e. /var/log/secure) also shows the two attempts using the same service name:

Aug 22 14:19:21 ip-172-30-0-106 unix_chkpwd[2195]: password check failed for user (alice)
Aug 22 14:19:21 ip-172-30-0-106 maxscale[2183]: pam_unix(mariadb:auth): authentication failure; logname= uid=995 euid=995 tty= ruser= rhost=  user=alice
Aug 22 14:19:23 ip-172-30-0-106 unix_chkpwd[2196]: password check failed for user (alice)
Aug 22 14:19:23 ip-172-30-0-106 maxscale[2183]: pam_unix(mariadb:auth): authentication failure; logname= uid=995 euid=995 tty= ruser= rhost=  user=alice

Comment by Geoff Montee (Inactive) [ 2019-08-22 ]

I repeated the previous test with a third user account added to the mix:

CREATE USER 'alice'@'%' IDENTIFIED VIA pam USING 'mariadb';
GRANT SELECT ON *.* TO 'alice'@'%' IDENTIFIED VIA pam USING 'mariadb';
 
CREATE USER 'alice'@'172.30.0.%' IDENTIFIED VIA pam USING 'mariadb';
GRANT SELECT ON *.* TO 'alice'@'172.30.0.%' IDENTIFIED VIA pam USING 'mariadb';
 
CREATE USER 'alice'@'172.30.%.%' IDENTIFIED VIA pam USING 'mariadb';
GRANT SELECT ON *.* TO 'alice'@'172.30.%.%' IDENTIFIED VIA pam USING 'mariadb';

As expected, MaxScale tries to authenticate the user using the "mariadb" PAM service three times.

The MaxScale error log:

2019-08-22 14:31:35   info   : Found 3 valid PAM user entries for 'alice'@'172.30.0.106'.
2019-08-22 14:31:37   warning: [PAMAuth] Pam authentication for user 'alice' failed: 'Authentication failure'.
2019-08-22 14:31:39   warning: [PAMAuth] Pam authentication for user 'alice' failed: 'Authentication failure'.
2019-08-22 14:31:41   warning: [PAMAuth] Pam authentication for user 'alice' failed: 'Authentication failure'.
2019-08-22 14:31:41   info   : Added normal PAM user 'alice'@'%' using service 'mariadb'.
2019-08-22 14:31:41   info   : Added normal PAM user 'alice'@'172.30.0.%' using service 'mariadb'.
2019-08-22 14:31:41   info   : Added normal PAM user 'alice'@'172.30.%.%' using service 'mariadb'.
2019-08-22 14:31:41   info   : Found 1 anonymous PAM user(s). Checking them for proxy grants.
2019-08-22 14:31:41   info   : Added anonymous PAM user ''@'%' with proxy grants using service 'mariadb'.
2019-08-22 14:31:41   info   : Found 3 valid PAM user entries for 'alice'@'172.30.0.106'.

The syslog:

Aug 22 14:31:35 ip-172-30-0-106 unix_chkpwd[2232]: password check failed for user (alice)
Aug 22 14:31:35 ip-172-30-0-106 maxscale[2220]: pam_unix(mariadb:auth): authentication failure; logname= uid=995 euid=995 tty= ruser= rhost=  user=alice
Aug 22 14:31:37 ip-172-30-0-106 unix_chkpwd[2233]: password check failed for user (alice)
Aug 22 14:31:37 ip-172-30-0-106 maxscale[2220]: pam_unix(mariadb:auth): authentication failure; logname= uid=995 euid=995 tty= ruser= rhost=  user=alice
Aug 22 14:31:39 ip-172-30-0-106 unix_chkpwd[2234]: password check failed for user (alice)
Aug 22 14:31:39 ip-172-30-0-106 maxscale[2220]: pam_unix(mariadb:auth): authentication failure; logname= uid=995 euid=995 tty= ruser= rhost=  user=alice

I think MaxScale should eliminate duplicate PAM services, so that it only tries each unique service once.

Comment by Esa Korhonen [ 2019-08-23 ]

This is fixed in 2.4.1, where role support was also added.

This is not really even a bug, as it is explained in Pam authentication documentation:
"If a username@host-combination matches multiple rows, they will all be attempted until authentication succeeds or all services fail."

Generated at Thu Feb 08 04:15:38 UTC 2024 using Jira 8.20.16#820016-sha1:9d11dbea5f4be3d4cc21f03a88dd11d8c8687422.