Yes, this post’s title is an ambitious one. The post will either not be comprehensive or it will be long. A friend jokes I’d have to write a book to be true to this post’s title, but since there would be no market for such a book, a blog it will be.
Pluggable Authentication Modules (PAM) is a user authentication technology used on some Unix and Unix-like systems, such as Solaris, Linux, and some BSDs. As the name says, it’s pluggable, and that’s about the best thing about PAM because authentication has changed a lot in the Unix space in the past twenty years, but PAM applications have not had to change quite as much.
PAM abstracts away two major aspects of Unix user authentication:
- interaction (e.g., “Password: ” prompts)
- sequencing of the login process (user authentication, access control, password expiration …)
I’ll assume that the reader has a passing familiarity with PAM for the rest of this blog entry.
Some of the Problems With PAM
I’m here to tell you that PAM is out of date. Very out of date, and it needs a complete overhaul or replacement. First let’s look at a laundry list of problems with PAM, in no particular order:
User interaction in PAM is designed with text-based login programs in mind
Nowadays graphical login programs (such as GDM) and biometric and smartcard use cases require much more advanced interaction facilities. Not only that, but PAM has been used in web applications, via Apache’s mod_auth_pam, and text-only prompts are decidedly old hat in web applications. (Of course, the use of user interaction via HTML forms + cookies is itself rather problematic, but let’s ignore that issue for the time being.)
Think of modern GUI login screens (see screenshots), which often have avatar selection instead of “Username: ” prompts, as well as choices of session types, locales, and accessibility options. A rich media experience is becoming a requirement. Those identification screens are not driven by PAM because PAM can’t do much more than prompt for a username as far as identification is concerned.
Related to this:
- There is not enough metadata in PAM conversations (interaction). Is a prompt for a username? For a password? For a new password? For an OTP? For a smartcard PIN?
- There is not enough information for the modules about the capabilities of the PAM application. Is it a GUI? a BUI? a text-based app? The best you can do is establish conventions for PAM_SERVICE naming.
Interaction with remote authentication
There is almost none, with the only information available to PAM about remote authentication is that it happened, the remote hostname, and the application name (via PAM_SERVICE). This is a huge problem, perhaps the most important one after graphical interaction limitations. It’d be nice if a module could handle SSHv2 pubkey authorization, or Kerberos principal-to-user-account authorization.
There’s quite a bit more to this. For example, a Kerberos ticket might include a KDC-issued session ID (e.g., in the Windows PAC) that would be quite handy to record for auditing purposes. And PAM is a great place to apply policy regarding, e.g., levels of assurance (LoA) and other such policies to remote authentication. This means that merely passing the name of the authenticated client principal is insufficient.
Fixing this has the potential to greatly simplify many remote application. But do not get confused: the remote authentication protocols themselves should NOT be implemented by PAM, only authorization to Unix accounts and policy enforcement.
The sequence of steps that PAM models is out of date. In particular there needs to be an “identification” step to cover username discovery, and that allows for asynchronous (cancellable) interactions. For example, a module might want to ask the user to touch a fingerprint reader or insert a smartcard, but progress might be made by the user simply typing in a username (or clicking on an avatar). These prompts need to be cancellable because one should want the biometric/smartcard prompts to go away as soon as the user has acted on them, without the use having to click on a button in a dialog.
Other sequencing problems include the rather odd requirement that first comes authentication, then authorization, then handling of password expiration. Some authentication methods, such as Kerberos and LDAP password validation, are not able to separate authentication from password expiration.
- Yet another sequencing problem relates to the pam_setcred(3PAM) call and associated entry point. The semantics of the flags for this function are not very well defined. It’s one thing for a module to, say, write out Kerberos tickets to a credentials cache, and quite another for a module to modify the credentials of the current process — but the setcred stack and flags do not make it easy to figure out how to properly configure a PAM service.
- PAM uses one configuration for two rather different steps in the login sequence by deriving a setcred configuration from an auth configuration. This is extremely obnoxious.
- Yet another sequencing problem is the idea that all passwords for a user should be changed at once. Instead authentication modules should be free to change expired passwords as necessary (reusing any new password entered earlier if at all possible), with the balance of password modules getting the new password afterwards. This requires separating password change readiness and local password quality policy so that they can be invoked when the first authentication module requires changing an expired password, while deferring actual password changes to post-authentication.
- The most serious sequencing problem in PAM, IMO, is the fact that too many details of sequencing are exposed to the application. If the application developer gets the sequencing wrong, the result can be a disastrous security vulnerability. I.e., the PAM API is too complex because it exposes too much of the sequence of steps to the application.
Configuration Complexity and Limitations
Configuration complexity: originally PAM was easy to configure… because there were few modules and few PAM applications. Nowadays PAM configuration almost requires a PhD, and it differs significantly from one implementation to another.
Configuration is selected by the PAM_SERVICE name — the name of the application. But often we need to use different configurations according to who the user is (some users have OTP tokens, some have smartcards, …).
- Incompatible API variations across implementations. So much for portability for PAM applications….
- Back to pam_setcred(), it’d be nice if PAM could abstract all the details of privilege / user context management. In particular it’d be nice if it could provide functions to temporarily and permanently switch between up to three contexts: fully privileged, post authentication reduced privileges, and user context (which also generally has reduced privileges, of course). And while we’re at it, it’d be nice if there was a convenient interface for spawning processes (think of posix_spawn(3C)) in reduced privilege and user contexts.
- Isolation and privilege separation: some modules don’t need all privileges, but they all must run in the same process, with all privileges. Think of OTP modules, logging modules, and modules like pam_xauth. Some modules can -effectively- decide who gets in, how, and with what privileges; there’s no point isolating such modules from each other, but isolating them from third-party modules that don’t need privilege is critical. One problem is that pam_get/set_data(3PAM) is a mechanism that modules use to communicate with each other, and its semantics prevent isolation. Of course, if the only way to manage privilege were by spawning new processes in the desired context, then there would be less pressure still to run the modules in the process that’s invoking them.
- No strong C type safety for PAM items (see pam_get/set_item(3PAM)).
I’m sure I’m leaving some problems out, but that’s enough for a start anyways. There’s enough there to start!
Towards Enhancement or Replacement
This is just a what’s-wrong-with-PAM blog entry, not a how-to-fix-or-replace-PAM blog entry, but I can’t help but touch on the latter briefly.
Some of these problems can be difficult or dangerous to fix in a backwards-compatible way. Changing the API so that sequencing is done in fewer function calls would risk either that new applications will fail open when used with old PAM implementations, or that old applications will fail open when used with new PAM implementations. I think a lot can be done to improve PAM backwards-compatibly, but the best approach would probably be to have a brand new API that can use old PAM modules where appropriate. If the new API is sufficiently simple to use, then it won’t be hard to retrofit PAM applications to use it. I’m becoming biased toward outright replacement of PAM with something new. Although if one were willing to accept this problem then it should be possible to extend PAM instead of replacing it, in spite of the enormity of the improvements needed.
The main downside to this vision is that it might encourage web developers to reuse PAM/whatever in web applications, which in turn will tend to perpetuate passwords-in-forms-POSTed-over-HTTPS-plus-cookies — something I’d rather not do. But it’s possible that one might want to combine some cryptographic authentication protocol with additional authentication factors, with the latter implemented through a PAM-like interaction. So I don’t want to dismiss the use of PAM or similar in web applications, not with prejudice.
With this vision the best way to represent interactions is using JSON with a schema, as that’s easy to handle in JS and it’s easier to handle in non-browser-based applications than any other possible representation that would also work for the browser case (XHTML, XML). One benefit of using JSON instead of XHTML is that it’s easier to extend an ad-hoc JSON schema backwards-compatibly to add metadata as we find it necessary than it is to extend XHTML… To make life simpler for /bin/login and sshd developer, a utility function could be provided with which to extract text prompts and meta-data for a text-based API.
The next problem to solve would be asynchronous cancellation of prompts such as “touch finger to fingerprint reader”, “insert smartcard”, and “enter smartcard PIN” (when the smartcard has a PIN pad). My approach for this would be to have a callback function for this. Yes, it’d be nice to be able to tie the modules needing to issue such cancellations to the application’s I/O event loop, but doing so would require private interface contracts with PKCS#11‘s C_WaitForSlotEvent() entry point (or actual extensions to PKCS#11), for one example, which leads me to believe that requiring threads and callbacks for asynchronous prompt cancellation is inevitable.
Another hard nut to crack will be simplification of configuration. The original PAM configuration was simple because there were few modules. By adding enough module types to match the fine-grained sequencing we can once again reduce the number of modules needed for each step, thus simplifying configuration in one way while complicating it in another (by exposing the whole sequence of login steps to the administrator. My current idea is that ultimately this can be achieved by ensuring that there are configurations that work for every use case with very minor tweaks, then provide a simple configuration selection scheme. Also, I’d like to group modules into sets by functionality (authentication vs. account authority vs. password sync vs…) and, for each function, specify sets of all-required and any-is-sufficient modules — reminiscent of pam.conf, I know, but if we have enough discrete login steps I believe this can work.
For remote login application integration I believe we need interfaces by which applications can give to the framework the following types of objects:
- GSS-API peer name, security context, and delegated credential objects;
- Peer certificate (e.g., for TLS user cert authentication use cases);
- Peer public key type and key (for SSHv2 pubkey userauth);
- Remote IP address, port number, …;
- The actual socket(s)? There is getpeeucred(3C) on Solaris, after all, which someday might add value with IPsec and IPsec channels…
(In general, if we could model as much remote authentication information as possible as GSS-API name objects, it would be a huge simplicity win. This could be as simple as defining incomplete GSS mechanisms with simple exported name token forms so that certificates, public keys, and so on could easily be imported, encapsulated in, and manipulated as a GSS-API name object.)
I could, and yet may go into excruciating detail of some or all of the problem areas listed above, but ~2100 words is a good start. As for extensions to PAM or a replacement for PAM, that topic really deserves a separate blog entry, or github repo.