|
|
Subscribe / Log in / New account

Development

Testing PAM modules and applications in the Matrix

January 13, 2016

This article was contributed by Andreas Schneider and Jakub Hrozek

A new tool, called pam_wrapper, was developed by the article authors; it makes it easy to either test an application that uses pluggable authentication modules (PAM) to authenticate a user, or to develop test cases to make sure that a PAM module under development is working correctly. It is a tool that enables developers to create unit tests for their PAM-using code in a simple manner.

PAM is a layer of abstraction on top of Unix authentication. It is written so that applications don't have to worry about the underlying authentication scheme, which is implemented in a module. If you're not familiar with PAM you can learn more here.

A "PAM conversation" is part of the process of doing authentication using PAM. It is essentially a question and answer game between the user and the system that is being used to authenticate the user. Normally, it just asks for a username and password, but it could also ask the user for the username then ten questions about Star Wars before actually asking for the password and authenticating the user.

Pam_wrapper is a component of the cwrap project, which provides a set of tools that make testing easier. Due to its origin in the Samba project, cwrap is especially targeted at client/server testing. Pam_wrapper is a preloadable library similar to the other cwrap components.

About pam_wrapper

The authors are working on different software projects like Samba, sssd, and libssh. Samba and sssd provide PAM modules and, until now, there were no tests for authentication using the modules available. There was no easy way to achieve that without a fully configured environment, so tests were done by people who run the modules in production or by dedicated QA teams.

The libssh project runs tests against the SSH daemon from the OpenSSH project. This was only possible in a special environment with root privileges. With pam_wrapper and the PAM module it provides, you can now run the OpenSSH daemon as a normal user performing the PAM conversation to test interactive logins. This means pam_wrapper is useful for both writing tests for PAM modules or using it to handle PAM conversations.

Testing either PAM modules or PAM applications does not require root privileges when using pam_wrapper. You can also set up a dummy user account database to test against.

Testing applications

In theory, testing PAM applications shouldn't require too much instrumentation. A PAM service file allows the administrator to specify a full path, which can point to a PAM module under test; both the PAM application itself and the module can usually run unprivileged. The problem is with the location that the PAM service files are loaded from — the directory (typically /etc/pam.d) is hardcoded in libpam.so during configure time, and there is no way to override it at runtime. The pam_wrapper library allows the developer to specify an alternate directory with PAM service files, which can point to different service configurations or include test modules. This also removes the requirement to run tests as root, because the test configurations can be stored under the UID running the test.

Pam_wrapper is a preloadable library. Preloading is a feature of the dynamic linker that loads the user-specified libraries before all others. Note that if you try to preload a library for binaries that have the suid or sgid bit set (see the chmod(1) man page), the user-specified preloaded libraries are ignored. The pam_wrapper library wraps all functions of libpam.so and allows you to define your own service directory for each test:

    LD_PRELOAD=libpam_wrapper.so PAM_WRAPPER=1 \
        PAM_WRAPPER_SERVICE_DIR=/path/to/servicedir ./myapplication

This command would run myapplication and tell libpam.so to read service files from the directory /path/to/servicedir instead of /etc/pam.d. The PAM_WRAPPER environment variable must be set to enable the library, which should restrict the ability to use it for attacks of any sort.

A service directory normally contains one file for the service that the test is being run against. For example, if you want to authenticate using sshd, your service file name would be sshd. In the file you need to specify which of the management groups the subsequent module is to be associated with. Valid entries are account, auth, password, and session.

The management groups handle different phases of the authentication process. The auth group modules manage authentication (i.e. if the user is who they claim to be), while the account group verifies that the user is permitted to do the action they are trying to do; it normally runs after authentication. The password group is used for password changes and the session group sets up the user environment — it can mount user-private directories, for example. They are described in the pam.d(5) man page.

Testing an application with pam_matrix

Another issue developers face when developing tests for PAM applications is that there must be some database that the tests authenticate against. A very simple test could use the pam_permit or pam_deny modules that either allow or deny all requests, but that doesn't provide tests that are like real deployments. Therefore, the pam_wrapper project added a simple PAM module called pam_matrix.so that will authenticate against a simple text database.

Let's assume you want to run tests against a server that requires PAM to authenticate users. This application uses PAM service file myapp. Normally, you would need a real user in the system with a password set — but this might not be possible in environments like Continuous Integration (CI) systems or on build hosts. Pam_wrapper and the pam_matrix module allow you to authenticate users via PAM without requiring an account on the local machine.

For that you need to create a service file that looks like this:

    auth        required    pam_matrix.so passdb=/tmp/passdb
    account     required    pam_matrix.so passdb=/tmp/passdb
    password    required    pam_matrix.so passdb=/tmp/passdb
    session     required    pam_matrix.so passdb=/tmp/passdb

Save this file as myapp and place it in a directory. Later, this directory will be referenced in the PAM_WRAPPER_SERVICE_DIR variable. The passdb option defines a file that contains users with a plain-text password for a specified service. The syntax of the file is:

    username:password:allowed_service

An example for that is:

    bob:secret:myapp

As an alternative to using the passdb PAM module option, it's possible to specify the database location by setting the PAM_MATRIX_PASSWD environment variable.

Testing a module with libpamtest and pam_wrapper helper modules

Writing tests for PAM applications or modules can be a tedious task. Each test would have to implement some way of passing data like passwords to the PAM modules executing the test (probably via a conversation function), run the PAM conversation, and collect output from the module or application under test. To simplify writing these tests, we added a library called libpamtest to the pam_wrapper project. This library allows the test developer to avoid code duplication and boilerplate code, and focus on writing tests instead. The libpamtest library comes with fully documented C and Python APIs.

Each libpamtest-driven test is defined by one or more instances of the structure pam_testcase that describes what kind of test is supposed to run (authentication, password change, ...) and what the expected error code is, so that both positive and negative tests are supported. The array of pam_testcase structures is then passed to a function called run_pamtest() that executes them with the help of a default conversation function provided by libpamtest. If the test requires a custom conversation function, another test driver called run_pamtest_conv() is also available that allows developers to supply their own conversation function.

The default conversation function provided by libpamtest allows the programmer to supply conversation input (typically a password) and also a string array that would capture any output that the conversation emits during the PAM transaction. As an example, the following test calls the PAM change password function, changes the password, and then verifies the new password by authenticating using the new password:

    enum pamtest_err perr;
    const char *new_authtoks[] = {
        "secret"              /* login with old password first */
        "new_secret",         /* provide a new password */
        "new_secret",         /* verify the new password */
        "new_secret",         /* login with the new password */
        NULL,
    };
    struct pamtest_conv_data conv_data = {
        .in_echo_off = new_authtoks,
    };
    struct pam_testcase tests[] = {
        /* pam function to execute and expected return code */
        pam_test(PAMTEST_CHAUTHTOK, PAM_SUCCESS),
        pam_test(PAMTEST_AUTHENTICATE, PAM_SUCCESS),
    };

    perr = run_pamtest("matrix",             /* PAM service */
                       "trinity",            /* user logging in */
                        &conv_data, tests);  /* conversation data and array of tests */

As you can see, the test is considerably shorter than a hand-written one would be. In addition, the test developer doesn't have to handle the conversation, or open and close the PAM handle. Everything is done behind the scenes.

If one of the PAM transaction steps failed (for example if the passwords didn't match the database), the perr return variable would indicate a test failure with value PAMTEST_ERR_CASE. The developer could then fetch the failed case using the pamtest_failed_case() function and examine the test case further.

In addition to the standard PAM actions like AUTHENTICATE or CHAUTHTOK, libpamtest also supports several custom actions that might be useful in tests. One is PAMTEST_GETENVLIST, which dumps the full PAM module environment into the test case's output data field. Another is PAMTEST_KEEPHANDLE, which prevents the PAM handle from being closed — the test could go and perform custom operations on the handle before closing it by calling pam_end().

Module stacking

Another aspect that is normally quite hard to test is module stacking. That is, testing that your module is able to read a password that is provided by another module that was executed earlier in the stack. This is a quite common setup especially for PAM modules that handle authenticating remote users. Since local users should take precedence, the password would be read by pam_unix first and passed down the stack if no local user could be authenticated. Conversely, your module might pass an authentication token on to the PAM stack for other modules (such as Gnome Keyring's PAM module) that come later in the stack.

Normally, handling these stack items is only allowed from the module context, not application context. Because the test runs in the application context, we had to develop a way to pass data between the two. So, in order to test the stacking, two simple modules called pam_set_items.so and pam_get_items.so were added.

The purpose of pam_set_items.so is to read environment variables with names corresponding to internal PAM module items and to put the data from the environment variables onto the stack. The pam_get_items.so module works in the opposite direction, reading the PAM module items and putting them into the environment for the application to read. Suppose you wanted to test that the pam_unix.so module is able to read a password from the stack and later pass it on. The PAM service file for such a test would look like this:

    auth required        /absolute/path/to/pam_set_items.so
    auth required        pam_unix.so
    auth required        /absolute/path/to/pam_get_items.so

The test itself would first set the auth token into the process environment with putenv(), run the test, and then make sure the token was put into the PAM environment by the module by calling pam_getenv(). It's very convenient to use libpamtest's PAMTEST_GETENVLIST test case to read the PAM environment:

    pamtest_err perr;
    const char *new_authtoks[] = {
        "secret"                    /* password */
    };
    struct pamtest_conv_data conv_data = {
        .in_echo_off = new_authtoks,
    };
    struct pam_testcase tests[] = {
        pam_test(PAMTEST_AUTHENTICATE, PAM_SUCCESS),
        pam_test(PAMTEST_GETENVLIST, PAM_SUCCESS),
    };
    setenv("PAM_AUTHTOK", "secret");

    perr = run_pamtest("matrix", "trinity", &conv_data, tests);
    /*
     * tests[1].case_out.envlist now contains list of key-value strings,
     * find PAM_AUTHTOK to see what the authtok is.
     */

Finally, because it is often inconvenient to write tests in a low-level programming language like C, we also developed Python bindings for libpamtest. Using the Python bindings, an authentication test might look like this:

    def test_auth(self):
        neo_password = "secret"
        tc = pypamtest.TestCase(pypamtest.PAMTEST_AUTHENTICATE)
        res = pypamtest.run_pamtest("neo", "matrix_py", [tc], [ neo_password ])

Of course libpamtest can be used with or without pam_wrapper's preloading and custom PAM service location.

Where to go from here?

We hope that this tool is useful for those developers who have struggled testing their PAM modules and applications. The authors are looking forward to more projects that implement tests for PAM modules. We are also looking forward for feedback for the current API and usability of pam_wrapper.

At the moment, only Linux-PAM and OpenPAM (FreeBSD) are tested and supported by pam_wrapper. The code is maintained in Git on the Samba Git server. If you want to discuss pam_wrapper you can do that on the samba-technical mailing list. For discussions, you can also join #cwrap on irc.freenode.net.

Comments (none posted)

Brief items

Quotes of the week

I have been sitting on this for a long time, and now I want to say it: open and accessible doesn’t beat usable and intelligent. It is something we really have to get past in the open source and web world if we want what we do to stay relevant. I’m tired of crap interfaces being considered better because they are open. I’m tired of people slagging off great tools and functionality because they aren’t open.
Christian Heilmann

The big problem with Android build is its non-modularity. You need to build the world and more to get a single image in the end. Instead of packages that could be built independently, that generated modular package images, that could be bundled in a single system image. Better yet would be the ability to pick only those components that matter.

Certainly, portability would be just as interesting, being able to build certain components on top a GNU system with GNU libc.

At times, it seems like this is done by design, to make it difficult for "fragmentation" and competition. Basically, making it difficult to exercise the freedom to modify the software and share your modifications with others.

Thadeu Lima de Souza Cascardo, in his detailed examination of the free-ness of smartphone software.

Comments (18 posted)

PostgreSQL 9.5 released

PostgreSQL 9.5 has been released with lots of new features for the database management system, including UPSERT, row-level security, and several "big data" features. We previewed some of these features back in July and August. "A most-requested feature by application developers for several years, 'UPSERT' is shorthand for 'INSERT, ON CONFLICT UPDATE', allowing new and updated rows to be treated the same. UPSERT simplifies web and mobile application development by enabling the database to handle conflicts between concurrent data changes. This feature also removes the last significant barrier to migrating legacy MySQL applications to PostgreSQL."

Full Story (comments: 44)

Openfire 4.0.0 released

Version 4.0.0 of the Openfire XMPP chat server has been released. There is an extensive changelog; users are also advised that many of the available plugins have been updated and will no longer work with pre-4.0 Openfire releases.

Comments (none posted)

Ansible 2.0 released

Version 2.0 of the Ansible configuration management system has been released. "This is by far one of the most ambitious Ansible releases to date, and it reflects an enormous amount of work by the community, which continues to amaze me. Approximately 300 users have contributed code to what has been known as 'v2' for some time, and 500 users have contributed code to modules since the last major Ansible release." New features include playbook-level exception handling, better error diagnostics, a new set of OpenStack modules, and more. See the changelog for more (terse) details.

Comments (none posted)

GNU Health 3.0 released

Version 3.0 of the GNU Health electronic medical record (EMR) system has been released. Among the added features are support for flexible patient name formats, improved reporting, updated unit tests, and the addition of modules for ophthalmology and updated World Health Organization procedural codes.

Comments (none posted)

openHAB 1.8 is available

Version 1.8 of the openHAB home-automation system has been released, along with the first beta for the upcoming 2.0 release. New in 1.8 are bindings for the RWE automation protocol popular in Germany and the Local Control Network (LCN) protocol popular with professional audio-video installers. In addition, the old Google Calendar plugin has been replaced with a general-purpose CalDAV plugin.

Comments (none posted)

Ardour 4.6 released

Version 4.6 of the Ardour audio editor is available. "4.6 includes some notable new features - deep support for the Presonus FaderPort control surface, Track/Bus duplication, a new Plugin sidebar for the Mixer window - as well as the usual dozens of fixes and improvements to all aspects of the application, particularly automation editing." The full list of enhancements is quite long; see the announcement for details.

Comments (none posted)

Newsletters and articles

Development newsletters from the past week

Comments (none posted)

Akonadi – still alive and rocking

At his blog, Daniel Vrátil provides an extensive update on the status of Akonadi, the KDE project's personal information management (PIM) data service. He focuses on the changes made during the port to KDE Frameworks 5, starting with the switch from a text-based to a binary protocol. "This means we spent almost zero time on serialization and we are able to transmit large chunks of data between the server and the applications very, very efficiently." The ripple effects include changes to the database operations and, eventually, to the public API. Finally, he addresses the disappearance of the KJots note-taking application. "What we did not realize back then was that we will effectively prevent people from accessing their notes, since we don’t have any other app for that! I apologize for that to all our users, and to restore the balance in the Force I decided to bring KJots back. Not as a part of the main KDE PIM suite but as a standalone app."

Comments (47 posted)

Page editor: Nathan Willis
Next page: Announcements>>


Copyright © 2016, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds