Understanding PAM

34404

Author: JT Smith

Pluggable Authentication Modules (PAM) is an oft misunderstood, and in at
least this admin’s opinion, underutilized mechanism on *nix systems. Sitting
in its little corner of the /etc directory, PAM sits overlooking its
configuration files and man pages, just waiting for someone to come along
and discover the power that it can give to administrators and developers
alike. When will they realize that PAM is not just for ‘Authentication’
anymore?

PAM is a collection of modules that essentially form a barrier between a
service on your system, and the user of the service. The modules can have
widely varying purposes, from disallowing a login to users from a particular
UNIX group (or netgroup, or subnet…), to implementing resource limits so
that your ‘research’ group can’t hog system resources.

PAM is used by major commercial UNIX flavors such as AIX, HP-UX and Solaris,
as well as the major free versions of UNIX, like FreeBSD. Almost all
distributions of Linux also implement PAM, though I believe Slackware is
still the one notable holdout in this area.

The power, flexibility and ubiquity of PAM is a boon for developers of Linux
applications, since instead of writing a full-fledged authentication layer
for their program, they can simply write a hook into PAM, and administrators
can configure it along with the rest of the system services using PAM. This,
in turn, makes the lives of administrators much easier, since we only have
to learn PAM, instead of learning and trying to remember a separate
configuration model for every system we decide to run.

How to Read A Config File

I’ll be looking at pam as it applies to Linux systems. Solaris and other
commercial UNIX systems have a slightly different configuration model,
centered around a single file, /etc/pam.conf. Though conceptually the two
implementations are just about identical, the Linux model uses a different
configuration file for each service which uses PAM. On most Linux systems,
these configuration files live in /etc/pam.d, and are named after the
service – for example, the ‘login’ configuration file is called
/etc/pam.d/login. Let’s have a quick look at a version of that file:


auth required /lib/security/pam_securetty.so
auth required /lib/security/pam_nologin.so
auth sufficient /lib/security/pam_ldap.so
auth required /lib/security/pam_unix_auth.so try_first_pass
account sufficient /lib/security/pam_ldap.so
account required /lib/security/pam_unix_acct.so
password required /lib/security/pam_cracklib.so
password required /lib/security/pam_ldap.so
password required /lib/security/pam_pwdb.so use_first_pass
session required /lib/security/pam_unix_session.so

PAM Management Realms

The first thing I’d like you to notice in the file is the leftmost column,
which, in all, contains four unique words, which represent four realms of
PAM management: auth, account, password and session. While there are many
modules which support more than one of these realms (indeed, pam_unix
supports all of them), others, like pam_cracklib for instance, are only
suited for one (the ‘password’ facility in pam_cracklib’s case). Knowing
what these four realms are responsible for is extrememly important if you
want to make effective use of PAM. Below is a quick explanation of how I
tend to think about these keywords in practice:

auth
The ‘auth’ realm (I call it a realm – the docs refer to it as a
‘management group’ or ‘facility’) is responsible for checking that
the user is who they say. The modules that can be listed in this
area generally support prompting for a password.
account
This area is responsible for a wide array of possible account
verification functionality. There are many modules available for
this facility. Constraints to the use of a service based on checking
group membership, time of day, whether a user account is local or
remote, etc., are generally enforced by modules which support this
facility.
password
The modules in this area are responsible for any functionality
needed in the course of updating passwords for a given service. Most
of the time, this section is pretty ‘ho-hum’, simply calling a
module that will prompt for a current password, and, assuming that’s
successful, prompt you for a new one. Other modules could be added
to perform password complexity or dictionary checking as well, such
as that performed by the pam_cracklib and pam_pwcheck modules.
session
Modules in this area perform any number of things that happen
either during the setup or cleanup of a service for a given user.
This may include any number of things; launching a system-wide
initialization script, performing special logging, mounting the
user’s home directory, or setting resource limits.

PAM Module Controls

The middle column holds a keyword that essentially determines what PAM
should do if the module either succeeds or fails. These keywords are called
‘controls’ in PAM-speak. Note that this keyword is not an indicator to the
module, but to the underlying PAM library. In probably 90% of the cases, you
can use one of the common keywords (requisite, required, sufficient or
optional). However, this is only the tip of the iceberg in terms of
unleashing the flexibility and power of PAM. You could actually use more
complicated syntax to account for seemingly any situation. Here are the
valid simple ‘one-word’ keywords PAM understands:

required
If a ‘required’ module returns a status that is not ‘success’,
the operation will ultimately fail, but only after the modules below
it are invoked. This seems senseless at first glance I suppose, but
it serves the purpose of always acting the same way from the point
of view of the user trying to utilize the service. The net effect is
that it becomes impossible for a potential cracker to determine
which module caused the failure – and the less information a
malicious user has about your system, the better. Important to note
is that even if all of the modules in the stack succeed, failure of
one ‘required’ module means the operation will ultimately fail. Of
course, if a required module succeeds, the operation can still fail
if a ‘required’ module later in the stack fails.
requisite
If a ‘requisite’ module fails, the operation not only fails, but
the operation is immediately terminated with a failure without
invoking any other modules: ‘do not pass go, do not collect $200’,
so to speak.
sufficient
If a sufficient module succeeds, it is enough to satisfy the
requirements of sufficient modules in that realm for use of the
service, and modules below it that are also listed as ‘sufficient’
are not invoked. If it fails, the operation fails unless a module
invoked after it succeeds. Important to note is that if a ‘required’
module fails before a ‘sufficient’ one succeeds, the operation will
fail anyway, ignoring the status of any ‘sufficient’ modules.
optional
An ‘optional’ module, according to the pam(8) manpage, will only
cause an operation to fail if it’s the only module in the stack for
that facility.

Stacking Modules

In the rightmost column we see the actual path to the module that gets
invoked. Note that in our sample config, four separate modules are listed
for the ‘auth’ realm. This is referred to as ‘stacking’ in PAM lingo. If
you’re thinking that this implies that ordering of the stacked modules is
significant, you’re right. Also, in addition to the more complex options you
can pass to the PAM library in the center column, there are, likewise,
options that you can feed to the module itself. This can be seen in our
example config file, where the pam_unix.so module takes the ‘try_first_pass’
argument. Furthermore, many modules are meant to be used with an
accompanying configuration file of some sort, allowing for more complex
options that would make the PAM config file a big mess otherwise.

Drilling Down: What’s Going On?

Now that we have a grip on how to basically parse a configuration file at a
high level, let’s drill down and see exactly what’s going on in our example
file. We’ll have a look at the module stack for the ‘auth’ realm, detailing
what will happen given a number of scenarios.

In our example file, we have four modules stacked for the auth realm:


auth       required     /lib/security/pam_securetty.so
auth       required     /lib/security/pam_env.so
auth       sufficient   /lib/security/pam_ldap.so
auth       required     /lib/security/pam_unix.so try_first_pass

As the modules are invoked in order, here is what will happen:

  1. The ‘pam_securetty’ module will check its config file,
    /etc/securetty, and see if the terminal being used for this login is
    listed in the file. If it’s not, root logins will not be permitted. If
    you try to log in as root on a ‘bad’ terminal, this module will fail.
    Since it’s ‘required’, it will still invoke all of the modules in the
    stack. However, even if every one of them succeeds, the login will fail.
    Interesting to note is that if the module were listed as ‘requisite’,
    the operation would terminate with a failure immediately, without
    invoking any of the other modules, no matter what their status.
  2. The ‘pam_env’ module will set environment variables based on what
    the administrator has set up in /etc/security/pam_env.conf. On a default
    setup of Redhat 9, Fedora Core 1, and Mandrake 9.2, the configuration
    file for this module doesn’t actually set any variables. A good use for
    this might be automatically setting a DISPLAY environment variable for a
    user logging in via SSH so they don’t have to set it themselves if they
    want to shoot an ‘xterm’ back to their remote desktop (though this can
    be taken care of by OpenSSH automagically).
  3. The ‘pam_ldap’ module will prompt the user for a password, and then
    check the ldap directory indicated in /etc/ldap.conf to authenticate the
    user. If this fails, the operation can still succeed if ‘pam_unix’
    succeeds in authenticating the user. If pam_ldap succeeds, ‘pam_unix’ will not
    be invoked.
  4. The ‘pam_unix’ module, in this case, will not prompt the user for a
    password. The ‘try_first_pass’ argument will tell the module to use the
    password given to it by the preceding module (in this case, pam_ldap).
    It will try to authenticate the user using the standard getpw* system
    calls. If pam_unix fails, and pam_ldap has failed, the operation will
    fail. If pam_ldap fails, but pam_unix succeeds, the operation will
    succeed (this is extremely helpful in cases where root is not in the
    ldap directory, but is still in the local /etc/passwd file!).

Depending on the feedback and corrections submitted for this article, I’m
happy to go into PAM further or with some other focus in a future
article. If you’ve done something amazing with PAM or notice a common
problem users have with PAM that hasn’t been discussed here, please send me
email (njcajun – linuxlaboratory dot org) or post a comment.