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 who do have the gidNumber attribute set return the original group, but for users who have no gidNumber, autogenerate a user-private group.

Use cases

This change is mostly useful for backwards compatibility in environments that used to manually set a gidNumber and need to retain the same primary GID number, but for newly added user, autogenerate the user private group from the uidNumber. Please see the “How to test” section for an example.

Overview of the solution

Internally, this hybrid domain would work as a MPG domain, so as if auto_private_groups was set true. The difference would be when returning the entries. In the hybrid model, if the user being returned had an original GID, this original GID would be used. Otherwise, a primary group generated from the UID would be returned.

Care must be taken in the getgrnam and getgrgid calls to not return the autogenerated group if requested for a user who also has the originalGID set.

Implementation details

As said above, for the most part, the hybrid domain would work in a similar manner as a domain that had auto_private_groups=true set in the config file. This means, that the uidNumber and gidNumber attributes in the sysdb cache are always set to the same value, but in case the original entry in LDAP did have a gidNumber stored, that original value is also stored in the cache using the origPrimaryGroupGidNumber attribute. The purpose of this attribute is to be able to return the original primary GID as a supplementary group so that any resources owned by this ID are still accessible to the user.

We will take advantage of this attribute when returning the user object during getpwnam (keep in mind that struct passwd that the getpwnam call returns also contains the primary GID number), initgroups and also for a special case during the getgrgid and getgrnam calls.

During the getpwnam call, the code will check whether the user being returned has the origPrimaryGroupGidNumber or not. If it does, its value would be returned as the GID number, otherwise the gidNumber attribute would be used. A similar logic will be implemented in the initgroups call, with the additional caveat that if the origPrimaryGroupGidNumber exists, it wouldn’t be returned as another secondary group.

For group requests in a domain where the user private groups are completely managed, SSSD would search the whole combined user and group space and return group objects based on user objects in the cache. The hybrid domain must behave a little differently here. When a request for a user-private group comes, the getgr* call in a hybrid domain should first try to search group objects only. If there is no match, then also the user objects need to be searched, but only those user objects that have no origPrimaryGroupGidNumber set can be returned as a private groups. Reusing the example above, getgrnam("hybriduser") would return a group object inferred from the user object, but getgrnam("posixuser") would not return anything.

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 two partial LDIFs:

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

uidNumber: 2345
cn: hybriduser
# note: no gidNumber attribute
gecos: hybrid user
homeDirectory: /home/hybriduser
loginShell: /bin/sh

The getpwnam output for these two users would be:

$ getent passwd posixuser
posixuser:*:1234:5678:posix user:/home/posixuser:/bin/sh
$ getent passwd hybriduser
hybriduser:*:2345:2345:hybrid user:/home/hybriduser:/bin/sh