LDAP provider integration tests

Related tickets:

Problem statement

We’d like to run some sssd/LDAP integration tests during day-to-day development. They should be low-overhead, completing in under 5 minutes, and run as part of “make check” and “contrib/ci/run”, under a non-privileged user. They may require special configure options to be executable, and be skipped if the options are not provided.

Use cases

A developer modifies a part of LDAP-involved data path and wishes to quickly check sanity of the change. He/she then runs “make check” or “contrib/ci/run”, which include the LDAP integration tests.

A developer submits a change (possibly) affecting the LDAP-involved data paths and a reviewer wishes to check the sanity of the change before ACK’ing it. The reviewer then requests a CI job run, which includes the LDAP integration tests.

Overview of the solution

The suite should use pytest test framework.

Tests are executed as part of “make check”, which is also included into “contrib/ci/run”. As our Makefiles use Automake’s parallel test execution harness and sssd data and socket directories are compiled-in currently and cannot be shared, there can only be one Automake-level integration test suite. Any possible parallelization should be implemented within.

Because “make check” and “contrib/ci/run” are supposed to be executable in largely arbitrary environments and under regular users, the sssd needs to be tricked into believing it is running under a root account and tests need to be tricked into using libnss_sss and pam_sss from the build instead of the NSS and PAM services specified for the system. The first two can be done with the help of “cwrap” wrappers. The latter would require cwrap support for the PAM library, which isn’t implemented yet, but might be in the future. As of now, only libnss_sss can be tested.

Because default, compiled-in sssd data and socket locations are not accessible to regular users, and there is currently no way to change them after the build, running the tests will require configuring the build with user-writeable locations. Otherwise the tests will be skipped during the “make check” run and Automake will report them as such. It is possible that in the future a way to change them after the build will be implemented and this requirement will be lifted.

Implementation details

All tests are invoked with src/tests/cwrap/cwrap_test_setup.sh sourced into the shell, which sets up NSS and UID wrappers to make tests assume they’re running under root and use libnss_sss from the build tree.

At the moment, running the tests requires configuring the build to have data and sockets located in user-writeable directories. The specific locations might be communicated to the test suite via a configure-generated Python or Bash module, or a C program outputting them when invoked. If at least one of these locations is non-writeable, the test suite will exit to Automake with code 77, indicating SKIPPED status.

However, a way to change these at startup time might be implemented later, removing this requirement. E.g. data and socket directories might be specified in the configuration file for the sssd daemons, and the socket location might be specified to libnss_sss and pam_sss via an environment variable. See #2545.

The OpenLDAP server can be executed with configuration and databases located under arbitrary (temporary) directories which will be created during testing. It is unknown yet how to make 389-ds do the same.

The communication with the LDAP server can be left unencrypted at least for the start, simplifying setup and debugging.

The LDAP server setup/teardown (for either of the servers) will be done in Bash to simplify initial development and later possibly converted to (a bit more robust) Python, when all the details are clear. The setup/teardown scripts will be executed from a pytest fixture setup/teardown.

The pytest suite will do further setup itself according to specific test requirements, including: directory population/cleanup, generating sssd configuration, starting/stopping sssd.

The tests themselves might include listing/retrieving rfc2307(bis) user and group information, including nested groups, perhaps using the standard “pwd” and “grp” modules. Some of the tests that can be implemented initially follow, most useful first.

Sanity

Fixture rfc2307:
    enumerate = true / false
    ldap_schema = rfc2307
    3 users
    3 user groups
    1 empty group
    1 two-user group

Fixture rfc2307bis:
    Fixture rfc2307
    ldap_schema = rfc2307bis
    1 group with empty group inside
    1 group with two empty groups inside
    1 group with a single-user group inside
    1 group with a two-user group inside
    1 group with two single-user groups inside
    A basic group membership loop: A->B->A
    A branched group membership loop: A->B, A->D, A->C->A

Tests:
    List all users/groups with pwd.getpwall/grall()
    Retrieve a user/group by UID/GID with pwd.getpwuid/grgid()
    Retrieve a non-existent user/group by UID/GID with pwd.getpwuid/grgid()
    Retrieve a user/group by name with pwd.getpwnam/grnam()
    Retrieve a non-existent user/group by name with pwd.getpwnam/grnam()

Cache

Fixture:
    enumerate = true / false
    enum_cache_timeout = 4s
    ldap_enumeration_refresh_timeout = 0
    3 users
    3 user groups

Tests:
    Cache refresh
    1. Enumerate users/groups with pwd.getpwall/grall()
    2. Within enum_cache_timeout:
        2.1 Add/remove user/group
        2.2 Enumerate users/groups with pwd.getpwall/grall(),
            check for change absence
    3. After enum_cache_timeout passed from step 1:
       enumerate users/groups with pwd.getpwall/grall(), check for change
    No-wait percentage
    ...
    Negative timeout
    ...

Filter users/groups

Fixture:
    3 users
    3 user groups
    filter_users/groups: none/one/two

Tests:
    Enumerate users/groups with pwd.getpwall/grall()
    Retrieve a filtered user/group by UID/GID with pwd.getpwuid/grgid()
    Retrieve a non-filtered user/group by UID/GID with pwd.getpwuid/grgid()

Override homedir

Fixture:
    1 user with homedir A
    1 user without homedir
    override_homedir = B

Tests:
    Retrieve the users with pwd.getpwuid/nam/all()

Fallback homedir

Fixture:
    1 user with homedir A
    1 user without homedir
    fallback_homedir = B

Tests:
    Retrieve the users using pwd.getpwuid/nam/all()

Override shell

Fixture:
    1 user with shell A
    1 user without shell
    override_shell = B

Tests:
    Retrieve the users using pwd.getpwuid/nam/all()

Vetoed shells / shell fallback

Fixture:
    1 user with shell A
    1 user with shell B
    1 user without shell
    override_shell = C

Tests:
    Retrieve the users using pwd.getpwuid/nam/all()

Default shell

Fixture:
    1 user with shell A
    1 user without shell
    default_shell = B

Tests:
    Retrieve the users using pwd.getpwuid/nam/all()

Configuration changes

Sssd, libnss_sss and pam_sss might require changes allowing configuration of data and socket locations.

Authors

Nikolai Kondrashov with help from Martin Kosek, Jakub Hrozek, Lukas Slebodnik and Simo Sorce.