Hybrid Private Groups for LDAP and AD domains

Problem statement

This change will augment the auto_private_groups option which currently is a boolean option with a third mode that would, for users whose uidNumber has the same value as the gidNumber attribute and no group exists in LDAP that has the same value of gidNumber, to autogenerate a user-private group.

Use cases

This change is mostly useful for backwards compatibility in environments that used to manually create a corresponding group for every user’s gidNumber and need to retain the same primary groups, but for newly added user, autogenerate the user private group from the gidNumber.

To keep backwards compatibility, if a group exists with the same gidNumber as set a user entry, this “real” group must not be shadowed by the autogenerated group even if the autogenerated group comes from a different domain than the user.

Please see the “How to test” section for a complete example.

Overview of the solution

Internally, a hybrid domain would work as a usual non-MPG domain. All logic will be implemented in the NSS responder, to account for the case where the primary group comes from a different domain than the user, because in that case, SSSD must iterate over the domains, which means calling into the cache_req code.

Care must be taken in the getgrnam and getgrgid calls to not return the autogenerated group if requested for a user whose gidNumber is also represented in LDAP with a real group.

Implementation details

As said above, the logic will be contained in the NSS responder. If a request for a group arrives, either by ID or by name, the NSS responder first issues a request for a group. If that group is not resolvable and the domain is configured in this special mode, the SSSD retries the same search in the user ID space or name space.

If a result is found with this fallback search, the resulting object would be transformed from a user object to a group object so that the NSS protocol can create a reply.

As a last step, if a group is requested by name, the NSS responder must, in case of returning the user group, verify that this user group is not shadowed by an entry in another domain. This is important because if the autogenerated group was returned even as an alias, the result would have been stored in the memory cache and subsequent getgrgid() requests would return this autogenerated group from the memory cache until it expires, but then return the real group entry from the on-disk cache. To avoid this confusing state, the NSS responder would also run a by-GID search and only return the result if the by-GID search returns nothing. For example, consider that there is a user hybrid_with_group whose uid and gid are the same, but there exists a group real_group with the same gid as the primary gid of the user entry. A getgrnam request arrives for the hybrid_with_group group, does not match a real group entry, falls back to the user space. In order to avoid returning the hybrid_with_group group, the NSS responder would search the group space again for hybrid_with_group’s primary GID, find out that the group real_group exists and return ENOENT.

Configuration changes

Since we are adding a new option value to the existing auto_private_groups call, the option must internally be converted from boolean to a string with three possible values. We would retain true and false for backwards compatibility but add a third option value hybrid.

How To Test

Considering these partial LDIFs:

objectclass: posixUser
uidNumber: 1234
gidNumber: 5678
cn: posixuser
gecos: posix user
homeDirectory: /home/posixuser
loginShell: /bin/sh

objectclass: posixGroup
gidNumber: 5678
cn: real_group

objectclass: posixUser
uidNumber: 2345
gidNumber: 2345
cn: hybriduser
gecos: hybrid user
homeDirectory: /home/hybriduser
loginShell: /bin/sh

objectclass: posixUser
uidNumber: 3456
gidNumber: 3456
cn: hybrid_with_group
gecos: hybrid with group
homeDirectory: /home/hybrid_with_group
loginShell: /bin/sh

objectclass: posixGroup
gidNumber: 3456
cn: real_group

The posixuser behaves as usual:

$ getent passwd posixuser
posixuser:*:1234:5678:posix user:/home/posixuser:/bin/sh
$ getent group 5678
$ getent group posixuser
returns nothing
$ id posixuser
uid=1234(posixuser) gid=5678(posixgroup) groups=5678(posixgroup)

The hybriduser’s primary group is autogenerated:

$ getent passwd hybriduser
hybriduser:*:2345:2345:posix user:/home/hybriduser:/bin/sh
$ getent group 2345
$ getent group hybriduser
$ id hybriduser
uid=2345(hybriduser) gid=2345(hybriduser) groups=2345(hybriduser)

The primary group of hybrid_with_group is still the one stored in LDAP, not autogenerated:

$ getent passwd hybrid_with_group
hybrid_with_group:*:3456:3456:posix user:/home/hybrid_with_group:/bin/sh
$ getent group 3456
$ getent group hybrid_with_group
returns nothing
$ id hybrid_with_group
uid=3456(hybrid_with_group) gid=3456(real_group) groups=3456(hybrid_with_group)