In a previous article, I described the flow of an application calling PAM libraries for authentication at a very high level. In this article, we will walk through the configuration files for a local
sudo, we switch users and do something. This privilege change requires verification that we are who we say we are and are allowed to perform the given action. The
/etc/sudoers file controls who can do what, but the process still calls PAM for any authentication checks. As a part of these calls, the process identifies itself, and then
libpam looks for a matching configuration file in the
$ cat /etc/pam.d/sudo #%PAM-1.0 #Type ReturnCode Modules Options auth include system-auth account include system-auth password include system-auth session optional pam_keyinit.so revoke session required pam_limits.so session include system-auth
Like many other *.d directories, package management may add or remove a file from this directory. The
sudo RPM adds the
$ rpm -qf /etc/pam.d/sudo sudo-1.9.0-0.1.b4.fc31.x86_64
An upstream version might have a variety of entries, but this distribution-provided package includes a configuration file that has several include statements to the common
/etc/pam.d/system-auth file which is supplied by the
Configuration file fields
The first field in this file identifies a Type of call made to PAM. Lines of the same type are grouped together. There are four types: auth, account, password, and session. Look at
man pam for a description of each type.
The second field is known as the ReturnCode. This field lets PAM know how to handle the results of the module test. Return codes indicate whether a test is required or optional. The codes might also be used to indicate the line is not a module test with options but rather the name of another configuration file with additional checks. A full description of the return codes is found in
man pam.conf, and the most common ones are discussed later in this article.
The rest of the line contains the module name and options for that module. The module name must match a module available in the
/etc/lib64/security directory. The options may be different depending on the type of call made. Some modules only perform tests for some types of calls. Use the man page for each module to see examples and learn about the uses and options available.
The order of entries within a type of call matters. This is mostly due to how the return codes are processed, but in some cases because of a module action. When
libpam receives a "done" or "die" message, it reports the overall result back to the calling process.
The configuration for
sudo has several include lines. These lines tell
libpam to include all lines of given type from the configuration file specified. There is also a substack option, which is similar in how it includes lines from a given configuration file, but on a "done" or "die," it reports back to the substack instruction instead of the original process calling
libpam. Older versions of PAM had other variations on how other configuration files were included. For example, when I started exploring PAM almost 20 years ago, there was a specific module called with the configuration files as an argument. The keyword "include" was not valid for the ReturnCode field.
Return codes in the central configuration files
/etc/pam.d/sudo file shown earlier is pretty short. Three of the four call types have only an include of another file. The
/etc/pam.d/system-auth file is more typical of a configuration file, with many checks for each type of call.
$ cat /etc/pam.d/system-auth #%PAM-1.0 # This file is auto-generated. # User changes will be destroyed the next time authconfig is run. auth required pam_env.so auth sufficient pam_unix.so nullok try_first_pass auth requisite pam_succeed_if.so uid >= 1000 quiet_success auth sufficient pam_sss.so forward_pass auth required pam_deny.so ...omitted...
The required keyword is perhaps the most common. It indicates that the module must pass the check for an overall pass to be handed back to the application. However, even on a failure, the following lines in that type will still be checked. This is a long time practice of not sharing any reason for an authentication failure. On systems 20 years ago, it might have been possible to guess how the authentication failed by how long it took to fail.
The requisite keyword is similar to required in that the check must pass, but in the case of a failure, it returns a "die" message. Requisite lets
libpam know that the following lines will not be checked, and to inform the calling process of the overall results—in this case, a failure.
The sufficient keyword is almost the opposite of requisite. On success, a "done" message is returned, and
libpam goes ahead and sends its overall results back to the calling application. Other results from this module are ignored, and checking continues.
The sufficient keyword is common when there could be multiple ways of verifying a criterion. For example, when verifying a password, the user might be defined in the local
/etc/shadow files, or they might only be defined in a central system accessed with
pam_unix module checks the local files. If there is success, there is no need to continue and check the centralized services. However, if the user is not defined locally, we do not want to record a failure, we want to ignore the result and try the
pam_sss module. Since the sufficient keyword never truly fails, it is common to add a required
pam_deny line after a series of sufficient lines. The
pam_deny module always fails much like the
The optional keyword is similar to sufficient in that it ignores any failures. However, on success, it acts more like the required keyword by setting an "ok" value and continuing to perform any additional checks.
Since both requisite and sufficient can be exit points from the stack of modules, the order in the configuration file is important. Lines after those keywords may or may not get executed.
Complex return codes
Beyond the simple keyword syntax, complex return codes are defined with key-value pairs inside square brackets. The
/etc/pam.d/system-auth file has a sample in the session section of the more complex syntax.
$ cat /etc/pam.d/system-auth ...omitted... session optional pam_keyinit.so revoke session required pam_limits.so -session optional pam_systemd.so session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid session required pam_unix.so
You can find the complex syntax matching each keyword in the
pam.conf man page. The
- shown above is also defined in the man page. It indicates that logging can be skipped if the module is not installed on the system.
Now that we can walk through when a call and exit occurs with
libpam, the next step is to better understand the use case for each module. Most modules have a man page that explains the use and shows examples of lines that should appear in the
pam.d configuration files. Some of the modules also reference supplemental files in the
/etc/security directory. Those files are well commented and also often have their own man page. The
pam_limits modules are good examples to get started with.
In the next article, I will walk through some of the changes made using the
authconfig utility. If you want to jump ahead to editing files yourself and you have a Red Hat Learning Subscription, check out the chapter on PAM in the Red Hat Security: Linux in Physical, Virtual, and Cloud (RH415) course.
[ Free online course: Red Hat Enterprise Linux technical overview. ]