.. highlight:: none

Hybrid Private Groups for LDAP and AD domains
=============================================

Related ticket(s):
------------------
    https://pagure.io/SSSD/sssd/issue/3822

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::

    cn=posixuser,ou=Users,dc=example,dc=com
    objectclass: posixUser
    uidNumber: 1234
    gidNumber: 5678
    cn: posixuser
    gecos: posix user
    homeDirectory: /home/posixuser
    loginShell: /bin/sh

    cn=posixgroup,ou=Groups,dc=example,dc=com
    objectclass: posixGroup
    gidNumber: 5678
    cn: real_group

    cn=hybriduser,ou=Users,dc=example,dc=com
    objectclass: posixUser
    uidNumber: 2345
    gidNumber: 2345
    cn: hybriduser
    gecos: hybrid user
    homeDirectory: /home/hybriduser
    loginShell: /bin/sh

    cn=hybrid_with_group,ou=Users,dc=example,dc=com
    objectclass: posixUser
    uidNumber: 3456
    gidNumber: 3456
    cn: hybrid_with_group
    gecos: hybrid with group
    homeDirectory: /home/hybrid_with_group
    loginShell: /bin/sh

    cn=real_group,ou=Groups,dc=example,dc=com
    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
    posixgroup:*: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
    hybriduser:*:2345:
    $ getent group hybriduser
    hybriduser:*:2345:
    $ 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
    real_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)

Authors
-------
 * Jakub Hrozek <jhrozek@redhat.com>