Linux users and authentication

20 Sep 2017

Sometimes I end up doing weird projects at work, and this was one of them. We needed to let people log into Raspberry PIs using Active Directory accounts and have some central auditing.

You can use the Kerberos/LDAP plugins that are available in Linux for this but I’ve never had much luck with getting them to work in the way I wanted. There’s also products you can but that do a pretty good job at supporting AD, but the one I have access to isn’t the most reliable if you put any load on it and doesn’t run on ARM.

So the question was, how bad was it going to be to build this myself?

It turns out, not that bad really.

Who’s Alice?

Before we get into authentication (e.g. are you who you claim you are) we first need to be able to just know the basic details about you. In Posix this boils down to the following: username, uid, gid, gecos, home directory, password hash, shell . In the very old days this was read directly from /etc/passwd but that obvious wasn’t a great idea so the Name Service Switch layer was added.

/etc/nsswitch.conf contains a list of databases and lookup modules:

passwd: compat mymachines system
group: compat mymachines systemd
shadow: compat
...

If you do a quick search in /lib for files starting with libnss_ you’ll find:

-rwxr-xr-x 1 root root 1.4M Jul 29 12:28 /lib/libnss3.so
-rwxr-xr-x 1 root root 243K Aug 20 19:39 /lib/libnssckbi-p11-kit.so
lrwxrwxrwx 1 root root   21 Jul 29 12:28 /lib/libnssckbi.so -> libnssckbi-p11-kit.so
-rwxr-xr-x 1 root root  31K Jul 10 18:41 /lib/libnss_compat-2.25.so
lrwxrwxrwx 1 root root   18 Jul 10 18:41 /lib/libnss_compat.so -> libnss_compat.so.2
lrwxrwxrwx 1 root root   21 Jul 10 18:41 /lib/libnss_compat.so.2 -> libnss_compat-2.25.so

So the nss library loads and asks each module listed in nsswitch.conf whether a uid/username exists.

One important thing to remember is that this happens inside the calling process, so your code ends up being loaded everywhere so any mistakes can break everything. Don’t develop these on a live system unless you’re happy with booting from an external system and changing your nsswitch back because if you stick end up with a bug that causes it to crash you’ve just broken your entire OS, not that I had to do that at any point.

For full details on the workings of NSS and the stuff you need to implement read the GNU NSS documentation.

The first thing you need to decide on is your module name, as this becomes part of the function names, I just used my initials. I'll leave the exact details of implementing these to the documentation, but there’s a difference between the ones you call as a user of the library and the implementer. One parameter gets removed, another added and an underscore added to the start of the function name.

_nss_ras_getpwnam_r : Returns  an entry for the given username
_nss_ras_getpwuid_r : Returns an entry to the given user id

The compiler command I used to create the library is:

gcc -fPIC -shared -o libnss_ras.so.2 -Wl,-soname,libnss_ras.so.2 ras_nss.c

Once it’s compiled just copy it into /lib/, add it to the nsswitch.conf file and your users will exist. Now you can use them to set file permissions and process user IDs.

Orignal:
passwd: compat mymachines systemd

Modified:
passwd: ras compat mymachines systemd

If you want to be able to open a tty or ssh session and log in as them then you’re going to need to deal with PAM as well.

Show me some ID

Now while authenticating users using crypt used to be a good solution it’s not really feasible for modern systems, especially if you want to allow things like restricted login hours or non-password ways of logging in. So the Pluggable Authentication Method (PAM) was created.

This uses the same principle of loading external libraries into processes depending on a configuration file as NSS, but its a lot more complicated as it also keeps track of sessions and handing tokens from one module in the system to another.

The magic functions you need to implement to get passwords working (assuming you’ve called your module ras as well) are:

pam_sm_authenticate : Checks the username/password.
pam_sm_setcred : Stores any credentials given back into the PAM system.
pam_sm_acct_mgmt : I cheat here and assume another module will kick out a bad account
    so just return True.

This library needs to get copied into /lib/security/ and then referenced from the PAM configs, which is where things get complicated as they’re not the same on every distro.

If you look in /etc/pam.d they’ll be a load of different files, most are for a specific service but they’ll just include the general ones. On Arch the base one for the auth and account modules is system-auth so we’ll modify this.

First the auth section:

Original:
auth      required  pam_unix.so     try_first_pass nullok

Modified:
auth      sufficient pam_ras.so
auth      required pam_unix.so nullok_secure use_first_pass

This tells pam that is the module we’ve written says the password is ok to pass it through and skip the rest.

The use_first_pass on the end of the unix module deals with accounts that aren’t handled by our module. If you don’t have this the unix module will ask for the password again, instead of just asking PAM for the current user token (password), so anyone logging in with a local account has the enter the password twice.

This might seem strange, but the auth modules can use whatever methods they want to authenticate users, say fingerprint or iris scanners, which obviously can’t provide a password to later modules in the stack. It doesn’t explain why it doesn’t check anyway, but I’m guessing they have a reason.

Next comes the account section:

Original:
account   required  pam_unix.so

Modified:
account   sufficient pam_cmp.so
account   [success=1 default=ignore] pam_unix.so

Again this says that out module is good enough and that an ‘I don’t know’ shouldn’t be treated as a failure for the default unix module.

This post has been taged as Linux