Netgroup NSS map support

Overview of Netgroups

Netgroups define network-wide groups used for permission checking when fielding requests for remote mounts, remote logins, and remote shells. For remote mounts, the information in netgroup is used to classify machines; for remote logins and remote shells, it is used to classify users.

Netgroups have a name, and contain one or more of the following members:

  • The name of another netgroup (supporting nested netgroups)
  • A three-tuple of (username,hostname,domainname) (parentheses included)

Overview of Netgroups in Name-Service Switch

The interface and behavior of netgroups in libc is a multi-step procedural interface as follows:

  1. The user calls setnetgrent(netgroupname)
    • This sets an internal, global iterator to the start of the list of members for the netgroup specified by netgroupname
  2. The user calls getnetgrent() repeatedly until it returns failure
    • This returns one set of username, hostname and domainname for each call, until there are no more associated with the netgroupname
  3. The user calls endnetgrent()
    • This cleans up after itself

Internally, libraries providing netgroups in libc must unroll the nested netgroups so that all results are returned by getnetgrent() without additional explicit calls.

Overview of Netgroups in LDAP

Netgroups in LDAP are entries containing the objectClass nisNetgroup. This objectClass specifies two options:

nisNetgroupTriple
A netgroup, specified as a literal string. So it would be (hostname,username,domainname)
memberNisNetgroup
The name of another netgroup whose contents need to be rolled into this entry.

Complete example (taken from http://directory.fedoraproject.org/wiki/Howto:Netgroups):

dn: cn=LinuxTeam,ou=Netgroup,dc=example,dc=com
objectClass: nisNetgroup
objectClass: top
cn: LinuxTeam
nisNetgroupTriple: (,frank,example.com)
nisNetgroupTriple: (,jill,example.com)
memberNisNetgroup: QA
memberNisNetgroup: Development
memberNisNetgroup: Operations
description: The Linux Team

SSSD

Overview of approach

Netgroups will be processed similarly to how we handle enumerations in SSSD.

High level

  1. When a setnetgrent() request arrives, we will first check the LDB cache and then we will go to the backends to update the cache.
  2. Once the cache is readied, we will then construct a result object that we can iterate through to return the result set.
  3. Once the result object is ready, we will reply to the setgetgrent() request to notify the calling application that it can start calling getnetgrent()
  4. The calling application will issue getnetgrent() calls until there are no more members available.
  5. The calling application will call endnetgrent()

Lower-level - setnetgrent

  1. Incoming requests to the SSSD will behave similarly to the user and group enumeration code, except that the individual result objects for different netgroup names will be stored in a hash table keyed on the netgroup name.
  2. During processing, if a netgroup contains nested netgroups, we will need to issue a recursive internal setnetgrent() request. This means we will need to have a nesting limit (and ideally, loop-detection)
  3. The response object must contain the complete unrolled results of all of its child netgroups, so that we do not need to maintain multiple iterators for reading through the children.
  4. The acknowledgement response to the initial setnetgrent() request will need to happen only after all nested netgroups have been cached.

Handling nested netgroups

During setnetgrent() processing, we will convert the results into a collection object (see libcollection). For each nested group, we will recurse into setnetgrent() and create a new collection object that can be added to the parent collection. In this way, we will be able to unroll the groups easily.

Later, in getnetgrent() processing, we will construct the response from the stored collection object, rather than directly from the ldb_result object as we do with user and group enumerations.

Public interfaces:

struct tevent_req setnetgrent_send(char *netgroupname, hash_table_t *nesting)
errno_t setnetgrent_recv(tevent_req *req, struct collection **entries)

Internally, the processing for setnetgrent_send() is expected to recurse into nested netgroups and add the resulting entries to its own list using the col_add_collection_to_collection() interface with the col_add_mode_clone mode.

Tracking nesting limits

The biggest danger in nesting is the risk of loops in the memberships. To resolve this, I propose that we keep track of subrequests in a dhash table. This would behave as follows:

  1. In setnetgrent_send() we would first check whether the hash_count of the hash table is equal to the nesting limit. If it is, we will return completion immediately.
  2. Next we will check whether netgroupname already exists in the hash table. If it does, then we know we have looped and will simply return completion immediately.
  3. At this point, we will add the current netgroup name to the hash table (with a NULL associated value) and continue processing this request.
  4. In setnetgrent_recv() we will remove the requested netgroupname from the hash table and amend the result collection.

This will allow us to protect against both loops and excessive nesting all at once.

Dangling Questions

  1. Is it permissible for a single client to request multiple different netgroups concurrently?
    • My reading of the documentation for [set|get|end]netgrent leads me to believe that this is not permitted by libc.
  2. Maybe this is too low-level at this time, but is a cleanup task planned?
    • Netgroups should be handled in the same way that users and groups are handled, so I will probably have to extend the existing cleanup task to also address the netgroups entries in the cache - sgallagh