One of the projects I’ve been working on lately has required me to investigate the way PAM handles external authentication. The setup is a bunch of different boxes running Solaris, Linux and FreeBSD, but all authenticating from a single central Kerberos domain. There’s also LDAP in the mix, but it’s not actually relevant, so I’ll ignore it for now.
The problem comes when you want to lock out an account on a machine. By this, I mean the account still exists in the passwd file (for whatever reason, it needs to stay there) and the account still exists within the Kerberos domain because it can log on to other machines.
What are the options? The PAM setup probably looks a little something like this:
auth sufficient pam_krb5 auth required pam_unix account required pam_krb5 account required pam_unix
This setup allows the user to authenticate with just their Kerberos credential, but they must pass both modules in the account section (the pam_krb5 one here doesn’t do much though).
So we can put whatever we want in the shadow password entry on the machine, it won’t affect the authentication – it never even gets there. But what about the account section? Can this do anything?
After some investigation and source code checking I came up with the following:
- On Solaris one can put *LK* in the shadow password field. This causes the pam_unix module to reject the login in the account stage.
- On FreeBSD one can do the same as Solaris but with *LOCKED*.
- On Linux you can put what you want in the field but pam_unix won’t take any notice.
If you’re thinking right now “hey, you can just put an exclamation mark in the shadow password field to lock the account out” you’re wrong. What this does on a standalone system is ensure that no possible password can decrypt to match this string which results in locking the account out. But, when we have external authentication it’s meaningless.
So what’s the solution? There’s another field we can use – the shadow password expiry field. This is a time stamp in seconds since the epoch at which the password expires. The default setting of 0 means no expiry. Simply setting this to 1 (or any number smaller than now) results in the account being locked out. This seems like it’ll work across all three operating systems I’m testing, although I’m yet to finish my analysis.
I am a little disappointed with the Linux PAM module. It’s a simple test to do, so I can’t see any reason why it shouldn’t be added.
Update 2nd November 2009:
I’ve since discovered that what I said above about the “default setting of 0 means no expiry” is incorrect. In fact, it varies on different operating systems. I got misled by the behaviour on Ubuntu 9.04 because of a Debian patch which made 0 mean no expiry, but which has been removed in Ubuntu 9.10 (details here).
Consequently my advice now is to set the shadow password expiry field to 1 to lock the account, and to unset it to unlock the account. This appears to work on all the operating systems I’ve tested it on.
Thanks for the info. A similar solution to what you suggest is netgroups, or so I’m told. The groups exist within the directory rather than on the local machines.
In my case though it’s not quite how you describe. Each machine points at a separate OU in the LDAP directory. So we can control which accounts can access which machines using our central host management system.
The problem I’m solving is the case where the user account does need to go on the machine (eg. it could be a Samba server), but the user shouldn’t be able to log in.
Using the method you describe would work, but would require additional groups to be created and managed (and there’s an annoying limit on the number of groups an individual can belong to with NFS). Groups in our setup are not currently managed by the central system either.
The route I’ve chosen works using the password field sent by the management system, which we can control centrally. And my LDAP management code can then lock out (or unlock) an account on the LDAP server without ever touching the end system.
Thanks for your comments though, it’s useful to know about other solutions. And it’s refreshing to get a non-spam comment 😀
As I understand it, you have a bunch of machines using pam_krb5 for authentication (and also LDAP for NSS). You want some users to logon to some hosts, but not all hosts, i.e. you want to deny some users from logging on to certain hosts?
In GNU/Linux there exists a different approach than the one you have suggested for Solaris/FreeBSD. You can use the pam module pam_access which will then allow you to deny groups of users to logon:
Sadly Solaris lacks an equivallent to pam_access, such that many people port Linux PAM’s pam_access to Solaris. I don’t believe there is an equivallent under FreeBSD either.
Once you place pam_access in your PAM stack (under “account”) you can use one configuration file in /etc/security/ and you’re done. The security file will remind you how how compat mode used to work in /etc/passwd in the days of NIS. You simply prefix the lines with “-” to deny and “+” to allow.
I hope this is helpful to you, however given its a Linux specific “innovation” which has not been replicated to other free operating systems its usefulness to you is probably limited. This, at any rate, is probably why pam_unix on Linux PAM is less useful than its cousins.
Of course, this should work with external authentication even if “auth” isn’t being consulted because “account” should still be consulted by the SSH Daemon (or some other access system). Let me know if you find a better solution, right now I’m still using NIS at work (shudder) so the problem does not yet exist for us.