JT Moree - 2017-11-21

pam-anyauth

This project allows pam to authenticate users against anything you desire

tl;dr

  • install pam-anyauth utilities using your preferred method
  • yum install pam-anyauth
  • apt install pam-anyauth
  • ./configure && make install
  • setup odbc and isql if using it for accessing credentials
  • configure files in /etc/pam-anyauth
  • test pam-anyauth
  • modify pam configuration to use pam_exec.so pam-anyauth

DEPENDENCIES

This utility can use putty and puttygen. install it if you want to enable that feature.

Also need isql from unixodbc or some equivalent if connecting to databases.

HOW DOES THIS WORK?

This utility is a wrapper around other processes. The parent process pam-anyauth calls two sub processes:
1) authenticate a user
2) ensure user is setup on local system

If you want to swap out either the auth or the ensure processes change the commands in the config file:

/etc/pam-anyauth/conf

The two options are included and commented out by default. AUTH is the command that authenticates the user. ENSURE makes sure the user exists and is setup with any extras.

The password is sent to each subprocesses using stdin. See AUTHENTICATING USERS

The username is an environment variable inherited from pam_exec: $PAM_USER.

DEBUG mode

You may turn on debugging output by setting DEBUGPREFERENCE=continue in the conf file or setting the environment variable.

CAVEATS

IF the user does not exist on the system ssh (and other programs?) may trash the password before it gets to pam-anyauth. Without a valid password pam-anyauth cannot make sure a user is valid and therefore cannot create the user.

This leads to a chicken and egg problem that I have not been able to solve. The workaround right now is to pre-seed the users. I use pam-anyauth-odbc-sync from a cron job to preseed users. See PAM-ANYAUTH-ODBC-SYNC

https://www.redhat.com/archives/pam-list/2006-July/msg00029.html this article suggests that pam_exec cannot be told to retrieve the password using try_first_pass or use_first_pass. I'm looking into writing a c wrapper pam module to work around this issue.

AUTHENTICATING USERS

The authentication process must read the user password from stdin and use the PAM_USER variable to get the username. Ex.

PAM_PASS=$(cat -)
echo "User: $PAM_USER has password $PAM_PASS"

The included odbc based authentication script uses isql to connect to an odbc database. The configuration settings (including a password!!! keep this file private) are stored in /etc/pam-anyauth/odbc.conf. Modify these for your environment. see PAM-ANYAUTH-ODBC for more details.

If you want to create your own process it must:

  • accept the password on stdin
  • ouput return code 0 on success and anything else on failure to find a match
  • output something on successful match (such as a username or id)

PAM-ANYAUTH-ENSURE

To make sure your ensure command works, pick a user and run the command

PAM_USER=<someuser>
echo '*' | pam-anyauth-ensure
echo "return code: $?"</someuser>

The return code should be 0.

PAM-ANYAUTH-ODBC

The included odbc based authentication script uses isql to connect to an odbc database. The configuration settings (including a password!!! keep this file private) are stored in /etc/pam-anyauth/odbc.conf. Modify these for your environment.

To use this process setup odbc on your system (this is outside the scope of this document) and get db connectivity working with isql before configuring this utility. Once isql is working make sure you have a way to check the user credentials in the database.

You will need to create some database inftrastructure to support your authentication. At the very least you need a query that the process will call to match the user and password specified by pam. See PAM_SQL_LOOKUP below

Develop your query then test using isql on the command line before trying to hook all this up to pam. Once the query is working modify PAM_SQL_LOOKUP in odbc.conf. Use placeholders for the {USERNAME} AND {PASSWORD}. Those tokens are substituted (after some sql injection sanity checking) before the query is run against the database.

Also set the username and password for the isql connection. You can test that it is working by running

PAM_USER=<someuser>
export PAM_USER
echo '<userpassword>' | pam-anyauth-odbc
echo "return code: $?"</userpassword></someuser>

PAM_SQL_LOOKUP

PAM_SQL_LOOKUP must be set to a query which will be executed against the specified data source. The query must return 'Y' as the first column of the result set to indicate that the user is enabled and not locked out. The query may optionally return nothing if the credentials are disabled, locked out, or invalid.

EXAMPLE 1

We wrote a function called [authenticate] that takes the username and password then checks them against the user tables in the database. The function returns a record if a valid, enabled, not-locked out matching username and password is found and nothing otherwise.

We then use this for the configuration

PAM_SQL_LOOKUP="select 'Y', IsEnabled, UserId from [dbo].authenticate;"

EXAMPLE 2

If you are using aspnetIdentity in an MVC application you should have a table aspnetUsers. Let's assume you are using clear text passwords for this example. The password is stored in the PasswordHash field. It should include a marker in the field for the format used (clear, hashed, or encrypted) along with the password hash and the salt. A number designates which method is used: 0 => clear, 1 => hash, 2 => encrypted. This code will parse out the clear text password and try to match it.

PAM_SQL_LOOKUP="select 'Y', Id from [dbo].[aspnetUsers] where Username = N'{USERNAME}' and left(PasswordHash,charindex('|',PasswordHash,0) - 1) = N'{PASSWORD}';"

PAM-ANYAUTH-ODBC-SYNC

A cron job is available but disabled by default for a sync process. If enabled the process will call the ensure script on all valid database users so that they will exist on the system before they try to login. This gets around the problem of ssh trashing the password for unknown users by pre-creating the users.

This utility requires another sql query to get the list of valid users. Check /etc/pam-anyauth/odbc.conf for

PAM_SQL_LIST="select Username, UserId from [core].[user] where IsEnabled = 1;"

The first column in the result set MUST be the username. You may return anything else after that. The username will be used by the process to ensure users.

pam-anyauth test

Test to make sure everything works together by testing a real user from your database. Dont forget to ensure the user first if the chicken and egg bug has not been fixed yet. (missing users always fail for now)

PAM_USER=<someuser>
export PAM_USER
echo '<userpassword>' | pam-anyauth
echo "return code: $?"</userpassword></someuser>

CONFIGURING PAM

pam-anyauth was developed to be used with pam_exec. On Debian/Ubuntu we modified /etc/pam.d/common-auth but on centos we modified /etc/pam.d/password-auth. It looks something like this

# here are the per-package modules (the "Primary" block)
auth [success=2 default=ignore] pam_unix.so nullok_secure
auth [success=1 default=ignore] pam_sss.so use_first_pass

auth sufficient pam_exec.so expose_authtok debug log=/var/log/pam-anyauth.log /usr/sbin/pam-anyauth #### added this line for pam-anyauth

# here's the fallback if no module succeeds
auth requisite pam_deny.so
# prime the stack with a positive return value if there isn't one already;
# this avoids us returning an error just because nothing sets a success code
# since the modules above will each just jump around
auth required pam_permit.so

After getting it to work you may want to take out the debug option on the pam-anyauth line.