Blame SOURCES/0026-files-allow-root-membership.patch

8aada9
From 8969c43dc2d8d0800c2f0b509d078378db855622 Mon Sep 17 00:00:00 2001
8aada9
From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrezina@redhat.com>
8aada9
Date: Tue, 23 Jun 2020 12:05:08 +0200
8aada9
Subject: [PATCH] files: allow root membership
8aada9
8aada9
There are two use cases that do not work with files provider:
8aada9
8aada9
1. User has primary GID 0:
8aada9
8aada9
This is fine by itself since SSSD does not store this user in cache and it is
8aada9
handled only by `nss_files` so the user (`tuser`) is returned correctly. The
8aada9
problem is when you try to resolve group that the user is member of. In this
8aada9
case that the membership is missing the group (but only if the user was
8aada9
previously resolved and thus stored in negative cache).
8aada9
8aada9
```
8aada9
tuser:x:1001:0::/home/tuser:/bin/bash
8aada9
tuser:x:1001:tuser
8aada9
8aada9
// tuser@files is ghost member of the group so it is returned because it is not in negative cache
8aada9
$ getent group tuser
8aada9
tuser:x:1001:tuser
8aada9
8aada9
// expire memcache
8aada9
// tuser@files is ghost member but not returned because it is in negative cache
8aada9
$ id tuser // returned from nss_files
8aada9
uid=1001(tuser) gid=0(root) groups=0(root),1001(tuser)
8aada9
[pbrezina /dev/shm/sssd]$ getent group tuser
8aada9
tuser:x:1001:
8aada9
```
8aada9
8aada9
**2. root is member of other group**
8aada9
8aada9
The root member is missing from the membership since it was filtered out by
8aada9
negative cache.
8aada9
8aada9
```
8aada9
tuser:x:1001:root
8aada9
8aada9
$ id root
8aada9
uid=0(root) gid=0(root) groups=0(root),1001(tuser)
8aada9
[pbrezina /dev/shm/sssd]$ getent group tuser
8aada9
tuser:x:1001:
8aada9
```
8aada9
8aada9
In files provider, only the users that we do not want to managed are stored
8aada9
as ghost member, therefore we can let nss_files handle group that has ghost
8aada9
members.
8aada9
8aada9
Tests are changed as well to work with this behavior. Users are added when
8aada9
required and ghost are expected to return ENOENT.
8aada9
8aada9
Resolves:
8aada9
https://github.com/SSSD/sssd/issues/5170
8aada9
8aada9
Reviewed-by: Sumit Bose <sbose@redhat.com>
8aada9
---
8aada9
 src/responder/nss/nss_protocol_grent.c | 18 +++++++
8aada9
 src/tests/intg/files_ops.py            | 13 +++++
8aada9
 src/tests/intg/test_files_provider.py  | 73 ++++++++++++++++----------
8aada9
 3 files changed, 77 insertions(+), 27 deletions(-)
8aada9
8aada9
diff --git a/src/responder/nss/nss_protocol_grent.c b/src/responder/nss/nss_protocol_grent.c
8aada9
index 9c443d0e7..6d8e71083 100644
8aada9
--- a/src/responder/nss/nss_protocol_grent.c
8aada9
+++ b/src/responder/nss/nss_protocol_grent.c
8aada9
@@ -141,6 +141,24 @@ nss_protocol_fill_members(struct sss_packet *packet,
8aada9
     members[0] = nss_get_group_members(domain, msg);
8aada9
     members[1] = nss_get_group_ghosts(domain, msg, group_name);
8aada9
 
8aada9
+    if (is_files_provider(domain) && members[1] != NULL) {
8aada9
+        /* If there is a ghost member in files provider it means that we
8aada9
+         * did not store the user on purpose (e.g. it has uid or gid 0).
8aada9
+         * Therefore nss_files does handle the user and therefore we
8aada9
+         * must let nss_files to also handle this group in order to
8aada9
+         * provide correct membership. */
8aada9
+        DEBUG(SSSDBG_TRACE_FUNC,
8aada9
+              "Unknown members found. nss_files will handle it.\n");
8aada9
+
8aada9
+        ret = sss_ncache_set_group(rctx->ncache, false, domain, group_name);
8aada9
+        if (ret != EOK) {
8aada9
+            DEBUG(SSSDBG_OP_FAILURE, "sss_ncache_set_group failed.\n");
8aada9
+        }
8aada9
+
8aada9
+        ret = ENOENT;
8aada9
+        goto done;
8aada9
+    }
8aada9
+
8aada9
     sss_packet_get_body(packet, &body, &body_len);
8aada9
 
8aada9
     num_members = 0;
8aada9
diff --git a/src/tests/intg/files_ops.py b/src/tests/intg/files_ops.py
8aada9
index c1c4465e7..57959f501 100644
8aada9
--- a/src/tests/intg/files_ops.py
8aada9
+++ b/src/tests/intg/files_ops.py
8aada9
@@ -103,6 +103,13 @@ class FilesOps(object):
8aada9
 
8aada9
         contents = self._read_contents()
8aada9
 
8aada9
+    def _has_line(self, key):
8aada9
+        try:
8aada9
+            self._get_named_line(key, self._read_contents())
8aada9
+            return True
8aada9
+        except KeyError:
8aada9
+            return False
8aada9
+
8aada9
 
8aada9
 class PasswdOps(FilesOps):
8aada9
     """
8aada9
@@ -132,6 +139,9 @@ class PasswdOps(FilesOps):
8aada9
     def userdel(self, name):
8aada9
         self._del_line(name)
8aada9
 
8aada9
+    def userexist(self, name):
8aada9
+        return self._has_line(name)
8aada9
+
8aada9
 
8aada9
 class GroupOps(FilesOps):
8aada9
     """
8aada9
@@ -158,3 +168,6 @@ class GroupOps(FilesOps):
8aada9
 
8aada9
     def groupdel(self, name):
8aada9
         self._del_line(name)
8aada9
+
8aada9
+    def groupexist(self, name):
8aada9
+        return self._has_line(name)
8aada9
diff --git a/src/tests/intg/test_files_provider.py b/src/tests/intg/test_files_provider.py
8aada9
index 023333020..90be198c3 100644
8aada9
--- a/src/tests/intg/test_files_provider.py
8aada9
+++ b/src/tests/intg/test_files_provider.py
8aada9
@@ -60,11 +60,13 @@ OV_USER1 = dict(name='ov_user1', passwd='x', uid=10010, gid=20010,
8aada9
                 dir='/home/ov/user1',
8aada9
                 shell='/bin/ov_user1_shell')
8aada9
 
8aada9
-ALT_USER1 = dict(name='altuser1', passwd='x', uid=60001, gid=70001,
8aada9
+ALT_USER1 = dict(name='alt_user1', passwd='x', uid=60001, gid=70001,
8aada9
                  gecos='User for tests from alt files',
8aada9
                  dir='/home/altuser1',
8aada9
                  shell='/bin/bash')
8aada9
 
8aada9
+ALL_USERS = [CANARY, USER1, USER2, OV_USER1, ALT_USER1]
8aada9
+
8aada9
 CANARY_GR = dict(name='canary',
8aada9
                  gid=300001,
8aada9
                  mem=[])
8aada9
@@ -365,21 +367,34 @@ def setup_pw_with_canary(passwd_ops_setup):
8aada9
     return setup_pw_with_list(passwd_ops_setup, [CANARY])
8aada9
 
8aada9
 
8aada9
-def setup_gr_with_list(grp_ops, group_list):
8aada9
+def add_group_members(pwd_ops, group):
8aada9
+    members = {x['name']: x for x in ALL_USERS}
8aada9
+    for member in group['mem']:
8aada9
+        if pwd_ops.userexist(member):
8aada9
+            continue
8aada9
+
8aada9
+        pwd_ops.useradd(**members[member])
8aada9
+
8aada9
+
8aada9
+def setup_gr_with_list(pwd_ops, grp_ops, group_list):
8aada9
     for group in group_list:
8aada9
+        add_group_members(pwd_ops, group)
8aada9
         grp_ops.groupadd(**group)
8aada9
+
8aada9
     ent.assert_group_by_name(CANARY_GR['name'], CANARY_GR)
8aada9
     return grp_ops
8aada9
 
8aada9
 
8aada9
 @pytest.fixture
8aada9
-def add_group_with_canary(group_ops_setup):
8aada9
-    return setup_gr_with_list(group_ops_setup, [GROUP1, CANARY_GR])
8aada9
+def add_group_with_canary(passwd_ops_setup, group_ops_setup):
8aada9
+    return setup_gr_with_list(
8aada9
+        passwd_ops_setup, group_ops_setup, [GROUP1, CANARY_GR]
8aada9
+    )
8aada9
 
8aada9
 
8aada9
 @pytest.fixture
8aada9
-def setup_gr_with_canary(group_ops_setup):
8aada9
-    return setup_gr_with_list(group_ops_setup, [CANARY_GR])
8aada9
+def setup_gr_with_canary(passwd_ops_setup, group_ops_setup):
8aada9
+    return setup_gr_with_list(passwd_ops_setup, group_ops_setup, [CANARY_GR])
8aada9
 
8aada9
 
8aada9
 def poll_canary(fn, name, threshold=20):
8aada9
@@ -766,7 +781,9 @@ def test_gid_zero_does_not_resolve(files_domain_only):
8aada9
     assert res == NssReturnCode.NOTFOUND
8aada9
 
8aada9
 
8aada9
-def test_add_remove_add_file_group(setup_gr_with_canary, files_domain_only):
8aada9
+def test_add_remove_add_file_group(
8aada9
+        setup_pw_with_canary, setup_gr_with_canary, files_domain_only
8aada9
+):
8aada9
     """
8aada9
     Test that removing a group is detected and the group
8aada9
     is removed from the sssd database. Similarly, an add
8aada9
@@ -776,6 +793,7 @@ def test_add_remove_add_file_group(setup_gr_with_canary, files_domain_only):
8aada9
     res, group = call_sssd_getgrnam(GROUP1["name"])
8aada9
     assert res == NssReturnCode.NOTFOUND
8aada9
 
8aada9
+    add_group_members(setup_pw_with_canary, GROUP1)
8aada9
     setup_gr_with_canary.groupadd(**GROUP1)
8aada9
     check_group(GROUP1)
8aada9
 
8aada9
@@ -817,8 +835,10 @@ def test_mod_group_gid(add_group_with_canary, files_domain_only):
8aada9
 
8aada9
 
8aada9
 @pytest.fixture
8aada9
-def add_group_nomem_with_canary(group_ops_setup):
8aada9
-    return setup_gr_with_list(group_ops_setup, [GROUP_NOMEM, CANARY_GR])
8aada9
+def add_group_nomem_with_canary(passwd_ops_setup, group_ops_setup):
8aada9
+    return setup_gr_with_list(
8aada9
+        passwd_ops_setup, group_ops_setup, [GROUP_NOMEM, CANARY_GR]
8aada9
+    )
8aada9
 
8aada9
 
8aada9
 def test_getgrnam_no_members(add_group_nomem_with_canary, files_domain_only):
8aada9
@@ -911,16 +931,19 @@ def test_getgrnam_ghost(setup_pw_with_canary,
8aada9
                         setup_gr_with_canary,
8aada9
                         files_domain_only):
8aada9
     """
8aada9
-    Test that a group with members while the members are not present
8aada9
-    are added as ghosts. This is also what nss_files does, getgrnam would
8aada9
-    return group members that do not exist as well.
8aada9
+    Test that group if not found (and will be handled by nss_files) if there
8aada9
+    are any ghost members.
8aada9
     """
8aada9
     user_and_group_setup(setup_pw_with_canary,
8aada9
                          setup_gr_with_canary,
8aada9
                          [],
8aada9
                          [GROUP12],
8aada9
                          False)
8aada9
-    check_group(GROUP12)
8aada9
+
8aada9
+    time.sleep(1)
8aada9
+    res, group = call_sssd_getgrnam(GROUP12["name"])
8aada9
+    assert res == NssReturnCode.NOTFOUND
8aada9
+
8aada9
     for member in GROUP12['mem']:
8aada9
         res, _ = call_sssd_getpwnam(member)
8aada9
         assert res == NssReturnCode.NOTFOUND
8aada9
@@ -932,7 +955,10 @@ def ghost_and_member_test(pw_ops, grp_ops, reverse):
8aada9
                          [USER1],
8aada9
                          [GROUP12],
8aada9
                          reverse)
8aada9
-    check_group(GROUP12)
8aada9
+
8aada9
+    time.sleep(1)
8aada9
+    res, group = call_sssd_getgrnam(GROUP12["name"])
8aada9
+    assert res == NssReturnCode.NOTFOUND
8aada9
 
8aada9
     # We checked that the group added has the same members as group12,
8aada9
     # so both user1 and user2. Now check that user1 is a member of
8aada9
@@ -1027,28 +1053,21 @@ def test_getgrnam_add_remove_ghosts(setup_pw_with_canary,
8aada9
     modgroup = dict(GROUP_NOMEM)
8aada9
     modgroup['mem'] = ['user1', 'user2']
8aada9
     add_group_nomem_with_canary.groupmod(old_name=modgroup['name'], **modgroup)
8aada9
-    check_group(modgroup)
8aada9
+    time.sleep(1)
8aada9
+    res, group = call_sssd_getgrnam(modgroup['name'])
8aada9
+    assert res == sssd_id.NssReturnCode.NOTFOUND
8aada9
 
8aada9
     modgroup['mem'] = ['user2']
8aada9
     add_group_nomem_with_canary.groupmod(old_name=modgroup['name'], **modgroup)
8aada9
-    check_group(modgroup)
8aada9
+    time.sleep(1)
8aada9
+    res, group = call_sssd_getgrnam(modgroup['name'])
8aada9
+    assert res == sssd_id.NssReturnCode.NOTFOUND
8aada9
 
8aada9
     res, _ = call_sssd_getpwnam('user1')
8aada9
     assert res == NssReturnCode.NOTFOUND
8aada9
     res, _ = call_sssd_getpwnam('user2')
8aada9
     assert res == NssReturnCode.NOTFOUND
8aada9
 
8aada9
-    # Add this user and verify it's been added as a member
8aada9
-    pwd_ops.useradd(**USER2)
8aada9
-    # The negative cache might still have user2 from the previous request,
8aada9
-    # flushing the caches might help to prevent a failed lookup after adding
8aada9
-    # the user.
8aada9
-    subprocess.call(["sss_cache", "-E"])
8aada9
-    res, groups = sssd_id_sync('user2')
8aada9
-    assert res == sssd_id.NssReturnCode.SUCCESS
8aada9
-    assert len(groups) == 2
8aada9
-    assert 'group_nomem' in groups
8aada9
-
8aada9
 
8aada9
 def realloc_users(pwd_ops, num):
8aada9
     # Intentionally not including the last one because
8aada9
-- 
8aada9
2.21.3
8aada9