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