URL: https://github.com/SSSD/sssd/pull/558 Author: jhrozek Title: #558: WIP: Add a test for sss_nss_getgrouplist_timeout and fix invalidating the initgroups cache Action: synchronized
To pull the PR as Git branch: git remote add ghsssd https://github.com/SSSD/sssd git fetch ghsssd pull/558/head:pr558 git checkout pr558
From 09c3b270e6efa56cb943db09160e40172c2fb11a Mon Sep 17 00:00:00 2001 From: Jakub Hrozek <jhro...@redhat.com> Date: Tue, 24 Apr 2018 16:31:38 +0200 Subject: [PATCH 1/2] NSS: Fix deleting named entries from the initgroup memory cache --- src/responder/nss/nss_cmd.c | 8 ++++++-- src/responder/nss/nss_get_object.c | 17 +++++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/responder/nss/nss_cmd.c b/src/responder/nss/nss_cmd.c index dae1759103..b669866d3e 100644 --- a/src/responder/nss/nss_cmd.c +++ b/src/responder/nss/nss_cmd.c @@ -493,12 +493,16 @@ static errno_t invalidate_cache(struct nss_cmd_ctx *cmd_ctx, return ret; } - memcache_delete_entry(cmd_ctx->nss_ctx, cmd_ctx->nss_ctx->rctx, NULL, - output_name, 0, memcache_type); if (memcache_type == SSS_MC_INITGROUPS) { + memcache_delete_entry(cmd_ctx->nss_ctx, cmd_ctx->nss_ctx->rctx, NULL, + result->lookup_name, 0, memcache_type); + /* Invalidate the passwd data as well */ memcache_delete_entry(cmd_ctx->nss_ctx, cmd_ctx->nss_ctx->rctx, result->domain, output_name, 0, SSS_MC_PASSWD); + } else { + memcache_delete_entry(cmd_ctx->nss_ctx, cmd_ctx->nss_ctx->rctx, NULL, + output_name, 0, memcache_type); } talloc_free(output_name); diff --git a/src/responder/nss/nss_get_object.c b/src/responder/nss/nss_get_object.c index 2ef34c564c..2f231df8b5 100644 --- a/src/responder/nss/nss_get_object.c +++ b/src/responder/nss/nss_get_object.c @@ -109,12 +109,17 @@ memcache_delete_entry(struct nss_ctx *nss_ctx, } if (name != NULL) { - ret = sized_output_name(NULL, rctx, name, dom, &sized_name); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Unable to create sized name [%d]: %s\n", - ret, sss_strerror(ret)); - return ret; + if (type == SSS_MC_INITGROUPS) { + sized_name = talloc_zero(NULL, struct sized_string); + to_sized_string(sized_name, name); + } else { + ret = sized_output_name(NULL, rctx, name, dom, &sized_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to create sized name [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } } ret = memcache_delete_entry_by_name(nss_ctx, sized_name, type); From 0e7f363b8dff7b99c9bcfbcf469243199c61b4a8 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek <jhro...@redhat.com> Date: Mon, 23 Apr 2018 21:33:49 +0200 Subject: [PATCH 2/2] TESTS: Add tests for the sss_nss_getgrouplist_timeout function --- src/tests/intg/Makefile.am | 2 + src/tests/intg/sssd_nss_ex.py | 86 +++++++++++ src/tests/intg/test_nss_ex.py | 261 ++++++++++++++++++++++++++++++++++ 3 files changed, 349 insertions(+) create mode 100644 src/tests/intg/sssd_nss_ex.py create mode 100644 src/tests/intg/test_nss_ex.py diff --git a/src/tests/intg/Makefile.am b/src/tests/intg/Makefile.am index f60751a444..223f8642a2 100644 --- a/src/tests/intg/Makefile.am +++ b/src/tests/intg/Makefile.am @@ -3,6 +3,7 @@ dist_noinst_DATA = \ config.py.m4 \ util.py \ sssd_nss.py \ + sssd_nss_ex.py \ sssd_id.py \ sssd_ldb.py \ sssd_netgroup.py \ @@ -40,6 +41,7 @@ dist_noinst_DATA = \ test_ssh_pubkey.py \ test_pam_responder.py \ test_sudo.py \ + test_nss_ex.py \ $(NULL) EXTRA_DIST = data/cwrap-dbus-system.conf.in diff --git a/src/tests/intg/sssd_nss_ex.py b/src/tests/intg/sssd_nss_ex.py new file mode 100644 index 0000000000..381f3cae34 --- /dev/null +++ b/src/tests/intg/sssd_nss_ex.py @@ -0,0 +1,86 @@ +# +# Shared module for integration tests that need to access the sssd_nss_ex +# interface directly +# +# Copyright (c) 2018 Red Hat, Inc. +# +# This is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import config +import errno +from ctypes import (cdll, c_int, c_char_p, c_char, + c_uint32, c_uint, POINTER, pointer) + + +def nss_sss_ex_ctypes_loader(func_name): + libnss_idmap_path = config.NSS_MODULE_DIR + "/libsss_nss_idmap.so" + libnss_idmap = cdll.LoadLibrary(libnss_idmap_path) + func = getattr(libnss_idmap, func_name) + return func + + +class NssExFlags(object): + """ 'enum' class for name the flags the sssd_nss_ex calls accept """ + NONE = 0, + SSS_NSS_EX_FLAG_NO_CACHE = 1, + SSS_NSS_EX_FLAG_INVALIDATE_CACHE = 2, + + +class SssNssGetgrouplistResult: + def __init__(self, errno, ngroups, groups): + self.errno = errno + self.ngroups = ngroups + self.groups = groups + + +def sss_nss_getgrouplist_timeout(name, + gid, + num_groups, + flags=NssExFlags.NONE, + timeout=5000): + """ + A python wrapper for: + + int sss_nss_getgrouplist_timeout(const char *name, gid_t group, + gid_t *groups, int *ngroups, + uint32_t flags, unsigned int timeout) + """ + func = nss_sss_ex_ctypes_loader("sss_nss_getgrouplist_timeout") + + func.restype = c_int + func.argtypes = [POINTER(c_char), c_uint32, POINTER(c_uint32), + POINTER(c_int), c_uint32, c_uint] + + group_array = (c_uint32 * num_groups)() + p_num_groups = pointer(c_int(num_groups)) + + res = func(c_char_p(name.encode('utf-8')), + c_uint32(gid), + group_array, + p_num_groups, + c_uint32(int(flags[0])), + c_uint(timeout)) + + groups = [] + group_num = 0 + + if res == 0: + group_num = p_num_groups[0] + elif res == errno.ERANGE: + group_num = num_groups + # else group_num == 0 and the loop will fall through + + for i in range(0, group_num): + groups.append(int(group_array[i])) + + return SssNssGetgrouplistResult(res, group_num, groups) diff --git a/src/tests/intg/test_nss_ex.py b/src/tests/intg/test_nss_ex.py new file mode 100644 index 0000000000..23ed9e183f --- /dev/null +++ b/src/tests/intg/test_nss_ex.py @@ -0,0 +1,261 @@ +# +# Integration test for the nss_ex interface +# +# Copyright (c) 2016 Red Hat, Inc. +# +# This is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +import os +import ent +import errno +import stat +import signal +import subprocess +import time +import ldap +import pytest +import ds_openldap +import ldap_ent +import config +from util import unindent +from sssd_nss_ex import sss_nss_getgrouplist_timeout, NssExFlags + + +LDAP_BASE_DN = "dc=example,dc=com" +SSSD_DOMAIN = "LDAP" +SCHEMA_RFC2307_BIS = "rfc2307bis" + + +@pytest.fixture(scope="module") +def ds_inst(request): + """LDAP server instance fixture""" + ds_inst = ds_openldap.DSOpenLDAP( + config.PREFIX, 10389, LDAP_BASE_DN, + "cn=admin", "Secret123") + try: + ds_inst.setup() + except: + ds_inst.teardown() + raise + request.addfinalizer(lambda: ds_inst.teardown()) + return ds_inst + + +@pytest.fixture(scope="module") +def ldap_conn(request, ds_inst): + """LDAP server connection fixture""" + ldap_conn = ds_inst.bind() + ldap_conn.ds_inst = ds_inst + request.addfinalizer(lambda: ldap_conn.unbind_s()) + return ldap_conn + + +def create_ldap_fixture(request, ldap_conn, ent_list): + """Add LDAP entries and add teardown for removing them""" + for entry in ent_list: + ldap_conn.add_s(entry[0], entry[1]) + + def teardown(): + for entry in ent_list: + try: + ldap_conn.delete_s(entry[0]) + except ldap.NO_SUCH_OBJECT: + # if the test already removed an object, it's fine + # to not care in the teardown + pass + request.addfinalizer(teardown) + + +def create_conf_fixture(request, contents): + """Generate sssd.conf and add teardown for removing it""" + conf = open(config.CONF_PATH, "w") + conf.write(contents) + conf.close() + os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR) + request.addfinalizer(lambda: os.unlink(config.CONF_PATH)) + + +def stop_sssd(): + pid_file = open(config.PIDFILE_PATH, "r") + pid = int(pid_file.read()) + os.kill(pid, signal.SIGTERM) + while True: + try: + os.kill(pid, signal.SIGCONT) + except: + break + time.sleep(1) + + +def create_sssd_fixture(request): + """Start sssd and add teardown for stopping it and removing state""" + if subprocess.call(["sssd", "-D", "-f"]) != 0: + raise Exception("sssd start failed") + + def teardown(): + try: + stop_sssd() + except: + pass + for path in os.listdir(config.DB_PATH): + os.unlink(config.DB_PATH + "/" + path) + for path in os.listdir(config.MCACHE_PATH): + os.unlink(config.MCACHE_PATH + "/" + path) + request.addfinalizer(teardown) + + +def load_data_to_ldap(request, ldap_conn, schema): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + + for gid in range(20000, 20010): + groupname = "group%d" % gid + ent_list.add_group_bis(groupname, gid, ("user1",)) + + ent_list.add_user("user2", 1002, 2002) + ent_list.add_group_bis("user2_group", 3001, member_uids=("user2",)) + create_ldap_fixture(request, ldap_conn, ent_list) + + +def load_2307bis_data_to_ldap(request, ldap_conn): + return load_data_to_ldap(request, ldap_conn, SCHEMA_RFC2307_BIS) + + +@pytest.fixture +def setup_rfc2307bis(request, ldap_conn): + load_2307bis_data_to_ldap(request, ldap_conn) + + conf = unindent("""\ + [sssd] + domains = LDAP + services = nss + + [nss] + debug_level = 10 + + [domain/LDAP] + ldap_schema = rfc2307bis + id_provider = ldap + auth_provider = ldap + sudo_provider = ldap + ldap_group_object_class = groupOfNames + ldap_uri = {ldap_conn.ds_inst.ldap_url} + ldap_search_base = {ldap_conn.ds_inst.base_dn} + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def run_getgrouplist_timeout(username, + pgid, + exp_groups, + flags=NssExFlags.NONE): + res = sss_nss_getgrouplist_timeout(username, pgid, 100, flags=flags) + assert res.errno == 0 + assert sorted(res.groups) == sorted(exp_groups) + assert res.ngroups == len(exp_groups) + + +def user1_grouplist_ok_and_erange(): + pgid = 2001 + exp_groups = [g for g in range(20000, 20010)] + exp_groups.append(pgid) + + # Positive test -- a large enough array + run_getgrouplist_timeout("user1", pgid, exp_groups) + + # Pass in an array too small, just make sure we don't crash + res = sss_nss_getgrouplist_timeout("user1", pgid, 5) + assert res.errno == errno.ERANGE + assert res.ngroups == 5 + # It is not reliable between successive runs /which/ groups + # will be returned, so we don't check a slice of the exp_groups + + +def test_sss_nss_getgrouplist_timeout(ldap_conn, + setup_rfc2307bis): + """ + Test calling the sss_nss_getgrouplist_timeout function + """ + user1_grouplist_ok_and_erange() + + # Test that the same case works fine just replying from the + # memory cache + stop_sssd() + user1_grouplist_ok_and_erange() + + +def test_sss_nss_getgrouplist_timeout_etime(ldap_conn, + setup_rfc2307bis): + """ + Test that the function does time out with a ridiculously low timeout + """ + res = sss_nss_getgrouplist_timeout("user1", 2001, 100, timeout=1) + assert res.errno == errno.ETIME + + +def test_sss_nss_getgrouplist_timeout_no_cache(ldap_conn, + setup_rfc2307bis): + """ + Test that the NssExFlags.SSS_NSS_EX_FLAG_NO_CACHE flag works well + with the getgrouplist_timeout function + """ + pgid = 2002 + exp_groups = [3001, pgid] + + # Cache the user first + run_getgrouplist_timeout("user2", pgid, exp_groups) + + # Modify the user group memberships + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + + ent_list.add_group_bis("addtl_group", 3002, member_uids=("user2",)) + for entry in ent_list: + ldap_conn.add_s(entry[0], entry[1]) + + # Still the same results since normally we get results from the cache + run_getgrouplist_timeout("user2", pgid, exp_groups) + + # Bypassing the cache should now return the extra group + exp_groups.append(3002) + run_getgrouplist_timeout("user2", pgid, exp_groups, + NssExFlags.SSS_NSS_EX_FLAG_NO_CACHE) + + +def test_sss_nss_getgrouplist_timeout_invalidate_cache(ldap_conn, + setup_rfc2307bis): + """ + Test that the NssExFlags.SSS_NSS_EX_FLAG_INVALIDATE_CACHE flag works well + with the getgrouplist_timeout function + """ + pgid = 2002 + exp_groups = [3001, pgid] + + # Cache the user first + run_getgrouplist_timeout("user2", pgid, exp_groups) + + # Modify the user group memberships + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + + ent_list.add_group_bis("addtl_group", 3002, member_uids=("user2",)) + for entry in ent_list: + ldap_conn.add_s(entry[0], entry[1]) + + # Still the same results since normally we get results from the cache + run_getgrouplist_timeout("user2", pgid, exp_groups, + NssExFlags.SSS_NSS_EX_FLAG_INVALIDATE_CACHE) + + # Bypassing the cache should now return the extra group + exp_groups.append(3002) + run_getgrouplist_timeout("user2", pgid, exp_groups)
_______________________________________________ sssd-devel mailing list -- sssd-devel@lists.fedorahosted.org To unsubscribe send an email to sssd-devel-le...@lists.fedorahosted.org Fedora Code of Conduct: https://docs.fedoraproject.org/en-US/project/code-of-conduct/ List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines List Archives: https://lists.fedorahosted.org/archives/list/sssd-devel@lists.fedorahosted.org