Implemented as of commit ced8f8933673f4efda1d666d26a1a949602035ed on April 29th 2016.
1. Problem Statement
Many Linux applications rely on a user's membership in a group to define access-control rules. In many cases, these groups are hard-coded by the application (particularly third-party and closed source applications). This can make managing access control very difficult in an environment where users are being managed by a central domain such as FreeIPA or Microsoft Active Directory.
In most cases, when a package is installed on the local system, it will create the management group it requires for operation. In order to manage membership in this group centrally, there are generally two mechanisms that are used, both with flaws.
- A configuration management tool such as puppet, chef or ansible is used to distribute edits to the /etc/group file across machines.
- Pros: Configuration management can handle differentiating the hard-coded group from different machines.
- Cons: Configuration management requires additional infrastructure beyond simply a domain controller/directory.
- The local entry for the group is removed from the /etc/group file and it is maintained in the directory.
- Pros: The group is always managed from the directory, so there is a single place to maintain changes to your entire infrastructure.
- Cons: It's very difficult to produce a different version of a group for different machines (requires LDAP views or similar technology). It also does not allow you to easily manage machine-local accounts as opposed to domain accounts.
At present, the name-service interface will (by default) stop at the first entry in the nsswitch.conf backend list for which a lookup succeeds.
We propose the addition of a new directive in the nsswitch.conf for the 'group' database. We would like to see [SUCCESS=merge] added that will behave as follows (using the example of the 'files' and 'sss' backends to illustrate).
/etc/group contains the following line:
When requested directly from the 'sss' backend, we would receive:
In the current case, with the standard nsswitch.conf configuration, we see:
[user@system:~]$ getent group wheel wheel:x:10:localadmin
This proposal suggests adding the following directive added:
group: files [SUCCESS=merge] sss
The effect would be for the output to now show:
[user@system:~]$ getent group wheel wheel:x:10:localadmin,remoteadmin
3. Special Cases
3.1. Group name and GID do not match between databases
The glibc interface should verify that all subsequent lookups of the group return the same name/GID pair. If they do not, that database should be treated as NSS_NOTFOUND.
This is to avoid situations where an LDAP or other database might have a different GID number for a particular name (or vice-versa). In those cases, we should simply ignore them and rely only on the previous databases (usually 'files').
This restriction is in place to make getgrnam and getgrgid consistent, so that looking up by gidnumber returns the same result as looking up by group name, sticking to the example above looking up by gid number = 10 gives the same list of users:
[user@system:~]$ getent group 10 wheel:x:10:localadmin,remoteadmin
3.2. Group membership has overlap between databases
The glibc interface that is managing the databases should perform a uniqueness check so that it only returns a single instance of the member user name.
3.3. Iterating over all groups and merging
The getgrent, setgrent, endgrent functions iterate over all entries in all databases. Multiple backends may have the same group. For a database with a MERGE after it, you must lookup the group in the next merged NSS backend, and merge, before returning the iterated result. Then when you move to the next backend, you must skip those results you already reported earlier. Merging is probably the only sane answer to make these functions return similar results to the other functions. The end effect is that the chain of merged backends acts as-if it were a single unified merged backend with respect to the set of returned results from iteration.
4. Implementation notes
- The NSS code is not well formatted, consider formatting patches first, and send them upstream to avoid having formatting and code changes together.
- All non-reentrant functions are written with locking and global data as wrappers around the reentrant functions. You need only modify the reentrant versions.
A high-level implementation of group merging is all that is required. It should be entirely possible to adjust the reentrant buffer pointer and length to hide the already-merged group data in the buffer and then call down to the NSS backend to get more data and then merge again. One should never have to change code in the nss_files, nss_db, or nis directories, just in the top-level nss directory code.
Consider having nss/grp-merge.c as the group merge implementation that is used by the higher level to merge two struct group together. Let other users define merge drivers for all the other types e.g. nss/host-merge.c
If the reentrant buffer can't be used to keep the merge state then you may need to extend the struct service_user to hold this data in some way.
The nss/nsswitch.h enum lookup_actions needs a new enumeration for returning data but with merge e.g. NSS_ACTION_MERGE
The nss/nsswitch.c (nss_parse_service_list needs to learn to parse the new MERGE action and all callers need to be updated.
- Stephen Gallagher
- Simo Sorce
- Carlos O'Donell