diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 05:31:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 05:31:45 +0000 |
commit | 74aa0bc6779af38018a03fd2cf4419fe85917904 (patch) | |
tree | 9cb0681aac9a94a49c153d5823e7a55d1513d91f /src/tests/intg | |
parent | Initial commit. (diff) | |
download | sssd-74aa0bc6779af38018a03fd2cf4419fe85917904.tar.xz sssd-74aa0bc6779af38018a03fd2cf4419fe85917904.zip |
Adding upstream version 2.9.4.upstream/2.9.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tests/intg')
52 files changed, 20082 insertions, 0 deletions
diff --git a/src/tests/intg/Makefile.am b/src/tests/intg/Makefile.am new file mode 100644 index 0000000..3866d3c --- /dev/null +++ b/src/tests/intg/Makefile.am @@ -0,0 +1,243 @@ +dist_noinst_DATA = \ + __init__.py \ + config.py.m4 \ + util.py \ + sssd_nss.py \ + sssd_id.py \ + sssd_ldb.py \ + sssd_netgroup.py \ + sssd_passwd.py \ + sssd_group.py \ + ds.py \ + ds_openldap.py \ + ent.py \ + ent_test.py \ + ldap_ent.py \ + ldap_local_override_test.py \ + util.py \ + test_enumeration.py \ + test_ldap.py \ + test_memory_cache.py \ + test_session_recording.py \ + test_ts_cache.py \ + test_netgroup.py \ + test_sssctl.py \ + files_ops.py \ + test_files_ops.py \ + test_files_provider.py \ + kdc.py \ + krb5utils.py \ + test_kcm.py \ + test_pac_responder.py \ + data/ad_data.ldif \ + data/ad_schema.ldif \ + data/cert_schema.ldif \ + data/ssh_schema.ldif \ + data/sudo_schema.ldif \ + test_pysss_nss_idmap.py \ + test_infopipe.py \ + test_ssh_pubkey.py \ + test_pam_responder.py \ + test_sudo.py \ + test_resolver.py \ + conftest.py \ + sssd_hosts.py \ + sssd_nets.py \ + test_confdb.py \ + test_sss_cache.py \ + $(NULL) + +EXTRA_DIST = data/cwrap-dbus-system.conf.in + +dbussysconfdir = $(sysconfdir)/dbus-1 +dbusservicedir = $(datadir)/dbus-1/system-services + +if INTG_BUILD +lib_LTLIBRARIES = getsockopt_wrapper.la + +getsockopt_wrapper_la_SOURCES = \ + getsockopt_wrapper.c +getsockopt_wrapper_la_CFLAGS = \ + $(AM_CFLAGS) +getsockopt_wrapper_la_LIBADD = \ + $(LIBADD_DL) \ + $(NULL) +getsockopt_wrapper_la_LDFLAGS = \ + -avoid-version \ + -module + +bin_PROGRAMS = sss_netgroup_thread_test + +sss_netgroup_thread_test_SOURCES = \ + sss_netgroup_thread_test.c \ + $(NULL) +sss_netgroup_thread_test_CFLAGS = \ + $(AM_CFLAGS) \ + $(NULL) +sss_netgroup_thread_test_LDADD = \ + -lpthread \ + $(NULL) + +nsslib_LTLIBRARIES = libnss_call.la +libnss_call_la_SOURCES = \ + nss_call.c \ + $(NULL) +libnss_call_la_LDFLAGS = \ + -module \ + -version-info 2:0:0 \ + $(NULL) + +dist_dbussysconf_DATA = cwrap-dbus-system.conf + +install-data-hook: + $(MKDIR_P) $(DESTDIR)$(runstatedir)/dbus + $(MKDIR_P) $(DESTDIR)$(sysconfdir)/session.d + +endif + +if BUILD_KCM_RENEWAL +KCM_RENEW = "enabled" +else +KCM_RENEW = "disabled" +endif + +if BUILD_FILES_PROVIDER +FILES_PROVIDER = "enabled" +else +FILES_PROVIDER = "disabled" +endif + + +cwrap-dbus-system.conf: data/cwrap-dbus-system.conf.in Makefile + $(SED) -e "s!@runstatedir[@]!$(runstatedir)!" \ + -e "s!@dbusservicedir[@]!$(dbusservicedir)!" \ + $< > $@ + +config.py: config.py.m4 + m4 -D "prefix=\`$(prefix)'" \ + -D "sysconfdir=\`$(sysconfdir)'" \ + -D "nsslibdir=\`$(nsslibdir)'" \ + -D "dbpath=\`$(dbpath)'" \ + -D "pubconfpath=\`$(pubconfpath)'" \ + -D "pidpath=\`$(pidpath)'" \ + -D "logpath=\`$(logpath)'" \ + -D "mcpath=\`$(mcpath)'" \ + -D "secdbpath=\`$(secdbpath)'" \ + -D "libexecpath=\`$(libexecdir)'" \ + -D "runstatedir=\`$(runstatedir)'" \ + -D "abs_builddir=\`$(abs_builddir)'" \ + -D "session_recording_shell=\`$(session_recording_shell)'" \ + -D "py2execdir=\`$(py2execdir)'" \ + -D "py3execdir=\`$(py3execdir)'" \ + -D "python2dir=\`$(python2dir)'" \ + -D "python3dir=\`$(python3dir)'" \ + $< > $@ + +root: + : "Create directory for emulated root's D-Bus cookies." + : "See http://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms" + $(MKDIR_P) -m 0700 root/.dbus-keyrings + +passwd: root + echo "root:x:0:0:root:$(abs_builddir)/root:/bin/bash" > $@ + +group: + echo "root:x:0:" > $@ + +PAM_SERVICE_DIR=pam_service_dir +pam_sss_service: + $(MKDIR_P) $(PAM_SERVICE_DIR) + echo "auth required $(DESTDIR)$(pammoddir)/pam_sss.so" > $(PAM_SERVICE_DIR)/$@ + echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + +pam_sss_alt_service: + $(MKDIR_P) $(PAM_SERVICE_DIR) + echo "auth required $(DESTDIR)$(pammoddir)/pam_sss.so" > $(PAM_SERVICE_DIR)/$@ + echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + +pam_sss_sc_required: + $(MKDIR_P) $(PAM_SERVICE_DIR) + echo "auth required $(DESTDIR)$(pammoddir)/pam_sss.so require_cert_auth retry=1" > $(PAM_SERVICE_DIR)/$@ + echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + +pam_sss_try_sc: + $(MKDIR_P) $(PAM_SERVICE_DIR) + echo "auth required $(DESTDIR)$(pammoddir)/pam_sss.so try_cert_auth" > $(PAM_SERVICE_DIR)/$@ + echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + +pam_sss_allow_missing_name: + $(MKDIR_P) $(PAM_SERVICE_DIR) + echo "auth required $(DESTDIR)$(pammoddir)/pam_sss.so allow_missing_name" > $(PAM_SERVICE_DIR)/$@ + echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + +pam_sss_domains: + $(MKDIR_P) $(PAM_SERVICE_DIR) + echo "auth sufficient $(DESTDIR)$(pammoddir)/pam_sss.so forward_pass domains=wrong.dom1" > $(PAM_SERVICE_DIR)/$@ + echo "auth sufficient $(DESTDIR)$(pammoddir)/pam_sss.so forward_pass domains=wrong.dom2" >> $(PAM_SERVICE_DIR)/$@ + echo "auth sufficient $(DESTDIR)$(pammoddir)/pam_sss.so forward_pass domains=wrong.dom3" >> $(PAM_SERVICE_DIR)/$@ + echo "auth sufficient $(DESTDIR)$(pammoddir)/pam_sss.so forward_pass domains=krb5_auth" >> $(PAM_SERVICE_DIR)/$@ + echo "auth required pam_deny.so" >> $(PAM_SERVICE_DIR)/$@ + echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + +CLEANFILES=config.py config.pyc passwd group + +clean-local: + rm -Rf root + rm -f $(builddir)/cwrap-dbus-system.conf + +PAM_CERT_DB_PATH="$(abs_builddir)/../test_CA/SSSD_test_CA.pem" +SOFTHSM2_CONF="$(abs_builddir)/../test_CA/softhsm2_one.conf" + +intgcheck-installed: config.py passwd group pam_sss_service pam_sss_alt_service pam_sss_sc_required pam_sss_try_sc pam_sss_allow_missing_name pam_sss_domains sss_netgroup_thread_test + pipepath="$(DESTDIR)$(pipepath)"; \ + if test $${#pipepath} -gt 80; then \ + echo "error: Pipe directory path too long," \ + "D-Bus won't be able to open sockets" >&2; \ + exit 1; \ + fi + set -e; \ + cd "$(abs_srcdir)"; \ + nss_wrapper=$$(pkg-config --libs nss_wrapper); \ + uid_wrapper=$$(pkg-config --libs uid_wrapper); \ + unset HOME; \ + PATH="$$(dirname -- $(SLAPD)):$$PATH" \ + PATH="$(DESTDIR)$(sbindir):$(DESTDIR)$(bindir):$$PATH" \ + PATH="$$PATH:$(abs_builddir):$(abs_srcdir)" \ + LANG=C \ + PYTHONPATH="$(abs_builddir):$(abs_srcdir)" \ + LDB_MODULES_PATH="$(DESTDIR)$(ldblibdir)" \ + NON_WRAPPED_UID=$$(id -u) \ + LD_PRELOAD="$(libdir)/getsockopt_wrapper.so:$$nss_wrapper:$$uid_wrapper" \ + LD_LIBRARY_PATH="$$LD_LIBRARY_PATH:$(DESTDIR)$(nsslibdir)" \ + NSS_WRAPPER_PASSWD="$(abs_builddir)/passwd" \ + NSS_WRAPPER_GROUP="$(abs_builddir)/group" \ + NSS_WRAPPER_MODULE_SO_PATH="$(DESTDIR)$(nsslibdir)/libnss_sss.so.2" \ + NSS_WRAPPER_MODULE_FN_PREFIX="sss" \ + UID_WRAPPER=1 \ + UID_WRAPPER_ROOT=1 \ + PAM_WRAPPER=0 \ + PAM_WRAPPER_SERVICE_DIR="$(abs_builddir)/$(PAM_SERVICE_DIR)" \ + PAM_WRAPPER_PATH=$$(pkg-config --libs pam_wrapper) \ + PAM_CERT_DB_PATH=$(PAM_CERT_DB_PATH) \ + ABS_SRCDIR=$(abs_srcdir) \ + SOFTHSM2_CONF=$(SOFTHSM2_CONF) \ + KCM_RENEW=$(KCM_RENEW) \ + FILES_PROVIDER=$(FILES_PROVIDER) \ + DBUS_SOCK_DIR="$(DESTDIR)$(runstatedir)/dbus/" \ + DBUS_SESSION_BUS_ADDRESS="unix:path=$$DBUS_SOCK_DIR/fake_socket" \ + DBUS_SYSTEM_BUS_ADDRESS="unix:path=$$DBUS_SOCK_DIR/system_bus_socket" \ + DBUS_SYSTEM_BUS_DEFAULT_ADDRESS="$$DBUS_SYSTEM_BUS_ADDRESS" \ + fakeroot $(PYTHON_EXEC_INTG) -m pytest -v -r a --tb=native $(INTGCHECK_PYTEST_ARGS) . + rm -f $(DESTDIR)$(logpath)/* diff --git a/src/tests/intg/Makefile.in b/src/tests/intg/Makefile.in new file mode 100644 index 0000000..32df7c7 --- /dev/null +++ b/src/tests/intg/Makefile.in @@ -0,0 +1,1347 @@ +# Makefile.in generated by automake 1.16.5 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2021 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +@INTG_BUILD_TRUE@bin_PROGRAMS = sss_netgroup_thread_test$(EXEEXT) +subdir = src/tests/intg +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/lib-ld.m4 \ + $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \ + $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \ + $(top_srcdir)/version.m4 $(top_srcdir)/src/build_macros.m4 \ + $(top_srcdir)/src/external/platform.m4 \ + $(top_srcdir)/src/conf_macros.m4 \ + $(top_srcdir)/src/external/pkg.m4 \ + $(top_srcdir)/src/external/libpopt.m4 \ + $(top_srcdir)/src/external/libtalloc.m4 \ + $(top_srcdir)/src/external/libtdb.m4 \ + $(top_srcdir)/src/external/libtevent.m4 \ + $(top_srcdir)/src/external/libldb.m4 \ + $(top_srcdir)/src/external/libdhash.m4 \ + $(top_srcdir)/src/external/libini_config.m4 \ + $(top_srcdir)/src/external/libgssapi_krb5.m4 \ + $(top_srcdir)/src/external/pam.m4 \ + $(top_srcdir)/src/external/ldap.m4 \ + $(top_srcdir)/src/external/libpcre.m4 \ + $(top_srcdir)/src/external/krb5.m4 \ + $(top_srcdir)/src/external/libcares.m4 \ + $(top_srcdir)/src/external/libcmocka.m4 \ + $(top_srcdir)/src/external/docbook.m4 \ + $(top_srcdir)/src/external/sizes.m4 \ + $(top_srcdir)/src/external/python.m4 \ + $(top_srcdir)/src/external/selinux.m4 \ + $(top_srcdir)/src/external/crypto.m4 \ + $(top_srcdir)/src/external/nsupdate.m4 \ + $(top_srcdir)/src/external/libkeyutils.m4 \ + $(top_srcdir)/src/external/libkrad.m4 \ + $(top_srcdir)/src/external/libnl.m4 \ + $(top_srcdir)/src/external/systemd.m4 \ + $(top_srcdir)/src/external/pac_responder.m4 \ + $(top_srcdir)/src/external/cifsidmap.m4 \ + $(top_srcdir)/src/external/signal.m4 \ + $(top_srcdir)/src/external/inotify.m4 \ + $(top_srcdir)/src/external/samba.m4 \ + $(top_srcdir)/src/external/sasl.m4 \ + $(top_srcdir)/src/external/libnfsidmap.m4 \ + $(top_srcdir)/src/external/cwrap.m4 \ + $(top_srcdir)/src/external/libresolv.m4 \ + $(top_srcdir)/src/external/intgcheck.m4 \ + $(top_srcdir)/src/external/systemtap.m4 \ + $(top_srcdir)/src/external/service.m4 \ + $(top_srcdir)/src/external/test_ca.m4 \ + $(top_srcdir)/src/external/ax_valgrind_check.m4 \ + $(top_srcdir)/src/external/libjansson.m4 \ + $(top_srcdir)/src/external/libcurl.m4 \ + $(top_srcdir)/src/external/libjose.m4 \ + $(top_srcdir)/src/external/libuuid.m4 \ + $(top_srcdir)/src/external/libunistring.m4 \ + $(top_srcdir)/src/external/libpasskey.m4 \ + $(top_srcdir)/src/external/p11-kit.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__dist_dbussysconf_DATA_DIST) \ + $(dist_noinst_DATA) $(am__DIST_COMMON) +mkinstalldirs = $(SHELL) $(top_srcdir)/build/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(libdir)" \ + "$(DESTDIR)$(nsslibdir)" "$(DESTDIR)$(dbussysconfdir)" +PROGRAMS = $(bin_PROGRAMS) +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +LTLIBRARIES = $(lib_LTLIBRARIES) $(nsslib_LTLIBRARIES) +am__DEPENDENCIES_1 = +@INTG_BUILD_TRUE@getsockopt_wrapper_la_DEPENDENCIES = \ +@INTG_BUILD_TRUE@ $(am__DEPENDENCIES_1) +am__getsockopt_wrapper_la_SOURCES_DIST = getsockopt_wrapper.c +@INTG_BUILD_TRUE@am_getsockopt_wrapper_la_OBJECTS = \ +@INTG_BUILD_TRUE@ getsockopt_wrapper_la-getsockopt_wrapper.lo +getsockopt_wrapper_la_OBJECTS = $(am_getsockopt_wrapper_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +getsockopt_wrapper_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(getsockopt_wrapper_la_CFLAGS) $(CFLAGS) \ + $(getsockopt_wrapper_la_LDFLAGS) $(LDFLAGS) -o $@ +@INTG_BUILD_TRUE@am_getsockopt_wrapper_la_rpath = -rpath $(libdir) +libnss_call_la_LIBADD = +am__libnss_call_la_SOURCES_DIST = nss_call.c +@INTG_BUILD_TRUE@am_libnss_call_la_OBJECTS = nss_call.lo +libnss_call_la_OBJECTS = $(am_libnss_call_la_OBJECTS) +libnss_call_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(libnss_call_la_LDFLAGS) $(LDFLAGS) -o \ + $@ +@INTG_BUILD_TRUE@am_libnss_call_la_rpath = -rpath $(nsslibdir) +am__sss_netgroup_thread_test_SOURCES_DIST = \ + sss_netgroup_thread_test.c +@INTG_BUILD_TRUE@am_sss_netgroup_thread_test_OBJECTS = sss_netgroup_thread_test-sss_netgroup_thread_test.$(OBJEXT) +sss_netgroup_thread_test_OBJECTS = \ + $(am_sss_netgroup_thread_test_OBJECTS) +sss_netgroup_thread_test_DEPENDENCIES = +sss_netgroup_thread_test_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(sss_netgroup_thread_test_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/build/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = \ + ./$(DEPDIR)/getsockopt_wrapper_la-getsockopt_wrapper.Plo \ + ./$(DEPDIR)/nss_call.Plo \ + ./$(DEPDIR)/sss_netgroup_thread_test-sss_netgroup_thread_test.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(getsockopt_wrapper_la_SOURCES) $(libnss_call_la_SOURCES) \ + $(sss_netgroup_thread_test_SOURCES) +DIST_SOURCES = $(am__getsockopt_wrapper_la_SOURCES_DIST) \ + $(am__libnss_call_la_SOURCES_DIST) \ + $(am__sss_netgroup_thread_test_SOURCES_DIST) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__dist_dbussysconf_DATA_DIST = cwrap-dbus-system.conf +DATA = $(dist_dbussysconf_DATA) $(dist_noinst_DATA) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/build/depcomp \ + $(top_srcdir)/build/mkinstalldirs +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CARES_CFLAGS = @CARES_CFLAGS@ +CARES_LIBS = @CARES_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CHECK_CFLAGS = @CHECK_CFLAGS@ +CHECK_LIBS = @CHECK_LIBS@ +CMOCKA_CFLAGS = @CMOCKA_CFLAGS@ +CMOCKA_LIBS = @CMOCKA_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPTO_CFLAGS = @CRYPTO_CFLAGS@ +CRYPTO_LIBS = @CRYPTO_LIBS@ +CSCOPE = @CSCOPE@ +CTAGS = @CTAGS@ +CURL_CFLAGS = @CURL_CFLAGS@ +CURL_LIBS = @CURL_LIBS@ +CYGPATH_W = @CYGPATH_W@ +DBUS_CFLAGS = @DBUS_CFLAGS@ +DBUS_LIBS = @DBUS_LIBS@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DHASH_CFLAGS = @DHASH_CFLAGS@ +DHASH_LIBS = @DHASH_LIBS@ +DLLTOOL = @DLLTOOL@ +DOCBOOK_XSLT = @DOCBOOK_XSLT@ +DOXYGEN = @DOXYGEN@ +DSYMUTIL = @DSYMUTIL@ +DTRACE = @DTRACE@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ENABLE_VALGRIND_drd = @ENABLE_VALGRIND_drd@ +ENABLE_VALGRIND_helgrind = @ENABLE_VALGRIND_helgrind@ +ENABLE_VALGRIND_memcheck = @ENABLE_VALGRIND_memcheck@ +ENABLE_VALGRIND_sgcheck = @ENABLE_VALGRIND_sgcheck@ +ETAGS = @ETAGS@ +EXEEXT = @EXEEXT@ +FAKETIME = @FAKETIME@ +FGREP = @FGREP@ +FIDO2_CFLAGS = @FIDO2_CFLAGS@ +FIDO2_LIBS = @FIDO2_LIBS@ +FILECMD = @FILECMD@ +GDM_PAM_EXTENSIONS_CFLAGS = @GDM_PAM_EXTENSIONS_CFLAGS@ +GDM_PAM_EXTENSIONS_LIBS = @GDM_PAM_EXTENSIONS_LIBS@ +GMSGFMT = @GMSGFMT@ +GPO_DEFAULT = @GPO_DEFAULT@ +GREP = @GREP@ +GSSAPI_KRB5_CFLAGS = @GSSAPI_KRB5_CFLAGS@ +GSSAPI_KRB5_LIBS = @GSSAPI_KRB5_LIBS@ +HAVE_FAKEROOT = @HAVE_FAKEROOT@ +HAVE_LDAPMODIFY = @HAVE_LDAPMODIFY@ +HAVE_MANPAGES = @HAVE_MANPAGES@ +HAVE_NSS_WRAPPER = @HAVE_NSS_WRAPPER@ +HAVE_PAM_WRAPPER = @HAVE_PAM_WRAPPER@ +HAVE_PYTHON2 = @HAVE_PYTHON2@ +HAVE_PYTHON2_BINDINGS = @HAVE_PYTHON2_BINDINGS@ +HAVE_PYTHON3 = @HAVE_PYTHON3@ +HAVE_PYTHON3_BINDINGS = @HAVE_PYTHON3_BINDINGS@ +HAVE_SELINUX = @HAVE_SELINUX@ +HAVE_SEMANAGE = @HAVE_SEMANAGE@ +HAVE_UID_WRAPPER = @HAVE_UID_WRAPPER@ +INI_CONFIG_CFLAGS = @INI_CONFIG_CFLAGS@ +INI_CONFIG_LIBS = @INI_CONFIG_LIBS@ +INI_CONFIG_V0_CFLAGS = @INI_CONFIG_V0_CFLAGS@ +INI_CONFIG_V0_LIBS = @INI_CONFIG_V0_LIBS@ +INI_CONFIG_V1_1_CFLAGS = @INI_CONFIG_V1_1_CFLAGS@ +INI_CONFIG_V1_1_LIBS = @INI_CONFIG_V1_1_LIBS@ +INI_CONFIG_V1_3_CFLAGS = @INI_CONFIG_V1_3_CFLAGS@ +INI_CONFIG_V1_3_LIBS = @INI_CONFIG_V1_3_LIBS@ +INI_CONFIG_V1_CFLAGS = @INI_CONFIG_V1_CFLAGS@ +INI_CONFIG_V1_LIBS = @INI_CONFIG_V1_LIBS@ +INOTIFY_LIBS = @INOTIFY_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTLLIBS = @INTLLIBS@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +JANSSON_CFLAGS = @JANSSON_CFLAGS@ +JANSSON_LIBS = @JANSSON_LIBS@ +JOSE_CFLAGS = @JOSE_CFLAGS@ +JOSE_LIBS = @JOSE_LIBS@ +JOURNALD_CFLAGS = @JOURNALD_CFLAGS@ +JOURNALD_LIBS = @JOURNALD_LIBS@ +KEYUTILS_LIBS = @KEYUTILS_LIBS@ +KRAD_LIBS = @KRAD_LIBS@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_CONFIG = @KRB5_CONFIG@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDB_CFLAGS = @LDB_CFLAGS@ +LDB_LIBS = @LDB_LIBS@ +LDFLAGS = @LDFLAGS@ +LIBADD_DL = @LIBADD_DL@ +LIBADD_DLD_LINK = @LIBADD_DLD_LINK@ +LIBADD_DLOPEN = @LIBADD_DLOPEN@ +LIBADD_SHL_LOAD = @LIBADD_SHL_LOAD@ +LIBADD_TIMER = @LIBADD_TIMER@ +LIBCLOCK_GETTIME = @LIBCLOCK_GETTIME@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBNL1_CFLAGS = @LIBNL1_CFLAGS@ +LIBNL1_LIBS = @LIBNL1_LIBS@ +LIBNL3_CFLAGS = @LIBNL3_CFLAGS@ +LIBNL3_LIBS = @LIBNL3_LIBS@ +LIBNL_CFLAGS = @LIBNL_CFLAGS@ +LIBNL_LIBS = @LIBNL_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +LT_DLLOADERS = @LT_DLLOADERS@ +LT_DLPREOPEN = @LT_DLPREOPEN@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MKINSTALLDIRS = @MKINSTALLDIRS@ +MSGFMT = @MSGFMT@ +MSGMERGE = @MSGMERGE@ +NDR_KRB5PAC_CFLAGS = @NDR_KRB5PAC_CFLAGS@ +NDR_KRB5PAC_LIBS = @NDR_KRB5PAC_LIBS@ +NDR_NBT_CFLAGS = @NDR_NBT_CFLAGS@ +NDR_NBT_LIBS = @NDR_NBT_LIBS@ +NFSIDMAP_CFLAGS = @NFSIDMAP_CFLAGS@ +NFSIDMAP_LIBS = @NFSIDMAP_LIBS@ +NFSIDMAP_OBJ = @NFSIDMAP_OBJ@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NSUPDATE = @NSUPDATE@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENLDAP_CFLAGS = @OPENLDAP_CFLAGS@ +OPENLDAP_LIBS = @OPENLDAP_LIBS@ +OPENSSL = @OPENSSL@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +P11TOOL = @P11TOOL@ +P11_KIT_CFLAGS = @P11_KIT_CFLAGS@ +P11_KIT_LIBS = @P11_KIT_LIBS@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PAM_LIBS = @PAM_LIBS@ +PAM_MISC_LIBS = @PAM_MISC_LIBS@ +PASSKEY_CFLAGS = @PASSKEY_CFLAGS@ +PASSKEY_LIBS = @PASSKEY_LIBS@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PCRE_CFLAGS = @PCRE_CFLAGS@ +PCRE_LIBS = @PCRE_LIBS@ +PKG_CONFIG = @PKG_CONFIG@ +PO4A = @PO4A@ +POPT_CFLAGS = @POPT_CFLAGS@ +POPT_LIBS = @POPT_LIBS@ +POSUB = @POSUB@ +PRERELEASE_VERSION = @PRERELEASE_VERSION@ +PYTHON = @PYTHON@ +PYTHON2 = @PYTHON2@ +PYTHON2_CFLAGS = @PYTHON2_CFLAGS@ +PYTHON2_EXEC_PREFIX = @PYTHON2_EXEC_PREFIX@ +PYTHON2_INCLUDES = @PYTHON2_INCLUDES@ +PYTHON2_LIBS = @PYTHON2_LIBS@ +PYTHON2_PREFIX = @PYTHON2_PREFIX@ +PYTHON2_VERSION = @PYTHON2_VERSION@ +PYTHON3 = @PYTHON3@ +PYTHON3_CFLAGS = @PYTHON3_CFLAGS@ +PYTHON3_EXEC_PREFIX = @PYTHON3_EXEC_PREFIX@ +PYTHON3_INCLUDES = @PYTHON3_INCLUDES@ +PYTHON3_LIBS = @PYTHON3_LIBS@ +PYTHON3_PREFIX = @PYTHON3_PREFIX@ +PYTHON3_VERSION = @PYTHON3_VERSION@ +PYTHON_CONFIG = @PYTHON_CONFIG@ +PYTHON_EXEC = @PYTHON_EXEC@ +PYTHON_EXEC_INTG = @PYTHON_EXEC_INTG@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +RESOLV_CFLAGS = @RESOLV_CFLAGS@ +RESOLV_LIBS = @RESOLV_LIBS@ +SAMBA_UTIL_CFLAGS = @SAMBA_UTIL_CFLAGS@ +SAMBA_UTIL_LIBS = @SAMBA_UTIL_LIBS@ +SASL_CFLAGS = @SASL_CFLAGS@ +SASL_LIBS = @SASL_LIBS@ +SED = @SED@ +SELINUX_LIBS = @SELINUX_LIBS@ +SEMANAGE_LIBS = @SEMANAGE_LIBS@ +SERVICE = @SERVICE@ +SET_MAKE = @SET_MAKE@ +SGML_CATALOG_FILES = @SGML_CATALOG_FILES@ +SHELL = @SHELL@ +SLAPD = @SLAPD@ +SMBCLIENT_CFLAGS = @SMBCLIENT_CFLAGS@ +SMBCLIENT_LIBS = @SMBCLIENT_LIBS@ +SOFTHSM2_PATH = @SOFTHSM2_PATH@ +SOFTHSM2_UTIL = @SOFTHSM2_UTIL@ +SSH_KEYGEN = @SSH_KEYGEN@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +SSSD_USER = @SSSD_USER@ +STRIP = @STRIP@ +SYSTEMD_DAEMON_CFLAGS = @SYSTEMD_DAEMON_CFLAGS@ +SYSTEMD_DAEMON_LIBS = @SYSTEMD_DAEMON_LIBS@ +SYSTEMD_LOGIN_CFLAGS = @SYSTEMD_LOGIN_CFLAGS@ +SYSTEMD_LOGIN_LIBS = @SYSTEMD_LOGIN_LIBS@ +TALLOC_CFLAGS = @TALLOC_CFLAGS@ +TALLOC_LIBS = @TALLOC_LIBS@ +TDB_CFLAGS = @TDB_CFLAGS@ +TDB_LIBS = @TDB_LIBS@ +TEST_DIR = @TEST_DIR@ +TEVENT_CFLAGS = @TEVENT_CFLAGS@ +TEVENT_LIBS = @TEVENT_LIBS@ +UNICODE_LIBS = @UNICODE_LIBS@ +USE_NLS = @USE_NLS@ +UUID_CFLAGS = @UUID_CFLAGS@ +UUID_LIBS = @UUID_LIBS@ +VALGRIND = @VALGRIND@ +VALGRIND_ENABLED = @VALGRIND_ENABLED@ +VERSION = @VERSION@ +XGETTEXT = @XGETTEXT@ +XMLLINT = @XMLLINT@ +XSLTPROC = @XSLTPROC@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +appmodpath = @appmodpath@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +cifspluginpath = @cifspluginpath@ +config_def_ccache_dir = @config_def_ccache_dir@ +config_def_ccname_template = @config_def_ccname_template@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dbpath = @dbpath@ +docdir = @docdir@ +dvidir = @dvidir@ +environment_file = @environment_file@ +exec_prefix = @exec_prefix@ +gpocachepath = @gpocachepath@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +initdir = @initdir@ +install_sh = @install_sh@ +krb5authdatapluginpath = @krb5authdatapluginpath@ +krb5pluginpath = @krb5pluginpath@ +krb5rcachedir = @krb5rcachedir@ +ldblibdir = @ldblibdir@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +logpath = @logpath@ +mandir = @mandir@ +mcpath = @mcpath@ +mkdir_p = @mkdir_p@ +nfsidmaplibdir = @nfsidmaplibdir@ +nfslibpath = @nfslibpath@ +nsslibdir = @nsslibdir@ +oldincludedir = @oldincludedir@ +pammoddir = @pammoddir@ +pdfdir = @pdfdir@ +pidpath = @pidpath@ +pipepath = @pipepath@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +pluginpath = @pluginpath@ +polkitdir = @polkitdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pubconfpath = @pubconfpath@ +py2execdir = @py2execdir@ +py3execdir = @py3execdir@ +pyexecdir = @pyexecdir@ +python2dir = @python2dir@ +python3dir = @python3dir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +secdbpath = @secdbpath@ +session_recording_shell = @session_recording_shell@ +sharedbuilddir = @sharedbuilddir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +subidlibpath = @subidlibpath@ +sudolibpath = @sudolibpath@ +sysconfdir = @sysconfdir@ +systemdconfdir = @systemdconfdir@ +systemdunitdir = @systemdunitdir@ +tapset_dir = @tapset_dir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +valgrind_enabled_tools = @valgrind_enabled_tools@ +valgrind_tools = @valgrind_tools@ +winbindpluginpath = @winbindpluginpath@ +dist_noinst_DATA = \ + __init__.py \ + config.py.m4 \ + util.py \ + sssd_nss.py \ + sssd_id.py \ + sssd_ldb.py \ + sssd_netgroup.py \ + sssd_passwd.py \ + sssd_group.py \ + ds.py \ + ds_openldap.py \ + ent.py \ + ent_test.py \ + ldap_ent.py \ + ldap_local_override_test.py \ + util.py \ + test_enumeration.py \ + test_ldap.py \ + test_memory_cache.py \ + test_session_recording.py \ + test_ts_cache.py \ + test_netgroup.py \ + test_sssctl.py \ + files_ops.py \ + test_files_ops.py \ + test_files_provider.py \ + kdc.py \ + krb5utils.py \ + test_kcm.py \ + test_pac_responder.py \ + data/ad_data.ldif \ + data/ad_schema.ldif \ + data/cert_schema.ldif \ + data/ssh_schema.ldif \ + data/sudo_schema.ldif \ + test_pysss_nss_idmap.py \ + test_infopipe.py \ + test_ssh_pubkey.py \ + test_pam_responder.py \ + test_sudo.py \ + test_resolver.py \ + conftest.py \ + sssd_hosts.py \ + sssd_nets.py \ + test_confdb.py \ + test_sss_cache.py \ + $(NULL) + +EXTRA_DIST = data/cwrap-dbus-system.conf.in +dbussysconfdir = $(sysconfdir)/dbus-1 +dbusservicedir = $(datadir)/dbus-1/system-services +@INTG_BUILD_TRUE@lib_LTLIBRARIES = getsockopt_wrapper.la +@INTG_BUILD_TRUE@getsockopt_wrapper_la_SOURCES = \ +@INTG_BUILD_TRUE@ getsockopt_wrapper.c + +@INTG_BUILD_TRUE@getsockopt_wrapper_la_CFLAGS = \ +@INTG_BUILD_TRUE@ $(AM_CFLAGS) + +@INTG_BUILD_TRUE@getsockopt_wrapper_la_LIBADD = \ +@INTG_BUILD_TRUE@ $(LIBADD_DL) \ +@INTG_BUILD_TRUE@ $(NULL) + +@INTG_BUILD_TRUE@getsockopt_wrapper_la_LDFLAGS = \ +@INTG_BUILD_TRUE@ -avoid-version \ +@INTG_BUILD_TRUE@ -module + +@INTG_BUILD_TRUE@sss_netgroup_thread_test_SOURCES = \ +@INTG_BUILD_TRUE@ sss_netgroup_thread_test.c \ +@INTG_BUILD_TRUE@ $(NULL) + +@INTG_BUILD_TRUE@sss_netgroup_thread_test_CFLAGS = \ +@INTG_BUILD_TRUE@ $(AM_CFLAGS) \ +@INTG_BUILD_TRUE@ $(NULL) + +@INTG_BUILD_TRUE@sss_netgroup_thread_test_LDADD = \ +@INTG_BUILD_TRUE@ -lpthread \ +@INTG_BUILD_TRUE@ $(NULL) + +@INTG_BUILD_TRUE@nsslib_LTLIBRARIES = libnss_call.la +@INTG_BUILD_TRUE@libnss_call_la_SOURCES = \ +@INTG_BUILD_TRUE@ nss_call.c \ +@INTG_BUILD_TRUE@ $(NULL) + +@INTG_BUILD_TRUE@libnss_call_la_LDFLAGS = \ +@INTG_BUILD_TRUE@ -module \ +@INTG_BUILD_TRUE@ -version-info 2:0:0 \ +@INTG_BUILD_TRUE@ $(NULL) + +@INTG_BUILD_TRUE@dist_dbussysconf_DATA = cwrap-dbus-system.conf +@BUILD_KCM_RENEWAL_FALSE@KCM_RENEW = "disabled" +@BUILD_KCM_RENEWAL_TRUE@KCM_RENEW = "enabled" +@BUILD_FILES_PROVIDER_FALSE@FILES_PROVIDER = "disabled" +@BUILD_FILES_PROVIDER_TRUE@FILES_PROVIDER = "enabled" +PAM_SERVICE_DIR = pam_service_dir +CLEANFILES = config.py config.pyc passwd group +PAM_CERT_DB_PATH = "$(abs_builddir)/../test_CA/SSSD_test_CA.pem" +SOFTHSM2_CONF = "$(abs_builddir)/../test_CA/softhsm2_one.conf" +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/tests/intg/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/tests/intg/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +install-libLTLIBRARIES: $(lib_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \ + } + +uninstall-libLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \ + done + +clean-libLTLIBRARIES: + -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES) + @list='$(lib_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +install-nsslibLTLIBRARIES: $(nsslib_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(nsslib_LTLIBRARIES)'; test -n "$(nsslibdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(nsslibdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(nsslibdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(nsslibdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(nsslibdir)"; \ + } + +uninstall-nsslibLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(nsslib_LTLIBRARIES)'; test -n "$(nsslibdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(nsslibdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(nsslibdir)/$$f"; \ + done + +clean-nsslibLTLIBRARIES: + -test -z "$(nsslib_LTLIBRARIES)" || rm -f $(nsslib_LTLIBRARIES) + @list='$(nsslib_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +getsockopt_wrapper.la: $(getsockopt_wrapper_la_OBJECTS) $(getsockopt_wrapper_la_DEPENDENCIES) $(EXTRA_getsockopt_wrapper_la_DEPENDENCIES) + $(AM_V_CCLD)$(getsockopt_wrapper_la_LINK) $(am_getsockopt_wrapper_la_rpath) $(getsockopt_wrapper_la_OBJECTS) $(getsockopt_wrapper_la_LIBADD) $(LIBS) + +libnss_call.la: $(libnss_call_la_OBJECTS) $(libnss_call_la_DEPENDENCIES) $(EXTRA_libnss_call_la_DEPENDENCIES) + $(AM_V_CCLD)$(libnss_call_la_LINK) $(am_libnss_call_la_rpath) $(libnss_call_la_OBJECTS) $(libnss_call_la_LIBADD) $(LIBS) + +sss_netgroup_thread_test$(EXEEXT): $(sss_netgroup_thread_test_OBJECTS) $(sss_netgroup_thread_test_DEPENDENCIES) $(EXTRA_sss_netgroup_thread_test_DEPENDENCIES) + @rm -f sss_netgroup_thread_test$(EXEEXT) + $(AM_V_CCLD)$(sss_netgroup_thread_test_LINK) $(sss_netgroup_thread_test_OBJECTS) $(sss_netgroup_thread_test_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getsockopt_wrapper_la-getsockopt_wrapper.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nss_call.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sss_netgroup_thread_test-sss_netgroup_thread_test.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +getsockopt_wrapper_la-getsockopt_wrapper.lo: getsockopt_wrapper.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(getsockopt_wrapper_la_CFLAGS) $(CFLAGS) -MT getsockopt_wrapper_la-getsockopt_wrapper.lo -MD -MP -MF $(DEPDIR)/getsockopt_wrapper_la-getsockopt_wrapper.Tpo -c -o getsockopt_wrapper_la-getsockopt_wrapper.lo `test -f 'getsockopt_wrapper.c' || echo '$(srcdir)/'`getsockopt_wrapper.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/getsockopt_wrapper_la-getsockopt_wrapper.Tpo $(DEPDIR)/getsockopt_wrapper_la-getsockopt_wrapper.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='getsockopt_wrapper.c' object='getsockopt_wrapper_la-getsockopt_wrapper.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(getsockopt_wrapper_la_CFLAGS) $(CFLAGS) -c -o getsockopt_wrapper_la-getsockopt_wrapper.lo `test -f 'getsockopt_wrapper.c' || echo '$(srcdir)/'`getsockopt_wrapper.c + +sss_netgroup_thread_test-sss_netgroup_thread_test.o: sss_netgroup_thread_test.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sss_netgroup_thread_test_CFLAGS) $(CFLAGS) -MT sss_netgroup_thread_test-sss_netgroup_thread_test.o -MD -MP -MF $(DEPDIR)/sss_netgroup_thread_test-sss_netgroup_thread_test.Tpo -c -o sss_netgroup_thread_test-sss_netgroup_thread_test.o `test -f 'sss_netgroup_thread_test.c' || echo '$(srcdir)/'`sss_netgroup_thread_test.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/sss_netgroup_thread_test-sss_netgroup_thread_test.Tpo $(DEPDIR)/sss_netgroup_thread_test-sss_netgroup_thread_test.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='sss_netgroup_thread_test.c' object='sss_netgroup_thread_test-sss_netgroup_thread_test.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sss_netgroup_thread_test_CFLAGS) $(CFLAGS) -c -o sss_netgroup_thread_test-sss_netgroup_thread_test.o `test -f 'sss_netgroup_thread_test.c' || echo '$(srcdir)/'`sss_netgroup_thread_test.c + +sss_netgroup_thread_test-sss_netgroup_thread_test.obj: sss_netgroup_thread_test.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sss_netgroup_thread_test_CFLAGS) $(CFLAGS) -MT sss_netgroup_thread_test-sss_netgroup_thread_test.obj -MD -MP -MF $(DEPDIR)/sss_netgroup_thread_test-sss_netgroup_thread_test.Tpo -c -o sss_netgroup_thread_test-sss_netgroup_thread_test.obj `if test -f 'sss_netgroup_thread_test.c'; then $(CYGPATH_W) 'sss_netgroup_thread_test.c'; else $(CYGPATH_W) '$(srcdir)/sss_netgroup_thread_test.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/sss_netgroup_thread_test-sss_netgroup_thread_test.Tpo $(DEPDIR)/sss_netgroup_thread_test-sss_netgroup_thread_test.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='sss_netgroup_thread_test.c' object='sss_netgroup_thread_test-sss_netgroup_thread_test.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sss_netgroup_thread_test_CFLAGS) $(CFLAGS) -c -o sss_netgroup_thread_test-sss_netgroup_thread_test.obj `if test -f 'sss_netgroup_thread_test.c'; then $(CYGPATH_W) 'sss_netgroup_thread_test.c'; else $(CYGPATH_W) '$(srcdir)/sss_netgroup_thread_test.c'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-dist_dbussysconfDATA: $(dist_dbussysconf_DATA) + @$(NORMAL_INSTALL) + @list='$(dist_dbussysconf_DATA)'; test -n "$(dbussysconfdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(dbussysconfdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(dbussysconfdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(dbussysconfdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(dbussysconfdir)" || exit $$?; \ + done + +uninstall-dist_dbussysconfDATA: + @$(NORMAL_UNINSTALL) + @list='$(dist_dbussysconf_DATA)'; test -n "$(dbussysconfdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(dbussysconfdir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(DATA) +install-binPROGRAMS: install-libLTLIBRARIES + +install-nsslibLTLIBRARIES: install-libLTLIBRARIES + +installdirs: + for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(libdir)" "$(DESTDIR)$(nsslibdir)" "$(DESTDIR)$(dbussysconfdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +@INTG_BUILD_FALSE@install-data-hook: +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic clean-libLTLIBRARIES \ + clean-libtool clean-local clean-nsslibLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/getsockopt_wrapper_la-getsockopt_wrapper.Plo + -rm -f ./$(DEPDIR)/nss_call.Plo + -rm -f ./$(DEPDIR)/sss_netgroup_thread_test-sss_netgroup_thread_test.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-dist_dbussysconfDATA \ + install-nsslibLTLIBRARIES + @$(NORMAL_INSTALL) + $(MAKE) $(AM_MAKEFLAGS) install-data-hook +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS install-libLTLIBRARIES + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/getsockopt_wrapper_la-getsockopt_wrapper.Plo + -rm -f ./$(DEPDIR)/nss_call.Plo + -rm -f ./$(DEPDIR)/sss_netgroup_thread_test-sss_netgroup_thread_test.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS uninstall-dist_dbussysconfDATA \ + uninstall-libLTLIBRARIES uninstall-nsslibLTLIBRARIES + +.MAKE: install-am install-data-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-binPROGRAMS clean-generic clean-libLTLIBRARIES \ + clean-libtool clean-local clean-nsslibLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-binPROGRAMS install-data install-data-am \ + install-data-hook install-dist_dbussysconfDATA install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am \ + install-libLTLIBRARIES install-man install-nsslibLTLIBRARIES \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-binPROGRAMS uninstall-dist_dbussysconfDATA \ + uninstall-libLTLIBRARIES uninstall-nsslibLTLIBRARIES + +.PRECIOUS: Makefile + + +@INTG_BUILD_TRUE@install-data-hook: +@INTG_BUILD_TRUE@ $(MKDIR_P) $(DESTDIR)$(runstatedir)/dbus +@INTG_BUILD_TRUE@ $(MKDIR_P) $(DESTDIR)$(sysconfdir)/session.d + +cwrap-dbus-system.conf: data/cwrap-dbus-system.conf.in Makefile + $(SED) -e "s!@runstatedir[@]!$(runstatedir)!" \ + -e "s!@dbusservicedir[@]!$(dbusservicedir)!" \ + $< > $@ + +config.py: config.py.m4 + m4 -D "prefix=\`$(prefix)'" \ + -D "sysconfdir=\`$(sysconfdir)'" \ + -D "nsslibdir=\`$(nsslibdir)'" \ + -D "dbpath=\`$(dbpath)'" \ + -D "pubconfpath=\`$(pubconfpath)'" \ + -D "pidpath=\`$(pidpath)'" \ + -D "logpath=\`$(logpath)'" \ + -D "mcpath=\`$(mcpath)'" \ + -D "secdbpath=\`$(secdbpath)'" \ + -D "libexecpath=\`$(libexecdir)'" \ + -D "runstatedir=\`$(runstatedir)'" \ + -D "abs_builddir=\`$(abs_builddir)'" \ + -D "session_recording_shell=\`$(session_recording_shell)'" \ + -D "py2execdir=\`$(py2execdir)'" \ + -D "py3execdir=\`$(py3execdir)'" \ + -D "python2dir=\`$(python2dir)'" \ + -D "python3dir=\`$(python3dir)'" \ + $< > $@ + +root: + : "Create directory for emulated root's D-Bus cookies." + : "See http://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms" + $(MKDIR_P) -m 0700 root/.dbus-keyrings + +passwd: root + echo "root:x:0:0:root:$(abs_builddir)/root:/bin/bash" > $@ + +group: + echo "root:x:0:" > $@ +pam_sss_service: + $(MKDIR_P) $(PAM_SERVICE_DIR) + echo "auth required $(DESTDIR)$(pammoddir)/pam_sss.so" > $(PAM_SERVICE_DIR)/$@ + echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + +pam_sss_alt_service: + $(MKDIR_P) $(PAM_SERVICE_DIR) + echo "auth required $(DESTDIR)$(pammoddir)/pam_sss.so" > $(PAM_SERVICE_DIR)/$@ + echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + +pam_sss_sc_required: + $(MKDIR_P) $(PAM_SERVICE_DIR) + echo "auth required $(DESTDIR)$(pammoddir)/pam_sss.so require_cert_auth retry=1" > $(PAM_SERVICE_DIR)/$@ + echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + +pam_sss_try_sc: + $(MKDIR_P) $(PAM_SERVICE_DIR) + echo "auth required $(DESTDIR)$(pammoddir)/pam_sss.so try_cert_auth" > $(PAM_SERVICE_DIR)/$@ + echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + +pam_sss_allow_missing_name: + $(MKDIR_P) $(PAM_SERVICE_DIR) + echo "auth required $(DESTDIR)$(pammoddir)/pam_sss.so allow_missing_name" > $(PAM_SERVICE_DIR)/$@ + echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + +pam_sss_domains: + $(MKDIR_P) $(PAM_SERVICE_DIR) + echo "auth sufficient $(DESTDIR)$(pammoddir)/pam_sss.so forward_pass domains=wrong.dom1" > $(PAM_SERVICE_DIR)/$@ + echo "auth sufficient $(DESTDIR)$(pammoddir)/pam_sss.so forward_pass domains=wrong.dom2" >> $(PAM_SERVICE_DIR)/$@ + echo "auth sufficient $(DESTDIR)$(pammoddir)/pam_sss.so forward_pass domains=wrong.dom3" >> $(PAM_SERVICE_DIR)/$@ + echo "auth sufficient $(DESTDIR)$(pammoddir)/pam_sss.so forward_pass domains=krb5_auth" >> $(PAM_SERVICE_DIR)/$@ + echo "auth required pam_deny.so" >> $(PAM_SERVICE_DIR)/$@ + echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@ + +clean-local: + rm -Rf root + rm -f $(builddir)/cwrap-dbus-system.conf + +intgcheck-installed: config.py passwd group pam_sss_service pam_sss_alt_service pam_sss_sc_required pam_sss_try_sc pam_sss_allow_missing_name pam_sss_domains sss_netgroup_thread_test + pipepath="$(DESTDIR)$(pipepath)"; \ + if test $${#pipepath} -gt 80; then \ + echo "error: Pipe directory path too long," \ + "D-Bus won't be able to open sockets" >&2; \ + exit 1; \ + fi + set -e; \ + cd "$(abs_srcdir)"; \ + nss_wrapper=$$(pkg-config --libs nss_wrapper); \ + uid_wrapper=$$(pkg-config --libs uid_wrapper); \ + unset HOME; \ + PATH="$$(dirname -- $(SLAPD)):$$PATH" \ + PATH="$(DESTDIR)$(sbindir):$(DESTDIR)$(bindir):$$PATH" \ + PATH="$$PATH:$(abs_builddir):$(abs_srcdir)" \ + LANG=C \ + PYTHONPATH="$(abs_builddir):$(abs_srcdir)" \ + LDB_MODULES_PATH="$(DESTDIR)$(ldblibdir)" \ + NON_WRAPPED_UID=$$(id -u) \ + LD_PRELOAD="$(libdir)/getsockopt_wrapper.so:$$nss_wrapper:$$uid_wrapper" \ + LD_LIBRARY_PATH="$$LD_LIBRARY_PATH:$(DESTDIR)$(nsslibdir)" \ + NSS_WRAPPER_PASSWD="$(abs_builddir)/passwd" \ + NSS_WRAPPER_GROUP="$(abs_builddir)/group" \ + NSS_WRAPPER_MODULE_SO_PATH="$(DESTDIR)$(nsslibdir)/libnss_sss.so.2" \ + NSS_WRAPPER_MODULE_FN_PREFIX="sss" \ + UID_WRAPPER=1 \ + UID_WRAPPER_ROOT=1 \ + PAM_WRAPPER=0 \ + PAM_WRAPPER_SERVICE_DIR="$(abs_builddir)/$(PAM_SERVICE_DIR)" \ + PAM_WRAPPER_PATH=$$(pkg-config --libs pam_wrapper) \ + PAM_CERT_DB_PATH=$(PAM_CERT_DB_PATH) \ + ABS_SRCDIR=$(abs_srcdir) \ + SOFTHSM2_CONF=$(SOFTHSM2_CONF) \ + KCM_RENEW=$(KCM_RENEW) \ + FILES_PROVIDER=$(FILES_PROVIDER) \ + DBUS_SOCK_DIR="$(DESTDIR)$(runstatedir)/dbus/" \ + DBUS_SESSION_BUS_ADDRESS="unix:path=$$DBUS_SOCK_DIR/fake_socket" \ + DBUS_SYSTEM_BUS_ADDRESS="unix:path=$$DBUS_SOCK_DIR/system_bus_socket" \ + DBUS_SYSTEM_BUS_DEFAULT_ADDRESS="$$DBUS_SYSTEM_BUS_ADDRESS" \ + fakeroot $(PYTHON_EXEC_INTG) -m pytest -v -r a --tb=native $(INTGCHECK_PYTEST_ARGS) . + rm -f $(DESTDIR)$(logpath)/* + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/tests/intg/__init__.py b/src/tests/intg/__init__.py new file mode 100644 index 0000000..542f4d1 --- /dev/null +++ b/src/tests/intg/__init__.py @@ -0,0 +1,13 @@ +import sys +import config + +if sys.version_info[0] > 2: + LOCAL_PYEXECDIR = config.PY3EXECDIR + LOCAL_PYDIR = config.PY3DIR +else: + LOCAL_PYEXECDIR = config.PY2EXECDIR + LOCAL_PYDIR = config.PY2DIR + +for path in [LOCAL_PYEXECDIR, LOCAL_PYDIR]: + if path not in sys.path: + sys.path.insert(0, path) diff --git a/src/tests/intg/config.py.m4 b/src/tests/intg/config.py.m4 new file mode 100644 index 0000000..ba9b1e8 --- /dev/null +++ b/src/tests/intg/config.py.m4 @@ -0,0 +1,25 @@ +""" +Build configuration variables. +""" + +PREFIX = "prefix" +SYSCONFDIR = "sysconfdir" +NSS_MODULE_DIR = "nsslibdir" +SSSDCONFDIR = SYSCONFDIR + "/sssd" +CONF_PATH = SSSDCONFDIR + "/sssd.conf" +CONF_SNIPPET_PATH = SSSDCONFDIR + "/conf.d/01.conf" +DB_PATH = "dbpath" +PID_PATH = "pidpath" +PUBCONF_PATH = "pubconfpath" +PIDFILE_PATH = PID_PATH + "/sssd.pid" +LOG_PATH = "logpath" +MCACHE_PATH = "mcpath" +SECDB_PATH = "secdbpath" +LIBEXEC_PATH = "libexecpath" +RUNSTATEDIR = "runstatedir" +ABS_BUILDDIR = "abs_builddir" +SESSION_RECORDING_SHELL = "session_recording_shell" +PY2EXECDIR = "py2execdir" +PY2DIR = "python2dir" +PY3EXECDIR = "py3execdir" +PY3DIR = "python3dir" diff --git a/src/tests/intg/conftest.py b/src/tests/intg/conftest.py new file mode 100644 index 0000000..f07a410 --- /dev/null +++ b/src/tests/intg/conftest.py @@ -0,0 +1 @@ +from files_ops import passwd_ops_setup, group_ops_setup # noqa diff --git a/src/tests/intg/cwrap-dbus-system.conf b/src/tests/intg/cwrap-dbus-system.conf new file mode 100644 index 0000000..1410491 --- /dev/null +++ b/src/tests/intg/cwrap-dbus-system.conf @@ -0,0 +1,83 @@ +<!-- This configuration file controls the systemwide message bus. + Add a system-local.conf and edit that rather than changing this + file directly. --> + +<!-- Note that there are any number of ways you can hose yourself + security-wise by screwing up this file; in particular, you + probably don't want to listen on any more addresses, add any more + auth mechanisms, run as a different user, etc. --> + +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> +<busconfig> + + <!-- Our well-known bus type, do not change this --> + <type>system</type> + + <!-- If we fork, keep the user's original umask to avoid affecting + the behavior of child processes. --> + <keep_umask/> + + + <!-- Fork into daemon mode --> + <fork/> + + <!-- We use system service launching using a helper --> + <standard_system_servicedirs/> + <servicedir>/usr/local/share/dbus-1/system-services</servicedir> + + + <!-- Write a pid file --> + <pidfile>/usr/local/var/run/dbus/messagebus.pid</pidfile> + + <!-- On Unix systems, the most secure authentication mechanism is + EXTERNAL, which uses credential-passing over Unix sockets. + + This authentication mechanism is not available on Windows, + is not suitable for use with the tcp: or nonce-tcp: transports, + and will not work on obscure flavours of Unix that do not have + a supported credentials-passing mechanism. On those platforms/transports, + comment out the <auth> element to allow fallback to DBUS_COOKIE_SHA1. --> + <auth>EXTERNAL</auth> + + <!-- Only listen on a local socket. (abstract=/path/to/socket + means use abstract namespace, don't really create filesystem + file; only Linux supports this. Use path=/whatever on other + systems.) --> + <listen>unix:path=/usr/local/var/run/dbus/system_bus_socket</listen> + <policy context="default"> + <!-- Allow everything to be sent --> + <allow send_destination="*" eavesdrop="true"/> + <!-- Allow everything to be received --> + <allow eavesdrop="true"/> + <!-- Allow anyone to own anything --> + <allow own="*"/> + </policy> + + <!-- Config files are placed here that among other things, punch + holes in the above policy for specific services. --> + <includedir>system.d</includedir> + + <!-- + <includedir>/etc/dbus-1/system.d</includedir> + --> + + <!-- This is included last so local configuration can override what's + in this standard file --> + <include ignore_missing="yes">/etc/dbus-1/system-local.conf</include> + + <include if_selinux_enabled="yes" selinux_root_relative="yes">contexts/dbus_contexts</include> + + <!-- For the session bus, override the default relatively-low limits + with essentially infinite limits, since the bus is just running + as the user anyway, using up bus resources is not something we need + to worry about. In some cases, we do set the limits lower than + "all available memory" if exceeding the limit is almost certainly a bug, + having the bus enforce a limit is nicer than a huge memory leak. But the + intent is that these limits should never be hit. --> + + <!-- the memory limits are 1G instead of say 4G because they can't exceed 32-bit signed int max --> + <!-- We do not override max_message_unix_fds here since the in-kernel + limit is also relatively low --> + +</busconfig> diff --git a/src/tests/intg/data/ad_data.ldif b/src/tests/intg/data/ad_data.ldif new file mode 100644 index 0000000..0d2ec44 --- /dev/null +++ b/src/tests/intg/data/ad_data.ldif @@ -0,0 +1,815 @@ +dn: cn=Users,dc=example,dc=com +objectClass: top +objectClass: container +cn: Users +description: Default container for upgraded user accounts +distinguishedName: cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20140923233930.0Z +whenChanged: 20140923233930.0Z +uSNCreated: 5696 +uSNChanged: 5696 +showInAdvancedViewOnly: FALSE +name: Users +objectGUID:: 6Gd2SrsmeEiT3Hmh/5hTqw== +systemFlags: -1946157056 +objectCategory: cn=Container,cn=Schema,cn=Configuration,dc=example,dc=com +isCriticalSystemObject: TRUE +dSCorePropagationData: 16010101000000.0Z + +dn: cn=CHILD1$,cn=Users,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: CHILD1$ +distinguishedName: cn=CHILD1$,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20140923224256.0Z +whenChanged: 20160423221800.0Z +uSNCreated: 20732 +uSNChanged: 2181674 +name: CHILD1$ +objectGUID:: ACE60RcYu0iZv4CMYPK+eg== +userAccountControl: 2080 +badPwdCount: 0 +codePage: 0 +countryCode: 0 +badPasswordTime: 0 +lastLogoff: 0 +lastLogon: 0 +pwdLastSet: 131059234804699243 +primaryGroupID: 513 +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EUAQAAA== +accountExpires: 9223372036854775807 +logonCount: 0 +sAMAccountName: CHILD1$ +sAMAccountType: 805306370 +objectCategory: cn=Person,cn=Schema,cn=Configuration,dc=example,dc=com +isCriticalSystemObject: TRUE +dSCorePropagationData: 16010101000000.0Z + +dn: cn=krbtgt,cn=Users,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: krbtgt +description: Key Distribution Center Service Account +distinguishedName: cn=krbtgt,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20140923234018.0Z +whenChanged: 20140923185530.0Z +uSNCreated: 12324 +memberOf: cn=Denied ROdc Password Replication Group,cn=Users,dc=example,dc=com +uSNChanged: 12723 +showInAdvancedViewOnly: TRUE +name: krbtgt +objectGUID:: F/Yrx8X81ESM6t14mMxcxA== +userAccountControl: 514 +badPwdCount: 0 +codePage: 0 +countryCode: 0 +badPasswordTime: 0 +lastLogoff: 0 +lastLogon: 0 +pwdLastSet: 130559892182968750 +primaryGroupID: 513 +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8E9gEAAA== +adminCount: 1 +accountExpires: 9223372036854775807 +logonCount: 0 +sAMAccountName: krbtgt +sAMAccountType: 805306368 +servicePrincipalName: kadmin/changepw +objectCategory: cn=Person,cn=Schema,cn=Configuration,dc=example,dc=com +isCriticalSystemObject: TRUE +dSCorePropagationData: 20140923185530.0Z +dSCorePropagationData: 16010101000000.0Z + +dn: cn=Domain Computers,cn=Users,dc=example,dc=com +objectClass: top +objectClass: group +cn: Domain Computers +description: All workstations and servers joined to the domain +distinguishedName: cn=Domain Computers,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20140923234018.0Z +whenChanged: 20140923234018.0Z +uSNCreated: 12330 +uSNChanged: 12332 +name: Domain Computers +objectGUID:: 09VIVs7CDkOMTnLtMkZMUA== +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EAwIAAA== +sAMAccountName: Domain Computers +sAMAccountType: 268435456 +groupType: -2147483646 +objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com +isCriticalSystemObject: TRUE +dSCorePropagationData: 16010101000000.0Z + +dn: cn=Domain Controllers,cn=Users,dc=example,dc=com +objectClass: top +objectClass: group +cn: Domain Controllers +description: All domain controllers in the domain +distinguishedName: cn=Domain Controllers,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20140923234018.0Z +whenChanged: 20140923185530.0Z +uSNCreated: 12333 +memberOf: cn=Denied ROdc Password Replication Group,cn=Users,dc=example,dc=com +uSNChanged: 12726 +name: Domain Controllers +objectGUID:: a6OG+FLmnECf3fAe0a8o6w== +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EBAIAAA== +adminCount: 1 +sAMAccountName: Domain Controllers +sAMAccountType: 268435456 +groupType: -2147483646 +objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com +isCriticalSystemObject: TRUE +dSCorePropagationData: 20140923185530.0Z +dSCorePropagationData: 16010101000000.0Z + +dn: cn=Schema Admins,cn=Users,dc=example,dc=com +objectClass: top +objectClass: group +cn: Schema Admins +description: Designated administrators of the schema +member: cn=Administrator,cn=Users,dc=example,dc=com +distinguishedName: cn=Schema Admins,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20140923234018.0Z +whenChanged: 20140923185530.0Z +uSNCreated: 12336 +memberOf: cn=Denied ROdc Password Replication Group,cn=Users,dc=example,dc=com +uSNChanged: 12708 +name: Schema Admins +objectGUID:: ONs7cn0OF0uEip0yMnLv2Q== +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EBgIAAA== +adminCount: 1 +sAMAccountName: Schema Admins +sAMAccountType: 268435456 +groupType: -2147483640 +objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com +isCriticalSystemObject: TRUE +dSCorePropagationData: 20140923185530.0Z +dSCorePropagationData: 16010101000000.0Z + +dn: cn=Enterprise Admins,cn=Users,dc=example,dc=com +objectClass: top +objectClass: group +cn: Enterprise Admins +description: Designated administrators of the enterprise +member: cn=Administrator,cn=Users,dc=example,dc=com +distinguishedName: cn=Enterprise Admins,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20140923234018.0Z +whenChanged: 20140923185530.0Z +uSNCreated: 12339 +memberOf: cn=Denied ROdc Password Replication Group,cn=Users,dc=example,dc=com +memberOf: cn=Administrators,cn=Builtin,dc=example,dc=com +uSNChanged: 12712 +name: Enterprise Admins +objectGUID:: rD6jEoiL8U6huv7c/OJPwg== +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EBwIAAA== +adminCount: 1 +sAMAccountName: Enterprise Admins +sAMAccountType: 268435456 +groupType: -2147483640 +objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com +isCriticalSystemObject: TRUE +dSCorePropagationData: 20140923185530.0Z +dSCorePropagationData: 16010101000000.0Z + +dn: cn=Cert Publishers,cn=Users,dc=example,dc=com +objectClass: top +objectClass: group +cn: Cert Publishers +description: Members of this group are permitted to publish certificates to the directory +member: cn=PLUTO,OU=Domain Controllers,dc=example,dc=com +distinguishedName: cn=Cert Publishers,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20140923234018.0Z +whenChanged: 20140923191508.0Z +uSNCreated: 12342 +memberOf: cn=Denied ROdc Password Replication Group,cn=Users,dc=example,dc=com +uSNChanged: 12749 +name: Cert Publishers +objectGUID:: zWTUMdl6tEWA1J0QnPLkRQ== +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EBQIAAA== +sAMAccountName: Cert Publishers +sAMAccountType: 536870912 +groupType: -2147483644 +objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com +isCriticalSystemObject: TRUE +dSCorePropagationData: 16010101000000.0Z + +dn: cn=Domain Admins,cn=Users,dc=example,dc=com +objectClass: top +objectClass: group +cn: Domain Admins +description: Designated administrators of the domain +member: cn=Administrator,cn=Users,dc=example,dc=com +distinguishedName: cn=Domain Admins,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20140923234018.0Z +whenChanged: 20140923185530.0Z +uSNCreated: 12345 +memberOf: cn=Denied ROdc Password Replication Group,cn=Users,dc=example,dc=com +memberOf: cn=Administrators,cn=Builtin,dc=example,dc=com +uSNChanged: 12711 +name: Domain Admins +objectGUID:: YxI+YLrC3UeNNsmMnXGTlg== +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EAAIAAA== +adminCount: 1 +sAMAccountName: Domain Admins +sAMAccountType: 268435456 +groupType: -2147483646 +objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com +isCriticalSystemObject: TRUE +dSCorePropagationData: 20140923185530.0Z +dSCorePropagationData: 16010101000000.0Z + +dn: cn=Domain Users,cn=Users,dc=example,dc=com +objectClass: top +objectClass: group +cn: Domain Users +description: All domain users +distinguishedName: cn=Domain Users,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20140923234018.0Z +whenChanged: 20150202222731.0Z +uSNCreated: 12348 +memberOf: cn=Users,cn=Builtin,dc=example,dc=com +uSNChanged: 213433 +name: Domain Users +objectGUID:: JRHvlJXoU0+LOYXs3vESow== +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EAQIAAA== +sAMAccountName: Domain Users +sAMAccountType: 268435456 +groupType: -2147483646 +objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com +isCriticalSystemObject: TRUE +dSCorePropagationData: 16010101000000.0Z +msSFU30NisDomain: example +gidNumber: 100000 + +dn: cn=Domain Guests,cn=Users,dc=example,dc=com +objectClass: top +objectClass: group +cn: Domain Guests +description: All domain guests +distinguishedName: cn=Domain Guests,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20140923234018.0Z +whenChanged: 20140923234018.0Z +uSNCreated: 12351 +memberOf: cn=Guests,cn=Builtin,dc=example,dc=com +uSNChanged: 12353 +name: Domain Guests +objectGUID:: Rx/t/vuPwUGOMoprY1KFog== +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EAgIAAA== +sAMAccountName: Domain Guests +sAMAccountType: 268435456 +groupType: -2147483646 +objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com +isCriticalSystemObject: TRUE +dSCorePropagationData: 16010101000000.0Z + +dn: cn=Group Policy Creator Owners,cn=Users,dc=example,dc=com +objectClass: top +objectClass: group +cn: Group Policy Creator Owners +description: Members in this group can modify group policy for the domain +member: cn=Administrator,cn=Users,dc=example,dc=com +distinguishedName: cn=Group Policy Creator Owners,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20140923234018.0Z +whenChanged: 20140923234018.0Z +uSNCreated: 12354 +memberOf: cn=Denied ROdc Password Replication Group,cn=Users,dc=example,dc=com +uSNChanged: 12391 +name: Group Policy Creator Owners +objectGUID:: V3HfwcWfZ0yv1br3tRP6bA== +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8ECAIAAA== +sAMAccountName: Group Policy Creator Owners +sAMAccountType: 268435456 +groupType: -2147483646 +objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com +isCriticalSystemObject: TRUE +dSCorePropagationData: 16010101000000.0Z + +dn: cn=RAS and IAS Servers,cn=Users,dc=example,dc=com +objectClass: top +objectClass: group +cn: RAS and IAS Servers +description: Servers in this group can access remote access properties of users +distinguishedName: cn=RAS and IAS Servers,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20140923234018.0Z +whenChanged: 20140923234018.0Z +uSNCreated: 12357 +uSNChanged: 12359 +name: RAS and IAS Servers +objectGUID:: PHyDebZK7UKVG9HG+mT8ng== +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EKQIAAA== +sAMAccountName: RAS and IAS Servers +sAMAccountType: 536870912 +groupType: -2147483644 +objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com +isCriticalSystemObject: TRUE +dSCorePropagationData: 16010101000000.0Z + +dn: cn=Allowed ROdc Password Replication Group,cn=Users,dc=example,dc=com +objectClass: top +objectClass: group +cn: Allowed ROdc Password Replication Group +description: Members in this group can have their passwords replicated to all read-only domain controllers in the domain +distinguishedName: cn=Allowed ROdc Password Replication Group,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20140923234018.0Z +whenChanged: 20140923234018.0Z +uSNCreated: 12402 +uSNChanged: 12404 +name: Allowed ROdc Password Replication Group +objectGUID:: pKN3Txn0SUenHm8Z58ZQYA== +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EOwIAAA== +sAMAccountName: Allowed ROdc Password Replication Group +sAMAccountType: 536870912 +groupType: -2147483644 +objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com +isCriticalSystemObject: TRUE +dSCorePropagationData: 16010101000000.0Z + +dn: cn=Denied ROdc Password Replication Group,cn=Users,dc=example,dc=com +objectClass: top +objectClass: group +cn: Denied ROdc Password Replication Group +description: Members in this group cannot have their passwords replicated to any read-only domain controllers in the domain +member: cn=Read-only Domain Controllers,cn=Users,dc=example,dc=com +member: cn=Group Policy Creator Owners,cn=Users,dc=example,dc=com +member: cn=Domain Admins,cn=Users,dc=example,dc=com +member: cn=Cert Publishers,cn=Users,dc=example,dc=com +member: cn=Enterprise Admins,cn=Users,dc=example,dc=com +member: cn=Schema Admins,cn=Users,dc=example,dc=com +member: cn=Domain Controllers,cn=Users,dc=example,dc=com +member: cn=krbtgt,cn=Users,dc=example,dc=com +distinguishedName: cn=Denied ROdc Password Replication Group,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20140923234018.0Z +whenChanged: 20140923234018.0Z +uSNCreated: 12405 +uSNChanged: 12433 +name: Denied ROdc Password Replication Group +objectGUID:: OoOtLxLbXUSdCGKeGvzc7Q== +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EPAIAAA== +sAMAccountName: Denied ROdc Password Replication Group +sAMAccountType: 536870912 +groupType: -2147483644 +objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com +isCriticalSystemObject: TRUE +dSCorePropagationData: 16010101000000.0Z + +dn: cn=Read-only Domain Controllers,cn=Users,dc=example,dc=com +objectClass: top +objectClass: group +cn: Read-only Domain Controllers +description: Members of this group are Read-Only Domain Controllers in the domain +distinguishedName: cn=Read-only Domain Controllers,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20140923234018.0Z +whenChanged: 20140923185530.0Z +uSNCreated: 12419 +memberOf: cn=Denied ROdc Password Replication Group,cn=Users,dc=example,dc=com +uSNChanged: 12725 +name: Read-only Domain Controllers +objectGUID:: GoeeiCJ87UqBN3C9MhqQ3w== +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8ECQIAAA== +adminCount: 1 +sAMAccountName: Read-only Domain Controllers +sAMAccountType: 268435456 +groupType: -2147483646 +objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com +isCriticalSystemObject: TRUE +dSCorePropagationData: 20140923185530.0Z +dSCorePropagationData: 16010101000000.0Z + +dn: cn=Enterprise Read-only Domain Controllers,cn=Users,dc=example,dc=com +objectClass: top +objectClass: group +cn: Enterprise Read-only Domain Controllers +description: Members of this group are Read-Only Domain Controllers in the enterprise +distinguishedName: cn=Enterprise Read-only Domain Controllers,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20140923234018.0Z +whenChanged: 20140923234018.0Z +uSNCreated: 12429 +uSNChanged: 12431 +name: Enterprise Read-only Domain Controllers +objectGUID:: qHRH+tAgFUy7660VnrFpTA== +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8E8gEAAA== +sAMAccountName: Enterprise Read-only Domain Controllers +sAMAccountType: 268435456 +groupType: -2147483640 +objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com +isCriticalSystemObject: TRUE +dSCorePropagationData: 16010101000000.0Z + +dn: cn=DnsAdmins,cn=Users,dc=example,dc=com +objectClass: top +objectClass: group +cn: DnsAdmins +description: DNS Administrators Group +distinguishedName: cn=DnsAdmins,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20140923234058.0Z +whenChanged: 20140923234058.0Z +uSNCreated: 12459 +uSNChanged: 12461 +name: DnsAdmins +objectGUID:: w4cyv6dWNEGQao3mL5RpTA== +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8ETQQAAA== +sAMAccountName: DnsAdmins +sAMAccountType: 536870912 +groupType: -2147483644 +objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com +dSCorePropagationData: 16010101000000.0Z + +dn: cn=DnsUpdateProxy,cn=Users,dc=example,dc=com +objectClass: top +objectClass: group +cn: DnsUpdateProxy +description: DNS clients who are permitted to perform dynamic updates on behalf of some other clients (such as DHCP servers). +distinguishedName: cn=DnsUpdateProxy,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20140923234058.0Z +whenChanged: 20140923234058.0Z +uSNCreated: 12464 +uSNChanged: 12464 +name: DnsUpdateProxy +objectGUID:: LMyHGT2RuEG+IGrGL80qMg== +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8ETgQAAA== +sAMAccountName: DnsUpdateProxy +sAMAccountType: 268435456 +groupType: -2147483646 +objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com +dSCorePropagationData: 16010101000000.0Z + +dn: cn=user1_dom1-19661,cn=Users,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user1_dom1-19661 +givenName: user1_dom1-19661 +distinguishedName: cn=user1_dom1-19661,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20160517121016.0Z +whenChanged: 20160517121017.0Z +displayName: user1_dom1-19661 +uSNCreated: 2223663 +memberOf: cn=group1_dom1-19661,cn=Users,dc=example,dc=com +uSNChanged: 2223667 +name: user1_dom1-19661 +objectGUID:: qyJVkvQrRUyig6rpPsXNUw== +userAccountControl: 512 +badPwdCount: 0 +codePage: 0 +countryCode: 0 +badPasswordTime: 0 +lastLogoff: 0 +lastLogon: 0 +pwdLastSet: 131079606172284326 +primaryGroupID: 513 +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EeUMBAA== +accountExpires: 0 +logonCount: 0 +sAMAccountName: user1_dom1-19661 +sAMAccountType: 805306368 +userPrincipalName: user1_dom1-19661@example.com +objectCategory: cn=Person,cn=Schema,cn=Configuration,dc=example,dc=com +dSCorePropagationData: 16010101000000.0Z +uid: user1_dom1-19661 +msSFU30Name: user1_dom1-19661 + +dn: cn=group1_dom1-19661,cn=Users,dc=example,dc=com +objectClass: top +objectClass: group +cn: group1_dom1-19661 +member: cn=user1_dom1-19661,cn=Users,dc=example,dc=com +distinguishedName: cn=group1_dom1-19661,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20160517121017.0Z +whenChanged: 20160517121018.0Z +uSNCreated: 2223669 +uSNChanged: 2223673 +name: group1_dom1-19661 +objectGUID:: 8BulXIrOCkmlc6HgV+PAvw== +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EekMBAA== +sAMAccountName: group1_dom1-19661 +sAMAccountType: 268435456 +groupType: -2147483640 +objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com +dSCorePropagationData: 16010101000000.0Z + +dn: cn=user2_dom1-19661,cn=Users,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user2_dom1-19661 +givenName: user2_dom1-19661 +distinguishedName: cn=user2_dom1-19661,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20160517121018.0Z +whenChanged: 20160517121019.0Z +displayName: user2_dom1-19661 +uSNCreated: 2223676 +memberOf: cn=group2_dom2-19661,cn=Users,dc=example_tree,dc=com +uSNChanged: 2223680 +name: user2_dom1-19661 +objectGUID:: YSnhUKGpFUC+SqxUvvXugA== +userAccountControl: 512 +badPwdCount: 0 +codePage: 0 +countryCode: 0 +badPasswordTime: 0 +lastLogoff: 0 +lastLogon: 0 +pwdLastSet: 131079606188221826 +primaryGroupID: 513 +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8Ee0MBAA== +accountExpires: 0 +logonCount: 0 +sAMAccountName: user2_dom1-19661 +sAMAccountType: 805306368 +userPrincipalName: user2_dom1-19661@example.com +objectCategory: cn=Person,cn=Schema,cn=Configuration,dc=example,dc=com +dSCorePropagationData: 16010101000000.0Z +uid: user2_dom1-19661 +msSFU30Name: user2_dom1-19661 + +dn: cn=group3_dom1-19661,cn=Users,dc=example,dc=com +objectClass: top +objectClass: group +cn: group3_dom1-19661 +member: cn=user3_dom3-19661,cn=Users,dc=child1,dc=example,dc=com +distinguishedName: cn=group3_dom1-19661,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20160517121145.0Z +whenChanged: 20160517121146.0Z +uSNCreated: 2223750 +uSNChanged: 2223754 +name: group3_dom1-19661 +objectGUID:: 7bIPzON/JEKmGsVlRmhU3g== +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EfEMBAA== +sAMAccountName: group3_dom1-19661 +sAMAccountType: 268435456 +groupType: -2147483640 +objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com +dSCorePropagationData: 16010101000000.0Z + +dn: cn=TelnetClients,cn=Users,dc=example,dc=com +objectClass: top +objectClass: group +cn: TelnetClients +distinguishedName: cn=TelnetClients,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20140923184913.0Z +whenChanged: 20140923184913.0Z +uSNCreated: 12704 +uSNChanged: 12706 +name: TelnetClients +objectGUID:: pen22ZTevU2Rb+8+krexQA== +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8ETwQAAA== +sAMAccountName: TelnetClients +sAMAccountType: 536870912 +groupType: -2147483644 +objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com +dSCorePropagationData: 16010101000000.0Z + +dn: cn=SSSDAD_TREE$,cn=Users,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: SSSDAD_TREE$ +distinguishedName: cn=SSSDAD_TREE$,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20141002150546.0Z +whenChanged: 20160504032042.0Z +uSNCreated: 31148 +uSNChanged: 2196300 +name: SSSDAD_TREE$ +objectGUID:: SYm5qEjtH0SySg5aQw6XNA== +userAccountControl: 2080 +badPwdCount: 0 +codePage: 0 +countryCode: 0 +badPasswordTime: 0 +lastLogoff: 0 +lastLogon: 0 +pwdLastSet: 131068056421414345 +primaryGroupID: 513 +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EUQQAAA== +accountExpires: 9223372036854775807 +logonCount: 0 +sAMAccountName: SSSDAD_TREE$ +sAMAccountType: 805306370 +objectCategory: cn=Person,cn=Schema,cn=Configuration,dc=example,dc=com +isCriticalSystemObject: TRUE +dSCorePropagationData: 16010101000000.0Z + +dn: cn=user1_dom1-17775,cn=Users,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user1_dom1-17775 +givenName: user1_dom1-17775 +distinguishedName: cn=user1_dom1-17775,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20160517104141.0Z +whenChanged: 20160517105245.0Z +displayName: user1_dom1-17775 +uSNCreated: 2220148 +memberOf: cn=group1_dom1-17775,cn=Users,dc=example,dc=com +uSNChanged: 2220869 +name: user1_dom1-17775 +objectGUID:: dCwgefPZTEaA5Gq7fuH9eQ== +userAccountControl: 512 +badPwdCount: 0 +codePage: 0 +countryCode: 0 +badPasswordTime: 0 +lastLogoff: 0 +lastLogon: 131079562057827406 +pwdLastSet: 131079557906733656 +primaryGroupID: 513 +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8ESUMBAA== +accountExpires: 0 +logonCount: 46 +sAMAccountName: user1_dom1-17775 +sAMAccountType: 805306368 +userPrincipalName: user1_dom1-17775@example.com +objectCategory: cn=Person,cn=Schema,cn=Configuration,dc=example,dc=com +dSCorePropagationData: 16010101000000.0Z +lastLogonTimestamp: 131079557817046156 +uid: user1_dom1-17775 +msSFU30Name: user1_dom1-17775 + +dn: cn=group1_dom1-17775,cn=Users,dc=example,dc=com +objectClass: top +objectClass: group +cn: group1_dom1-17775 +member: cn=user1_dom1-17775,cn=Users,dc=example,dc=com +distinguishedName: cn=group1_dom1-17775,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20160517104143.0Z +whenChanged: 20160517104143.0Z +uSNCreated: 2220154 +uSNChanged: 2220158 +name: group1_dom1-17775 +objectGUID:: UfJpBGL6gE2d5hqzqNlRGQ== +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8ESkMBAA== +sAMAccountName: group1_dom1-17775 +sAMAccountType: 268435456 +groupType: -2147483640 +objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com +dSCorePropagationData: 16010101000000.0Z + +dn: cn=user2_dom1-17775,cn=Users,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user2_dom1-17775 +givenName: user2_dom1-17775 +distinguishedName: cn=user2_dom1-17775,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20160517104143.0Z +whenChanged: 20160517105302.0Z +displayName: user2_dom1-17775 +uSNCreated: 2220161 +memberOf: cn=group2_dom2-17775,cn=Users,dc=example_tree,dc=com +uSNChanged: 2220886 +name: user2_dom1-17775 +objectGUID:: r22lHyI8Y0eMVzeTH2dzoQ== +userAccountControl: 512 +badPwdCount: 0 +codePage: 0 +countryCode: 0 +badPasswordTime: 0 +lastLogoff: 0 +lastLogon: 131079561237671156 +pwdLastSet: 131079553041264906 +primaryGroupID: 513 +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8ES0MBAA== +accountExpires: 0 +logonCount: 14 +sAMAccountName: user2_dom1-17775 +sAMAccountType: 805306368 +userPrincipalName: user2_dom1-17775@example.com +objectCategory: cn=Person,cn=Schema,cn=Configuration,dc=example,dc=com +dSCorePropagationData: 16010101000000.0Z +lastLogonTimestamp: 131079559824702406 +uid: user2_dom1-17775 +msSFU30Name: user2_dom1-17775 + +dn: cn=group3_dom1-17775,cn=Users,dc=example,dc=com +objectClass: top +objectClass: group +cn: group3_dom1-17775 +member: cn=user3_dom3-17775,cn=Users,dc=child1,dc=example,dc=com +distinguishedName: cn=group3_dom1-17775,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20160517104312.0Z +whenChanged: 20160517104312.0Z +uSNCreated: 2220239 +uSNChanged: 2220243 +name: group3_dom1-17775 +objectGUID:: jkkwGJCVb0K4OCjHZVDmdQ== +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8ETEMBAA== +sAMAccountName: group3_dom1-17775 +sAMAccountType: 268435456 +groupType: -2147483640 +objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com +dSCorePropagationData: 16010101000000.0Z + +dn: cn=Administrator,cn=Users,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: Administrator +description: Built-in account for administering the computer/domain +distinguishedName: cn=Administrator,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20140923233931.0Z +whenChanged: 20160510092815.0Z +uSNCreated: 8196 +memberOf: cn=Group Policy Creator Owners,cn=Users,dc=example,dc=com +memberOf: cn=Domain Admins,cn=Users,dc=example,dc=com +memberOf: cn=Enterprise Admins,cn=Users,dc=example,dc=com +memberOf: cn=Schema Admins,cn=Users,dc=example,dc=com +memberOf: cn=Administrators,cn=Builtin,dc=example,dc=com +uSNChanged: 2204950 +name: Administrator +objectGUID:: QeHMqu/QPEyjJ+KQEqcKFw== +userAccountControl: 66048 +badPwdCount: 0 +codePage: 0 +countryCode: 0 +badPasswordTime: 131074379403763791 +lastLogoff: 0 +lastLogon: 131079606125409326 +logonHours:: //////////////////////////// +pwdLastSet: 130553133586093750 +primaryGroupID: 513 +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8E9AEAAA== +adminCount: 1 +accountExpires: 0 +logonCount: 7477 +sAMAccountName: Administrator +sAMAccountType: 805306368 +objectCategory: cn=Person,cn=Schema,cn=Configuration,dc=example,dc=com +isCriticalSystemObject: TRUE +dSCorePropagationData: 20140923185530.0Z +dSCorePropagationData: 16010101000000.0Z +lastLogonTimestamp: 131073460951421705 + +dn: cn=Guest,cn=Users,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: Guest +description: Built-in account for guest access to the computer/domain +distinguishedName: cn=Guest,cn=Users,dc=example,dc=com +instanceType: 4 +whenCreated: 20140923233931.0Z +whenChanged: 20140923233931.0Z +uSNCreated: 8197 +memberOf: cn=Guests,cn=Builtin,dc=example,dc=com +uSNChanged: 8197 +name: Guest +objectGUID:: pZVy9Q6Eh02XuYDEXDE9Cg== +userAccountControl: 66082 +badPwdCount: 0 +codePage: 0 +countryCode: 0 +badPasswordTime: 0 +lastLogoff: 0 +lastLogon: 0 +pwdLastSet: 0 +primaryGroupID: 514 +objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8E9QEAAA== +accountExpires: 9223372036854775807 +logonCount: 0 +sAMAccountName: Guest +sAMAccountType: 805306368 +objectCategory: cn=Person,cn=Schema,cn=Configuration,dc=example,dc=com +isCriticalSystemObject: TRUE +dSCorePropagationData: 16010101000000.0Z diff --git a/src/tests/intg/data/ad_schema.ldif b/src/tests/intg/data/ad_schema.ldif new file mode 100644 index 0000000..6ae533a --- /dev/null +++ b/src/tests/intg/data/ad_schema.ldif @@ -0,0 +1,42 @@ +dn: cn=ad,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: ad +structuralObjectClass: olcSchemaConfig +olcAttributeTypes: {0}( 1.2.840.113556.1.4.750 NAME 'groupType' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE ) +olcAttributeTypes: {1}( 1.2.840.113556.1.4.221 NAME 'sAMAccountName' EQUALITY caseIgnoreMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE ) +olcAttributeTypes: {2}( 1.2.840.113556.1.4.35 NAME 'employeeID' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE ) +olcAttributeTypes: {3}( 1.2.840.113556.1.2.1 NAME 'instanceType' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE ) +olcAttributeTypes: {4}( 1.2.840.113556.1.4.782 NAME 'objectCategory' EQUALITY caseExactMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE ) +olcAttributeTypes: {5}( 1.2.840.113556.1.2.2 NAME 'whenCreated' EQUALITY caseExactMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE ) +olcAttributeTypes: {6}( 1.2.840.113556.1.2.3 NAME 'whenChanged' EQUALITY caseExactMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE ) +olcAttributeTypes: {7}( 1.2.840.113556.1.2.19 NAME 'uSNCreated' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE ) +olcAttributeTypes: {8}( 1.2.840.113556.1.2.120 NAME 'uSNChanged' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE ) +olcAttributeTypes: {9}( 1.2.840.113556.1.2.169 NAME 'showInAdvancedViewOnly' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) +olcAttributeTypes: {10}( 1.2.840.113556.1.4.2 NAME 'objectGUID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE) +olcAttributeTypes: {11}( 1.2.840.113556.1.4.375 NAME 'systemFlags' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE ) +olcAttributeTypes: {12}( 1.2.840.113556.1.4.868 NAME 'isCriticalSystemObject' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) +olcAttributeTypes: {13}( 1.2.840.113556.1.4.1357 NAME 'dSCorePropagationData' EQUALITY caseExactMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' ) +olcAttributeTypes: {14}( 1.2.840.113556.1.4.8 NAME 'userAccountControl' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE ) +olcAttributeTypes: {15}( 1.2.840.113556.1.4.12 NAME 'badPwdCount' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE ) +olcAttributeTypes: {16}( 1.2.840.113556.1.4.146 NAME 'objectSid' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE) +olcAttributeTypes: {17}( 1.2.840.113556.1.2.102 NAME 'memberOf' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 ) +olcAttributeTypes: {18}( 1.2.840.113556.1.4.16 NAME 'codePage' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE ) +olcAttributeTypes: {19}( 1.2.840.113556.1.4.302 NAME 'sAMAccountType' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE ) +olcAttributeTypes: {20}( 1.2.840.113556.1.4.150 NAME 'adminCount' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE ) +olcAttributeTypes: {21}( 1.2.840.113556.1.4.25 NAME 'countryCode' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE ) +olcAttributeTypes: {22}( 1.2.840.113556.1.4.49 NAME 'badPasswordTime' EQUALITY caseExactMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE ) +olcAttributeTypes: {23}( 1.2.840.113556.1.6.18.1.339 NAME 'msSFU30NisDomain' EQUALITY caseExactMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE ) +olcAttributeTypes: {24}( 1.2.840.113556.1.4.51 NAME 'lastLogoff' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE ) +olcAttributeTypes: {25}( 1.2.840.113556.1.4.52 NAME 'lastLogon' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE ) +olcAttributeTypes: {26}( 1.2.840.113556.1.4.96 NAME 'pwdLastSet' EQUALITY numericStringMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.36' SINGLE-VALUE ) +olcAttributeTypes: {27}( 1.2.840.113556.1.4.64 NAME 'logonHours' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE) +olcAttributeTypes: {28}( 1.2.840.113556.1.4.98 NAME 'primaryGroupID' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE ) +olcAttributeTypes: {29}( 1.2.840.113556.1.4.159 NAME 'accountExpires' EQUALITY numericStringMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.36' SINGLE-VALUE ) +olcAttributeTypes: {30}( 1.2.840.113556.1.4.169 NAME 'logonCount' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE ) +olcAttributeTypes: {31}( 1.2.840.113556.1.4.771 NAME 'servicePrincipalName' EQUALITY caseExactMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE ) +olcAttributeTypes: {31}( 1.2.840.113556.1.4.656 NAME 'userPrincipalName' EQUALITY caseExactMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE ) +olcAttributeTypes: {32}( 1.2.840.113556.1.6.18.1.309 NAME 'msSFU30Name' EQUALITY caseExactMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE ) +olcAttributeTypes: {33}( 1.2.840.113556.1.4.1696 NAME 'lastLogonTimestamp' EQUALITY numericStringMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.36' SINGLE-VALUE ) +olcObjectClasses: {1}( 1.2.840.113556.1.5.9 NAME 'user' DESC 'a user' SUP organizationalPerson STRUCTURAL MUST ( cn $ objectSid $ instanceType $ sAMAccountName $ objectCategory ) MAY ( userPassword $ description $ distinguishedName $ name $ userAccountControl $ badPwdCount $ memberOf $ codePage $ sAMAccountType $ adminCount $ countryCode $ dSCorePropagationData $ whenCreated $ whenChanged $ uSNCreated $ uSNChanged $ badPasswordTime $ msSFU30NisDomain $ lastLogoff $ lastLogon $ objectGUID $ pwdLastSet $ logonCount $ logonHours $ primaryGroupID $ accountExpires $ isCriticalSystemObject $ servicePrincipalName $ userPrincipalName $ msSFU30Name $ lastLogonTimestamp $ showInAdvancedViewOnly $ givenName $ displayName $ uid ) ) +olcObjectClasses: {2}( 1.2.840.113556.1.5.8 NAME 'group' DESC 'a group of users' SUP top STRUCTURAL MUST ( groupType $ cn $ objectSid $ instanceType $ sAMAccountName $ objectCategory ) MAY ( member $ description $ distinguishedName $ name $ memberOf $ sAMAccountType $ adminCount $ dSCorePropagationData $ whenCreated $ whenChanged $ uSNCreated $ uSNChanged $ msSFU30NisDomain $ objectGUID $ isCriticalSystemObject $ gidNumber ) ) +olcObjectClasses: {3}( 1.2.840.113556.1.3.23 NAME 'container' DESC 'asdasd' SUP top STRUCTURAL MUST ( cn $ instanceType $ objectCategory ) MAY ( whenCreated $ whenChanged $ uSNCreated $ uSNChanged $ showInAdvancedViewOnly $ objectGUID $ systemFlags $ isCriticalSystemObject $ dSCorePropagationData $ description $ distinguishedName $ name ) ) diff --git a/src/tests/intg/data/cert_schema.ldif b/src/tests/intg/data/cert_schema.ldif new file mode 100644 index 0000000..0003c66 --- /dev/null +++ b/src/tests/intg/data/cert_schema.ldif @@ -0,0 +1,11 @@ +dn: cn=cert,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: cert +olcAttributeTypes: ( 1.2.840.113556.1.4.645 NAME 'userCert' + DESC 'MANDATORY: X.509 user certificate' + EQUALITY octetStringMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 ) +olcObjectClasses: ( 1.2.840.113556.1.3.46 NAME 'mailRecipient' SUP top AUXILIARY + DESC 'MANDATORY: X.509 objectclass' + MAY ( userCert $ uid ) + ) diff --git a/src/tests/intg/data/cwrap-dbus-system.conf.in b/src/tests/intg/data/cwrap-dbus-system.conf.in new file mode 100644 index 0000000..7369054 --- /dev/null +++ b/src/tests/intg/data/cwrap-dbus-system.conf.in @@ -0,0 +1,83 @@ +<!-- This configuration file controls the systemwide message bus. + Add a system-local.conf and edit that rather than changing this + file directly. --> + +<!-- Note that there are any number of ways you can hose yourself + security-wise by screwing up this file; in particular, you + probably don't want to listen on any more addresses, add any more + auth mechanisms, run as a different user, etc. --> + +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> +<busconfig> + + <!-- Our well-known bus type, do not change this --> + <type>system</type> + + <!-- If we fork, keep the user's original umask to avoid affecting + the behavior of child processes. --> + <keep_umask/> + + + <!-- Fork into daemon mode --> + <fork/> + + <!-- We use system service launching using a helper --> + <standard_system_servicedirs/> + <servicedir>@dbusservicedir@</servicedir> + + + <!-- Write a pid file --> + <pidfile>@runstatedir@/dbus/messagebus.pid</pidfile> + + <!-- On Unix systems, the most secure authentication mechanism is + EXTERNAL, which uses credential-passing over Unix sockets. + + This authentication mechanism is not available on Windows, + is not suitable for use with the tcp: or nonce-tcp: transports, + and will not work on obscure flavours of Unix that do not have + a supported credentials-passing mechanism. On those platforms/transports, + comment out the <auth> element to allow fallback to DBUS_COOKIE_SHA1. --> + <auth>EXTERNAL</auth> + + <!-- Only listen on a local socket. (abstract=/path/to/socket + means use abstract namespace, don't really create filesystem + file; only Linux supports this. Use path=/whatever on other + systems.) --> + <listen>unix:path=@runstatedir@/dbus/system_bus_socket</listen> + <policy context="default"> + <!-- Allow everything to be sent --> + <allow send_destination="*" eavesdrop="true"/> + <!-- Allow everything to be received --> + <allow eavesdrop="true"/> + <!-- Allow anyone to own anything --> + <allow own="*"/> + </policy> + + <!-- Config files are placed here that among other things, punch + holes in the above policy for specific services. --> + <includedir>system.d</includedir> + + <!-- + <includedir>/etc/dbus-1/system.d</includedir> + --> + + <!-- This is included last so local configuration can override what's + in this standard file --> + <include ignore_missing="yes">/etc/dbus-1/system-local.conf</include> + + <include if_selinux_enabled="yes" selinux_root_relative="yes">contexts/dbus_contexts</include> + + <!-- For the session bus, override the default relatively-low limits + with essentially infinite limits, since the bus is just running + as the user anyway, using up bus resources is not something we need + to worry about. In some cases, we do set the limits lower than + "all available memory" if exceeding the limit is almost certainly a bug, + having the bus enforce a limit is nicer than a huge memory leak. But the + intent is that these limits should never be hit. --> + + <!-- the memory limits are 1G instead of say 4G because they can't exceed 32-bit signed int max --> + <!-- We do not override max_message_unix_fds here since the in-kernel + limit is also relatively low --> + +</busconfig> diff --git a/src/tests/intg/data/ssh_schema.ldif b/src/tests/intg/data/ssh_schema.ldif new file mode 100644 index 0000000..efe0570 --- /dev/null +++ b/src/tests/intg/data/ssh_schema.ldif @@ -0,0 +1,11 @@ +dn: cn=openssh-lpk,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: openssh-lpk +olcAttributeTypes: ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey' + DESC 'MANDATORY: OpenSSH Public key' + EQUALITY octetStringMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 ) +olcObjectClasses: ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' SUP top AUXILIARY + DESC 'MANDATORY: OpenSSH LPK objectclass' + MAY ( sshPublicKey $ uid ) + ) diff --git a/src/tests/intg/data/sudo_schema.ldif b/src/tests/intg/data/sudo_schema.ldif new file mode 100644 index 0000000..8c1f4e3 --- /dev/null +++ b/src/tests/intg/data/sudo_schema.ldif @@ -0,0 +1,11 @@ +dn: cn=sudo,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: sudo +olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.1 NAME 'sudoUser' DESC 'User(s) who may run sudo' EQUALITY caseExactIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.2 NAME 'sudoHost' DESC 'Host(s) who may run sudo' EQUALITY caseExactIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.3 NAME 'sudoCommand' DESC 'Command(s) to be executed by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.4 NAME 'sudoRunAs' DESC 'User(s) impersonated by sudo (deprecated)' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.5 NAME 'sudoOption' DESC 'Options(s) followed by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.6 NAME 'sudoRunAsUser' DESC 'User(s) impersonated by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.7 NAME 'sudoRunAsGroup' DESC 'Group(s) impersonated by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcObjectClasses: ( 1.3.6.1.4.1.15953.9.2.1 NAME 'sudoRole' SUP top STRUCTURAL DESC 'Sudoer Entries' MUST ( cn ) MAY ( sudoUser $ sudoHost $ sudoCommand $ sudoRunAs $ sudoRunAsUser $ sudoRunAsGroup $ sudoOption $ description ) ) diff --git a/src/tests/intg/ds.py b/src/tests/intg/ds.py new file mode 100644 index 0000000..f4631b2 --- /dev/null +++ b/src/tests/intg/ds.py @@ -0,0 +1,59 @@ +# +# Abstract directory server instance class +# +# Copyright (c) 2015 Red Hat, Inc. +# Author: Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 ldap + + +class DS(object): + """Abstract directory server instance.""" + + def __init__(self, dir, port, base_dn, admin_rdn, admin_pw): + """ + Initialize the instance. + + Arguments: + dir Path to the root of the filesystem hierarchy to create + the instance under. + port TCP port on localhost to bind the server to. + base_dn Base DN. + admin_rdn Administrator DN, relative to BASE_DN. + admin_pw Administrator password. + """ + self.dir = dir + self.port = port + self.ldap_url = "ldap://localhost:" + str(self.port) + self.base_dn = base_dn + self.admin_rdn = admin_rdn + self.admin_dn = admin_rdn + "," + base_dn + self.admin_pw = admin_pw + + def setup(self): + """Setup the instance""" + raise NotImplementedError() + + def teardown(self): + """Teardown the instance""" + raise NotImplementedError() + + def bind(self): + """Connect to the server and bind as admin, return connection.""" + conn = ldap.initialize(self.ldap_url) + conn.simple_bind_s(self.admin_dn, self.admin_pw) + return conn diff --git a/src/tests/intg/ds_openldap.py b/src/tests/intg/ds_openldap.py new file mode 100644 index 0000000..1581343 --- /dev/null +++ b/src/tests/intg/ds_openldap.py @@ -0,0 +1,398 @@ +# +# OpenLDAP directory server instance class +# +# Copyright (c) 2015 Red Hat, Inc. +# Author: Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> +# Author: Lukas Slebodnik <lslebodn@redhat.com> +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 hashlib +import base64 +import time +import ldap +import os +import errno +import signal +import shutil +import subprocess +from util import unindent, first_dir +from ds import DS + +try: + from urllib import quote as url_quote +except ImportError: + from urllib.parse import quote as url_quote + + +def hash_password(password): + """Generate userPassword value for a password.""" + salt = os.urandom(4) + hash = hashlib.sha1(password.encode('utf-8')) + hash.update(salt) + hash_base64 = base64.standard_b64encode(hash.digest() + salt) + return "{SSHA}" + hash_base64.decode('utf-8') + + +class DSOpenLDAP(DS): + """OpenLDAP directory server instance.""" + + def __init__(self, dir, port, base_dn, admin_rdn, admin_pw): + """ + Initialize the instance. + + Arguments: + dir Path to the root of the filesystem hierarchy to create + the instance under. + port TCP port on localhost to bind the server to. + base_dn Base DN. + admin_rdn Administrator DN, relative to BASE_DN. + admin_pw Administrator password. + """ + DS.__init__(self, dir, port, base_dn, admin_rdn, admin_pw) + self.run_dir = self.dir + "/var/run/ldap" + self.pid_path = self.run_dir + "/slapd.pid" + self.conf_dir = self.dir + "/etc/ldap" + self.conf_slapd_d_dir = self.conf_dir + "/slapd.d" + self.data_dir = self.dir + "/var/lib/ldap" + + def _setup_config(self): + """Setup the instance initial configuration.""" + dist_lib_dir = first_dir("/usr/lib64/openldap", + "/usr/lib/openldap", + "/usr/lib/ldap") + dist_conf_dir = first_dir("/etc/ldap", + "/etc/openldap") + args_file = self.run_dir + "/slapd.args" + admin_pw_hash = hash_password(self.admin_pw) + uid = os.geteuid() + gid = os.getegid() + + # + # Add configuration + # + config = unindent(""" + dn: cn=config + objectClass: olcGlobal + cn: config + olcPidFile: {self.pid_path} + olcArgsFile: {args_file} + # Read slapd.conf(5) for possible values + olcLogLevel: none + + # Frontend settings + dn: olcDatabase={{-1}}frontend,cn=config + objectClass: olcDatabaseConfig + objectClass: olcFrontendConfig + olcDatabase: {{-1}}frontend + # The maximum number of entries that is returned for + # a search operation + olcSizeLimit: 500 + # Allow unlimited access to local connection from the local root + olcAccess: {{0}}to * by dn.exact=gidNumber={gid}+uidNumber={uid}, + cn=peercred,cn=external,cn=auth manage by * break + # Allow unauthenticated read access for schema and + # base DN autodiscovery + olcAccess: {{1}}to dn.exact="" by * read + olcAccess: {{2}}to dn.base="cn=Subschema" by * read + + # Config db settings + dn: olcDatabase=config,cn=config + objectClass: olcDatabaseConfig + olcDatabase: config + # Allow unlimited access to local connection from the local root + olcAccess: to * by dn.exact=gidNumber={gid}+uidNumber={uid}, + cn=peercred,cn=external,cn=auth manage by * break + olcRootDN: {self.admin_rdn},cn=config + olcRootPW: {admin_pw_hash} + + # Load schemas + dn: cn=schema,cn=config + objectClass: olcSchemaConfig + cn: schema + + include: file://{dist_conf_dir}/schema/core.ldif + include: file://{dist_conf_dir}/schema/cosine.ldif + include: file://{dist_conf_dir}/schema/nis.ldif + include: file://{dist_conf_dir}/schema/inetorgperson.ldif + + # Load module + dn: cn=module{{0}},cn=config + objectClass: olcModuleList + cn: module{{0}} + olcModulePath: {dist_lib_dir} + olcModuleLoad: back_mdb + + # Set defaults for the backend + dn: olcBackend=mdb,cn=config + objectClass: olcBackendConfig + olcBackend: mdb + + # The database definition. + dn: olcDatabase=mdb,cn=config + objectClass: olcDatabaseConfig + objectClass: olcMdbConfig + olcDatabase: mdb + olcDbCheckpoint: 512 30 + olcLastMod: TRUE + olcSuffix: {self.base_dn} + olcDbDirectory: {self.data_dir} + olcRootDN: {self.admin_dn} + olcRootPW: {admin_pw_hash} + olcDbIndex: objectClass eq + olcDbIndex: cn,uid eq + olcDbIndex: uidNumber,gidNumber eq + olcDbIndex: member,memberUid eq + olcAccess: to attrs=userPassword,shadowLastChange + by self write + by anonymous auth + by * none + olcAccess: to dn.base="" by * read + olcAccess: to * + by * read + """).format(**locals()) + + slapadd = subprocess.Popen( + ["slapadd", "-F", self.conf_slapd_d_dir, "-b", "cn=config"], + stdin=subprocess.PIPE, close_fds=True + ) + slapadd.communicate(config.encode('utf-8')) + if slapadd.returncode != 0: + raise Exception("Failed to add configuration with slapadd") + + # + # Add database config (example from distribution) + # + db_config = unindent(""" + # One 0.25 GB cache + set_cachesize 0 268435456 1 + + # Transaction Log settings + set_lg_regionmax 262144 + set_lg_bsize 2097152 + """) + db_config_file = open(self.data_dir + "/DB_CONFIG", "w") + db_config_file.write(db_config) + db_config_file.close() + + # Import ad schema + subprocess.check_call( + ["slapadd", "-F", self.conf_slapd_d_dir, "-b", "cn=config", + "-l", "data/ssh_schema.ldif"], + ) + + # Import sudo schema + subprocess.check_call( + ["slapadd", "-F", self.conf_slapd_d_dir, "-b", "cn=config", + "-l", "data/sudo_schema.ldif"], + ) + + # Import cert schema + subprocess.check_call( + ["slapadd", "-F", self.conf_slapd_d_dir, "-b", "cn=config", + "-l", "data/cert_schema.ldif"], + ) + + def _start_daemon(self): + """Start the instance.""" + if subprocess.call(["slapd", "-F", self.conf_slapd_d_dir, + "-h", self.url_list]) != 0: + raise Exception("Failed to start slapd") + + # + # Wait until it is available + # + attempt = 0 + while True: + try: + ldap_conn = ldap.initialize(self.ldapi_url) + ldap_conn.simple_bind_s(self.admin_rdn + ",cn=config", + self.admin_pw) + ldap_conn.unbind_s() + ldap_conn = ldap.initialize(self.ldap_url) + ldap_conn.simple_bind_s(self.admin_dn, self.admin_pw) + ldap_conn.unbind_s() + break + except ldap.SERVER_DOWN: + pass + attempt = attempt + 1 + if attempt > 30: + raise Exception("Failed to start slapd") + time.sleep(1) + + def setup(self): + """Setup the instance.""" + ldapi_socket = self.run_dir + "/ldapi" + self.ldapi_url = "ldapi://" + url_quote(ldapi_socket, "") + self.url_list = self.ldapi_url + " " + self.ldap_url + + os.makedirs(self.conf_slapd_d_dir) + os.makedirs(self.run_dir) + os.makedirs(self.data_dir) + + # + # Setup initial configuration + # + self._setup_config() + + self._start_daemon() + + # + # Relax requirement of member attribute presence in groupOfNames + # + modlist = [ + (ldap.MOD_DELETE, "olcObjectClasses", + b"{7}( 2.5.6.9 NAME 'groupOfNames' " + b"DESC 'RFC2256: a group of names (DNs)' SUP top " + b"STRUCTURAL MUST ( member $ cn ) MAY ( businessCategory $ " + b"seeAlso $ owner $ ou $ o $ description ) )"), + (ldap.MOD_ADD, "olcObjectClasses", + b"{7}( 2.5.6.9 NAME 'groupOfNames' " + b"DESC 'RFC2256: a group of names (DNs)' SUP top " + b"STRUCTURAL MUST ( cn ) MAY ( member $ businessCategory $ " + b"seeAlso $ owner $ ou $ o $ description ) )"), + ] + ldap_conn = ldap.initialize(self.ldapi_url) + ldap_conn.simple_bind_s(self.admin_rdn + ",cn=config", self.admin_pw) + ldap_conn.modify_s("cn={0}core,cn=schema,cn=config", modlist) + ldap_conn.unbind_s() + + # + # Add data + # + ldap_conn = ldap.initialize(self.ldap_url) + ldap_conn.simple_bind_s(self.admin_dn, self.admin_pw) + ldap_conn.add_s(self.base_dn, [ + ("objectClass", [b"dcObject", b"organization"]), + ("o", b"Example Company"), + ]) + ldap_conn.add_s("cn=Manager," + self.base_dn, [ + ("objectClass", b"organizationalRole"), + ]) + for ou in ("Users", "Groups", "Netgroups", "Services", "Policies", + "Hosts", "Networks"): + ldap_conn.add_s("ou=" + ou + "," + self.base_dn, [ + ("objectClass", [b"top", b"organizationalUnit"]), + ]) + ldap_conn.add_s("ou=sudoers," + self.base_dn, [ + ("objectClass", [b"top", b"organizationalUnit"]), + ]) + ldap_conn.add_s("cn=testrule,ou=sudoers," + self.base_dn, [ + ("objectClass", [b"top", b"sudoRole"]), + ("sudoUser", [b"tuser"]), + ]) + ldap_conn.unbind_s() + + def _stop_daemon(self): + """Stop the instance.""" + # Wait for slapd to stop + try: + pid_file = open(self.pid_path, "r") + try: + os.kill(int(pid_file.read()), signal.SIGTERM) + finally: + pid_file.close() + attempt = 0 + while os.path.isfile(self.pid_path): + attempt = attempt + 1 + if attempt > 30: + raise Exception("Failed to stop slapd") + time.sleep(1) + except IOError as e: + if e.errno != errno.ENOENT: + raise + + def teardown(self): + """Teardown the instance.""" + self._stop_daemon() + + for path in (self.conf_slapd_d_dir, self.run_dir, self.data_dir): + shutil.rmtree(path, True) + + +class FakeAD(DSOpenLDAP): + """Fake Active Directory based on OpenLDAP directory server.""" + + def _setup_config(self): + """Setup the instance initial configuration.""" + + # Import ad schema + subprocess.check_call( + ["slapadd", "-F", self.conf_slapd_d_dir, "-b", "cn=config", + "-l", "data/ad_schema.ldif"], + ) + + def setup(self): + """Setup the instance.""" + ldapi_socket = self.run_dir + "/ldapi" + self.ldapi_url = "ldapi://" + url_quote(ldapi_socket, "") + self.url_list = self.ldapi_url + " " + self.ldap_url + + os.makedirs(self.conf_slapd_d_dir) + os.makedirs(self.run_dir) + os.makedirs(self.data_dir) + + super(FakeAD, self)._setup_config() + self._setup_config() + + # Start the daemon + super(FakeAD, self)._start_daemon() + + # Relax requirement of surname attribute presence in person + modlist = [ + (ldap.MOD_DELETE, "olcObjectClasses", + b"{4}( 2.5.6.6 NAME 'person' DESC 'RFC2256: a person' SUP top " + b"STRUCTURAL MUST ( sn $ cn ) MAY ( userPassword $ " + b"telephoneNumber $ seeAlso $ description ) )"), + (ldap.MOD_ADD, "olcObjectClasses", + b"{4}( 2.5.6.6 NAME 'person' DESC 'RFC2256: a person' SUP top " + b"STRUCTURAL MUST ( cn ) MAY ( sn $ userPassword $ " + b"telephoneNumber $ seeAlso $ description ) )"), + ] + ldap_conn = ldap.initialize(self.ldapi_url) + ldap_conn.simple_bind_s(self.admin_rdn + ",cn=config", self.admin_pw) + ldap_conn.modify_s("cn={0}core,cn=schema,cn=config", modlist) + ldap_conn.unbind_s() + + # restart daemon for reloading schema + super(FakeAD, self)._stop_daemon() + super(FakeAD, self)._start_daemon() + + # Add data + ldap_conn = ldap.initialize(self.ldap_url) + ldap_conn.simple_bind_s(self.admin_dn, self.admin_pw) + ldap_conn.add_s(self.base_dn, [ + ("objectClass", [b"dcObject", b"organization"]), + ("o", b"Example Company"), + ]) + ldap_conn.add_s("cn=Manager," + self.base_dn, [ + ("objectClass", b"organizationalRole"), + ]) + for ou in ("Users", "Groups", "Netgroups", "Services", "Policies"): + ldap_conn.add_s("ou=" + ou + "," + self.base_dn, [ + ("objectClass", [b"top", b"organizationalUnit"]), + ]) + ldap_conn.unbind_s() + + # import data from real AD + subprocess.check_call( + ["ldapadd", "-x", "-w", self.admin_pw, "-D", + self.admin_dn, "-H", self.ldap_url, + "-f", "data/ad_data.ldif"], + ) + + def teardown(self): + """Teardown the instance.""" + super(FakeAD, self).teardown() diff --git a/src/tests/intg/ent.py b/src/tests/intg/ent.py new file mode 100644 index 0000000..d716ae5 --- /dev/null +++ b/src/tests/intg/ent.py @@ -0,0 +1,506 @@ +# +# Abstract passwd/group entry management +# +# Copyright (c) 2015 Red Hat, Inc. +# Author: Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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/>. +# + +from pprint import pformat +import pwd +import grp + +_PASSWD_LIST_DESC = {None: ("user", {})} +_GROUP_DESC = {"mem": ("member list", {None: ("member", {})})} +_GROUP_LIST_DESC = {None: ("group", _GROUP_DESC)} + + +def _get_desc(desc_map, key): + """ + Get an item description from a container description map. + + Arguments: + desc_map Container description map. + key Item key, None for wildcard description. + """ + assert isinstance(desc_map, dict) + if key in desc_map: + return desc_map[key] + if None in desc_map: + desc = desc_map[None] + if key is not None: + desc = (desc[0] + " " + pformat(key), desc[1]) + return desc + elif key is None: + return ("item", {}) + else: + return (pformat(key), {}) + + +def _diff(ent, pattern, desc_map={}): + """ + Describe difference between an entry and a pattern. + Return None, if none. + + Arguments: + ent Entry. + pattern Pattern. + desc_map Container pattern description map. + + An entry is a value, a list of entries, or a dictionary of entries. + Entries are used to store passwd and group database entries as + dictionaries, in lists and dictionaries. + + A pattern is a value, a tuple, a list, or a dictionary of patterns. + + E.g. 123, "abc", [ 123, "abc" ], { "abc": 123 }, { "abc": ( 123 ) } + + A pattern can be matched against a value, a list, or a dictionary entry. + + A value is considered matching, if it's equal to the pattern. + + E.g. 123 == 123, 123 != 456, "abc" == "abc", "abc" != "def", 123 != "abc" + + A list is considered matching a pattern, if the pattern is a list or a + tuple, where each of pattern list items matches an entry list item and + vice versa, or where each pattern tuple item matches an entry list item, + but not necessarily the other way around. + + E.g. [] != "abc", [] == [], [ "abc", 123 ] == [ 123, "abc" ], + [ "abc" ] != [ 123 ], [ 123 ] != [], + [] == (), [ "abc", 123 ] == ( 123, "abc" ), + [ "abc" ] != ( 123 ), [ 123 ] == (), [ 123, "abc" ] == ( 123 ) + + NOTE: For the sake of readability, it is recommended to use + "contains_only" function to create patterns matching all entry list + items (list patterns), and "contains" function to create patterns + matching a subset of entry list items (tuple patterns). + + A dictionary is considered matching a pattern, if it is also a dictionary, + and all of pattern values match identically-keyed values of the + dictionary. + + E.g. {} == {}, {} != "abc", { "abc": 123, "def": 456 } == { "abc": 123 }, + { "abc": 123 } == {} + + Container pattern description map is a dictionary with keys being item + keys/indices and values being (name, description map) tuples. None key + points to a wildcard description, others to specific item descriptions. + The description map argument is optional, and is used to generate more + readable difference explanations. + """ + assert isinstance(desc_map, dict) + + if isinstance(pattern, dict): + if not isinstance(ent, dict): + return "not a dict, " + str(type(ent)) + + for key, value in pattern.items(): + item_name, item_map = _get_desc(desc_map, key) + d = _diff(ent[key], value, item_map) + if d: + return item_name + " mismatch: " + d + elif isinstance(pattern, tuple): + if not isinstance(ent, list): + return "not a list, " + str(type(ent)) + + pattern_matches = [0 for pv in pattern] + + for ei, ev in enumerate(ent): + for pi, pv in enumerate(pattern): + d = _diff(ev, pv) + if not d: + pattern_matches[pi] += 1 + + unmatched_pattern = [pattern[pi] for pi in range(0, len(pattern)) + if pattern_matches[pi] == 0] + + items = _get_desc(desc_map, None)[0] + "s" + if len(unmatched_pattern) > 0: + return "\nexpected " + items + " not found:\n" + \ + pformat(unmatched_pattern) + elif isinstance(pattern, list): + if not isinstance(ent, list): + return "not a list, " + str(type(ent)) + + pattern_matches = [0 for pv in pattern] + ent_matches = [0 for ev in ent] + + for ei, ev in enumerate(ent): + for pi, pv in enumerate(pattern): + d = _diff(ev, pv) + if not d: + pattern_matches[pi] += 1 + ent_matches[ei] += 1 + + unmatched_pattern = [pattern[pi] for pi in range(0, len(pattern)) + if pattern_matches[pi] == 0] + unmatched_ent = [ent[pi] for pi in range(0, len(ent)) + if ent_matches[pi] == 0] + + items = _get_desc(desc_map, None)[0] + "s" + d = "" + if len(unmatched_pattern) > 0: + d += "\nexpected " + items + " not found:\n" + \ + pformat(unmatched_pattern) + if len(unmatched_ent) != 0: + d += "\nunexpected " + items + " found:\n" + \ + pformat(unmatched_ent) + if len(d) > 0: + return d + else: + if pattern != ent: + return pformat(pattern) + " != " + pformat(ent) + + return None + + +def contains_only(*args): + """ + Produce a pattern matching all list items against arguments. + Use this function instead of constructing bare lists, for readability. + """ + return list(args) + + +def contains(*args): + """ + Produce a pattern matching a subset of list items against arguments. + Use this function instead of constructing bare tuples, for readability. + """ + return args + + +def _convert_passwd(passwd): + """ + Convert a passwd entry returned by pwd module to an entry dictionary. + """ + return dict( + name=passwd.pw_name, + passwd=passwd.pw_passwd, + uid=passwd.pw_uid, + gid=passwd.pw_gid, + gecos=passwd.pw_gecos, + dir=passwd.pw_dir, + shell=passwd.pw_shell + ) + + +def get_passwd_by_name(name): + """Get a passwd database entry by name.""" + return _convert_passwd(pwd.getpwnam(name)) + + +def get_passwd_by_uid(uid): + """Get a passwd database entry by UID.""" + return _convert_passwd(pwd.getpwuid(uid)) + + +def assert_passwd_by_name(name, pattern): + """Assert a passwd entry, retrieved by name, matches a pattern.""" + try: + ent = get_passwd_by_name(name) + except KeyError as err: + assert False, err + d = _diff(ent, pattern) + assert not d, d + + +def assert_passwd_by_uid(uid, pattern): + """Assert a passwd entry, retrieved by UID, matches a pattern.""" + try: + ent = get_passwd_by_uid(uid) + except KeyError as err: + assert False, err + d = _diff(ent, pattern) + assert not d, d + + +def get_passwd_list(): + """Get passwd database entry list with root user removed.""" + passwd_list = pwd.getpwall() + for i, v in enumerate(passwd_list): + if v.pw_name == "root" and v.pw_uid == 0 and v.pw_gid == 0: + del passwd_list[i] + return list(map(_convert_passwd, passwd_list)) + raise Exception("no root user found") + + +def assert_passwd_list(pattern): + """Assert retrieved passwd list matches a pattern.""" + d = _diff(get_passwd_list(), pattern, _PASSWD_LIST_DESC) + assert not d, d + + +def _diff_each_passwd_by_name(pattern_dict): + """ + Describe difference between each pattern_dict value and a passwd entry + retrieved by name being the corresponding key. + """ + try: + ent = dict((k, get_passwd_by_name(k)) for k in pattern_dict.keys()) + except KeyError as err: + return str(err) + return _diff(ent, pattern_dict, _PASSWD_LIST_DESC) + + +def _diff_each_passwd_by_uid(pattern_dict): + """ + Describe difference between each pattern_dict value and a passwd entry + retrieved by UID being the corresponding key. + """ + try: + ent = dict((k, get_passwd_by_uid(k)) for k in pattern_dict.keys()) + except KeyError as err: + return str(err) + return _diff(ent, pattern_dict, _PASSWD_LIST_DESC) + + +def _diff_each_passwd_with_name(pattern_seq): + """ + Describe difference between each pattern in pattern_seq sequence and a + passwd entry retrieved by name being the pattern's "name" value. + """ + return _diff_each_passwd_by_name(dict((p["name"], p) for p in pattern_seq)) + + +def _diff_each_passwd_with_uid(pattern_seq): + """ + Describe difference between each pattern in pattern_seq sequence and a + passwd entry retrieved by UID being the pattern's "uid" value. + """ + return _diff_each_passwd_by_uid(dict((p["uid"], p) for p in pattern_seq)) + + +def assert_each_passwd_by_name(pattern_dict): + """ + Assert each pattern_dict value matches a passwd entry retrieved by + name being the corresponding key. + """ + d = _diff_each_passwd_by_name(pattern_dict) + assert not d, d + + +def assert_each_passwd_by_uid(pattern_dict): + """ + Assert each pattern_dict value matches a passwd entry retrieved by + UID being the corresponding key. + """ + d = _diff_each_passwd_by_uid(pattern_dict) + assert not d, d + + +def assert_each_passwd_with_name(pattern_seq): + """ + Assert each pattern in pattern_seq sequence matches a passwd entry + retrieved by name being the pattern's "name" value. + """ + d = _diff_each_passwd_with_name(pattern_seq) + assert not d, d + + +def assert_each_passwd_with_uid(pattern_seq): + """ + Assert each pattern in pattern_seq sequence matches a passwd entry + retrieved by UID being the pattern's "uid" value. + """ + d = _diff_each_passwd_with_uid(pattern_seq) + assert not d, d + + +def _diff_passwd(pattern): + """ + Describe difference between passwd database and a pattern. + Each pattern entry must have "name" and "uid" attribute. + """ + d = _diff(get_passwd_list(), pattern, _PASSWD_LIST_DESC) + if d: + return "list mismatch: " + d + d = _diff_each_passwd_with_name(pattern) + if d: + return "name retrieval mismatch: " + d + d = _diff_each_passwd_with_uid(pattern) + if d: + return "UID retrieval mismatch: " + d + return None + + +def assert_passwd(pattern): + """ + Assert passwd database matches a pattern. + Each pattern entry must have "name" and "uid" attribute. + """ + d = _diff_passwd(pattern) + assert not d, d + + +def _convert_group(group): + """ + Convert a group entry returned by grp module to an entry dictionary. + """ + return dict( + name=group.gr_name, + passwd=group.gr_passwd, + gid=group.gr_gid, + mem=group.gr_mem + ) + + +def get_group_by_name(name): + """Get a group database entry by name.""" + return _convert_group(grp.getgrnam(name)) + + +def get_group_by_gid(gid): + """Get a group database entry by GID.""" + return _convert_group(grp.getgrgid(gid)) + + +def assert_group_by_name(name, pattern): + """Assert a group entry, retrieved by name, matches a pattern.""" + try: + ent = get_group_by_name(name) + except KeyError as err: + assert False, err + d = _diff(ent, pattern, _GROUP_DESC) + assert not d, d + + +def assert_group_by_gid(gid, pattern): + """Assert a group entry, retrieved by GID, matches a pattern.""" + try: + ent = get_group_by_gid(gid) + except KeyError as err: + assert False, err + d = _diff(ent, pattern, _GROUP_DESC) + assert not d, d + + +def get_group_list(): + """Get group database entry list with root group removed.""" + group_list = grp.getgrall() + for i, v in enumerate(group_list): + if v.gr_name == "root" and v.gr_gid == 0: + del group_list[i] + return list(map(_convert_group, group_list)) + raise Exception("no root group found") + + +def assert_group_list(pattern): + """Assert retrieved group list matches a pattern.""" + d = _diff(get_group_list(), pattern, _GROUP_LIST_DESC) + assert not d, d + + +def _diff_each_group_by_name(pattern_dict): + """ + Describe difference between each pattern_dict value and a group entry + retrieved by name being the corresponding key. + """ + try: + ent = dict((k, get_group_by_name(k)) for k in pattern_dict.keys()) + except KeyError as err: + return str(err) + return _diff(ent, pattern_dict, _GROUP_LIST_DESC) + + +def _diff_each_group_by_gid(pattern_dict): + """ + Describe difference between each pattern_dict value and a group entry + retrieved by GID being the corresponding key. + """ + try: + ent = dict((k, get_group_by_gid(k)) for k in pattern_dict.keys()) + except KeyError as err: + return str(err) + return _diff(ent, pattern_dict, _GROUP_LIST_DESC) + + +def _diff_each_group_with_name(pattern_seq): + """ + Describe difference between each pattern in pattern_seq sequence and a + group entry retrieved name being the pattern's "name" value. + """ + return _diff_each_group_by_name(dict((p["name"], p) for p in pattern_seq)) + + +def _diff_each_group_with_gid(pattern_seq): + """ + Describe difference between each pattern in pattern_seq sequence and a + group entry retrieved by GID being the pattern's "gid" value. + """ + return _diff_each_group_by_gid(dict((p["gid"], p) for p in pattern_seq)) + + +def assert_each_group_by_name(pattern_dict): + """ + Assert each pattern_dict value matches a group entry retrieved by + name being the corresponding key. + """ + d = _diff_each_group_by_name(pattern_dict) + assert not d, d + + +def assert_each_group_by_gid(pattern_dict): + """ + Assert each pattern_dict value matches a group entry retrieved by + GID being the corresponding key. + """ + d = _diff_each_group_by_gid(pattern_dict) + assert not d, d + + +def assert_each_group_with_name(pattern_seq): + """ + Assert each pattern in pattern_seq sequence matches a group entry + retrieved by name being the pattern's "name" value. + """ + d = _diff_each_group_with_name(pattern_seq) + assert not d, d + + +def assert_each_group_with_gid(pattern_seq): + """ + Assert each pattern in pattern_seq sequence matches a group entry + retrieved by GID being the pattern's "gid" value. + """ + d = _diff_each_group_with_gid(pattern_seq) + assert not d, d + + +def _diff_group(pattern): + """ + Describe difference between group database and a pattern. + Each pattern entry must have "name" and "gid" attribute. + """ + d = _diff(get_group_list(), pattern, _GROUP_LIST_DESC) + if d: + return "list mismatch: " + d + d = _diff_each_group_with_name(pattern) + if d: + return "name retrieval mismatch: " + d + d = _diff_each_group_with_gid(pattern) + if d: + return "GID retrieval mismatch: " + d + return None + + +def assert_group(pattern): + """ + Assert group database matches a pattern. + Each pattern entry must have "name" and "gid" attribute. + """ + d = _diff_group(pattern) + assert not d, d diff --git a/src/tests/intg/ent_test.py b/src/tests/intg/ent_test.py new file mode 100644 index 0000000..4d741a3 --- /dev/null +++ b/src/tests/intg/ent_test.py @@ -0,0 +1,429 @@ +# +# ent.py module tests +# +# Copyright (c) 2015 Red Hat, Inc. +# Author: Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 re +import pytest +import ent +from util import backup_envvar_file, restore_envvar_file + + +@pytest.fixture(scope="module") +def passwd_path(request): + name = "NSS_WRAPPER_PASSWD" + request.addfinalizer(lambda: restore_envvar_file(name)) + return backup_envvar_file(name) + + +@pytest.fixture(scope="module") +def group_path(request): + name = "NSS_WRAPPER_GROUP" + request.addfinalizer(lambda: restore_envvar_file(name)) + return backup_envvar_file(name) + + +USER1 = dict(name="user1", passwd="x", uid=1001, gid=2001, + gecos="User 1", dir="/home/user1", shell="/bin/bash") +USER2 = dict(name="user2", passwd="x", uid=1002, gid=2002, + gecos="User 2", dir="/home/user2", shell="/bin/bash") +USER_LIST = [USER1, USER2] +USER_NAME_DICT = dict((u["name"], u) for u in USER_LIST) +USER_UID_DICT = dict((u["uid"], u) for u in USER_LIST) + + +EMPTY_GROUP = dict(name="empty_group", passwd="x", gid=2000, + mem=ent.contains_only()) +GROUP1 = dict(name="group1", passwd="x", gid=2001, + mem=ent.contains_only()) +GROUP2 = dict(name="group2", passwd="x", gid=2002, + mem=ent.contains_only()) +ONE_USER_GROUP1 = dict(name="one_user_group1", passwd="x", gid=2011, + mem=ent.contains_only("user1")) +ONE_USER_GROUP2 = dict(name="one_user_group2", passwd="x", gid=2012, + mem=ent.contains_only("user2")) +TWO_USER_GROUP = dict(name="two_user_group", passwd="x", gid=2020, + mem=ent.contains_only("user1", "user2")) +GROUP_LIST = [EMPTY_GROUP, + GROUP1, + GROUP2, + ONE_USER_GROUP1, + ONE_USER_GROUP2, + TWO_USER_GROUP] +GROUP_NAME_DICT = dict((g["name"], g) for g in GROUP_LIST) +GROUP_GID_DICT = dict((g["gid"], g) for g in GROUP_LIST) + + +@pytest.fixture(scope="module") +def users_and_groups(request, passwd_path, group_path): + passwd_contents = "".join([ + "{name}:{passwd}:{uid}:{gid}:{gecos}:{dir}:{shell}\n".format(**u) + for u in USER_LIST + ]) + group_contents = "".join([ + "%s:%s:%s:%s\n" % (g["name"], g["passwd"], g["gid"], + ",".join(g["mem"])) + for g in GROUP_LIST + ]) + + with open(passwd_path, "a") as f: + f.write(passwd_contents) + with open(group_path, "a") as f: + f.write(group_contents) + + +def test_assert_passwd_by_name(users_and_groups): + ent.assert_passwd_by_name("user1", {}) + ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001)) + ent.assert_passwd_by_name("user1", USER1) + + try: + ent.assert_passwd_by_name("user3", {}) + assert False + except AssertionError as e: + assert str(e) in ("'getpwnam(): name not found: user3'", + "\"getpwnam(): name not found: 'user3'\"") + + try: + ent.assert_passwd_by_name("user2", dict(name="user1")) + assert False + except AssertionError as e: + assert str(e) == "'name' mismatch: 'user1' != 'user2'" + + +def test_assert_passwd_by_uid(users_and_groups): + ent.assert_passwd_by_uid(1001, {}) + ent.assert_passwd_by_uid(1001, dict(name="user1", uid=1001)) + ent.assert_passwd_by_uid(1001, USER1) + + try: + ent.assert_passwd_by_uid(1003, {}) + assert False + except AssertionError as e: + assert str(e) == "'getpwuid(): uid not found: 1003'" + + try: + ent.assert_passwd_by_uid(1002, dict(name="user1")) + assert False + except AssertionError as e: + assert str(e) == "'name' mismatch: 'user1' != 'user2'" + + +def test_assert_passwd_list(users_and_groups): + ent.assert_passwd_list(ent.contains()) + ent.assert_passwd_list(ent.contains(USER1)) + ent.assert_passwd_list(ent.contains_only(*USER_LIST)) + try: + ent.assert_passwd_list(ent.contains_only()) + assert False + except AssertionError as e: + assert not re.search("expected users not found:", str(e)) + assert re.search("unexpected users found:", str(e)) + try: + ent.assert_passwd_list(ent.contains(dict(name="non_existent"))) + assert False + except AssertionError as e: + assert re.search("expected users not found:", str(e)) + assert not re.search("unexpected users found:", str(e)) + + +def test_assert_each_passwd_by_name(users_and_groups): + ent.assert_each_passwd_by_name({}) + ent.assert_each_passwd_by_name(dict(user1=USER1)) + ent.assert_each_passwd_by_name(USER_NAME_DICT) + try: + ent.assert_each_passwd_by_name(dict(user3={})) + assert False + except AssertionError as e: + assert str(e) in ("'getpwnam(): name not found: user3'", + "\"getpwnam(): name not found: 'user3'\"") + try: + ent.assert_each_passwd_by_name(dict(user1=dict(name="user2"))) + assert False + except AssertionError as e: + assert str(e) == \ + "user 'user1' mismatch: 'name' mismatch: 'user2' != 'user1'" + + +def test_assert_each_passwd_by_uid(users_and_groups): + ent.assert_each_passwd_by_uid({}) + ent.assert_each_passwd_by_uid({1001: USER1}) + ent.assert_each_passwd_by_uid(USER_UID_DICT) + try: + ent.assert_each_passwd_by_uid({1003: {}}) + assert False + except AssertionError as e: + assert str(e) == "'getpwuid(): uid not found: 1003'" + try: + ent.assert_each_passwd_by_uid({1001: dict(uid=1002)}) + assert False + except AssertionError as e: + assert str(e) == \ + "user 1001 mismatch: 'uid' mismatch: 1002 != 1001" + + +def test_assert_each_passwd_with_name(users_and_groups): + ent.assert_each_passwd_with_name([]) + ent.assert_each_passwd_with_name([USER1]) + ent.assert_each_passwd_with_name(USER_LIST) + try: + ent.assert_each_passwd_with_name([dict(name="user3")]) + assert False + except AssertionError as e: + assert str(e) in ("'getpwnam(): name not found: user3'", + "\"getpwnam(): name not found: 'user3'\"") + try: + ent.assert_each_passwd_with_name([dict(name="user1", uid=1002)]) + assert False + except AssertionError as e: + assert str(e) == \ + "user 'user1' mismatch: 'uid' mismatch: 1002 != 1001" + + +def test_assert_each_passwd_with_uid(users_and_groups): + ent.assert_each_passwd_with_uid([]) + ent.assert_each_passwd_with_uid([USER1]) + ent.assert_each_passwd_with_uid(USER_LIST) + try: + ent.assert_each_passwd_with_uid([dict(uid=1003)]) + assert False + except AssertionError as e: + assert str(e) == "'getpwuid(): uid not found: 1003'" + try: + ent.assert_each_passwd_with_uid([dict(name="user2", uid=1001)]) + assert False + except AssertionError as e: + assert str(e) == \ + "user 1001 mismatch: 'name' mismatch: 'user2' != 'user1'" + + +def test_assert_passwd(users_and_groups): + ent.assert_passwd(ent.contains()) + ent.assert_passwd(ent.contains(USER1)) + ent.assert_passwd(ent.contains_only(*USER_LIST)) + try: + ent.assert_passwd(ent.contains(dict(name="user3", uid=1003))) + assert False + except AssertionError as e: + assert re.search("list mismatch:", str(e)) + assert re.search("expected users not found:", str(e)) + assert not re.search("unexpected users found:", str(e)) + try: + ent.assert_passwd(ent.contains_only(USER1)) + assert False + except AssertionError as e: + assert re.search("list mismatch:", str(e)) + assert not re.search("expected users not found:", str(e)) + assert re.search("unexpected users found:", str(e)) + + +def test_group_member_matching(users_and_groups): + ent.assert_group_by_name("empty_group", dict(mem=ent.contains())) + ent.assert_group_by_name("empty_group", dict(mem=ent.contains_only())) + try: + ent.assert_group_by_name("empty_group", + dict(mem=ent.contains("user1"))) + except AssertionError as e: + assert re.search("member list mismatch:", str(e)) + assert re.search("expected members not found:", str(e)) + + ent.assert_group_by_name("one_user_group1", dict(mem=ent.contains())) + ent.assert_group_by_name("one_user_group1", + dict(mem=ent.contains("user1"))) + ent.assert_group_by_name("one_user_group1", + dict(mem=ent.contains_only("user1"))) + try: + ent.assert_group_by_name("one_user_group1", + dict(mem=ent.contains_only())) + except AssertionError as e: + assert re.search("member list mismatch:", str(e)) + assert re.search("unexpected members found:", str(e)) + assert not re.search("expected members not found:", str(e)) + try: + ent.assert_group_by_name("one_user_group1", + dict(mem=ent.contains_only("user3"))) + except AssertionError as e: + assert re.search("member list mismatch:", str(e)) + assert re.search("unexpected members found:", str(e)) + assert re.search("expected members not found:", str(e)) + try: + ent.assert_group_by_name("one_user_group1", + dict(mem=ent.contains("user3"))) + except AssertionError as e: + assert re.search("member list mismatch:", str(e)) + assert not re.search("unexpected members found:", str(e)) + assert re.search("expected members not found:", str(e)) + + ent.assert_group_by_name("two_user_group", dict(mem=ent.contains())) + ent.assert_group_by_name("two_user_group", + dict(mem=ent.contains("user1"))) + ent.assert_group_by_name("two_user_group", + dict(mem=ent.contains("user1", "user2"))) + ent.assert_group_by_name("two_user_group", + dict(mem=ent.contains_only("user1", "user2"))) + try: + ent.assert_group_by_name("two_user_group", + dict(mem=ent.contains_only("user1"))) + except AssertionError as e: + assert re.search("member list mismatch:", str(e)) + assert re.search("unexpected members found:", str(e)) + assert not re.search("expected members not found:", str(e)) + + +def test_assert_group_by_name(users_and_groups): + ent.assert_group_by_name("group1", {}) + ent.assert_group_by_name("group1", dict(name="group1", gid=2001)) + ent.assert_group_by_name("group1", GROUP1) + + try: + ent.assert_group_by_name("group3", {}) + assert False + except AssertionError as e: + assert str(e) in ("'getgrnam(): name not found: group3'", + "\"getgrnam(): name not found: 'group3'\"") + + try: + ent.assert_group_by_name("group2", dict(name="group1")) + assert False + except AssertionError as e: + assert str(e) == "'name' mismatch: 'group1' != 'group2'" + + +def test_assert_group_by_gid(users_and_groups): + ent.assert_group_by_gid(2001, {}) + ent.assert_group_by_gid(2001, dict(name="group1", gid=2001)) + ent.assert_group_by_gid(2001, GROUP1) + + try: + ent.assert_group_by_gid(2003, {}) + assert False + except AssertionError as e: + assert str(e) == "'getgrgid(): gid not found: 2003'" + + try: + ent.assert_group_by_gid(2002, dict(name="group1")) + assert False + except AssertionError as e: + assert str(e) == "'name' mismatch: 'group1' != 'group2'" + + +def test_assert_group_list(users_and_groups): + ent.assert_group_list(ent.contains()) + ent.assert_group_list(ent.contains(GROUP1)) + ent.assert_group_list(ent.contains_only(*GROUP_LIST)) + try: + ent.assert_group_list(ent.contains_only()) + assert False + except AssertionError as e: + assert not re.search("expected groups not found:", str(e)) + assert re.search("unexpected groups found:", str(e)) + try: + ent.assert_group_list(ent.contains(dict(name="non_existent"))) + assert False + except AssertionError as e: + assert re.search("expected groups not found:", str(e)) + assert not re.search("unexpected groups found:", str(e)) + + +def test_assert_each_group_by_name(users_and_groups): + ent.assert_each_group_by_name({}) + ent.assert_each_group_by_name(dict(group1=GROUP1)) + ent.assert_each_group_by_name(GROUP_NAME_DICT) + try: + ent.assert_each_group_by_name(dict(group3={})) + assert False + except AssertionError as e: + assert str(e) in ("'getgrnam(): name not found: group3'", + "\"getgrnam(): name not found: 'group3'\"") + try: + ent.assert_each_group_by_name(dict(group1=dict(name="group2"))) + assert False + except AssertionError as e: + assert str(e) == "group 'group1' mismatch: " + \ + "'name' mismatch: 'group2' != 'group1'" + + +def test_assert_each_group_by_gid(users_and_groups): + ent.assert_each_group_by_gid({}) + ent.assert_each_group_by_gid({2001: GROUP1}) + ent.assert_each_group_by_gid(GROUP_GID_DICT) + try: + ent.assert_each_group_by_gid({2003: {}}) + assert False + except AssertionError as e: + assert str(e) == "'getgrgid(): gid not found: 2003'" + try: + ent.assert_each_group_by_gid({2001: dict(gid=2002)}) + assert False + except AssertionError as e: + assert str(e) == \ + "group 2001 mismatch: 'gid' mismatch: 2002 != 2001" + + +def test_assert_each_group_with_name(users_and_groups): + ent.assert_each_group_with_name([]) + ent.assert_each_group_with_name([GROUP1]) + ent.assert_each_group_with_name(GROUP_LIST) + try: + ent.assert_each_group_with_name([dict(name="group3")]) + assert False + except AssertionError as e: + assert str(e) in ("'getgrnam(): name not found: group3'", + "\"getgrnam(): name not found: 'group3'\"") + try: + ent.assert_each_group_with_name([dict(name="group1", gid=2002)]) + assert False + except AssertionError as e: + assert str(e) == \ + "group 'group1' mismatch: 'gid' mismatch: 2002 != 2001" + + +def test_assert_each_group_with_gid(users_and_groups): + ent.assert_each_group_with_gid([]) + ent.assert_each_group_with_gid([GROUP1]) + ent.assert_each_group_with_gid(GROUP_LIST) + try: + ent.assert_each_group_with_gid([dict(gid=2003)]) + assert False + except AssertionError as e: + assert str(e) == "'getgrgid(): gid not found: 2003'" + try: + ent.assert_each_group_with_gid([dict(name="group2", gid=2001)]) + assert False + except AssertionError as e: + assert str(e) == \ + "group 2001 mismatch: 'name' mismatch: 'group2' != 'group1'" + + +def test_assert_group(users_and_groups): + ent.assert_group(ent.contains()) + ent.assert_group(ent.contains(GROUP1)) + ent.assert_group(ent.contains_only(*GROUP_LIST)) + try: + ent.assert_group(ent.contains(dict(name="group3", gid=2003))) + assert False + except AssertionError as e: + assert re.search("list mismatch:", str(e)) + assert re.search("expected groups not found:", str(e)) + assert not re.search("unexpected groups found:", str(e)) + try: + ent.assert_group(ent.contains_only(GROUP1)) + assert False + except AssertionError as e: + assert re.search("list mismatch:", str(e)) + assert not re.search("expected groups not found:", str(e)) + assert re.search("unexpected groups found:", str(e)) diff --git a/src/tests/intg/files_ops.py b/src/tests/intg/files_ops.py new file mode 100644 index 0000000..57959f5 --- /dev/null +++ b/src/tests/intg/files_ops.py @@ -0,0 +1,173 @@ +# +# SSSD integration test - operations on UNIX user and group database +# +# Copyright (c) 2016 Red Hat, Inc. +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 os.path +import tempfile +import pytest + +import ent +from util import backup_envvar_file, restore_envvar_file + + +@pytest.fixture +def passwd_ops_setup(request): + pwd_file = os.environ["NSS_WRAPPER_PASSWD"] + backup_envvar_file("NSS_WRAPPER_PASSWD") + request.addfinalizer(lambda: restore_envvar_file("NSS_WRAPPER_PASSWD")) + pwd_ops = PasswdOps(pwd_file) + return pwd_ops + + +@pytest.fixture +def group_ops_setup(request): + grp_file = os.environ["NSS_WRAPPER_GROUP"] + backup_envvar_file("NSS_WRAPPER_GROUP") + request.addfinalizer(lambda: restore_envvar_file("NSS_WRAPPER_GROUP")) + grp_ops = GroupOps(grp_file) + return grp_ops + + +@pytest.fixture +def group_db_setup(request): + group = request.param + grp_ops = group_ops_setup(request) + grp_ops.groupadd(**group) + ent.assert_group_by_name(group['name'], group) + return grp_ops + + +class FilesOps(object): + """ + A naive implementation of operations as a basis for user or group + operations. Uses rename to (hopefully) trigger the same fs-level + notifications as shadow-utils would. + """ + def __init__(self, file_name): + self.file_name = file_name + self.tmp_dir = os.path.dirname(self.file_name) + + @staticmethod + def _get_named_line(name, contents): + for num, line in enumerate(contents, 0): + pname = line.split(':')[0] + if name == pname: + return num + raise KeyError("%s not found" % name) + + def _read_contents(self): + with open(self.file_name, "r") as pfile: + contents = pfile.readlines() + return contents + + def _write_contents(self, contents): + tmp_file = tempfile.NamedTemporaryFile(mode='w', dir=self.tmp_dir, + delete=False) + tmp_file.writelines(contents) + tmp_file.flush() + + os.rename(tmp_file.name, self.file_name) + + def _append_line(self, new_line): + contents = self._read_contents() + contents.extend(new_line) + self._write_contents(contents) + + def _subst_line(self, key, line): + contents = self._read_contents() + kindex = self._get_named_line(key, contents) + contents[kindex] = line + self._write_contents(contents) + + def _del_line(self, key): + contents = self._read_contents() + kindex = self._get_named_line(key, contents) + contents.pop(kindex) + self._write_contents(contents) + + contents = self._read_contents() + + def _has_line(self, key): + try: + self._get_named_line(key, self._read_contents()) + return True + except KeyError: + return False + + +class PasswdOps(FilesOps): + """ + A naive implementation of user operations + """ + def __init__(self, file_name): + super(PasswdOps, self).__init__(file_name) + + def _pwd2line(self, name, uid, gid, passwd, gecos, homedir, shell): + pwd_fmt = "{name}:{passwd}:{uid}:{gid}:{gecos}:{homedir}:{shell}\n" + return pwd_fmt.format(name=name, + passwd=passwd, + uid=uid, + gid=gid, + gecos=gecos, + homedir=homedir, + shell=shell) + + def useradd(self, name, uid, gid, passwd='', gecos='', dir='', shell=''): + pwd_line = self._pwd2line(name, uid, gid, passwd, gecos, dir, shell) + self._append_line(pwd_line) + + def usermod(self, name, uid, gid, passwd='', gecos='', dir='', shell=''): + pwd_line = self._pwd2line(name, uid, gid, passwd, gecos, dir, shell) + self._subst_line(name, pwd_line) + + def userdel(self, name): + self._del_line(name) + + def userexist(self, name): + return self._has_line(name) + + +class GroupOps(FilesOps): + """ + A naive implementation of group operations + """ + def __init__(self, file_name): + super(GroupOps, self).__init__(file_name) + + def _grp2line(self, name, gid, mem, passwd): + member_list = ",".join(m for m in mem) + grp_fmt = "{name}:{passwd}:{gid}:{member_list}\n" + return grp_fmt.format(name=name, + passwd=passwd, + gid=gid, + member_list=member_list) + + def groupadd(self, name, gid, mem, passwd="*"): + grp_line = self._grp2line(name, gid, mem, passwd) + self._append_line(grp_line) + + def groupmod(self, old_name, name, gid, mem, passwd="*"): + grp_line = self._grp2line(name, gid, mem, passwd) + self._subst_line(old_name, grp_line) + + def groupdel(self, name): + self._del_line(name) + + def groupexist(self, name): + return self._has_line(name) diff --git a/src/tests/intg/getsockopt_wrapper.c b/src/tests/intg/getsockopt_wrapper.c new file mode 100644 index 0000000..725693c --- /dev/null +++ b/src/tests/intg/getsockopt_wrapper.c @@ -0,0 +1,94 @@ +/* gcc -Wall -fPIC -shared -o getsockopt_wrapper.so getsockopt_wrapper.c -ldl */ + +/* for RTLD_NEXT */ +#define _GNU_SOURCE 1 + +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <dlfcn.h> + +static bool is_dbus_socket(int fd) +{ + int ret; + struct sockaddr_storage addr = { 0 }; + socklen_t addrlen = sizeof(addr); + struct sockaddr_un *unix_socket; + + ret = getsockname(fd, (struct sockaddr *)&addr, &addrlen); + if (ret != 0) return false; + + if (addr.ss_family != AF_UNIX) return false; + + unix_socket = (struct sockaddr_un *)&addr; + + return NULL != strstr(unix_socket->sun_path, "system_bus_socket"); +} + +static bool peer_is_private_pam(int fd) +{ + int ret; + struct sockaddr_storage addr = { 0 }; + socklen_t addrlen = sizeof(addr); + struct sockaddr_un *unix_socket; + + ret = getpeername(fd, (struct sockaddr *)&addr, &addrlen); + if (ret != 0) return false; + + if (addr.ss_family != AF_UNIX) return false; + + unix_socket = (struct sockaddr_un *)&addr; + + return NULL != strstr(unix_socket->sun_path, "private/pam"); +} + +static void fake_peer_uid_gid(uid_t *uid, gid_t *gid) +{ + char *val; + + val = getenv("SSSD_INTG_PEER_UID"); + if (val != NULL) { + *uid = atoi(val); + } + + val = getenv("SSSD_INTG_PEER_GID"); + if (val != NULL) { + *gid = atoi(val); + } +} + +typedef typeof(getsockopt) getsockopt_fn_t; + +static getsockopt_fn_t *orig_getsockopt = NULL; + +int getsockopt(int sockfd, int level, int optname, + void *optval, socklen_t *optlen) +{ + int ret; +#ifdef __OpenBSD__ + struct sockpeercred *cr; +#else + struct ucred *cr; +#endif + + if (orig_getsockopt == NULL) { + orig_getsockopt = (getsockopt_fn_t *)dlsym(RTLD_NEXT, "getsockopt"); + } + + ret = orig_getsockopt(sockfd, level, optname, optval, optlen); + + if (ret == 0 && level == SOL_SOCKET && optname == SO_PEERCRED + && *optlen == sizeof(*cr)) { + cr = optval; + if (cr->uid != 0 && is_dbus_socket(sockfd)) { + cr->uid = 0; + } else if (peer_is_private_pam(sockfd)) { + fake_peer_uid_gid(&cr->uid, &cr->gid); + } + } + + return ret; +} diff --git a/src/tests/intg/kdc.py b/src/tests/intg/kdc.py new file mode 100644 index 0000000..a574e1f --- /dev/null +++ b/src/tests/intg/kdc.py @@ -0,0 +1,178 @@ +# +# MIT Kerberos server class +# +# Copyright (c) 2016 Red Hat, Inc. +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 signal +import shutil +import subprocess + +from util import unindent + + +class KDC(object): + """ + MIT Kerberos KDC instance + """ + + def __init__(self, basedir, realm, + includedir=None, + kdc_port=10088, + kadmin_port=10749, + master_key='master'): + self.basedir = basedir + self.realm = realm + self.kdc_port = kdc_port + self.kadmin_port = kadmin_port + self.master_key = master_key + + self.kdc_basedir = self.basedir + "/var/krb5kdc" + self.includedir = includedir or (self.kdc_basedir + "/include") + self.kdc_logdir = self.kdc_basedir + "/log" + self.kdc_conf_path = self.kdc_basedir + "/kdc.conf" + self.krb5_conf_path = self.kdc_basedir + "/krb5.conf" + + self.kdc_pid_file = self.kdc_basedir + "/kdc.pid" + + self.acl_file = self.kdc_basedir + "/kadm5.acl" + + self.admin_princ = "admin/admin@" + self.realm + + def start_kdc(self, extra_args=[]): + args = ["krb5kdc", '-P', self.kdc_pid_file] + extra_args + return self._run_in_env(args, self.get_krb5_env()) + + def stop_kdc(self): + try: + with open(self.kdc_pid_file, "r") as pid_file: + os.kill(int(pid_file.read()), signal.SIGTERM) + except IOError as ioex: + if ioex.errno == 2: + pass + else: + raise ioex + + def teardown(self): + self.stop_kdc() + shutil.rmtree(self.kdc_basedir) + + def set_up(self): + self._create_config() + self._create_acl() + self._create_kdb() + + def get_krb5_env(self): + my_env = os.environ.copy() + my_env['KRB5_CONFIG'] = self.krb5_conf_path + my_env['KRB5_KDC_PROFILE'] = self.kdc_conf_path + return my_env + + def add_config(self, include_files): + for name, contents in include_files.items(): + include_fpath = os.path.join(self.includedir, name) + with open(include_fpath, 'w') as include_file: + include_file.write(contents) + + def add_principal(self, princ, password=None): + args = ["kadmin.local", "-q"] + if password is None: + args += ["addprinc -randkey %s" % (princ)] + else: + args += ["addprinc -pw %s %s" % (password, princ)] + return self._run_in_env(args, self.get_krb5_env()) + + def _run_in_env(self, args, env): + cmd = subprocess.Popen(args, env=env) + out, err = cmd.communicate() + return cmd.returncode, out, err + + def _create_config(self): + try: + os.makedirs(self.kdc_basedir) + os.makedirs(self.kdc_logdir) + os.makedirs(self.includedir) + except OSError as osex: + if osex.errno == 17: + pass + + kdc_conf = self._format_kdc_conf() + with open(self.kdc_conf_path, 'w') as kdc_conf_file: + kdc_conf_file.write(kdc_conf) + + krb5_conf = self._format_krb5_conf() + with open(self.krb5_conf_path, 'w') as krb5_conf_file: + krb5_conf_file.write(krb5_conf) + + def _create_acl(self): + with open(self.acl_file, 'w') as acl_fobject: + acl_fobject.write(self.admin_princ) + + def _create_kdb(self): + self._run_in_env( + ['kdb5_util', 'create', '-W', '-s', '-P', self.master_key], + self.get_krb5_env() + ) + + def _format_kdc_conf(self): + database_path = self.kdc_basedir + "/principal" + key_stash = self.kdc_basedir + "/stash." + self.realm + + kdc_logfile = "FILE:" + self.kdc_logdir + "/krb5kdc.log" + kadmin_logfile = "FILE:" + self.kdc_logdir + "/kadmin.log" + libkrb5_logfile = "FILE:" + self.kdc_logdir + "/libkrb5.log" + + kdc_conf = unindent(""" + [kdcdefaults] + kdc_ports = {self.kdc_port} + kdc_tcp_ports = {self.kdc_port} + + [realms] + {self.realm} = {{ + kadmind_port = {self.kadmin_port} + database_name = {database_path} + key_stash_file = {key_stash} + max_life = 7d + max_renewable_life = 14d + acl_file = {self.acl_file} + }} + + [logging] + kdc = {kdc_logfile} + admin_server = {kadmin_logfile} + default = {libkrb5_logfile} + """).format(**locals()) + return kdc_conf + + def _format_krb5_conf(self): + kdc_uri = "localhost:%d" % self.kdc_port + kadmin_uri = "localhost:%d" % self.kadmin_port + + krb5_conf = unindent(""" + includedir {self.includedir} + + [libdefaults] + default_realm = {self.realm} + dns_lookup_kdc = false + dns_lookup_realm = false + + [realms] + {self.realm} = {{ + kdc = {kdc_uri} + admin_server = {kadmin_uri} + }} + """).format(**locals()) + return krb5_conf diff --git a/src/tests/intg/krb5utils.py b/src/tests/intg/krb5utils.py new file mode 100644 index 0000000..2d1b79e --- /dev/null +++ b/src/tests/intg/krb5utils.py @@ -0,0 +1,174 @@ +# +# MIT Kerberos server class +# +# Copyright (c) 2016 Red Hat, Inc. +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 subprocess + + +class NoPrincipals(Exception): + def __init__(self): + Exception.__init__(self, 'No principals in the collection') + + +class PrincNotFound(Exception): + def __init__(self, principal): + Exception.__init__(self, 'Principal %s not found' % principal) + + +class Krb5Utils(object): + """ + Helper class to test Kerberos command line utilities + """ + def __init__(self, krb5_conf_path): + self.krb5_conf_path = krb5_conf_path + + def spawn_in_env(self, args, stdin=None, extra_env=None): + my_env = os.environ.copy() + my_env['KRB5_CONFIG'] = self.krb5_conf_path + + if 'KRB5CCNAME' in my_env: + del my_env['KRB5CCNAME'] + if extra_env is not None: + my_env.update(extra_env) + + cmd = subprocess.Popen(args, + env=my_env, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + return cmd + + def _run_in_env(self, args, stdin=None, extra_env=None): + cmd = self.spawn_in_env(args, stdin, extra_env) + out, err = cmd.communicate(stdin) + return cmd.returncode, out.decode('utf-8'), err.decode('utf-8') + + def kinit(self, principal, password, options=None, env=None): + args = ["kinit", principal] + if options: + args.extend(options) + return self._run_in_env(args, password.encode('utf-8'), env) + + def kvno(self, principal, env=None): + args = ["kvno", principal] + return self._run_in_env(args, env) + + def kdestroy(self, all_ccaches=False, env=None): + args = ["kdestroy"] + if all_ccaches is True: + args += ["-A"] + retval, _, _ = self._run_in_env(args, env) + return retval + + def kswitch(self, principal, env=None): + args = ["kswitch", '-p', principal] + retval, _, _ = self._run_in_env(args, env) + return retval + + def _check_klist_l(self, line, exp_principal, exp_cache): + try: + princ, cache = line.split() + except ValueError: + return False + + if exp_cache is not None and cache != exp_cache: + return False + + if exp_principal != princ: + return False + + return True + + def num_princs(self, env=None): + args = ["klist", "-l"] + retval, out, err = self._run_in_env(args, extra_env=env) + if retval != 0: + return 0 + + outlines = [ln for ln in out.split('\n') if len(ln) > 1] + return len(outlines) - 2 + + def list_princs(self, env=None): + args = ["klist", "-l"] + retval, out, err = self._run_in_env(args, extra_env=env) + if retval == 1: + raise NoPrincipals + elif retval != 0: + raise Exception("klist failed: %d: %s\n", retval, err) + + outlines = out.split('\n') + if len(outlines) < 2: + raise Exception("Not enough output from klist -l") + + return [ln for ln in outlines[2:] if len(ln) > 0] + + def list_times(self, env=None): + p = self.spawn_in_env(['klist', '-A']) + output = p.stdout.read().splitlines() + for line in output: + if not line: + continue + + line_str = line.decode("utf-8") + if line_str[0].isdigit(): + return line_str + + def has_principal(self, exp_principal, exp_cache=None, env=None): + try: + princlist = self.list_princs(env) + except NoPrincipals: + return False + + for line in princlist: + matches = self._check_klist_l(line, exp_principal, exp_cache) + if matches is True: + return True + + return False + + def default_principal(self, env=None): + principals = self.list_princs(env) + return principals[0].split()[0] + + def _parse_klist_a(self, out): + dflprinc = None + thisrealm = None + ccache_dict = dict() + + for line in [ln for ln in out.split('\n') if len(ln) > 0]: + if line.startswith("Default principal"): + dflprinc = line.split()[2] + thisrealm = '@' + dflprinc.split('@')[1] + elif thisrealm is not None and line.endswith(thisrealm): + svc = line.split()[-1] + if dflprinc in ccache_dict: + ccache_dict[dflprinc].append(svc) + else: + ccache_dict[dflprinc] = [svc] + + return ccache_dict + + def list_all_princs(self, env=None): + args = ["klist", "-A"] + retval, out, err = self._run_in_env(args, extra_env=env) + if retval == 1: + raise NoPrincipals + elif retval != 0: + raise Exception("klist -A failed: %d: %s\n", retval, err) + + return self._parse_klist_a(out) diff --git a/src/tests/intg/ldap_ent.py b/src/tests/intg/ldap_ent.py new file mode 100644 index 0000000..2a56f3d --- /dev/null +++ b/src/tests/intg/ldap_ent.py @@ -0,0 +1,235 @@ +# +# LDAP modlist generation +# +# Copyright (c) 2015 Red Hat, Inc. +# Author: Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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/>. +# + + +def user(base_dn, uid, uidNumber, gidNumber, + userPassword=None, + gecos=None, + homeDirectory=None, + loginShell=None, + cn=None, + sn=None, + sshPubKey=(), + mail=None): + """ + Generate an RFC2307(bis) user add-modlist for passing to ldap.add* + """ + uidNumber = str(uidNumber).encode('utf-8') + gidNumber = str(gidNumber).encode('utf-8') + user = ( + "uid=" + uid + ",ou=Users," + base_dn, + [ + ('objectClass', [b'top', b'inetOrgPerson', b'mailRecipient', + b'posixAccount', b'ldapPublicKey']), + ('cn', [uidNumber if cn is None else cn.encode('utf-8')]), + ('sn', [b'User' if sn is None else sn.encode('utf-8')]), + ('uidNumber', [uidNumber]), + ('gidNumber', [gidNumber]), + ('userPassword', [b'Password' + uidNumber + if userPassword is None + else userPassword.encode('utf-8')]), + ('homeDirectory', [b'/home/' + uid.encode('utf-8') + if homeDirectory is None + else homeDirectory.encode('utf-8')]), + ('loginShell', [b'/bin/bash' + if loginShell is None + else loginShell.encode('utf-8')]), + ] + ) + if gecos is not None: + user[1].append(('gecos', [gecos.encode('utf-8')])) + if len(sshPubKey) > 0: + pubkeys = [key.encode('utf-8') for key in sshPubKey] + user[1].append(('sshPublicKey', pubkeys)) + if mail is not None: + user[1].append(('mail', [mail.encode('utf-8')])) + return user + + +def group(base_dn, cn, gidNumber, member_uids=()): + """ + Generate an RFC2307 group add-modlist for passing to ldap.add*. + """ + gidNumber = str(gidNumber).encode('utf-8') + attr_list = [ + ('objectClass', [b'top', b'posixGroup']), + ('gidNumber', [gidNumber]) + ] + if len(member_uids) > 0: + mem_uids = [member.encode('utf-8') for member in member_uids] + attr_list.append(('memberUid', mem_uids)) + return ("cn=" + cn + ",ou=Groups," + base_dn, attr_list) + + +def group_bis(base_dn, cn, gidNumber, member_uids=(), member_gids=()): + """ + Generate an RFC2307bis group add-modlist for passing to ldap.add*. + """ + gidNumber = str(gidNumber).encode('utf-8') + attr_list = [ + ('objectClass', [b'top', b'extensibleObject', b'groupOfNames']), + ('gidNumber', [gidNumber]) + ] + member_list = [] + for uid in member_uids: + member_list.append("uid=" + uid + ",ou=Users," + base_dn) + for gid in member_gids: + member_list.append("cn=" + gid + ",ou=Groups," + base_dn) + if len(member_list) > 0: + mem_list = [member.encode('utf-8') for member in member_list] + attr_list.append(('member', mem_list)) + return ("cn=" + cn + ",ou=Groups," + base_dn, attr_list) + + +def netgroup(base_dn, cn, triples=(), members=()): + """ + Generate an RFC2307bis netgroup add-modlist for passing to ldap.add*. + """ + attr_list = [ + ('objectClass', [b'top', b'nisNetgroup']) + ] + if triples: + triples = [triple.encode('utf-8') for triple in triples] + attr_list.append(('nisNetgroupTriple', triples)) + if members: + members = [member.encode('utf-8') for member in members] + attr_list.append(('memberNisNetgroup', members)) + return ("cn=" + cn + ",ou=Netgroups," + base_dn, attr_list) + + +def sudo_rule(base_dn, name, users=(), hosts=(), commands=()): + """ + Generate a sudo rule for passing to ldap.add* + """ + attr_list = [ + ('objectClass', [b'top', b'sudoRole']), + ('cn', [name.encode('utf-8')]) + ] + + if len(users) > 0: + sudo_user_list = [u.encode('utf-8') for u in users] + attr_list.append(('sudoUser', sudo_user_list)) + if len(hosts) > 0: + sudo_host_list = [h.encode('utf-8') for h in hosts] + attr_list.append(('sudoHost', sudo_host_list)) + if len(commands) > 0: + sudo_command_list = [cmd.encode('utf-8') for cmd in commands] + attr_list.append(('sudoCommand', sudo_command_list)) + return ("cn=" + name + ",ou=sudoers," + base_dn, attr_list) + + +def ip_host(base_dn, name, aliases=(), addresses=()): + """ + Generate an RFC2307 ipHost add-modlist for passing to ldap.add*. + """ + attr_list = [ + ('objectClass', [b'top', b'device', b'ipHost']), + ] + if (len(aliases)) > 0: + alias_list = [alias.encode('utf-8') for alias in aliases] + alias_list.insert(0, name.encode('utf-8')) + attr_list.append(('cn', alias_list)) + else: + attr_list.append(('cn', [name.encode('utf-8')])) + if len(addresses) > 0: + addr_list = [addr.encode('utf-8') for addr in addresses] + attr_list.append(('ipHostNumber', addr_list)) + return ("cn=" + name + ",ou=Hosts," + base_dn, attr_list) + + +def ip_net(base_dn, name, address, aliases=()): + """ + Generate an RFC2307 ipNetwork add-modlist for passing to ldap.add*. + """ + attr_list = [ + ('objectClass', [b'top', b'ipNetwork']), + ('ipNetworkNumber', [address.encode('utf-8')]), + ] + if (len(aliases)) > 0: + alias_list = [alias.encode('utf-8') for alias in aliases] + alias_list.insert(0, name.encode('utf-8')) + attr_list.append(('cn', alias_list)) + else: + attr_list.append(('cn', [name.encode('utf-8')])) + return ("cn=" + name + ",ou=Networks," + base_dn, attr_list) + + +class List(list): + """LDAP add-modlist list""" + + def __init__(self, base_dn): + self.base_dn = base_dn + + def add_user(self, uid, uidNumber, gidNumber, + base_dn=None, + userPassword=None, + gecos=None, + homeDirectory=None, + loginShell=None, + cn=None, + sn=None, + sshPubKey=(), + mail=None): + """Add an RFC2307(bis) user add-modlist.""" + self.append(user(base_dn or self.base_dn, + uid, uidNumber, gidNumber, + userPassword=userPassword, + gecos=gecos, + homeDirectory=homeDirectory, + loginShell=loginShell, + cn=cn, + sn=sn, + sshPubKey=sshPubKey, + mail=mail)) + + def add_group(self, cn, gidNumber, member_uids=[], + base_dn=None): + """Add an RFC2307 group add-modlist.""" + self.append(group(base_dn or self.base_dn, + cn, gidNumber, member_uids)) + + def add_group_bis(self, cn, gidNumber, + member_uids=[], member_gids=[], + base_dn=None): + """Add an RFC2307bis group add-modlist.""" + self.append(group_bis(base_dn or self.base_dn, + cn, gidNumber, + member_uids, member_gids)) + + def add_netgroup(self, cn, triples=(), members=(), base_dn=None): + """Add an RFC2307bis netgroup add-modlist.""" + self.append(netgroup(base_dn or self.base_dn, + cn, triples, members)) + + def add_sudo_rule(self, name, + users=(), hosts=(), commands=(), + base_dn=None): + self.append(sudo_rule(base_dn or self.base_dn, + name, users, hosts, commands)) + + def add_host(self, name, aliases=[], addresses=[], base_dn=None): + """Add an RFC2307 ipHost add-modlist.""" + self.append(ip_host(base_dn or self.base_dn, + name, aliases, addresses)) + + def add_ipnet(self, name, address, aliases=[], base_dn=None): + """Add an RFC2307 ipNetwork add-modlist.""" + self.append(ip_net(base_dn or self.base_dn, + name, address, aliases)) diff --git a/src/tests/intg/ldap_local_override_test.py b/src/tests/intg/ldap_local_override_test.py new file mode 100644 index 0000000..dd153f6 --- /dev/null +++ b/src/tests/intg/ldap_local_override_test.py @@ -0,0 +1,1193 @@ +# +# integration test for sss_override tool +# +# Copyright (c) 2015 Red Hat, Inc. +# Author: Pavel Reichl <preichl@redhat.com> +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 stat +import ent +import pwd +import config +import signal +import subprocess +import time +import pytest +import ds_openldap +import ldap_ent +import sssd_id +from util import unindent + +try: + from subprocess import check_output +except ImportError: + # In Python 2.6, the module subprocess does not have the function + # check_output. This is a fallback implementation. + def check_output(*popenargs, **kwargs): + if 'stdout' in kwargs: + raise ValueError('stdout argument not allowed, it will be ' + 'overridden.') + process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, + **kwargs) + output, _ = process.communicate() + retcode = process.poll() + if retcode: + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + raise subprocess.CalledProcessError(retcode, cmd, output=output) + return output + + +@pytest.fixture(scope="module") +def ds_inst(request): + """LDAP server instance fixture""" + ds_inst = ds_openldap.DSOpenLDAP( + config.PREFIX, 10389, 'dc=example,dc=com', + "cn=admin", "Secret123") + try: + ds_inst.setup() + except Exception: + 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: + ldap_conn.delete_s(entry[0]) + 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 OSError: + break + time.sleep(1) + + +def start_sssd(): + """Start sssd""" + if subprocess.call(["sssd", "-D", "--logger=files"]) != 0: + raise Exception("sssd start failed") + + +def restart_sssd(): + stop_sssd() + start_sssd() + + +def create_sssd_fixture(request): + """Start sssd and add teardown for stopping it and removing state""" + if subprocess.call(["sssd", "-D", "--logger=files"]) != 0: + raise Exception("sssd start failed") + + def teardown(): + try: + stop_sssd() + except Exception: + 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) + + +OVERRIDE_FILENAME = "export_file" + + +def prepare_sssd(request, ldap_conn, use_fully_qualified_names=False, + case_sensitive=True, override_homedir_option=False): + """Prepare SSSD with defaults""" + conf_override_homedir_option = "" + if override_homedir_option: + conf_override_homedir_option = "override_homedir = /home/ov_option/%u" + + conf = unindent("""\ + [sssd] + domains = LDAP + services = nss + + [nss] + memcache_timeout = 1 + {conf_override_homedir_option} + + [domain/LDAP] + ldap_auth_disable_tls_never_use_in_production = true + ldap_schema = rfc2307 + id_provider = ldap + auth_provider = ldap + sudo_provider = ldap + ldap_uri = {ldap_conn.ds_inst.ldap_url} + ldap_search_base = {ldap_conn.ds_inst.base_dn} + use_fully_qualified_names = {use_fully_qualified_names} + case_sensitive = {case_sensitive} + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + def teardown(): + # remove user export file + try: + os.unlink(OVERRIDE_FILENAME) + except OSError: + pass + request.addfinalizer(teardown) + + +# +# Common asserts for users +# + +def assert_user_default(): + + # Assert entries are not overriden + with pytest.raises(KeyError): + pwd.getpwnam('ov_user1') + with pytest.raises(KeyError): + pwd.getpwnam('ov_user1@LDAP') + with pytest.raises(KeyError): + pwd.getpwnam('ov_user2') + with pytest.raises(KeyError): + pwd.getpwnam('ov_user2@LDAP') + + user1 = dict(name='user1', passwd='*', uid=10001, gid=20001, + gecos='User Number 1', + dir='/home/user1', + shell='/bin/user1_shell') + user2 = dict(name='user2', passwd='*', uid=10002, gid=20001, + gecos='User Number 2', + dir='/home/user2', + shell='/bin/user2_shell') + + ent.assert_passwd_by_name('user1', user1) + ent.assert_passwd_by_name('user1@LDAP', user1) + + ent.assert_passwd_by_name('user2', user2) + ent.assert_passwd_by_name('user2@LDAP', user2) + + +def assert_user_overriden(override_name=True): + + if override_name: + name1 = "ov_user1" + name2 = "ov_user2" + else: + name1 = "user1" + name2 = "user2" + + user1 = dict(name=name1, passwd='*', uid=10010, gid=20010, + gecos='Overriden User 1', + dir='/home/ov/user1', + shell='/bin/ov_user1_shell') + + user2 = dict(name=name2, passwd='*', uid=10020, gid=20020, + gecos='Overriden User 2', + dir='/home/ov/user2', + shell='/bin/ov_user2_shell') + + ent.assert_passwd_by_name('user1', user1) + ent.assert_passwd_by_name('user1@LDAP', user1) + + if override_name: + ent.assert_passwd_by_name('ov_user1', user1) + ent.assert_passwd_by_name('ov_user1@LDAP', user1) + + ent.assert_passwd_by_name('user2', user2) + ent.assert_passwd_by_name('user2@LDAP', user2) + + if override_name: + ent.assert_passwd_by_name('ov_user2', user2) + ent.assert_passwd_by_name('ov_user2@LDAP', user2) + + +# +# Common fixtures for users +# + + +@pytest.fixture +def env_two_users_and_group(request, ldap_conn): + + prepare_sssd(request, ldap_conn) + + # Add entries + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 10001, 20001, + gecos='User Number 1', + loginShell='/bin/user1_shell', + homeDirectory='/home/user1') + + ent_list.add_user("user2", 10002, 20001, + gecos='User Number 2', + loginShell='/bin/user2_shell', + homeDirectory='/home/user2') + + ent_list.add_group("group", 2001, + ["user2", "user1"]) + + create_ldap_fixture(request, ldap_conn, ent_list) + + # Assert entries are not overriden + assert_user_default() + + +@pytest.fixture +def env_two_users_and_group_overriden(request, ldap_conn, + env_two_users_and_group): + + # Override + subprocess.check_call(["sss_override", "user-add", "user1", + "-u", "10010", + "-g", "20010", + "-n", "ov_user1", + "-c", "Overriden User 1", + "-h", "/home/ov/user1", + "-s", "/bin/ov_user1_shell"]) + + subprocess.check_call(["sss_override", "user-add", "user2@LDAP", + "-u", "10020", + "-g", "20020", + "-n", "ov_user2", + "-c", "Overriden User 2", + "-h", "/home/ov/user2", + "-s", "/bin/ov_user2_shell"]) + + # Restart SSSD so the override might take effect + restart_sssd() + + # Assert entries are overriden + assert_user_overriden() + + +# +# Simple user override +# + + +@pytest.fixture +def env_simple_user_override(request, ldap_conn, env_two_users_and_group): + + # Override + subprocess.check_call(["sss_override", "user-add", "user1", + "-u", "10010", + "-g", "20010", + "-n", "ov_user1", + "-c", "Overriden User 1", + "-h", "/home/ov/user1", + "-s", "/bin/ov_user1_shell"]) + + subprocess.check_call(["sss_override", "user-add", "user2@LDAP", + "-u", "10020", + "-g", "20020", + "-n", "ov_user2", + "-c", "Overriden User 2", + "-h", "/home/ov/user2", + "-s", "/bin/ov_user2_shell"]) + + # Restart SSSD so the override might take effect + restart_sssd() + + +def test_simple_user_override(ldap_conn, env_simple_user_override): + """Test entries are overriden""" + + assert_user_overriden() + + +# +# Root user override +# + + +@pytest.fixture +def env_root_user_override(request, ldap_conn, env_two_users_and_group): + + # Assert entries are not overriden + ent.assert_passwd_by_name( + 'root', + dict(name='root', uid=0, gid=0)) + + ent.assert_passwd_by_uid(0, dict(name="root")) + + # Override + subprocess.check_call(["sss_override", "user-add", "user1", + "-u", "0", + "-g", "0", + "-n", "ov_user1", + "-c", "Overriden User 1", + "-h", "/home/ov/user1", + "-s", "/bin/ov_user1_shell"]) + + subprocess.check_call(["sss_override", "user-add", "user2", + "-u", "10020", + "-g", "20020", + "-n", "root", + "-c", "Overriden User 2", + "-h", "/home/ov/user2", + "-s", "/bin/ov_user2_shell"]) + + # Restart SSSD so the override might take effect + restart_sssd() + + +def test_root_user_override(ldap_conn, env_root_user_override): + """Test entries are not overriden to root""" + + # Override does not have to happen completly, trying to set uid or gid + # to 0 is simply ignored. + ent.assert_passwd_by_name( + 'ov_user1', + dict(name='ov_user1', passwd='*', uid=10001, gid=20001, + gecos='Overriden User 1', + dir='/home/ov/user1', + shell='/bin/ov_user1_shell')) + + # We can create override with name root. This test is just for tracking + # that this particular behavior won't change. + ent.assert_passwd_by_name( + 'user2', + dict(name='root', passwd='*', uid=10020, gid=20020, + gecos='Overriden User 2', + dir='/home/ov/user2', + shell='/bin/ov_user2_shell')) + + ent.assert_passwd_by_uid(0, dict(name="root")) + + +# +# Override replaces previous override +# + + +@pytest.fixture +def env_replace_user_override(request, ldap_conn): + + prepare_sssd(request, ldap_conn) + + # Add entries + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 10001, 20001, + gecos='User Number 1', + loginShell='/bin/user1_shell', + homeDirectory='/home/user1') + + create_ldap_fixture(request, ldap_conn, ent_list) + + # Assert entries are not overriden + ent.assert_passwd_by_name( + 'user1', + dict(name='user1', passwd='*', uid=10001, gid=20001, + gecos='User Number 1', + dir='/home/user1', + shell='/bin/user1_shell')) + + # Override + subprocess.check_call(["sss_override", "user-add", "user1", + "-u", "10010", + "-g", "20010", + "-n", "ov_user1", + "-c", "Overriden User 1", + "-h", "/home/ov/user1", + "-s", "/bin/ov_user1_shell"]) + + # Restart SSSD so the override might take effect + restart_sssd() + + # Assert entries are overriden + ent.assert_passwd_by_name( + 'user1', + dict(name='ov_user1', passwd='*', uid=10010, gid=20010, + gecos='Overriden User 1', + dir='/home/ov/user1', + shell='/bin/ov_user1_shell')) + + # Override of override + subprocess.check_call(["sss_override", "user-add", "user1", + "-u", "10100", + "-g", "20100", + "-n", "ov2_user1", + "-c", "Overriden2 User 1", + "-h", "/home/ov2/user1", + "-s", "/bin/ov2_user1_shell"]) + + # Restart SSSD so the override might take effect + restart_sssd() + + +def test_replace_user_override(ldap_conn, env_replace_user_override): + + user = dict(name='ov2_user1', passwd='*', uid=10100, gid=20100, + gecos='Overriden2 User 1', + dir='/home/ov2/user1', + shell='/bin/ov2_user1_shell') + + ent.assert_passwd_by_name('ov2_user1', user) + ent.assert_passwd_by_name('ov2_user1@LDAP', user) + + with pytest.raises(KeyError): + pwd.getpwnam('ov_user1') + with pytest.raises(KeyError): + pwd.getpwnam('ov_user1@LDAP') + + +# +# Override removal +# + + +@pytest.fixture +def env_remove_user_override(request, ldap_conn, + env_two_users_and_group_overriden): + + # Drop all overrides + subprocess.check_call(["sss_override", "user-del", "user1"]) + subprocess.check_call(["sss_override", "user-del", "user2@LDAP"]) + + # Avoid hitting memory cache + time.sleep(2) + + +def test_remove_user_override(ldap_conn, env_remove_user_override): + + # Test entries are not overriden + assert_user_default() + + +# +# Override import/export +# + + +@pytest.fixture +def env_imp_exp_user_override(request, ldap_conn, + env_two_users_and_group_overriden): + + # Export overrides + subprocess.check_call(["sss_override", "user-export", OVERRIDE_FILENAME]) + + # Drop all overrides + subprocess.check_call(["sss_override", "user-del", "user1"]) + subprocess.check_call(["sss_override", "user-del", "user2@LDAP"]) + + # Avoid hitting memory cache + time.sleep(2) + + # Assert entries are not overridden + assert_user_default() + + # Import overrides + subprocess.check_call(["sss_override", "user-import", + OVERRIDE_FILENAME]) + restart_sssd() + + +def test_imp_exp_user_override(ldap_conn, env_imp_exp_user_override): + + assert_user_overriden() + + +# Regression test for bug 3179 + + +def test_imp_exp_user_override_noname(ldap_conn, + env_two_users_and_group): + + # Override + subprocess.check_call(["sss_override", "user-add", "user1", + "-u", "10010", + "-g", "20010", + "-c", "Overriden User 1", + "-h", "/home/ov/user1", + "-s", "/bin/ov_user1_shell"]) + + subprocess.check_call(["sss_override", "user-add", "user2@LDAP", + "-u", "10020", + "-g", "20020", + "-c", "Overriden User 2", + "-h", "/home/ov/user2", + "-s", "/bin/ov_user2_shell"]) + + # Restart SSSD so the override might take effect + restart_sssd() + + # Assert entries are overriden + assert_user_overriden(override_name=False) + + # Export overrides + subprocess.check_call(["sss_override", "user-export", OVERRIDE_FILENAME]) + + # Drop all overrides + subprocess.check_call(["sss_override", "user-del", "user1"]) + subprocess.check_call(["sss_override", "user-del", "user2@LDAP"]) + + # Avoid hitting memory cache + time.sleep(2) + + # Assert entries are not overridden + assert_user_default() + + # Import overrides + subprocess.check_call(["sss_override", "user-import", + OVERRIDE_FILENAME]) + restart_sssd() + + assert_user_overriden(override_name=False) + + +# +# Override user-show +# + + +@pytest.fixture +def env_show_user_override(request, ldap_conn, + env_two_users_and_group_overriden): + pass + + +def test_show_user_override(ldap_conn, env_show_user_override): + + out = check_output(['sss_override', 'user-show', 'user1']).decode('utf-8') + assert out == "user1@LDAP:ov_user1:10010:20010:Overriden User 1:"\ + "/home/ov/user1:/bin/ov_user1_shell:\n" + + out = check_output(['sss_override', 'user-show', + 'user2@LDAP']).decode('utf-8') + assert out == "user2@LDAP:ov_user2:10020:20020:Overriden User 2:"\ + "/home/ov/user2:/bin/ov_user2_shell:\n" + + # Return error on non-existing user + ret = subprocess.call(['sss_override', 'user-show', 'nonexisting_user']) + assert ret == 1 + + +# +# Override user-find +# + + +@pytest.fixture +def env_find_user_override(request, ldap_conn, + env_two_users_and_group_overriden): + pass + + +def test_find_user_override(ldap_conn, env_find_user_override): + + out = check_output(['sss_override', 'user-find']).decode('utf-8') + + # Expected override of users + exp_usr_ovrd = ['user1@LDAP:ov_user1:10010:20010:Overriden User 1:' + '/home/ov/user1:/bin/ov_user1_shell:', + 'user2@LDAP:ov_user2:10020:20020:Overriden User 2:' + '/home/ov/user2:/bin/ov_user2_shell:'] + + assert set(out.splitlines()) == set(exp_usr_ovrd) + + out = check_output(['sss_override', 'user-find', '--domain=LDAP']) + + assert set(out.decode('utf-8').splitlines()) == set(exp_usr_ovrd) + + # Unexpected parameter is reported + ret = subprocess.call(['sss_override', 'user-find', 'PARAM']) + assert ret == 1 + + +# +# Group tests +# + + +# +# Common group asserts +# + +def assert_group_overriden(override_name=True): + + # Assert entries are overridden + empty_group = dict(gid=3002, mem=ent.contains_only()) + group = dict(gid=3001, mem=ent.contains_only("user1", "user2")) + + ent.assert_group_by_name("group", group) + ent.assert_group_by_name("group@LDAP", group) + + if override_name: + ent.assert_group_by_name("ov_group", group) + ent.assert_group_by_name("ov_group@LDAP", group) + + ent.assert_group_by_name("empty_group", empty_group) + ent.assert_group_by_name("empty_group@LDAP", empty_group) + + if override_name: + ent.assert_group_by_name("ov_empty_group", empty_group) + ent.assert_group_by_name("ov_empty_group@LDAP", empty_group) + + +def assert_group_default(): + + # Assert entries are not overridden + with pytest.raises(KeyError): + pwd.getpwnam('ov_group') + with pytest.raises(KeyError): + pwd.getpwnam('ov_group@LDAP') + with pytest.raises(KeyError): + pwd.getpwnam('ov_empty_group') + with pytest.raises(KeyError): + pwd.getpwnam('ov_empty_group@LDAP') + + empty_group = dict(gid=2002, mem=ent.contains_only()) + group = dict(gid=2001, mem=ent.contains_only("user1", "user2")) + + ent.assert_group_by_name("group", group) + ent.assert_group_by_name("group@LDAP", group) + ent.assert_group_by_name("empty_group", empty_group) + ent.assert_group_by_name("empty_group@LDAP", empty_group) + + +# +# Common fixtures for groups +# + + +@pytest.fixture +def env_group_basic(request, ldap_conn): + prepare_sssd(request, ldap_conn) + + # Add entries + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 10001, 20001, + gecos='User Number 1', + loginShell='/bin/user1_shell', + homeDirectory='/home/user1') + + ent_list.add_user("user2", 10002, 20001, + gecos='User Number 2', + loginShell='/bin/user2_shell', + homeDirectory='/home/user2') + + ent_list.add_group("group", 2001, + ["user2", "user1"]) + ent_list.add_group("empty_group", 2002, []) + + create_ldap_fixture(request, ldap_conn, ent_list) + + # Assert entries are not overriden + with pytest.raises(KeyError): + pwd.getpwnam('ov_group') + with pytest.raises(KeyError): + pwd.getpwnam('ov_group@LDAP') + with pytest.raises(KeyError): + pwd.getpwnam('ov_empty_group') + with pytest.raises(KeyError): + pwd.getpwnam('ov_empty_group@LDAP') + + +@pytest.fixture +def env_group_override(request, ldap_conn, env_group_basic): + + # Override + subprocess.check_call(["sss_override", "group-add", "group", + "-n", "ov_group", + "-g", "3001"]) + + subprocess.check_call(["sss_override", "group-add", "empty_group@LDAP", + "--name", "ov_empty_group", + "--gid", "3002"]) + + # Restart SSSD so the override might take effect + restart_sssd() + + # Assert entries are overridden + assert_group_overriden() + + +# +# Simple group override +# + + +@pytest.fixture +def env_simple_group_override(request, ldap_conn, env_group_basic): + + # Override + subprocess.check_call(["sss_override", "group-add", "group", + "-n", "ov_group", + "-g", "3001"]) + + subprocess.check_call(["sss_override", "group-add", "empty_group@LDAP", + "--name", "ov_empty_group", + "--gid", "3002"]) + + # Restart SSSD so the override might take effect + restart_sssd() + + +def test_simple_group_override(ldap_conn, env_simple_group_override): + """Test entries are overriden""" + + assert_group_overriden() + + +# +# Root group override +# + + +@pytest.fixture +def env_root_group_override(request, ldap_conn, env_group_basic): + + # Override + subprocess.check_call(["sss_override", "group-add", "group", + "-n", "ov_group", + "-g", "0"]) + + subprocess.check_call(["sss_override", "group-add", "empty_group@LDAP", + "--name", "ov_empty_group", + "--gid", "0"]) + + # Restart SSSD so the override might take effect + restart_sssd() + + +def test_root_group_override(ldap_conn, env_root_group_override): + """Test entries are overriden""" + + group = dict(gid=2001, mem=ent.contains_only("user1", "user2")) + empty_group = dict(gid=2002, mem=ent.contains_only()) + + ent.assert_group_by_name("group", group) + ent.assert_group_by_name("ov_group", group) + ent.assert_group_by_name("group@LDAP", group) + ent.assert_group_by_name("ov_group@LDAP", group) + ent.assert_group_by_name("empty_group", empty_group) + ent.assert_group_by_name("ov_empty_group", empty_group) + ent.assert_group_by_name("empty_group@LDAP", empty_group) + ent.assert_group_by_name("ov_empty_group@LDAP", empty_group) + + +# +# Replace group override +# + + +@pytest.fixture +def env_replace_group_override(request, ldap_conn, env_group_override): + + # Override of override + subprocess.check_call(["sss_override", "group-add", "group", + "-n", "ov2_group", + "-g", "4001"]) + + subprocess.check_call(["sss_override", "group-add", "empty_group@LDAP", + "--name", "ov2_empty_group", + "--gid", "4002"]) + + # Restart SSSD so the override might take effect + restart_sssd() + + +def test_replace_group_override(ldap_conn, env_replace_group_override): + + # Test overrides are overridden + with pytest.raises(KeyError): + pwd.getpwnam('ov_group') + with pytest.raises(KeyError): + pwd.getpwnam('ov_group@LDAP') + with pytest.raises(KeyError): + pwd.getpwnam('ov_empty_group') + with pytest.raises(KeyError): + pwd.getpwnam('ov_empty_group@LDAP') + + group = dict(gid=4001, mem=ent.contains_only("user1", "user2")) + empty_group = dict(gid=4002, mem=ent.contains_only()) + + ent.assert_group_by_name("group", group) + ent.assert_group_by_name("ov2_group", group) + ent.assert_group_by_name("group@LDAP", group) + ent.assert_group_by_name("ov2_group@LDAP", group) + + ent.assert_group_by_name("empty_group", empty_group) + ent.assert_group_by_name("empty_group@LDAP", empty_group) + ent.assert_group_by_name("ov2_empty_group", empty_group) + ent.assert_group_by_name("ov2_empty_group@LDAP", empty_group) + + +# +# Remove group override +# + + +@pytest.fixture +def env_remove_group_override(request, ldap_conn, env_group_override): + + # Drop all overrides + subprocess.check_call(["sss_override", "group-del", "group"]) + subprocess.check_call(["sss_override", "group-del", "empty_group@LDAP"]) + + # Avoid hitting memory cache + time.sleep(2) + + +def test_remove_group_override(ldap_conn, env_remove_group_override): + + # Test overrides were dropped + assert_group_default() + + +# +# Override group import/export +# + + +@pytest.fixture +def env_imp_exp_group_override(request, ldap_conn, env_group_override): + + # Export overrides + subprocess.check_call(["sss_override", "group-export", + OVERRIDE_FILENAME]) + + # Drop all overrides + subprocess.check_call(["sss_override", "group-del", "group"]) + subprocess.check_call(["sss_override", "group-del", "empty_group@LDAP"]) + + # Avoid hitting memory cache + time.sleep(2) + + assert_group_default() + + # Import overrides + subprocess.check_call(["sss_override", "group-import", + OVERRIDE_FILENAME]) + restart_sssd() + + +def test_imp_exp_group_override(ldap_conn, env_imp_exp_group_override): + + assert_group_overriden() + + +# Regression test for bug 3179 + + +def test_imp_exp_group_override_noname(ldap_conn, env_group_basic): + + # Override - do not use -n here) + subprocess.check_call(["sss_override", "group-add", "group", + "-g", "3001"]) + + subprocess.check_call(["sss_override", "group-add", "empty_group@LDAP", + "--gid", "3002"]) + + # Restart SSSD so the override might take effect + restart_sssd() + + # Assert entries are overridden + assert_group_overriden(override_name=False) + + # Export overrides + subprocess.check_call(["sss_override", "group-export", + OVERRIDE_FILENAME]) + + # Drop all overrides + subprocess.check_call(["sss_override", "group-del", "group"]) + subprocess.check_call(["sss_override", "group-del", "empty_group@LDAP"]) + + # Avoid hitting memory cache + time.sleep(2) + + assert_group_default() + + # Import overrides + subprocess.check_call(["sss_override", "group-import", + OVERRIDE_FILENAME]) + restart_sssd() + + assert_group_overriden(override_name=False) + + +# Regression test for bug #2802 +# sss_override segfaults when accidentally adding --help flag to some commands + + +@pytest.fixture +def env_regr_2802_override(request, ldap_conn): + + prepare_sssd(request, ldap_conn) + + +def test_regr_2802_override(ldap_conn, env_regr_2802_override): + + subprocess.check_call(["sss_override", "user-del", "--help"]) + + +# Regression test for bug #2757 +# sss_override does not work correctly when 'use_fully_qualified_names = True' + + +@pytest.fixture +def env_regr_2757_override(request, ldap_conn): + + prepare_sssd(request, ldap_conn, use_fully_qualified_names=True) + + # Add entries + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 10001, 20001) + + create_ldap_fixture(request, ldap_conn, ent_list) + + # Assert entries are not overridden + ent.assert_passwd_by_name( + 'user1@LDAP', + dict(name='user1@LDAP', passwd='*', uid=10001, gid=20001)) + with pytest.raises(KeyError): + pwd.getpwnam('alias1') + with pytest.raises(KeyError): + pwd.getpwnam('alias1@LDAP') + + # Override + subprocess.check_call(["sss_override", "user-add", "user1@LDAP", + "-n", "alias1"]) + restart_sssd() + + +def test_regr_2757_override(ldap_conn, env_regr_2757_override): + + # Assert entries are overridden + ent.assert_passwd_by_name( + 'user1@LDAP', + dict(name='alias1@LDAP', passwd='*', uid=10001, gid=20001)) + ent.assert_passwd_by_name( + 'alias1@LDAP', + dict(name='alias1@LDAP', passwd='*', uid=10001, gid=20001)) + + with pytest.raises(KeyError): + pwd.getpwnam('user1') + with pytest.raises(KeyError): + pwd.getpwnam('alias1') + + +# Regression test for bug #2790 +# sss_override --name doesn't work with RFC2307 and ghost users + + +@pytest.fixture +def env_regr_2790_override(request, ldap_conn): + + prepare_sssd(request, ldap_conn) + + # Add entries + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 10001, 20001) + ent_list.add_user("user2", 10002, 20002) + ent_list.add_group("group1", 2001, + ["user1", "user2"]) + ent_list.add_group("group2", 2002, + ["user2"]) + + create_ldap_fixture(request, ldap_conn, ent_list) + + # Assert entries are not overridden + with pytest.raises(KeyError): + pwd.getpwnam('alias1') + with pytest.raises(KeyError): + pwd.getpwnam('alias1@LDAP') + with pytest.raises(KeyError): + pwd.getpwnam('alias2') + with pytest.raises(KeyError): + pwd.getpwnam('alias2@LDAP') + + # Override + subprocess.check_call(["sss_override", "user-add", "user1", + "-n", "alias1"]) + subprocess.check_call(["sss_override", "user-add", "user2", + "-n", "alias2"]) + + restart_sssd() + + +def test_regr_2790_override(ldap_conn, env_regr_2790_override): + + # Assert entries are overridden + (res, errno, grp_list) = sssd_id.get_user_groups("alias1") + assert res == sssd_id.NssReturnCode.SUCCESS, \ + "Could not find groups for user1 %d" % errno + assert sorted(grp_list) == sorted(["20001", "group1"]) + + (res, errno, grp_list) = sssd_id.get_user_groups("alias2") + assert res == sssd_id.NssReturnCode.SUCCESS, \ + "Could not find groups for user2 %d" % errno + assert sorted(grp_list) == sorted(["20002", "group1", "group2"]) + + +# Test fully qualified and case-insensitive names +@pytest.fixture +def env_mix_cased_name_override(request, ldap_conn): + """Setup test for mixed case names""" + + prepare_sssd(request, ldap_conn, True, False) + + # Add entries + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 10001, 20001) + ent_list.add_user("uSeR2", 10002, 20002) + + create_ldap_fixture(request, ldap_conn, ent_list) + + pwd.getpwnam('user1@LDAP') + pwd.getpwnam('user2@LDAP') + with pytest.raises(KeyError): + pwd.getpwnam('ov_user1@LDAP') + with pytest.raises(KeyError): + pwd.getpwnam('ov_user2@LDAP') + + # Override + subprocess.check_call(["sss_override", "user-add", "user1@LDAP", + "-u", "10010", + "-g", "20010", + "-n", "ov_user1", + "-c", "Overriden User 1", + "-h", "/home/ov/user1", + "-s", "/bin/ov_user1_shell"]) + + subprocess.check_call(["sss_override", "user-add", "user2@LDAP", + "-u", "10020", + "-g", "20020", + "-n", "ov_user2", + "-c", "Overriden User 2", + "-h", "/home/ov/user2", + "-s", "/bin/ov_user2_shell"]) + + restart_sssd() + + +def test_mix_cased_name_override(ldap_conn, env_mix_cased_name_override): + """Test if names with upper and lower case letter are overridden""" + + # Assert entries are overridden + user1 = dict(name='ov_user1@LDAP', passwd='*', uid=10010, gid=20010, + gecos='Overriden User 1', + dir='/home/ov/user1', + shell='/bin/ov_user1_shell') + + user2 = dict(name='ov_user2@LDAP', passwd='*', uid=10020, gid=20020, + gecos='Overriden User 2', + dir='/home/ov/user2', + shell='/bin/ov_user2_shell') + + ent.assert_passwd_by_name('user1@LDAP', user1) + ent.assert_passwd_by_name('ov_user1@LDAP', user1) + + ent.assert_passwd_by_name('user2@LDAP', user2) + ent.assert_passwd_by_name('ov_user2@LDAP', user2) + + +# Test with override_homedir option +@pytest.fixture +def env_override_homedir_option(request, ldap_conn): + """Setup test for override_homedir option and overrides""" + + prepare_sssd(request, ldap_conn, override_homedir_option=True) + + # Add entries + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 10001, 20001) + ent_list.add_user("user2", 10002, 20002) + + create_ldap_fixture(request, ldap_conn, ent_list) + + pwd.getpwnam('user1@LDAP') + pwd.getpwnam('user2@LDAP') + with pytest.raises(KeyError): + pwd.getpwnam('ov_user1@LDAP') + with pytest.raises(KeyError): + pwd.getpwnam('ov_user2@LDAP') + + # Override + subprocess.check_call(["sss_override", "user-add", "user1@LDAP", + "-u", "10010", + "-g", "20010", + "-n", "ov_user1", + "-c", "Overriden User 1", + # no homedir override + "-s", "/bin/ov_user1_shell"]) + + subprocess.check_call(["sss_override", "user-add", "user2@LDAP", + "-u", "10020", + "-g", "20020", + "-n", "ov_user2", + "-c", "Overriden User 2", + "-h", "/home/ov/user2", + "-s", "/bin/ov_user2_shell"]) + + restart_sssd() + + +def test_override_homedir_option(ldap_conn, env_override_homedir_option): + """Test if overrides will overwrite override_homedir option""" + + # Assert entries are overridden, user1 has no homedir override and + # override_homedir option should be used, user2 has a homedir override + # which should be used. + user1 = dict(name='ov_user1', passwd='*', uid=10010, gid=20010, + gecos='Overriden User 1', + dir='/home/ov_option/ov_user1', + shell='/bin/ov_user1_shell') + + user2 = dict(name='ov_user2', passwd='*', uid=10020, gid=20020, + gecos='Overriden User 2', + dir='/home/ov/user2', + shell='/bin/ov_user2_shell') + + ent.assert_passwd_by_name('user1@LDAP', user1) + ent.assert_passwd_by_name('ov_user1@LDAP', user1) + ent.assert_passwd_by_name('user1', user1) + ent.assert_passwd_by_name('ov_user1', user1) + + ent.assert_passwd_by_name('user2@LDAP', user2) + ent.assert_passwd_by_name('ov_user2@LDAP', user2) + ent.assert_passwd_by_name('user2', user2) + ent.assert_passwd_by_name('ov_user2', user2) diff --git a/src/tests/intg/nss_call.c b/src/tests/intg/nss_call.c new file mode 100644 index 0000000..a8c7d30 --- /dev/null +++ b/src/tests/intg/nss_call.c @@ -0,0 +1,166 @@ +/* + NSS module which calls glibc's user and group lookup functions again + + DO NOT USE THIS IN /etc/nsswitch.conf, it will cause an infinite loop. + + The main use case is to run proxy provider tests with cwrap's nss-wrapper. + The proxy provider loads the NSS modules directly with dlopen() and is not + using glibc's NSS mechanism. Since nss-wrapper just wraps the standard + glibc calls and does not provide an NSS module on its own we have to use + this workaround to make proxy provider work with nss-wrapper. + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (c) 2023 Red Hat, Inc. + + This program 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; either version 3 of the License, or + (at your option) any later version. + + 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/>. +*/ + +#define _DEFAULT_SOURCE +#define _GNU_SOURCE + +#include <stdlib.h> +#include <nss.h> +#include <sys/types.h> +#include <pwd.h> +#include <grp.h> + + + +#define NSSRET(r) return (((r) == 0) ? NSS_STATUS_SUCCESS : NSS_STATUS_NOTFOUND ) + +enum nss_status _nss_call_getpwnam_r(const char *name, struct passwd *result, + char *buffer, size_t buflen, int *errnop) +{ + struct passwd *res; + int ret = getpwnam_r(name, result, buffer, buflen, &res); + NSSRET(ret); +} + +enum nss_status _nss_call_getpwuid_r(uid_t uid, struct passwd *result, + char *buffer, size_t buflen, int *errnop) +{ + struct passwd *res; + int ret = getpwuid_r(uid, result, buffer, buflen, &res); + NSSRET(ret); +} + +enum nss_status _nss_call_setpwent(void) +{ + setpwent(); + return NSS_STATUS_SUCCESS; +} + +enum nss_status _nss_call_getpwent_r(struct passwd *result, + char *buffer, size_t buflen, + int *errnop) +{ + struct passwd *res; + int ret = getpwent_r(result, buffer, buflen, &res); + NSSRET(ret); +} + +enum nss_status _nss_call_endpwent(void) +{ + endpwent(); + return NSS_STATUS_SUCCESS; +} + +enum nss_status _nss_call_getgrnam_r(const char *name, struct group *result, + char *buffer, size_t buflen, int *errnop) +{ + struct group *res; + int ret = getgrnam_r(name, result, buffer, buflen, &res); + NSSRET(ret); +} + +enum nss_status _nss_call_getgrgid_r(gid_t gid, struct group *result, + char *buffer, size_t buflen, int *errnop) +{ + struct group *res; + int ret = getgrgid_r(gid, result, buffer, buflen, &res); + NSSRET(ret); +} + +enum nss_status _nss_call_setgrent(void) +{ + setgrent(); + return NSS_STATUS_SUCCESS; +} + +enum nss_status _nss_call_getgrent_r(struct group *result, + char *buffer, size_t buflen, int *errnop) +{ + struct group *res; + int ret = getgrent_r(result, buffer, buflen, &res); + NSSRET(ret); +} + +enum nss_status _nss_call_endgrent(void) +{ + endgrent(); + return NSS_STATUS_SUCCESS; +} + +enum nss_status _nss_call_initgroups_dyn(const char *user, gid_t group, + long int *start, long int *size, + gid_t **groups, long int limit, + int *errnop) +{ + int ngroups = 0; + gid_t *grps = NULL; + long int max_ret; + long int i; + int ret; + + ret = getgrouplist(user, group, grps, &ngroups); + if (ret != -1) { + return NSS_STATUS_UNAVAIL; + } + + grps = malloc(ngroups * sizeof(gid_t)); + if (grps == NULL) { + return NSS_STATUS_UNAVAIL; + } + + max_ret = ngroups; + /* check we have enough space in the buffer */ + if ((*size - *start) < ngroups) { + long int newsize; + gid_t *newgroups; + + newsize = *size + ngroups; + if ((limit > 0) && (newsize > limit)) { + newsize = limit; + max_ret = newsize - *start; + } + + newgroups = (gid_t *)realloc((*groups), newsize * sizeof(**groups)); + if (!newgroups) { + free(grps); + return NSS_STATUS_UNAVAIL; + } + *groups = newgroups; + *size = newsize; + } + + for (i = 0; i < max_ret; i++) { + (*groups)[*start] = grps[i]; + *start += 1; + } + free(grps); + + return NSS_STATUS_SUCCESS; +} diff --git a/src/tests/intg/sss_netgroup_thread_test.c b/src/tests/intg/sss_netgroup_thread_test.c new file mode 100644 index 0000000..d3bee9b --- /dev/null +++ b/src/tests/intg/sss_netgroup_thread_test.c @@ -0,0 +1,81 @@ +/* + Helper program to test if innetgr() is thread-safe + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (c) 2021 Red Hat, Inc. + + This program 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; either version 3 of the License, or + (at your option) any later version. + + 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/>. +*/ + +#include <stdio.h> +#include <stdbool.h> +#include <pthread.h> +#include <pwd.h> +#include <grp.h> +#include <netdb.h> +#include <unistd.h> +#include <sys/types.h> + + + +struct data { + const char *group; + const char *host; + const char *user; + const char *domain; + bool *failed; +}; + +static void *full_netgroup(void *arg) +{ + int ret; + size_t c = 0; + struct data *data = arg; + + do { + ret = innetgr(data->group, data->host, data->user, data->domain); + if (ret != 1) { + *(data->failed) = true; + } + c++; + } while (!*(data->failed) && c<100000); + + pthread_exit(NULL); +} + +int main() +{ + pthread_t thread[2]; + bool failed[2] = {false, false}; + + struct data data[3] = {{"ng1", "host1", "user924", "domain1", &failed[0]}, + {"ng2", "host2", "user925", "domain2", &failed[1]}, + {NULL, NULL, NULL, NULL, NULL}}; + + + pthread_create(&thread[0], NULL, full_netgroup, &data[0]); + pthread_create(&thread[1], NULL, full_netgroup, &data[1]); + + pthread_join(thread[1], NULL); + pthread_join(thread[0], NULL); + + if (failed[0] || failed[1]) { + printf ("Test failed.\n"); + return 1; + } + + return 0; +} diff --git a/src/tests/intg/sssd_group.py b/src/tests/intg/sssd_group.py new file mode 100644 index 0000000..b6a37c5 --- /dev/null +++ b/src/tests/intg/sssd_group.py @@ -0,0 +1,132 @@ +# +# Module for simulation of utility "getent group -s sss" from coreutils +# +# Copyright (c) 2016 Red Hat, Inc. +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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/>. +# + +from ctypes import (c_int, c_char_p, c_ulong, POINTER, Structure, + create_string_buffer) +from sssd_nss import NssReturnCode, SssdNssError, nss_sss_ctypes_loader + +GROUP_BUFLEN = 1024 + + +class Group(Structure): + _fields_ = [("gr_name", c_char_p), + ("gr_passwd", c_char_p), + ("gr_gid", c_int), + ("gr_mem", POINTER(c_char_p))] + + +def getgrnam_r(name, result_p, buffer_p, buflen): + """ + ctypes wrapper for: + enum nss_status _nss_sss_getgrnam_r(const char *name, + struct group *result, + char *buffer, + size_t buflen, + int *errnop) + """ + func = nss_sss_ctypes_loader("_nss_sss_getgrnam_r") + func.restype = c_int + func.argtypes = [c_char_p, POINTER(Group), + c_char_p, c_ulong, POINTER(c_int)] + + errno = POINTER(c_int)(c_int(0)) + + name = name.encode('utf-8') + res = func(c_char_p(name), result_p, buffer_p, buflen, errno) + + return (int(res), int(errno[0]), result_p) + + +def getgrgid_r(gid, result_p, buffer_p, buflen): + """ + ctypes wrapper for: + enum nss_status _nss_sss_getgrgid_r(gid_t gid, + struct passwd *result, + char *buffer, + size_t buflen, + int *errnop) + """ + func = nss_sss_ctypes_loader("_nss_sss_getgrgid_r") + func.restype = c_int + func.argtypes = [c_ulong, POINTER(Group), + c_char_p, c_ulong, POINTER(c_int)] + + errno = POINTER(c_int)(c_int(0)) + + res = func(gid, result_p, buffer_p, buflen, errno) + + return (int(res), int(errno[0]), result_p) + + +def set_group_dict(res, result_p): + if res != NssReturnCode.SUCCESS: + return dict() + + group_dict = dict() + group_dict['name'] = result_p[0].gr_name.decode('utf-8') + group_dict['gid'] = result_p[0].gr_gid + group_dict['mem'] = list() + + i = 0 + while result_p[0].gr_mem[i] is not None: + grp_name = result_p[0].gr_mem[i].decode('utf-8') + group_dict['mem'].append(grp_name) + i = i + 1 + + return group_dict + + +def call_sssd_getgrnam(name): + """ + A Python wrapper to retrieve a group by name. Returns: + (res, group_dict) + if res is NssReturnCode.SUCCESS, then group_dict contains the keys + corresponding to the C passwd structure fields. Otherwise, the dictionary + is empty and errno indicates the error code + """ + result = Group() + result_p = POINTER(Group)(result) + buff = create_string_buffer(GROUP_BUFLEN) + + res, errno, result_p = getgrnam_r(name, result_p, buff, GROUP_BUFLEN) + if errno != 0: + raise SssdNssError(errno, "getgrnam_r") + + group_dict = set_group_dict(res, result_p) + return res, group_dict + + +def call_sssd_getgrgid(gid): + """ + A Python wrapper to retrieve a group by GID. Returns: + (res, group_dict) + if res is NssReturnCode.SUCCESS, then group_dict contains the keys + corresponding to the C passwd structure fields. Otherwise, the dictionary + is empty and errno indicates the error code + """ + result = Group() + result_p = POINTER(Group)(result) + buff = create_string_buffer(GROUP_BUFLEN) + + res, errno, result_p = getgrgid_r(gid, result_p, buff, GROUP_BUFLEN) + if errno != 0: + raise SssdNssError(errno, "getgrgid_r") + + group_dict = set_group_dict(res, result_p) + return res, group_dict diff --git a/src/tests/intg/sssd_hosts.py b/src/tests/intg/sssd_hosts.py new file mode 100644 index 0000000..1473277 --- /dev/null +++ b/src/tests/intg/sssd_hosts.py @@ -0,0 +1,142 @@ +# +# Module for simulation of utility "getent hosts -s sss" from coreutils +# +# Authors: +# Samuel Cabrero <scabrero@suse.com> +# +# Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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/>. + +from ctypes import (c_int, c_char_p, c_ulong, POINTER, + Structure, create_string_buffer) +from sssd_nss import NssReturnCode, SssdNssError, nss_sss_ctypes_loader +import socket +from ipaddress import IPv4Address, IPv6Address + +HOST_BUFLEN = 1024 + + +class Hostent(Structure): + _fields_ = [("h_name", c_char_p), + ("h_aliases", POINTER(c_char_p)), + ("h_addrtype", c_int), + ("h_length", c_int), + ("h_addr_list", POINTER(c_char_p))] + + +def gethostbyname_r(name, result_p, buffer_p, buflen): + """ + ctypes wrapper for: + enum nss_status _nss_sss_gethostbyname_r(const char *name, + struct hostent *result, + char *buffer, + size_t buflen, + int *errnop, + int *h_errnop) + """ + func = nss_sss_ctypes_loader("_nss_sss_gethostbyname_r") + func.restype = c_int + func.argtypes = [c_char_p, POINTER(Hostent), + c_char_p, c_ulong, POINTER(c_int), POINTER(c_int)] + + errno = POINTER(c_int)(c_int(0)) + h_errno = POINTER(c_int)(c_int(0)) + + name = name.encode('utf-8') + res = func(c_char_p(name), result_p, buffer_p, buflen, errno, h_errno) + + return (int(res), int(errno[0]), int(h_errno[0]), result_p) + + +def gethostbyname2_r(name, af, result_p, buffer_p, buflen): + """ + ctypes wrapper for: + enum nss_status _nss_sss_gethostbyname2_r(const char *name, + int af, + struct hostent *result, + char *buffer, + size_t buflen, + int *errnop, + int *h_errnop) + """ + func = nss_sss_ctypes_loader("_nss_sss_gethostbyname2_r") + func.restype = c_int + func.argtypes = [c_char_p, c_int, POINTER(Hostent), + c_char_p, c_ulong, POINTER(c_int), POINTER(c_int)] + + errno = POINTER(c_int)(c_int(0)) + h_errno = POINTER(c_int)(c_int(0)) + + name = name.encode('utf-8') + res = func(c_char_p(name), af, result_p, buffer_p, buflen, errno, h_errno) + + return (int(res), int(errno[0]), int(h_errno[0]), result_p) + + +def set_hostent_dict(res, result_p): + if res != NssReturnCode.SUCCESS: + return dict() + + hostent_dict = dict() + hostent_dict['name'] = result_p[0].h_name.decode('utf-8') + hostent_dict['aliases'] = list() + hostent_dict['addrtype'] = result_p[0].h_addrtype + hostent_dict['length'] = result_p[0].h_length + hostent_dict['addresses'] = list() + + i = 0 + while result_p[0].h_aliases[i] is not None: + alias = result_p[0].h_aliases[i].decode('utf-8') + hostent_dict['aliases'].append(alias) + i = i + 1 + + i = 0 + while result_p[0].h_addr_list[i] is not None: + length = result_p[0].h_length + binaddr = result_p[0].h_addr_list[i][:length] + if result_p[0].h_addrtype == socket.AF_INET: + addr = IPv4Address(binaddr) + addr = socket.inet_ntop(socket.AF_INET, addr.packed) + elif result_p[0].h_addrtype == socket.AF_INET6: + addr = IPv6Address(binaddr) + addr = socket.inet_ntop(socket.AF_INET, addr.packed) + else: + raise Exception("Failed to parse IP address") + + hostent_dict['addresses'].append(addr) + i = i + 1 + + return hostent_dict + + +def call_sssd_gethostbyname(name): + """ + A Python wrapper to retrieve a host by name. Returns: + (res, hostent_dict) + if res is NssReturnCode.SUCCESS, then hostent_dict contains the keys + corresponding to the C hostent structure fields. Otherwise, the dictionary + is empty and errno indicates the error code + """ + result = Hostent() + result_p = POINTER(Hostent)(result) + buff = create_string_buffer(HOST_BUFLEN) + + (res, errno, h_errno, result_p) = gethostbyname_r(name, result_p, + buff, HOST_BUFLEN) + if errno != 0: + raise SssdNssError(errno, "gethostbyname_r") + + hostent_dict = set_hostent_dict(res, result_p) + return (res, h_errno, hostent_dict) diff --git a/src/tests/intg/sssd_id.py b/src/tests/intg/sssd_id.py new file mode 100644 index 0000000..3a45e3b --- /dev/null +++ b/src/tests/intg/sssd_id.py @@ -0,0 +1,129 @@ +# +# Module for simulation of utility "id" from coreutils +# +# Copyright (c) 2015 Red Hat, Inc. +# Author: Lukas Slebodnik <lslebodn@redhat.com> +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 pwd +import grp +from ctypes import (c_int, c_char, c_uint32, c_long, c_char_p, + POINTER, pointer) +from sssd_nss import NssReturnCode, nss_sss_ctypes_loader + + +def call_sssd_initgroups(user, gid): + """ + Function will initialize the supplementary group access list + for given user. It will gather groups only provided by sssd. + + Arguments are the same as for C function initgroups + @param string user name of user + @param int gid the additional gid will be also added to the list. + + @return (int, int, List[int]) (err, errno, gids) + gids should contain user group IDs if err is NssReturnCode.SUCCESS + otherwise errno will contain non-zero value. + """ + func = nss_sss_ctypes_loader('_nss_sss_initgroups_dyn') + + func.restype = c_int + func.argtypes = [POINTER(c_char), c_uint32, POINTER(c_long), + POINTER(c_long), POINTER(POINTER(c_uint32)), c_long, + POINTER(c_int)] + + start = POINTER(c_long)(c_long(0)) + size = POINTER(c_long)(c_long(0)) + groups = POINTER(c_uint32)() + p_groups = pointer(groups) + limit = c_long(-1) + errno = POINTER(c_int)(c_int(0)) + + res = func(c_char_p(user.encode('utf-8)')), c_uint32(gid), start, size, + p_groups, limit, errno) + + gids = [] + if res == NssReturnCode.SUCCESS: + gids_count = size[0] + assert gids_count > 0, "_nss_sss_initgroups_dyn should return " \ + "one gid" + + for i in range(0, gids_count): + gids.append(int(p_groups.contents[i])) + + # add primary group if missing + if gid not in gids: + gids.append(gid) + + return (int(res), errno[0], gids) + + +def get_user_gids(user): + """ + Function will initialize the supplementary group access list + for given user. It will gather groups only provided by sssd. + + Arguments are the same as for C function initgroups + @param string user name of user + + @return (int, int, List[int]) (err, errno, gids) + gids should contain user group IDs if err is NssReturnCode.SUCCESS + otherwise errno will contain non-zero value. + """ + pwd_user = pwd.getpwnam(user) + uid = pwd_user.pw_uid + gid = pwd_user.pw_gid + + user = pwd.getpwuid(uid).pw_name + + return call_sssd_initgroups(user, gid) + + +def gid_to_str(gid): + """ + Function will map numeric GID into names. + If there isn't a group for GID (getgrgid failed) + then the function will return decimal representation of ID. + + @param int gid ID of groups which should be converted to string. + @return string name of group with requested ID or decimal + representation of ID + """ + try: + return grp.getgrgid(gid).gr_name + except KeyError: + return str(gid) + + +def get_user_groups(user): + """ + Function will initialize the supplementary group access list + for given user. It will gather groups only provided by sssd. + + Arguments are the same as for C function initgroups + @param string user name of user + + @return (int, int, List[string]) (err, errno, groups) + groups should contain names of user groups + if err is NssReturnCode.SUCCESS + otherwise errno will contain non-zero value. + """ + (res, errno, gids) = get_user_gids(user) + groups = [] + + if res == NssReturnCode.SUCCESS: + groups = [gid_to_str(gid) for gid in gids] + + return (res, errno, groups) diff --git a/src/tests/intg/sssd_ldb.py b/src/tests/intg/sssd_ldb.py new file mode 100644 index 0000000..746dbfa --- /dev/null +++ b/src/tests/intg/sssd_ldb.py @@ -0,0 +1,96 @@ +# +# SSSD integration test - access the ldb cache +# +# Copyright (c) 2016 Red Hat, Inc. +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 ldb +import config + + +class CacheType(object): + sysdb = 1 + timestamps = 2 + + +class TsCacheEntry(object): + user = 1 + group = 2 + + +class SssdLdb(object): + def __init__(self, domain_name): + self._domain_name = domain_name + self._sysdb = self._create_dbconn(CacheType.sysdb, + domain_name) + self._timestamps = self._create_dbconn(CacheType.timestamps, + domain_name) + + def _create_dbconn(self, cache_type, domain_name): + if cache_type == CacheType.sysdb: + db_path = os.path.join(config.DB_PATH, + "cache_%s.ldb" % domain_name) + elif cache_type == CacheType.timestamps: + db_path = os.path.join(config.DB_PATH, + "timestamps_%s.ldb" % domain_name) + else: + raise ValueError("Unknown cache type\n") + + pyldb = ldb.Ldb() + pyldb.connect(db_path) + return pyldb + + def _get_dbconn(self, cache_type): + dbconn = None + if cache_type == CacheType.sysdb: + dbconn = self._sysdb + elif cache_type == CacheType.timestamps: + dbconn = self._timestamps + return dbconn + + def _entry_basedn(self, entry_type): + if entry_type == TsCacheEntry.user: + rdn = "users" + elif entry_type == TsCacheEntry.group: + rdn = "groups" + else: + raise ValueError("Unknown entry type\n") + return "cn=%s,cn=%s,cn=sysdb" % (rdn, self._domain_name) + + def _basedn(self, name, domain, entry_type): + return "name=%s@%s,%s" % (name, domain.lower(), + self._entry_basedn(entry_type)) + + def get_entry_attr(self, cache_type, entry_type, name, domain, attr): + dbconn = self._get_dbconn(cache_type) + basedn = self._basedn(name, domain, entry_type) + + res = dbconn.search(base=basedn, scope=ldb.SCOPE_BASE, attrs=[attr]) + if res.count != 1: + return None + + return res.msgs[0].get(attr).get(0) + + def invalidate_entry(self, name, entry_type, domain): + dbconn = self._get_dbconn(CacheType.timestamps) + + m = ldb.Message() + m.dn = ldb.Dn(dbconn, self._basedn(name, domain, entry_type)) + m["dataExpireTimestamp"] = ldb.MessageElement(str(1), + ldb.FLAG_MOD_REPLACE, + "dataExpireTimestamp") + dbconn.modify(m) diff --git a/src/tests/intg/sssd_netgroup.py b/src/tests/intg/sssd_netgroup.py new file mode 100644 index 0000000..81d017f --- /dev/null +++ b/src/tests/intg/sssd_netgroup.py @@ -0,0 +1,247 @@ +# +# Module for simulation of utility "getent netgroup -s sss" from coreutils +# +# Copyright (c) 2016 Red Hat, Inc. +# Author: Lukas Slebodnik <lslebodn@redhat.com> +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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/>. +# +from ctypes import (c_int, c_char, c_char_p, c_size_t, c_void_p, c_ulong, + POINTER, Structure, Union, create_string_buffer, get_errno) +from sssd_nss import NssReturnCode, nss_sss_ctypes_loader + + +class NetgroupType(object): + """ 'enum' class for type of netgroup """ + TRIPLE_VAL = 0 + GROUP_VAL = 1 + + +class Triple(Structure): + _fields_ = [("host", c_char_p), + ("user", c_char_p), + ("domain", c_char_p)] + + +class Val(Union): + _fields_ = [("triple", Triple), + ("group", c_char_p)] + + +class Idx(Union): + _fields_ = [("cursor", POINTER(c_char)), + ("position", c_ulong)] + + +class NameList(Structure): + pass + + +NameList._fields_ = [("next", POINTER(NameList)), + ("name", POINTER(c_char))] + + +class Netgrent(Structure): + _fields_ = [("type", c_int), + ("val", Val), + ("data", POINTER(c_char)), + ("data_size", c_size_t), + ("idx", Idx), + ("first", c_int), + ("known_groups", POINTER(NameList)), + ("needed_groups", POINTER(NameList)), + ("nip", c_void_p)] + + +class NetgroupRetriever(object): + def __init__(self, name): + self.name = name.encode('utf-8') + self.needed_groups = [] + self.known_groups = [] + self.netgroups = [] + + @staticmethod + def _setnetgrent(netgroup): + """ + This private method is ctypes wrapper for + enum nss_status _nss_sss_setnetgrent(const char *netgroup, + struct __netgrent *result) + + @param string name name of netgroup + + @return (int, POINTER(Netgrent)) (err, result_p) + err is a constant from class NssReturnCode and in case of SUCCESS + result_p will contain POINTER(Netgrent) which can be used in + _getnetgrent_r or _getnetgrent_r. + """ + func = nss_sss_ctypes_loader('_nss_sss_setnetgrent') + func.restype = c_int + func.argtypes = [c_char_p, POINTER(Netgrent)] + + result = Netgrent() + result_p = POINTER(Netgrent)(result) + + res = func(c_char_p(netgroup), result_p) + + return (int(res), result_p) + + @staticmethod + def _getnetgrent_r(result_p, buff, buff_len): + """ + This private method is ctypes wrapper for + enum nss_status _nss_sss_getnetgrent_r(struct __netgrent *result, + char *buffer, size_t buflen, + int *errnop) + @param POINTER(Netgrent) result_p pointer to initialized C structure + struct __netgrent + @param ctypes.c_char_Array buff buffer used by C functions + @param int buff_len size of c_char_Array passed as a parameter buff + + @return (int, int, List[(string, string, string]) + (err, errno, netgroups) + if err is NssReturnCode.SUCCESS netgroups will contain list of + touples. Each touple will consist of 3 elements either string or + """ + func = nss_sss_ctypes_loader('_nss_sss_getnetgrent_r') + func.restype = c_int + func.argtypes = [POINTER(Netgrent), POINTER(c_char), c_size_t, + POINTER(c_int)] + + errno = POINTER(c_int)(c_int(0)) + + res = func(result_p, buff, buff_len, errno) + + return (int(res), int(errno[0]), result_p) + + @staticmethod + def _endnetgrent(result_p): + """ + This private method is ctypes wrapper for + enum nss_status _nss_sss_endnetgrent(struct __netgrent *result) + + @param POINTER(Netgrent) result_p pointer to initialized C structure + struct __netgrent + + @return int a constant from class NssReturnCode + """ + func = nss_sss_ctypes_loader('_nss_sss_endnetgrent') + func.restype = c_int + func.argtypes = [POINTER(Netgrent)] + + res = func(result_p) + + return int(res) + + def get_netgroups(self): + """ + Function will return netgroup triplets for given user. All nested + netgroups will be retrieved as part of executions and will content + will be merged with direct triplets. + Missing nested netgroups will not cause failure and are considered + as an empty netgroup without triplets. + + @param string name name of netgroup + + @return (int, int, List[(string, string, string]) + (err, errno, netgroups) + if err is NssReturnCode.SUCCESS netgroups will contain list of + touples. Each touple will consist of 3 elements either string or + None (host, user, domain). + """ + res, errno, result = self._flat_fetch_netgroups(self.name) + if res != NssReturnCode.SUCCESS: + return (res, errno, self.netgroups) + + self.netgroups += result + + while self.needed_groups: + name = self.needed_groups.pop(0) + + nest_res, nest_errno, result = self._flat_fetch_netgroups(name) + # do not fail for missing nested netgroup + if nest_res not in (NssReturnCode.SUCCESS, NssReturnCode.NOTFOUND): + return (nest_res, nest_errno, self.netgroups) + + self.netgroups = result + self.netgroups + + return (res, errno, self.netgroups) + + def _flat_fetch_netgroups(self, name): + """ + Function will return netgroup triplets for given user. The nested + netgroups will not be returned. Missing nested netgroups will be + appended to the array needed_groups + + @param string name name of netgroup + + @return (int, int, List[(string, string, string]) + (err, errno, netgroups) + if err is NssReturnCode.SUCCESS netgroups will contain list of + touples. Each touple will consist of 3 elements either string or + None (host, user, domain). + """ + buff_len = 1024 * 1024 + buff = create_string_buffer(buff_len) + + result = [] + + res, result_p = self._setnetgrent(name) + if res != NssReturnCode.SUCCESS: + return (res, get_errno(), result) + + res, errno, result_p = self._getnetgrent_r(result_p, buff, buff_len) + while res == NssReturnCode.SUCCESS: + if result_p[0].type == NetgroupType.GROUP_VAL: + nested_netgroup = result_p[0].val.group + if nested_netgroup not in self.known_groups: + self.needed_groups.append(nested_netgroup) + self.known_groups.append(nested_netgroup) + + if result_p[0].type == NetgroupType.TRIPLE_VAL: + triple = result_p[0].val.triple + result.append((triple.host and triple.host.decode('utf-8') + or "", + triple.user and triple.user.decode('utf-8') + or "", + triple.domain and triple.domain.decode('utf-8') + or "")) + + res, errno, result_p = self._getnetgrent_r(result_p, buff, + buff_len) + + if res != NssReturnCode.RETURN: + return (res, errno, result) + + res = self._endnetgrent(result_p) + + return (res, errno, result) + + +def get_sssd_netgroups(name): + """ + Function will return netgroup triplets for given user. It will gather + netgroups only provided by sssd. + The equivalent of "getent netgroup -s sss user" + + @param string name name of netgroup + + @return (int, int, List[(string, string, string]) (err, errno, netgroups) + if err is NssReturnCode.SUCCESS netgroups will contain list of touples. + Each touple will consist of 3 elements either string or None + (host, user, domain). + """ + + retriever = NetgroupRetriever(name) + + return retriever.get_netgroups() diff --git a/src/tests/intg/sssd_nets.py b/src/tests/intg/sssd_nets.py new file mode 100644 index 0000000..a64b5ad --- /dev/null +++ b/src/tests/intg/sssd_nets.py @@ -0,0 +1,147 @@ +# +# Module for simulation of utility "getent networks -s sss" from coreutils +# +# Authors: +# Samuel Cabrero <scabrero@suse.com> +# +# Copyright (C) 2020 SUSE LINUX GmbH, Nuernberg, Germany. +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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/>. + +from ctypes import (c_int, c_char_p, c_ulong, c_uint32, POINTER, + Structure, create_string_buffer) +from sssd_nss import NssReturnCode, SssdNssError, nss_sss_ctypes_loader +import socket +from ipaddress import IPv4Address +from struct import unpack + +IP_NETWORK_BUFLEN = 1024 + + +class Netent(Structure): + _fields_ = [("n_name", c_char_p), + ("n_aliases", POINTER(c_char_p)), + ("n_addrtype", c_int), + ("n_net", c_uint32)] + + +def getnetbyname_r(name, result_p, buffer_p, buflen): + """ + ctypes wrapper for: + enum nss_status _nss_sss_getnetbyname_r(const char *name, + struct netent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop) + """ + func = nss_sss_ctypes_loader("_nss_sss_getnetbyname_r") + func.restype = c_int + func.argtypes = [c_char_p, POINTER(Netent), + c_char_p, c_ulong, POINTER(c_int), POINTER(c_int)] + + errno = POINTER(c_int)(c_int(0)) + h_errno = POINTER(c_int)(c_int(0)) + + name = name.encode('utf-8') + res = func(c_char_p(name), result_p, buffer_p, buflen, errno, h_errno) + + return (int(res), int(errno[0]), int(h_errno[0]), result_p) + + +def getnetbyaddr_r(addr, af, result_p, buffer_p, buflen): + """ + ctypes wrapper for: + enum nss_status _nss_sss_getnetbyaddr_r(uint32_t addr, int type, + struct netent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop) + """ + func = nss_sss_ctypes_loader("_nss_sss_getnetbyaddr_r") + func.restype = c_int + func.argtypes = [c_uint32, c_int, POINTER(Netent), + c_char_p, c_ulong, POINTER(c_int), POINTER(c_int)] + + errno = POINTER(c_int)(c_int(0)) + h_errno = POINTER(c_int)(c_int(0)) + + res = func(addr, af, result_p, buffer_p, buflen, errno, h_errno) + + return (int(res), int(errno[0]), int(h_errno[0]), result_p) + + +def set_netent_dict(res, result_p): + if res != NssReturnCode.SUCCESS: + return dict() + + netent_dict = dict() + netent_dict['name'] = result_p[0].n_name.decode('utf-8') + netent_dict['aliases'] = list() + netent_dict['addrtype'] = result_p[0].n_addrtype + netent_dict['address'] = result_p[0].n_net + + i = 0 + while result_p[0].n_aliases[i] is not None: + alias = result_p[0].n_aliases[i].decode('utf-8') + netent_dict['aliases'].append(alias) + i = i + 1 + + return netent_dict + + +def call_sssd_getnetbyname(name): + """ + A Python wrapper to retrieve an IP network by name. Returns: + (res, netent_dict) + if res is NssReturnCode.SUCCESS, then netent_dict contains the keys + corresponding to the C netent structure fields. Otherwise, the dictionary + is empty and errno indicates the error code + """ + result = Netent() + result_p = POINTER(Netent)(result) + buff = create_string_buffer(IP_NETWORK_BUFLEN) + + (res, errno, h_errno, result_p) = getnetbyname_r(name, result_p, + buff, IP_NETWORK_BUFLEN) + if errno != 0: + raise SssdNssError(errno, "getnetbyname_r") + + netent_dict = set_netent_dict(res, result_p) + return (res, h_errno, netent_dict) + + +def call_sssd_getnetbyaddr(addrstr, af): + """ + A Python wrapper to retrieve an IP network by address. Returns: + (res, netent_dict) + if res is NssReturnCode.SUCCESS, then netent_dict contains the keys + corresponding to the C netent structure fields. Otherwise, the dictionary + is empty and errno indicates the error code + """ + result = Netent() + result_p = POINTER(Netent)(result) + buff = create_string_buffer(IP_NETWORK_BUFLEN) + + if isinstance(addrstr, bytes): + addrstr = addrstr.decode('utf-8') + addr = IPv4Address(addrstr) + binaddr = unpack('<I', addr.packed)[0] + binaddr = socket.ntohl(binaddr) + + (res, errno, h_errno, result_p) = getnetbyaddr_r(binaddr, af, + result_p, buff, + IP_NETWORK_BUFLEN) + if errno != 0: + raise SssdNssError(errno, "getnetbyaddr_r") + + netent_dict = set_netent_dict(res, result_p) + return (res, h_errno, netent_dict) diff --git a/src/tests/intg/sssd_nss.py b/src/tests/intg/sssd_nss.py new file mode 100644 index 0000000..c89750b --- /dev/null +++ b/src/tests/intg/sssd_nss.py @@ -0,0 +1,78 @@ +# +# Shared module for integration tests that need to access the sssd_nss +# module directly +# +# Copyright (c) 2016 Red Hat, Inc. +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 ctypes + + +class NssReturnCode(object): + """ 'enum' class for name service switch return code """ + TRYAGAIN = -2, + UNAVAIL = -1 + NOTFOUND = 0 + SUCCESS = 1 + RETURN = 2 + + +class HostError(object): + """ 'enum' class for h_errno (glibc >= 2.19) """ + HOST_NOT_FOUND = 1 + TRY_AGAIN = 2 + NO_RECOVERY = 3 + NO_DATA = 4 + + @classmethod + def tostring(cls, val): + if (val == 1): + return "HOST_NOT_FOUND" + if (val == 2): + return "TRY_AGAIN" + if (val == 3): + return "NO_RECOVERY" + if (val == 4): + return "NO_DATA" + return "UNKNOWN" + + +class SssdNssError(Exception): + """ Raised when one of the NSS operations fail """ + def __init__(self, errno, nssop): + self.errno = errno + self.nssop = nssop + + def __str__(self): + return "NSS operation %s failed %d" % (self.nssop, self.errno) + + +class SssdNssHostError(Exception): + """ Raised when one of the NSS hosts operations fail """ + def __init__(self, h_errno, nssop): + self.h_errno = h_errno + self.nssop = nssop + + def __str__(self): + str_herr = HostError.tostring(self.h_errno) + return "NSS host operation %s failed: %s" % (self.nssop, str_herr) + + +def nss_sss_ctypes_loader(func_name): + libnss_sss_path = config.NSS_MODULE_DIR + "/libnss_sss.so.2" + libnss_sss = ctypes.cdll.LoadLibrary(libnss_sss_path) + func = getattr(libnss_sss, func_name) + return func diff --git a/src/tests/intg/sssd_passwd.py b/src/tests/intg/sssd_passwd.py new file mode 100644 index 0000000..f8c91da --- /dev/null +++ b/src/tests/intg/sssd_passwd.py @@ -0,0 +1,210 @@ +# +# Module for simulation of utility "getent passwd -s sss" from coreutils +# +# Copyright (c) 2016 Red Hat, Inc. +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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/>. +# + +from ctypes import (c_int, c_char_p, c_ulong, POINTER, + Structure, create_string_buffer, get_errno) +from sssd_nss import NssReturnCode, SssdNssError, nss_sss_ctypes_loader + +PASSWD_BUFLEN = 1024 + + +class Passwd(Structure): + _fields_ = [("pw_name", c_char_p), + ("pw_passwd", c_char_p), + ("pw_uid", c_int), + ("pw_gid", c_int), + ("pw_gecos", c_char_p), + ("pw_dir", c_char_p), + ("pw_shell", c_char_p)] + + +def set_user_dict(res, result_p): + if res != NssReturnCode.SUCCESS: + return dict() + + user_dict = dict() + user_dict['name'] = result_p[0].pw_name.decode('utf-8') + user_dict['passwd'] = result_p[0].pw_passwd.decode('utf-8') + user_dict['uid'] = result_p[0].pw_uid + user_dict['gid'] = result_p[0].pw_gid + user_dict['gecos'] = result_p[0].pw_gecos.decode('utf-8') + user_dict['dir'] = result_p[0].pw_dir.decode('utf-8') + user_dict['shell'] = result_p[0].pw_shell.decode('utf-8') + return user_dict + + +def getpwnam_r(name, result_p, buffer_p, buflen): + """ + ctypes wrapper for: + enum nss_status _nss_sss_getpwnam_r(const char *name, + struct passwd *result, + char *buffer, + size_t buflen, + int *errnop) + """ + func = nss_sss_ctypes_loader("_nss_sss_getpwnam_r") + func.restype = c_int + func.argtypes = [c_char_p, POINTER(Passwd), + c_char_p, c_ulong, POINTER(c_int)] + + errno = POINTER(c_int)(c_int(0)) + + name = name.encode('utf-8') + res = func(c_char_p(name), result_p, buffer_p, buflen, errno) + + return (int(res), int(errno[0]), result_p) + + +def getpwuid_r(uid, result_p, buffer_p, buflen): + """ + ctypes wrapper for: + enum nss_status _nss_sss_getpwuid_r(uid_t uid, + struct passwd *result, + char *buffer, + size_t buflen, + int *errnop) + """ + func = nss_sss_ctypes_loader("_nss_sss_getpwuid_r") + func.restype = c_int + func.argtypes = [c_ulong, POINTER(Passwd), + c_char_p, c_ulong, POINTER(c_int)] + + errno = POINTER(c_int)(c_int(0)) + + res = func(uid, result_p, buffer_p, buflen, errno) + + return (int(res), int(errno[0]), result_p) + + +def setpwent(): + """ + ctypes wrapper for: + void setpwent(void) + """ + func = nss_sss_ctypes_loader("_nss_sss_setpwent") + func.argtypes = [] + + res = func() + assert res == NssReturnCode.SUCCESS + + errno = get_errno() + if errno != 0: + raise SssdNssError(errno, "setpwent") + + +def endpwent(): + """ + ctypes wrapper for: + void endpwent(void) + """ + func = nss_sss_ctypes_loader("_nss_sss_endpwent") + func.argtypes = [] + + res = func() + assert res == NssReturnCode.SUCCESS + + errno = get_errno() + if errno != 0: + raise SssdNssError(errno, "endpwent") + + +def getpwent_r(result_p, buffer_p, buflen): + """ + ctypes wrapper for: + enum nss_status _nss_sss_getpwent_r(struct passwd *result, + char *buffer, size_t buflen, + int *errnop) + """ + func = nss_sss_ctypes_loader("_nss_sss_getpwent_r") + func.restype = c_int + func.argtypes = [POINTER(Passwd), c_char_p, c_ulong, POINTER(c_int)] + + errno = POINTER(c_int)(c_int(0)) + + res = func(result_p, buffer_p, buflen, errno) + return (int(res), int(errno[0]), result_p) + + +def getpwent(): + result = Passwd() + result_p = POINTER(Passwd)(result) + buff = create_string_buffer(PASSWD_BUFLEN) + + res, errno, result_p = getpwent_r(result_p, buff, PASSWD_BUFLEN) + if errno != 0: + raise SssdNssError(errno, "getpwent_r") + + user_dict = set_user_dict(res, result_p) + return res, user_dict + + +def call_sssd_getpwnam(name): + """ + A Python wrapper to retrieve a user by name. Returns: + (res, user_dict) + if res is NssReturnCode.SUCCESS, then user_dict contains the keys + corresponding to the C passwd structure fields. Otherwise, the dictionary + is empty and errno indicates the error code + """ + result = Passwd() + result_p = POINTER(Passwd)(result) + buff = create_string_buffer(PASSWD_BUFLEN) + + res, errno, result_p = getpwnam_r(name, result_p, buff, PASSWD_BUFLEN) + if errno != 0: + raise SssdNssError(errno, "getpwnam_r") + + user_dict = set_user_dict(res, result_p) + return res, user_dict + + +def call_sssd_getpwuid(uid): + """ + A Python wrapper to retrieve a user by UID. Returns: + (res, user_dict) + if res is NssReturnCode.SUCCESS, then user_dict contains the keys + corresponding to the C passwd structure fields. Otherwise, the dictionary + is empty and errno indicates the error code + """ + result = Passwd() + result_p = POINTER(Passwd)(result) + buff = create_string_buffer(PASSWD_BUFLEN) + + res, errno, result_p = getpwuid_r(uid, result_p, buff, PASSWD_BUFLEN) + if errno != 0: + raise SssdNssError(errno, "getpwuid_r") + + user_dict = set_user_dict(res, result_p) + return res, user_dict + + +def call_sssd_enumeration(): + """ + enumerate users from sssd module only + """ + setpwent() + user_list = [] + + res, user = getpwent() + while res == NssReturnCode.SUCCESS: + user_list.append(user) + res, user = getpwent() + + endpwent() + return user_list diff --git a/src/tests/intg/test_confdb.py b/src/tests/intg/test_confdb.py new file mode 100644 index 0000000..781c369 --- /dev/null +++ b/src/tests/intg/test_confdb.py @@ -0,0 +1,158 @@ +# +# Confdb integration tests +# +# Copyright (c) 2022 Red Hat, Inc. +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 stat +import signal +import subprocess +import time +import pytest + +import config +from util import unindent + + +def create_conf_file(contents): + """Create sssd.conf with specified contents""" + with open(config.CONF_PATH, "w") as conf: + conf.write(contents) + os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR) + + +def cleanup_conf_file(): + """Remove sssd.conf, if it exists""" + if os.path.lexists(config.CONF_PATH): + os.unlink(config.CONF_PATH) + + +def create_conf_cleanup(request): + """Add teardown for removing sssd.conf""" + request.addfinalizer(cleanup_conf_file) + + +def create_conf_fixture(request, contents): + """ + Create sssd.conf with specified contents and add teardown for removing it + """ + create_conf_file(contents) + create_conf_cleanup(request) + + +def create_sssd_process(): + """Start the SSSD process""" + if subprocess.call(["sssd", "-D", "--logger=files"]) != 0: + raise Exception("sssd start failed") + + +def get_sssd_pid(): + with open(config.PIDFILE_PATH, "r") as pid_file: + pid = int(pid_file.read()) + return pid + + +def cleanup_sssd_process(): + """Stop the SSSD process and remove its state""" + try: + pid = get_sssd_pid() + os.kill(pid, signal.SIGTERM) + while True: + try: + os.kill(pid, signal.SIGCONT) + except OSError: + break + time.sleep(1) + except OSError: + # Ignore the error. + 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) + + +def test_domains__domains(request): + """ + Test that SSSD starts with explicitly configured domain. + """ + conf = unindent("""\ + [sssd] + services = nss, sudo + domains = test + + [domain/test] + id_provider = proxy + proxy_lib_name = files + auth_provider = none + """) + + create_conf_fixture(request, conf) + + try: + create_sssd_process() + except Exception: + assert False + finally: + cleanup_sssd_process() + + +def test_domains__enabled(request): + """ + Test that SSSD starts without domains option. + """ + conf = unindent("""\ + [sssd] + services = nss, sudo + + [domain/test] + enabled = true + id_provider = proxy + proxy_lib_name = files + auth_provider = none + """) + + create_conf_fixture(request, conf) + + try: + create_sssd_process() + except Exception: + assert False + finally: + cleanup_sssd_process() + + +def test_domains__empty(request): + """ + Test that SSSD fails without any domain enabled. + """ + conf = unindent("""\ + [sssd] + services = nss, sudo + enable_files_domain = false + + [domain/test] + id_provider = proxy + proxy_lib_name = files + auth_provider = none + """) + + create_conf_fixture(request, conf) + with pytest.raises(Exception): + create_sssd_process() + + cleanup_sssd_process() diff --git a/src/tests/intg/test_enumeration.py b/src/tests/intg/test_enumeration.py new file mode 100644 index 0000000..9c5775b --- /dev/null +++ b/src/tests/intg/test_enumeration.py @@ -0,0 +1,792 @@ +# +# LDAP integration test +# +# Copyright (c) 2015 Red Hat, Inc. +# Author: Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 stat +import pwd +import grp +import ent +import config +import signal +import subprocess +import time +import ldap +import pytest +import ds_openldap +import ldap_ent +from util import unindent + +LDAP_BASE_DN = "dc=example,dc=com" + +# Enumeration is run periodically. It is scheduled during SSSD startup +# and then it runs every four seconds. We do not know precisely when +# the enumeration refresh is triggered so sleeping for four seconds is +# not enough, we have to sleep longer time to adapt for delays (it is +# not scheduled on precise time because of other sssd operation, it +# takes some time to finish so each run moves it further away from +# exact 4 seconds period, cpu scheduler, context switches, ...). +# The random offset is set to 0 to prevent it from being applied to the +# periodic tasks, but mainly to the initial delay. +# I agree that just little bit longer timeout may work as well. +# However we can be sure when using twice the period because we know +# that enumeration was indeed run at least once. +ENUMERATION_TIMEOUT = 4 +INTERACTIVE_TIMEOUT = ENUMERATION_TIMEOUT * 2 + + +@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 Exception: + 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_entries(ldap_conn, ent_list=None): + """Add LDAP entries from ent_list""" + if ent_list is not None: + for entry in ent_list: + ldap_conn.add_s(entry[0], entry[1]) + + +def cleanup_ldap_entries(ldap_conn, ent_list=None): + """Remove LDAP entries added by create_ldap_entries""" + if ent_list is None: + for ou in ("Users", "Groups", "Netgroups", "Services", "Policies"): + for entry in ldap_conn.search_s(f"ou={ou}," + f"{ldap_conn.ds_inst.base_dn}", + ldap.SCOPE_ONELEVEL, + attrlist=[]): + ldap_conn.delete_s(entry[0]) + else: + for entry in ent_list: + ldap_conn.delete_s(entry[0]) + + +def create_ldap_cleanup(request, ldap_conn, ent_list=None): + """Add teardown for removing all user/group LDAP entries""" + request.addfinalizer(lambda: cleanup_ldap_entries(ldap_conn, ent_list)) + + +def create_ldap_fixture(request, ldap_conn, ent_list=None): + """Add LDAP entries and add teardown for removing them""" + create_ldap_entries(ldap_conn, ent_list) + create_ldap_cleanup(request, ldap_conn, ent_list) + + +SCHEMA_RFC2307 = "rfc2307" +SCHEMA_RFC2307_BIS = "rfc2307bis" + + +def format_basic_conf(ldap_conn, schema): + """ + Format a basic SSSD configuration + + The files domain is defined but not enabled in order to avoid enumerating + users from the files domain that would otherwise by implicitly enabled + """ + schema_conf = "ldap_schema = " + schema + "\n" + if schema == SCHEMA_RFC2307_BIS: + schema_conf += "ldap_group_object_class = groupOfNames\n" + return unindent("""\ + [sssd] + debug_level = 0xffff + domains = LDAP + services = nss, pam + enable_files_domain = false + + [nss] + debug_level = 0xffff + memcache_timeout = 0 + + [pam] + debug_level = 0xffff + + [domain/files] + id_provider = proxy + proxy_lib_name = files + auth_provider = none + + [domain/LDAP] + ldap_auth_disable_tls_never_use_in_production = true + debug_level = 0xffff + enumerate = true + {schema_conf} + id_provider = ldap + auth_provider = ldap + ldap_enumeration_refresh_offset = 0 + ldap_uri = {ldap_conn.ds_inst.ldap_url} + ldap_search_base = {ldap_conn.ds_inst.base_dn} + """).format(**locals()) + + +def format_interactive_conf(ldap_conn, schema): + """Format an SSSD configuration with all caches refreshing in 4 seconds""" + return \ + format_basic_conf(ldap_conn, schema) + \ + unindent(""" + [nss] + memcache_timeout = 0 + enum_cache_timeout = {0} + entry_negative_timeout = 0 + + [domain/LDAP] + ldap_enumeration_refresh_timeout = {0} + ldap_purge_cache_timeout = 1 + entry_cache_timeout = {0} + """).format(ENUMERATION_TIMEOUT) + + +def create_conf_file(contents): + """Create sssd.conf with specified contents""" + conf = open(config.CONF_PATH, "w") + conf.write(contents) + conf.close() + os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR) + + +def cleanup_conf_file(): + """Remove sssd.conf, if it exists""" + if os.path.lexists(config.CONF_PATH): + os.unlink(config.CONF_PATH) + + +def create_conf_cleanup(request): + """Add teardown for removing sssd.conf""" + request.addfinalizer(cleanup_conf_file) + + +def create_conf_fixture(request, contents): + """ + Create sssd.conf with specified contents and add teardown for removing it + """ + create_conf_file(contents) + create_conf_cleanup(request) + + +def create_sssd_process(): + """Start the SSSD process""" + if subprocess.call(["sssd", "-D", "--logger=files"]) != 0: + raise Exception("sssd start failed") + + +def cleanup_sssd_process(): + """Stop the SSSD process and remove its state""" + try: + 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 OSError: + break + time.sleep(1) + except OSError: + 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) + + +def create_sssd_cleanup(request): + """Add teardown for stopping SSSD and removing its state""" + request.addfinalizer(cleanup_sssd_process) + + +def create_sssd_fixture(request): + """Start SSSD and add teardown for stopping it and removing its state""" + create_sssd_process() + create_sssd_cleanup(request) + + +@pytest.fixture +def sanity_rfc2307(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + ent_list.add_user("user2", 1002, 2002) + ent_list.add_user("user3", 1003, 2003) + + ent_list.add_group("group1", 2001) + ent_list.add_group("group2", 2002) + ent_list.add_group("group3", 2003) + + ent_list.add_group("empty_group", 2010) + + ent_list.add_group("two_user_group", 2012, ["user1", "user2"]) + create_ldap_fixture(request, ldap_conn, ent_list) + + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def populate_rfc2307bis(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + ent_list.add_user("user2", 1002, 2002) + ent_list.add_user("user3", 1003, 2003) + + ent_list.add_group_bis("group1", 2001) + ent_list.add_group_bis("group2", 2002) + ent_list.add_group_bis("group3", 2003) + + ent_list.add_group_bis("empty_group1", 2010) + ent_list.add_group_bis("empty_group2", 2011) + + ent_list.add_group_bis("two_user_group", 2012, ["user1", "user2"]) + ent_list.add_group_bis("group_empty_group", 2013, [], ["empty_group1"]) + ent_list.add_group_bis("group_two_empty_groups", 2014, + [], ["empty_group1", "empty_group2"]) + ent_list.add_group_bis("one_user_group1", 2015, ["user1"]) + ent_list.add_group_bis("one_user_group2", 2016, ["user2"]) + ent_list.add_group_bis("group_one_user_group", 2017, + [], ["one_user_group1"]) + ent_list.add_group_bis("group_two_user_group", 2018, + [], ["two_user_group"]) + ent_list.add_group_bis("group_two_one_user_groups", 2019, + [], ["one_user_group1", "one_user_group2"]) + + create_ldap_fixture(request, ldap_conn, ent_list) + + +@pytest.fixture +def sanity_rfc2307_bis(request, ldap_conn): + populate_rfc2307bis(request, ldap_conn) + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_sanity_rfc2307(ldap_conn, sanity_rfc2307): + time.sleep(INTERACTIVE_TIMEOUT) + passwd_pattern = ent.contains_only( + dict(name='user1', passwd='*', uid=1001, gid=2001, gecos='1001', + dir='/home/user1', shell='/bin/bash'), + dict(name='user2', passwd='*', uid=1002, gid=2002, gecos='1002', + dir='/home/user2', shell='/bin/bash'), + dict(name='user3', passwd='*', uid=1003, gid=2003, gecos='1003', + dir='/home/user3', shell='/bin/bash') + ) + ent.assert_passwd(passwd_pattern) + + group_pattern = ent.contains_only( + dict(name='group1', passwd='*', gid=2001, mem=ent.contains_only()), + dict(name='group2', passwd='*', gid=2002, mem=ent.contains_only()), + dict(name='group3', passwd='*', gid=2003, mem=ent.contains_only()), + dict(name='empty_group', passwd='*', gid=2010, + mem=ent.contains_only()), + dict(name='two_user_group', passwd='*', gid=2012, + mem=ent.contains_only("user1", "user2")) + ) + ent.assert_group(group_pattern) + + with pytest.raises(KeyError): + pwd.getpwnam("non_existent_user") + with pytest.raises(KeyError): + pwd.getpwuid(1) + with pytest.raises(KeyError): + grp.getgrnam("non_existent_group") + with pytest.raises(KeyError): + grp.getgrgid(1) + + +def test_sanity_rfc2307_bis(ldap_conn, sanity_rfc2307_bis): + time.sleep(INTERACTIVE_TIMEOUT) + passwd_pattern = ent.contains_only( + dict(name='user1', passwd='*', uid=1001, gid=2001, gecos='1001', + dir='/home/user1', shell='/bin/bash'), + dict(name='user2', passwd='*', uid=1002, gid=2002, gecos='1002', + dir='/home/user2', shell='/bin/bash'), + dict(name='user3', passwd='*', uid=1003, gid=2003, gecos='1003', + dir='/home/user3', shell='/bin/bash') + ) + ent.assert_passwd(passwd_pattern) + + group_pattern = ent.contains_only( + dict(name='group1', passwd='*', gid=2001, mem=ent.contains_only()), + dict(name='group2', passwd='*', gid=2002, mem=ent.contains_only()), + dict(name='group3', passwd='*', gid=2003, mem=ent.contains_only()), + dict(name='empty_group1', passwd='*', gid=2010, + mem=ent.contains_only()), + dict(name='empty_group2', passwd='*', gid=2011, + mem=ent.contains_only()), + dict(name='two_user_group', passwd='*', gid=2012, + mem=ent.contains_only("user1", "user2")), + dict(name='group_empty_group', passwd='*', gid=2013, + mem=ent.contains_only()), + dict(name='group_two_empty_groups', passwd='*', gid=2014, + mem=ent.contains_only()), + dict(name='one_user_group1', passwd='*', gid=2015, + mem=ent.contains_only("user1")), + dict(name='one_user_group2', passwd='*', gid=2016, + mem=ent.contains_only("user2")), + dict(name='group_one_user_group', passwd='*', gid=2017, + mem=ent.contains_only("user1")), + dict(name='group_two_user_group', passwd='*', gid=2018, + mem=ent.contains_only("user1", "user2")), + dict(name='group_two_one_user_groups', passwd='*', gid=2019, + mem=ent.contains_only("user1", "user2")) + ) + ent.assert_group(group_pattern) + + with pytest.raises(KeyError): + pwd.getpwnam("non_existent_user") + with pytest.raises(KeyError): + pwd.getpwuid(1) + with pytest.raises(KeyError): + grp.getgrnam("non_existent_group") + with pytest.raises(KeyError): + grp.getgrgid(1) + + +@pytest.fixture +def blank_rfc2307(request, ldap_conn): + """Create blank RFC2307 directory fixture with interactive SSSD conf""" + create_ldap_cleanup(request, ldap_conn) + create_conf_fixture(request, + format_interactive_conf(ldap_conn, SCHEMA_RFC2307)) + create_sssd_fixture(request) + + +@pytest.fixture +def blank_rfc2307_bis(request, ldap_conn): + """Create blank RFC2307bis directory fixture with interactive SSSD conf""" + create_ldap_cleanup(request, ldap_conn) + create_conf_fixture(request, + format_interactive_conf(ldap_conn, SCHEMA_RFC2307_BIS)) + create_sssd_fixture(request) + + +@pytest.fixture +def user_and_group_rfc2307(request, ldap_conn): + """ + Create an RFC2307 directory fixture with interactive SSSD conf, + one user and one group + """ + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user", 1001, 2000) + ent_list.add_group("group", 2001) + create_ldap_fixture(request, ldap_conn, ent_list) + create_conf_fixture(request, + format_interactive_conf(ldap_conn, SCHEMA_RFC2307)) + create_sssd_fixture(request) + return None + + +@pytest.fixture +def user_and_groups_rfc2307_bis(request, ldap_conn): + """ + Create an RFC2307bis directory fixture with interactive SSSD conf, + one user and two groups + """ + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user", 1001, 2000) + ent_list.add_group_bis("group1", 2001) + ent_list.add_group_bis("group2", 2002) + create_ldap_fixture(request, ldap_conn, ent_list) + create_conf_fixture(request, + format_interactive_conf(ldap_conn, SCHEMA_RFC2307_BIS)) + create_sssd_fixture(request) + return None + + +def test_add_remove_user(ldap_conn, blank_rfc2307): + """Test user addition and removal are reflected by SSSD""" + e = ldap_ent.user(ldap_conn.ds_inst.base_dn, "user", 2001, 2000) + time.sleep(INTERACTIVE_TIMEOUT) + # Add the user + ent.assert_passwd(ent.contains_only()) + ldap_conn.add_s(*e) + time.sleep(INTERACTIVE_TIMEOUT) + ent.assert_passwd(ent.contains_only(dict(name="user", uid=2001))) + # Remove the user + ldap_conn.delete_s(e[0]) + time.sleep(INTERACTIVE_TIMEOUT) + ent.assert_passwd(ent.contains_only()) + + +def test_add_remove_group_rfc2307(ldap_conn, blank_rfc2307): + """Test RFC2307 group addition and removal are reflected by SSSD""" + e = ldap_ent.group(ldap_conn.ds_inst.base_dn, "group", 2001) + time.sleep(INTERACTIVE_TIMEOUT) + # Add the group + ent.assert_group(ent.contains_only()) + ldap_conn.add_s(*e) + time.sleep(INTERACTIVE_TIMEOUT) + ent.assert_group(ent.contains_only(dict(name="group", gid=2001))) + # Remove the group + ldap_conn.delete_s(e[0]) + time.sleep(INTERACTIVE_TIMEOUT) + ent.assert_group(ent.contains_only()) + + +def test_add_remove_group_rfc2307_bis(ldap_conn, blank_rfc2307_bis): + """Test RFC2307bis group addition and removal are reflected by SSSD""" + e = ldap_ent.group_bis(ldap_conn.ds_inst.base_dn, "group", 2001) + time.sleep(INTERACTIVE_TIMEOUT) + # Add the group + ent.assert_group(ent.contains_only()) + ldap_conn.add_s(*e) + time.sleep(INTERACTIVE_TIMEOUT) + ent.assert_group(ent.contains_only(dict(name="group", gid=2001))) + # Remove the group + ldap_conn.delete_s(e[0]) + time.sleep(INTERACTIVE_TIMEOUT) + ent.assert_group(ent.contains_only()) + + +def test_add_remove_membership_rfc2307(ldap_conn, user_and_group_rfc2307): + """Test user membership addition and removal are reflected by SSSD""" + time.sleep(INTERACTIVE_TIMEOUT) + # Add user to group + ent.assert_group_by_name("group", dict(mem=ent.contains_only())) + ldap_conn.modify_s("cn=group,ou=Groups," + ldap_conn.ds_inst.base_dn, + [(ldap.MOD_REPLACE, "memberUid", b"user")]) + time.sleep(INTERACTIVE_TIMEOUT) + ent.assert_group_by_name("group", dict(mem=ent.contains_only("user"))) + # Remove user from group + ldap_conn.modify_s("cn=group,ou=Groups," + ldap_conn.ds_inst.base_dn, + [(ldap.MOD_DELETE, "memberUid", None)]) + time.sleep(INTERACTIVE_TIMEOUT) + ent.assert_group_by_name("group", dict(mem=ent.contains_only())) + + +def test_add_remove_membership_rfc2307_bis(ldap_conn, + user_and_groups_rfc2307_bis): + """ + Test user and group membership addition and removal are reflected by SSSD, + with RFC2307bis schema + """ + base_dn_bytes = ldap_conn.ds_inst.base_dn.encode('utf-8') + + time.sleep(INTERACTIVE_TIMEOUT) + # Add user to group1 + ent.assert_group_by_name("group1", dict(mem=ent.contains_only())) + ldap_conn.modify_s("cn=group1,ou=Groups," + ldap_conn.ds_inst.base_dn, + [(ldap.MOD_REPLACE, "member", + b"uid=user,ou=Users," + base_dn_bytes)]) + time.sleep(INTERACTIVE_TIMEOUT) + ent.assert_group_by_name("group1", dict(mem=ent.contains_only("user"))) + + # Add group1 to group2 + ldap_conn.modify_s("cn=group2,ou=Groups," + ldap_conn.ds_inst.base_dn, + [(ldap.MOD_REPLACE, "member", + b"cn=group1,ou=Groups," + base_dn_bytes)]) + time.sleep(INTERACTIVE_TIMEOUT) + ent.assert_group_by_name("group2", dict(mem=ent.contains_only("user"))) + + # Remove group1 from group2 + ldap_conn.modify_s("cn=group2,ou=Groups," + ldap_conn.ds_inst.base_dn, + [(ldap.MOD_DELETE, "member", None)]) + time.sleep(INTERACTIVE_TIMEOUT) + ent.assert_group_by_name("group2", dict(mem=ent.contains_only())) + + # Remove user from group1 + ldap_conn.modify_s("cn=group1,ou=Groups," + ldap_conn.ds_inst.base_dn, + [(ldap.MOD_DELETE, "member", None)]) + time.sleep(INTERACTIVE_TIMEOUT) + ent.assert_group_by_name("group1", dict(mem=ent.contains_only())) + + +@pytest.fixture +def override_homedir(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user_with_homedir_A", 1001, 2001, + homeDirectory="/home/A") + ent_list.add_user("user_with_homedir_B", 1002, 2002, + homeDirectory="/home/B") + ent_list.add_user("user_with_empty_homedir", 1003, 2003, + homeDirectory="") + create_ldap_fixture(request, ldap_conn, ent_list) + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [nss] + override_homedir = /home/B + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +def test_override_homedir(override_homedir): + """Test the effect of the "override_homedir" option""" + ent.assert_passwd( + ent.contains_only( + dict(name="user_with_homedir_A", uid=1001, dir="/home/B"), + dict(name="user_with_homedir_B", uid=1002, dir="/home/B"), + dict(name="user_with_empty_homedir", uid=1003, dir="/home/B") + ) + ) + + +@pytest.fixture +def fallback_homedir(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user_with_homedir_A", 1001, 2001, + homeDirectory="/home/A") + ent_list.add_user("user_with_homedir_B", 1002, 2002, + homeDirectory="/home/B") + ent_list.add_user("user_with_empty_homedir", 1003, 2003, + homeDirectory="") + create_ldap_fixture(request, ldap_conn, ent_list) + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [nss] + fallback_homedir = /home/B + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +def test_fallback_homedir(fallback_homedir): + """Test the effect of the "fallback_homedir" option""" + ent.assert_passwd( + ent.contains_only( + dict(name="user_with_homedir_A", uid=1001, dir="/home/A"), + dict(name="user_with_homedir_B", uid=1002, dir="/home/B"), + dict(name="user_with_empty_homedir", uid=1003, dir="/home/B") + ) + ) + + +@pytest.fixture +def override_shell(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user_with_shell_A", 1001, 2001, + loginShell="/bin/A") + ent_list.add_user("user_with_shell_B", 1002, 2002, + loginShell="/bin/B") + ent_list.add_user("user_with_empty_shell", 1003, 2003, + loginShell="") + create_ldap_fixture(request, ldap_conn, ent_list) + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [nss] + override_shell = /bin/B + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +def test_override_shell(override_shell): + """Test the effect of the "override_shell" option""" + ent.assert_passwd( + ent.contains_only( + dict(name="user_with_shell_A", uid=1001, shell="/bin/B"), + dict(name="user_with_shell_B", uid=1002, shell="/bin/B"), + dict(name="user_with_empty_shell", uid=1003, shell="/bin/B") + ) + ) + + +@pytest.fixture +def shell_fallback(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user_with_sh_shell", 1001, 2001, + loginShell="/bin/sh") + ent_list.add_user("user_with_not_installed_shell", 1002, 2002, + loginShell="/bin/not_installed") + ent_list.add_user("user_with_empty_shell", 1003, 2003, + loginShell="") + create_ldap_fixture(request, ldap_conn, ent_list) + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [nss] + shell_fallback = /bin/fallback + allowed_shells = /bin/not_installed + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +def test_shell_fallback(shell_fallback): + """Test the effect of the "shell_fallback" option""" + ent.assert_passwd( + ent.contains_only( + dict(name="user_with_sh_shell", uid=1001, shell="/bin/sh"), + dict(name="user_with_not_installed_shell", uid=1002, + shell="/bin/fallback"), + dict(name="user_with_empty_shell", uid=1003, shell="") + ) + ) + + +@pytest.fixture +def default_shell(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user_with_sh_shell", 1001, 2001, + loginShell="/bin/sh") + ent_list.add_user("user_with_not_installed_shell", 1002, 2002, + loginShell="/bin/not_installed") + ent_list.add_user("user_with_empty_shell", 1003, 2003, + loginShell="") + create_ldap_fixture(request, ldap_conn, ent_list) + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [nss] + default_shell = /bin/default + allowed_shells = /bin/default, /bin/not_installed + shell_fallback = /bin/fallback + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +def test_default_shell(default_shell): + """Test the effect of the "default_shell" option""" + ent.assert_passwd( + ent.contains_only( + dict(name="user_with_sh_shell", uid=1001, shell="/bin/sh"), + dict(name="user_with_not_installed_shell", uid=1002, + shell="/bin/fallback"), + dict(name="user_with_empty_shell", uid=1003, + shell="/bin/default") + ) + ) + + +@pytest.fixture +def vetoed_shells(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user_with_sh_shell", 1001, 2001, + loginShell="/bin/sh") + ent_list.add_user("user_with_vetoed_shell", 1002, 2002, + loginShell="/bin/vetoed") + ent_list.add_user("user_with_empty_shell", 1003, 2003, + loginShell="") + create_ldap_fixture(request, ldap_conn, ent_list) + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [nss] + default_shell = /bin/default + vetoed_shells = /bin/vetoed + shell_fallback = /bin/fallback + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +def test_vetoed_shells(vetoed_shells): + """Test the effect of the "vetoed_shells" option""" + ent.assert_passwd( + ent.contains_only( + dict(name="user_with_sh_shell", uid=1001, shell="/bin/sh"), + dict(name="user_with_vetoed_shell", uid=1002, + shell="/bin/fallback"), + dict(name="user_with_empty_shell", uid=1003, + shell="/bin/default") + ) + ) + + +@pytest.fixture +def sanity_rfc2307_bis_mpg(request, ldap_conn): + populate_rfc2307bis(request, ldap_conn) + + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_group_bis("conflict1", 1001) + ent_list.add_group_bis("conflict2", 1002) + create_ldap_fixture(request, ldap_conn, ent_list) + + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + \ + unindent(""" + [domain/LDAP] + auto_private_groups = True + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_ldap_auto_private_groups_enumerate(ldap_conn, + sanity_rfc2307_bis_mpg): + """ + Test the auto_private_groups together with enumeration + """ + passwd_pattern = ent.contains_only( + dict(name='user1', passwd='*', uid=1001, gid=1001, gecos='1001', + dir='/home/user1', shell='/bin/bash'), + dict(name='user2', passwd='*', uid=1002, gid=1002, gecos='1002', + dir='/home/user2', shell='/bin/bash'), + dict(name='user3', passwd='*', uid=1003, gid=1003, gecos='1003', + dir='/home/user3', shell='/bin/bash') + ) + ent.assert_passwd(passwd_pattern) + + group_pattern = ent.contains_only( + dict(name='user1', passwd='*', gid=1001, mem=ent.contains_only()), + dict(name='user2', passwd='*', gid=1002, mem=ent.contains_only()), + dict(name='user3', passwd='*', gid=1003, mem=ent.contains_only()), + dict(name='group1', passwd='*', gid=2001, mem=ent.contains_only()), + dict(name='group2', passwd='*', gid=2002, mem=ent.contains_only()), + dict(name='group3', passwd='*', gid=2003, mem=ent.contains_only()), + dict(name='empty_group1', passwd='*', gid=2010, + mem=ent.contains_only()), + dict(name='empty_group2', passwd='*', gid=2011, + mem=ent.contains_only()), + dict(name='two_user_group', passwd='*', gid=2012, + mem=ent.contains_only("user1", "user2")), + dict(name='group_empty_group', passwd='*', gid=2013, + mem=ent.contains_only()), + dict(name='group_two_empty_groups', passwd='*', gid=2014, + mem=ent.contains_only()), + dict(name='one_user_group1', passwd='*', gid=2015, + mem=ent.contains_only("user1")), + dict(name='one_user_group2', passwd='*', gid=2016, + mem=ent.contains_only("user2")), + dict(name='group_one_user_group', passwd='*', gid=2017, + mem=ent.contains_only("user1")), + dict(name='group_two_user_group', passwd='*', gid=2018, + mem=ent.contains_only("user1", "user2")), + dict(name='group_two_one_user_groups', passwd='*', gid=2019, + mem=ent.contains_only("user1", "user2")) + ) + ent.assert_group(group_pattern) + + with pytest.raises(KeyError): + grp.getgrnam("conflict1") + ent.assert_group_by_gid(1002, dict(name="user2", mem=ent.contains_only())) diff --git a/src/tests/intg/test_files_ops.py b/src/tests/intg/test_files_ops.py new file mode 100644 index 0000000..f778a89 --- /dev/null +++ b/src/tests/intg/test_files_ops.py @@ -0,0 +1,85 @@ +# +# SSSD integration test - operations on UNIX user and group database +# +# Copyright (c) 2016 Red Hat, Inc. +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 pwd +import grp +import pytest + +import ent + + +USER1 = dict(name='user1', passwd='*', uid=10001, gid=20001, + gecos='User for tests', + dir='/home/user1', + shell='/bin/bash') + +GROUP1 = dict(name='group1', + gid=30001, + mem=['user1']) + + +def test_useradd(passwd_ops_setup): + with pytest.raises(KeyError): + pwd.getpwnam("user1") + passwd_ops_setup.useradd(**USER1) + ent.assert_passwd_by_name("user1", USER1) + + +def test_usermod(passwd_ops_setup): + passwd_ops_setup.useradd(**USER1) + ent.assert_passwd_by_name("user1", USER1) + + USER1['shell'] = '/bin/zsh' + passwd_ops_setup.usermod(**USER1) + ent.assert_passwd_by_name("user1", USER1) + + +def test_userdel(passwd_ops_setup): + passwd_ops_setup.useradd(**USER1) + ent.assert_passwd_by_name("user1", USER1) + + passwd_ops_setup.userdel("user1") + with pytest.raises(KeyError): + pwd.getpwnam("user1") + + +def test_groupadd(group_ops_setup): + with pytest.raises(KeyError): + grp.getgrnam("group1") + group_ops_setup.groupadd(**GROUP1) + ent.assert_group_by_name("group1", GROUP1) + + +def test_groupmod(group_ops_setup): + group_ops_setup.groupadd(**GROUP1) + ent.assert_group_by_name("group1", GROUP1) + + modgroup = dict(GROUP1) + modgroup['mem'] = [] + + group_ops_setup.groupmod(old_name=GROUP1["name"], **modgroup) + ent.assert_group_by_name("group1", modgroup) + + +def test_groupdel(group_ops_setup): + group_ops_setup.groupadd(**GROUP1) + ent.assert_group_by_name("group1", GROUP1) + + group_ops_setup.groupdel("group1") + with pytest.raises(KeyError): + grp.getgrnam("group1") diff --git a/src/tests/intg/test_files_provider.py b/src/tests/intg/test_files_provider.py new file mode 100644 index 0000000..fa503dd --- /dev/null +++ b/src/tests/intg/test_files_provider.py @@ -0,0 +1,1341 @@ +# +# SSSD files domain tests +# +# Copyright (c) 2016 Red Hat, Inc. +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 stat +import time +import config +import signal +import subprocess +import pwd +import grp +import pytest +import tempfile + +import ent +import sssd_id +from sssd_nss import NssReturnCode +from sssd_passwd import (call_sssd_getpwnam, + call_sssd_getpwuid) +from sssd_group import call_sssd_getgrnam, call_sssd_getgrgid +from files_ops import PasswdOps, GroupOps +from util import unindent + +def have_files_provider(): + return os.environ['FILES_PROVIDER'] == "enabled" + +# Sync this with files_ops.c +FILES_REALLOC_CHUNK = 64 + +CANARY = dict(name='canary', passwd='x', uid=100001, gid=200001, + gecos='Used to check if passwd is resolvable', + dir='/home/canary', + shell='/bin/bash') + +USER1 = dict(name='user1', passwd='x', uid=10001, gid=20001, + gecos='User for tests', + dir='/home/user1', + shell='/bin/bash') + +USER2 = dict(name='user2', passwd='x', uid=10002, gid=20001, + gecos='User2 for tests', + dir='/home/user2', + shell='/bin/bash') + +OV_USER1 = dict(name='ov_user1', passwd='x', uid=10010, gid=20010, + gecos='Overriden User 1', + dir='/home/ov/user1', + shell='/bin/ov_user1_shell') + +ALT_USER1 = dict(name='alt_user1', passwd='x', uid=60001, gid=70001, + gecos='User for tests from alt files', + dir='/home/altuser1', + shell='/bin/bash') + +ALL_USERS = [CANARY, USER1, USER2, OV_USER1, ALT_USER1] + +CANARY_GR = dict(name='canary', + gid=300001, + mem=[]) + +GROUP1 = dict(name='group1', + gid=30001, + mem=['user1']) + +OV_GROUP1 = dict(name='ov_group1', + gid=30002, + mem=['user1']) + +GROUP12 = dict(name='group12', + gid=30012, + mem=['user1', 'user2']) + +GROUP_NOMEM = dict(name='group_nomem', + gid=40000, + mem=[]) + +ALT_GROUP1 = dict(name='alt_group1', + gid=80001, + mem=['alt_user1']) + + +def start_sssd(): + """Start sssd and add teardown for stopping it and removing state""" + os.environ["SSS_FILES_PASSWD"] = os.environ["NSS_WRAPPER_PASSWD"] + os.environ["SSS_FILES_GROUP"] = os.environ["NSS_WRAPPER_GROUP"] + if subprocess.call(["sssd", "-D", "--logger=files"]) != 0: + raise Exception("sssd start failed") + + +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 OSError: + break + time.sleep(1) + + +def restart_sssd(): + stop_sssd() + start_sssd() + + +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 create_sssd_fixture(request): + start_sssd() + + def teardown(): + try: + stop_sssd() + except Exception: + 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) + + +# Fixtures +@pytest.fixture +def files_domain_only(request): + conf = unindent("""\ + [sssd] + domains = files + services = nss + + [domain/files] + id_provider = files + fallback_to_nss = False + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +@pytest.fixture +def files_multiple_sources(request): + _, alt_passwd_path = tempfile.mkstemp(prefix='altpasswd') + request.addfinalizer(lambda: os.unlink(alt_passwd_path)) + alt_pwops = PasswdOps(alt_passwd_path) + + _, alt_group_path = tempfile.mkstemp(prefix='altgroup') + request.addfinalizer(lambda: os.unlink(alt_group_path)) + alt_grops = GroupOps(alt_group_path) + + passwd_list = ",".join([os.environ["NSS_WRAPPER_PASSWD"], alt_passwd_path]) + group_list = ",".join([os.environ["NSS_WRAPPER_GROUP"], alt_group_path]) + + conf = unindent("""\ + [sssd] + domains = files + services = nss + + [nss] + debug_level = 10 + + [domain/files] + id_provider = files + fallback_to_nss = False + passwd_files = {passwd_list} + group_files = {group_list} + debug_level = 10 + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return alt_pwops, alt_grops + + +@pytest.fixture +def files_multiple_sources_nocreate(request): + """ + Sets up SSSD with multiple sources, but does not actually create + the files. + """ + alt_passwd_path = tempfile.mktemp(prefix='altpasswd') + request.addfinalizer(lambda: os.unlink(alt_passwd_path)) + + alt_group_path = tempfile.mktemp(prefix='altgroup') + request.addfinalizer(lambda: os.unlink(alt_group_path)) + + passwd_list = ",".join([os.environ["NSS_WRAPPER_PASSWD"], alt_passwd_path]) + group_list = ",".join([os.environ["NSS_WRAPPER_GROUP"], alt_group_path]) + + conf = unindent("""\ + [sssd] + domains = files + services = nss + + [nss] + debug_level = 10 + + [domain/files] + id_provider = files + fallback_to_nss = False + passwd_files = {passwd_list} + group_files = {group_list} + debug_level = 10 + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return alt_passwd_path, alt_group_path + + +@pytest.fixture +def proxy_to_files_domain_only(request): + conf = unindent("""\ + [sssd] + domains = proxy + services = nss + + [domain/proxy] + id_provider = proxy + proxy_lib_name = files + auth_provider = none + resolver_provider = none + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +@pytest.fixture +def no_sssd_domain(request): + conf = unindent("""\ + [sssd] + services = nss + enable_files_domain = true + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +@pytest.fixture +def no_files_domain(request): + conf = unindent("""\ + [sssd] + services = nss + enable_files_domain = true + + [domain/disabled.files] + id_provider = files + fallback_to_nss = False + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +@pytest.fixture +def disabled_files_domain(request): + conf = unindent("""\ + [sssd] + domains = proxy + services = nss + enable_files_domain = false + + [domain/proxy] + id_provider = proxy + proxy_lib_name = files + auth_provider = none + resolver_provider = none + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +@pytest.fixture +def domain_resolution_order(request): + conf = unindent("""\ + [sssd] + domains = files + services = nss + domain_resolution_order = foo + + [domain/files] + id_provider = files + fallback_to_nss = False + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +@pytest.fixture +def default_domain_suffix(request): + conf = unindent("""\ + [sssd] + domains = files + services = nss + default_domain_suffix = foo + + [domain/files] + id_provider = files + fallback_to_nss = False + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +@pytest.fixture +def override_homedir_and_shell(request): + conf = unindent("""\ + [sssd] + domains = files + services = nss + + [domain/files] + id_provider = files + fallback_to_nss = False + override_homedir = /test/bar + override_shell = /bin/bar + + [nss] + override_homedir = /test/foo + override_shell = /bin/foo + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def setup_pw_with_list(pwd_ops, user_list): + for user in user_list: + pwd_ops.useradd(**user) + ent.assert_passwd_by_name(CANARY['name'], CANARY) + return pwd_ops + + +@pytest.fixture +def add_user_with_canary(passwd_ops_setup): + return setup_pw_with_list(passwd_ops_setup, [CANARY, USER1]) + + +@pytest.fixture +def setup_pw_with_canary(passwd_ops_setup): + return setup_pw_with_list(passwd_ops_setup, [CANARY]) + + +def add_group_members(pwd_ops, group): + members = {x['name']: x for x in ALL_USERS} + for member in group['mem']: + if pwd_ops.userexist(member): + continue + + pwd_ops.useradd(**members[member]) + + +def setup_gr_with_list(pwd_ops, grp_ops, group_list): + for group in group_list: + add_group_members(pwd_ops, group) + grp_ops.groupadd(**group) + + ent.assert_group_by_name(CANARY_GR['name'], CANARY_GR) + return grp_ops + + +@pytest.fixture +def add_group_with_canary(passwd_ops_setup, group_ops_setup): + return setup_gr_with_list( + passwd_ops_setup, group_ops_setup, [GROUP1, CANARY_GR] + ) + + +@pytest.fixture +def setup_gr_with_canary(passwd_ops_setup, group_ops_setup): + return setup_gr_with_list(passwd_ops_setup, group_ops_setup, [CANARY_GR]) + + +def poll_canary(fn, name, threshold=20): + """ + If we query SSSD while it's updating its cache, it would return NOTFOUND + rather than a result from potentially outdated or incomplete cache. In + reality this doesn't hurt because the order of the modules is normally + "sss files" so the user lookup would fall back to files. But in tests + we use this loop to wait until the canary user who is always there is + resolved. + """ + for _ in range(0, threshold): + res, _ = fn(name) + if res == NssReturnCode.SUCCESS: + return True + elif res == NssReturnCode.NOTFOUND: + time.sleep(0.1) + continue + else: + return False + return False + + +def sssd_getpwnam_sync(name): + ret = poll_canary(call_sssd_getpwnam, CANARY["name"]) + if ret is False: + return NssReturnCode.NOTFOUND, None + + return call_sssd_getpwnam(name) + + +def sssd_getpwuid_sync(uid): + ret = poll_canary(call_sssd_getpwnam, CANARY["name"]) + if ret is False: + return NssReturnCode.NOTFOUND, None + + return call_sssd_getpwuid(uid) + + +def sssd_getgrnam_sync(name): + ret = poll_canary(call_sssd_getgrnam, CANARY_GR["name"]) + if ret is False: + return NssReturnCode.NOTFOUND, None + + return call_sssd_getgrnam(name) + + +def sssd_getgrgid_sync(name): + ret = poll_canary(call_sssd_getgrnam, CANARY_GR["name"]) + if ret is False: + return NssReturnCode.NOTFOUND, None + + return call_sssd_getgrgid(name) + + +def sssd_id_sync(name): + sssd_getpwnam_sync(CANARY["name"]) + res, _, groups = sssd_id.get_user_groups(name) + return res, groups + + +# Helper functions +def user_generator(seqnum): + return dict(name='user%d' % seqnum, + passwd='x', + uid=10000 + seqnum, + gid=20000 + seqnum, + gecos='User for tests', + dir='/home/user%d' % seqnum, + shell='/bin/bash') + + +def check_user(exp_user, delay=1.0): + if delay > 0: + time.sleep(delay) + + res, found_user = sssd_getpwnam_sync(exp_user["name"]) + assert res == NssReturnCode.SUCCESS + assert found_user == exp_user + + +def group_generator(seqnum): + return dict(name='group%d' % seqnum, + gid=30000 + seqnum, + mem=[]) + + +def check_group(exp_group, delay=1.0): + if delay > 0: + time.sleep(delay) + + res, found_group = sssd_getgrnam_sync(exp_group["name"]) + assert res == NssReturnCode.SUCCESS + assert found_group == exp_group + + +def check_group_by_gid(exp_group, delay=1.0): + if delay > 0: + time.sleep(delay) + + res, found_group = sssd_getgrgid_sync(exp_group["gid"]) + assert res == NssReturnCode.SUCCESS + assert found_group == exp_group + + +def check_group_list(exp_groups_list): + for exp_group in exp_groups_list: + check_group(exp_group) + + +def assert_user_overriden(): + """ + There is an issue in nss_wrapper [0] and nss_wrapper always looks into + the files first before using the NSS module. This lets this check fail + because the user is found in the file and hence will be returned + without overridden values. + In order to work this around while there's no fix for nss_wrapper, let's + use the fully-qualified name when looking up the USER1 + + https://bugzilla.samba.org/show_bug.cgi?id=12883) + """ + ent.assert_passwd_by_name(USER1["name"] + "@files", OV_USER1) + ent.assert_passwd_by_name(OV_USER1["name"], OV_USER1) + + +def assert_group_overriden(): + """ + There is an issue in nss_wrapper [0] and nss_wrapper always looks into + the files first before using the NSS module. This lets this check fail + because the user is found in the file and hence will be returned + without overridden values. + In order to work this around while there's no fix for nss_wrapper, let's + use the fully-qualified name when looking up the GROUP1 + + https://bugzilla.samba.org/show_bug.cgi?id=12883) + """ + ent.assert_group_by_name(GROUP1["name"] + "@files", OV_GROUP1) + ent.assert_group_by_name(OV_GROUP1["name"], OV_GROUP1) + + +# User tests +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_getpwnam_after_start(add_user_with_canary, files_domain_only): + """ + Test that after startup without any additional operations, a user + can be resolved through sssd + """ + res, user = sssd_getpwnam_sync(USER1["name"]) + assert res == NssReturnCode.SUCCESS + assert user == USER1 + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_getpwuid_after_start(add_user_with_canary, files_domain_only): + """ + Test that after startup without any additional operations, a user + can be resolved through sssd + """ + res, user = sssd_getpwuid_sync(USER1["uid"]) + assert res == NssReturnCode.SUCCESS + assert user == USER1 + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_user_overriden(add_user_with_canary, files_domain_only): + """ + Test that user override works with files domain only + """ + # Override + subprocess.check_call(["sss_override", "user-add", USER1["name"], + "-u", str(OV_USER1["uid"]), + "-g", str(OV_USER1["gid"]), + "-n", OV_USER1["name"], + "-c", OV_USER1["gecos"], + "-h", OV_USER1["dir"], + "-s", OV_USER1["shell"]]) + + restart_sssd() + + assert_user_overriden() + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_group_overriden(add_group_with_canary, files_domain_only): + """ + Test that user override works with files domain only + """ + # Override + subprocess.check_call(["sss_override", "group-add", GROUP1["name"], + "-n", OV_GROUP1["name"], + "-g", str(OV_GROUP1["gid"])]) + + restart_sssd() + + assert_group_overriden() + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_getpwnam_neg(files_domain_only): + """ + Test that a nonexistent user cannot be resolved by name + """ + res, _ = call_sssd_getpwnam("nosuchuser") + assert res == NssReturnCode.NOTFOUND + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_getpwuid_neg(files_domain_only): + """ + Test that a nonexistent user cannot be resolved by UID + """ + res, _ = call_sssd_getpwuid(12345) + assert res == NssReturnCode.NOTFOUND + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_root_does_not_resolve(files_domain_only): + """ + SSSD currently does not resolve the root user even though it can + be resolved through the NSS interface + """ + nss_root = pwd.getpwnam("root") + assert nss_root is not None + + res, _ = call_sssd_getpwnam("root") + assert res == NssReturnCode.NOTFOUND + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_uid_zero_does_not_resolve(files_domain_only): + """ + SSSD currently does not resolve the UID 0 even though it can + be resolved through the NSS interface + """ + nss_root = pwd.getpwuid(0) + assert nss_root is not None + + res, _ = call_sssd_getpwuid(0) + assert res == NssReturnCode.NOTFOUND + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_add_remove_add_file_user(setup_pw_with_canary, files_domain_only): + """ + Test that removing a user is detected and the user + is removed from the sssd database. Similarly, an add + should be detected. Do this several times to test retaining + the inotify watch for moved and unlinked files. + """ + res, _ = call_sssd_getpwnam(USER1["name"]) + assert res == NssReturnCode.NOTFOUND + + setup_pw_with_canary.useradd(**USER1) + check_user(USER1) + + setup_pw_with_canary.userdel(USER1["name"]) + time.sleep(1.0) + res, _ = sssd_getpwnam_sync(USER1["name"]) + assert res == NssReturnCode.NOTFOUND + + setup_pw_with_canary.useradd(**USER1) + check_user(USER1) + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_mod_user_shell(add_user_with_canary, files_domain_only): + """ + Test that modifying a user shell is detected and the user + is modified in the sssd database + """ + res, user = sssd_getpwnam_sync(USER1["name"]) + assert res == NssReturnCode.SUCCESS + assert user == USER1 + + moduser = dict(USER1) + moduser['shell'] = '/bin/zsh' + add_user_with_canary.usermod(**moduser) + + check_user(moduser) + + +def incomplete_user_setup(pwd_ops, del_field, exp_field): + adduser = dict(USER1) + del adduser[del_field] + exp_user = dict(USER1) + exp_user[del_field] = exp_field + + pwd_ops.useradd(**adduser) + + return exp_user + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_user_no_shell(setup_pw_with_canary, files_domain_only): + """ + Test that resolving a user without a shell defined works and returns + a fallback value + """ + check_user(incomplete_user_setup(setup_pw_with_canary, 'shell', '')) + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_user_no_dir(setup_pw_with_canary, files_domain_only): + """ + Test that resolving a user without a homedir defined works and returns + a fallback value + """ + check_user(incomplete_user_setup(setup_pw_with_canary, 'dir', '')) + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_user_no_gecos(setup_pw_with_canary, files_domain_only): + """ + Test that resolving a user without a gecos defined works and returns + a fallback value + """ + check_user(incomplete_user_setup(setup_pw_with_canary, 'gecos', '')) + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_user_no_passwd(setup_pw_with_canary, files_domain_only): + """ + Test that resolving a user without a password defined works and returns + a fallback value + """ + check_user(incomplete_user_setup(setup_pw_with_canary, 'passwd', 'x')) + + +def bad_incomplete_user_setup(pwd_ops, del_field): + adduser = dict(USER1) + adduser[del_field] = '' + + pwd_ops.useradd(**adduser) + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_incomplete_user_fail(setup_pw_with_canary, files_domain_only): + """ + Test resolving an incomplete user where the missing field is required + to be present in the user record and thus the user shouldn't resolve. + + We cannot test UID and GID missing because nss_wrapper doesn't even + load the malformed passwd file, then. + """ + bad_incomplete_user_setup(setup_pw_with_canary, 'name') + res, user = sssd_getpwnam_sync(USER1["name"]) + assert res == NssReturnCode.NOTFOUND + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_getgrnam_after_start(add_group_with_canary, files_domain_only): + """ + Test that after startup without any additional operations, a group + can be resolved through sssd by name + """ + check_group(GROUP1) + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_getgrgid_after_start(add_group_with_canary, files_domain_only): + """ + Test that after startup without any additional operations, a group + can be resolved through sssd by GID + """ + check_group_by_gid(GROUP1) + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_getgrnam_neg(files_domain_only): + """ + Test that a nonexistent group cannot be resolved + """ + res, user = sssd_getgrnam_sync("nosuchgroup") + assert res == NssReturnCode.NOTFOUND + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_getgrgid_neg(files_domain_only): + """ + Test that a nonexistent group cannot be resolved + """ + res, user = sssd_getgrgid_sync(123456) + assert res == NssReturnCode.NOTFOUND + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_root_group_does_not_resolve(files_domain_only): + """ + SSSD currently does not resolve the root group even though it can + be resolved through the NSS interface + """ + nss_root = grp.getgrnam("root") + assert nss_root is not None + + res, user = call_sssd_getgrnam("root") + assert res == NssReturnCode.NOTFOUND + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_gid_zero_does_not_resolve(files_domain_only): + """ + SSSD currently does not resolve the group with GID 0 even though it + can be resolved through the NSS interface + """ + nss_root = grp.getgrgid(0) + assert nss_root is not None + + res, user = call_sssd_getgrgid(0) + assert res == NssReturnCode.NOTFOUND + + +@pytest.mark.flaky(max_runs=5) +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_add_remove_add_file_group( + setup_pw_with_canary, setup_gr_with_canary, files_domain_only +): + """ + Test that removing a group is detected and the group + is removed from the sssd database. Similarly, an add + should be detected. Do this several times to test retaining + the inotify watch for moved and unlinked files. + """ + res, group = call_sssd_getgrnam(GROUP1["name"]) + assert res == NssReturnCode.NOTFOUND + + add_group_members(setup_pw_with_canary, GROUP1) + setup_gr_with_canary.groupadd(**GROUP1) + check_group(GROUP1) + + setup_gr_with_canary.groupdel(GROUP1["name"]) + time.sleep(1) + res, group = call_sssd_getgrnam(GROUP1["name"]) + assert res == NssReturnCode.NOTFOUND + + setup_gr_with_canary.groupadd(**GROUP1) + check_group(GROUP1) + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_mod_group_name(add_group_with_canary, files_domain_only): + """ + Test that modifying a group name is detected and the group + is modified in the sssd database + """ + check_group(GROUP1) + + modgroup = dict(GROUP1) + modgroup['name'] = 'group1_mod' + add_group_with_canary.groupmod(old_name=GROUP1["name"], **modgroup) + + check_group(modgroup) + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_mod_group_gid(add_group_with_canary, files_domain_only): + """ + Test that modifying a group name is detected and the group + is modified in the sssd database + """ + check_group(GROUP1) + + modgroup = dict(GROUP1) + modgroup['gid'] = 30002 + add_group_with_canary.groupmod(old_name=GROUP1["name"], **modgroup) + + check_group(modgroup) + + +@pytest.fixture +def add_group_nomem_with_canary(passwd_ops_setup, group_ops_setup): + return setup_gr_with_list( + passwd_ops_setup, group_ops_setup, [GROUP_NOMEM, CANARY_GR] + ) + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_getgrnam_no_members(add_group_nomem_with_canary, files_domain_only): + """ + Test that after startup without any additional operations, a group + can be resolved through sssd + """ + check_group(GROUP_NOMEM) + + +def groupadd_list(grp_ops, groups): + for group in groups: + grp_ops.groupadd(**group) + + +def useradd_list(pwd_ops, users): + for usr in users: + pwd_ops.useradd(**usr) + + +def user_and_group_setup(pwd_ops, grp_ops, users, groups, reverse): + """ + The reverse is added so that we test cases where a group is added first, + then a user for this group is created -- in that case, we need to properly + link the group after the user is added. + """ + if reverse is False: + useradd_list(pwd_ops, users) + groupadd_list(grp_ops, groups) + else: + groupadd_list(grp_ops, groups) + useradd_list(pwd_ops, users) + + +def members_check(added_groups): + # Test that users are members as per getgrnam + check_group_list(added_groups) + + # Test that users are members as per initgroups + for group in added_groups: + for member in group['mem']: + res, groups = sssd_id_sync(member) + assert res == sssd_id.NssReturnCode.SUCCESS + assert group['name'] in groups + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_getgrnam_members_users_first(setup_pw_with_canary, + setup_gr_with_canary, + files_domain_only): + """ + A user is linked with a group + """ + user_and_group_setup(setup_pw_with_canary, + setup_gr_with_canary, + [USER1], + [GROUP1], + False) + members_check([GROUP1]) + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_getgrnam_members_users_multiple(setup_pw_with_canary, + setup_gr_with_canary, + files_domain_only): + """ + Multiple users are linked with a group + """ + user_and_group_setup(setup_pw_with_canary, + setup_gr_with_canary, + [USER1, USER2], + [GROUP12], + False) + members_check([GROUP12]) + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_getgrnam_members_groups_first(setup_pw_with_canary, + setup_gr_with_canary, + files_domain_only): + """ + A group is linked with a user + """ + user_and_group_setup(setup_pw_with_canary, + setup_gr_with_canary, + [USER1], + [GROUP1], + True) + members_check([GROUP1]) + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_getgrnam_ghost(setup_pw_with_canary, + setup_gr_with_canary, + files_domain_only): + """ + Test that group if not found (and will be handled by nss_files) if there + are any ghost members. + """ + user_and_group_setup(setup_pw_with_canary, + setup_gr_with_canary, + [], + [GROUP12], + False) + + time.sleep(1) + res, group = call_sssd_getgrnam(GROUP12["name"]) + assert res == NssReturnCode.NOTFOUND + + for member in GROUP12['mem']: + res, _ = call_sssd_getpwnam(member) + assert res == NssReturnCode.NOTFOUND + + +def ghost_and_member_test(pw_ops, grp_ops, reverse): + user_and_group_setup(pw_ops, + grp_ops, + [USER1], + [GROUP12], + reverse) + + time.sleep(1) + res, group = call_sssd_getgrnam(GROUP12["name"]) + assert res == NssReturnCode.NOTFOUND + + # We checked that the group added has the same members as group12, + # so both user1 and user2. Now check that user1 is a member of + # group12 and its own primary GID but user2 doesn't exist, it's + # just a ghost entry + res, groups = sssd_id_sync('user1') + assert res == sssd_id.NssReturnCode.SUCCESS + assert len(groups) == 2 + assert 'group12' in groups + + res, _ = call_sssd_getpwnam('user2') + assert res == NssReturnCode.NOTFOUND + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_getgrnam_user_ghost_and_member(setup_pw_with_canary, + setup_gr_with_canary, + files_domain_only): + """ + Test that a group with one member and one ghost. + """ + ghost_and_member_test(setup_pw_with_canary, + setup_gr_with_canary, + False) + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_getgrnam_user_member_and_ghost(setup_pw_with_canary, + setup_gr_with_canary, + files_domain_only): + """ + Test that a group with one member and one ghost, adding the group + first and then linking the member + """ + ghost_and_member_test(setup_pw_with_canary, + setup_gr_with_canary, + True) + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_getgrnam_add_remove_members(setup_pw_with_canary, + add_group_nomem_with_canary, + files_domain_only): + """ + Test that a user is linked with a group + """ + pwd_ops = setup_pw_with_canary + + check_group(GROUP_NOMEM) + + for usr in [USER1, USER2]: + pwd_ops.useradd(**usr) + + modgroup = dict(GROUP_NOMEM) + modgroup['mem'] = ['user1', 'user2'] + add_group_nomem_with_canary.groupmod(old_name=modgroup['name'], **modgroup) + check_group(modgroup) + + res, groups = sssd_id_sync('user1') + assert res == sssd_id.NssReturnCode.SUCCESS + assert len(groups) == 2 + assert 'group_nomem' in groups + + res, groups = sssd_id_sync('user2') + assert res == sssd_id.NssReturnCode.SUCCESS + assert 'group_nomem' in groups + + modgroup['mem'] = ['user2'] + add_group_nomem_with_canary.groupmod(old_name=modgroup['name'], **modgroup) + check_group(modgroup) + + # User1 exists, but is not a member of any supplementary group anymore + res, _ = call_sssd_getpwnam('user1') + assert res == sssd_id.NssReturnCode.SUCCESS + res, groups = sssd_id_sync('user1') + assert res == sssd_id.NssReturnCode.NOTFOUND + + # user2 still is + res, groups = sssd_id_sync('user2') + assert res == sssd_id.NssReturnCode.SUCCESS + assert len(groups) == 2 + assert 'group_nomem' in groups + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_getgrnam_add_remove_ghosts(setup_pw_with_canary, + add_group_nomem_with_canary, + files_domain_only): + """ + Test that a user is linked with a group + """ + check_group(GROUP_NOMEM) + + modgroup = dict(GROUP_NOMEM) + modgroup['mem'] = ['user1', 'user2'] + add_group_nomem_with_canary.groupmod(old_name=modgroup['name'], **modgroup) + time.sleep(1) + res, group = call_sssd_getgrnam(modgroup['name']) + assert res == sssd_id.NssReturnCode.NOTFOUND + + modgroup['mem'] = ['user2'] + add_group_nomem_with_canary.groupmod(old_name=modgroup['name'], **modgroup) + time.sleep(1) + res, group = call_sssd_getgrnam(modgroup['name']) + assert res == sssd_id.NssReturnCode.NOTFOUND + + res, _ = call_sssd_getpwnam('user1') + assert res == NssReturnCode.NOTFOUND + res, _ = call_sssd_getpwnam('user2') + assert res == NssReturnCode.NOTFOUND + + +def realloc_users(pwd_ops, num): + # Intentionally not including the last one because + # canary is added first + for i in range(1, num): + user = user_generator(i) + pwd_ops.useradd(**user) + + user = user_generator(num - 1) + check_user(user) + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_realloc_users_exact(setup_pw_with_canary, files_domain_only): + """ + Test that returning exactly FILES_REALLOC_CHUNK users (see files_ops.c) + works fine to test reallocation logic. Test exact number of users to + check for off-by-one errors. + """ + realloc_users(setup_pw_with_canary, FILES_REALLOC_CHUNK) + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_realloc_users(setup_pw_with_canary, files_domain_only): + """ + Test that returning exactly FILES_REALLOC_CHUNK users (see files_ops.c) + works fine to test reallocation logic. + """ + realloc_users(setup_pw_with_canary, FILES_REALLOC_CHUNK * 3) + + +def realloc_groups(grp_ops, num): + for i in range(1, num): + group = group_generator(i) + grp_ops.groupadd(**group) + + group = group_generator(num - 1) + check_group(group) + + +@pytest.mark.flaky(max_runs=5) +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_realloc_groups_exact(setup_gr_with_canary, files_domain_only): + """ + Test that returning exactly FILES_REALLOC_CHUNK groups (see files_ops.c) + works fine to test reallocation logic. Test exact number of groups to + check for off-by-one errors. + """ + realloc_groups(setup_gr_with_canary, FILES_REALLOC_CHUNK * 3) + + +@pytest.mark.flaky(max_runs=5) +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_realloc_groups(setup_gr_with_canary, files_domain_only): + """ + Test that returning exactly FILES_REALLOC_CHUNK groups (see files_ops.c) + works fine to test reallocation logic. Test exact number of groups to + check for off-by-one errors. + """ + realloc_groups(setup_gr_with_canary, FILES_REALLOC_CHUNK * 3) + + +# Files domain autoconfiguration tests +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_no_sssd_domain(add_user_with_canary, no_sssd_domain): + """ + Test that if no sssd domain is configured, sssd will add the implicit one + """ + res, user = sssd_getpwnam_sync(USER1["name"]) + assert res == NssReturnCode.SUCCESS + assert user == USER1 + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_proxy_to_files_domain_only(add_user_with_canary, + proxy_to_files_domain_only): + """ + Test that implicit_files domain is not started together with proxy to files + """ + res, _ = call_sssd_getpwnam("{0}@implicit_files".format(USER1["name"])) + assert res == NssReturnCode.NOTFOUND + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_no_files_domain(add_user_with_canary, no_files_domain): + """ + Test that if no files domain is configured, sssd will add the implicit one + """ + res, user = sssd_getpwnam_sync(USER1["name"]) + assert res == NssReturnCode.SUCCESS + assert user == USER1 + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_disable_files_domain(add_user_with_canary, disabled_files_domain): + """ + Test disabled files domain + """ + # The local user will not be resolvable through nss_sss now + res, user = sssd_getpwnam_sync(USER1["name"]) + assert res != NssReturnCode.SUCCESS + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_multiple_passwd_group_files(add_user_with_canary, + add_group_with_canary, + files_multiple_sources): + """ + Test that users and groups can be mirrored from multiple files + """ + alt_pwops, alt_grops = files_multiple_sources + alt_pwops.useradd(**ALT_USER1) + alt_grops.groupadd(**ALT_GROUP1) + + check_user(USER1) + check_user(ALT_USER1) + + check_group(GROUP1) + check_group(ALT_GROUP1) + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_multiple_files_created_after_startup(add_user_with_canary, + add_group_with_canary, + files_multiple_sources_nocreate): + """ + Test that users and groups can be mirrored from multiple files, + but those files are not created when SSSD starts, only afterwards. + """ + alt_passwd_path, alt_group_path = files_multiple_sources_nocreate + + check_user(USER1) + check_group(GROUP1) + + # touch the files + for fpath in (alt_passwd_path, alt_group_path): + with open(fpath, "w"): + os.utime(fpath) + + alt_pwops = PasswdOps(alt_passwd_path) + alt_grops = GroupOps(alt_group_path) + alt_pwops.useradd(**ALT_USER1) + alt_grops.groupadd(**ALT_GROUP1) + + check_user(ALT_USER1) + check_group(ALT_GROUP1) + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_files_with_domain_resolution_order(add_user_with_canary, + domain_resolution_order): + """ + Test that when using domain_resolution_order the user won't be using + its fully-qualified name. + """ + check_user(USER1) + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_files_with_default_domain_suffix(add_user_with_canary, + default_domain_suffix): + """ + Test that when using domain_resolution_order the user won't be using + its fully-qualified name. + """ + ret = poll_canary(call_sssd_getpwuid, CANARY["uid"]) + if ret is False: + return NssReturnCode.NOTFOUND, None + + res, found_user = call_sssd_getpwuid(USER1["uid"]) + assert res == NssReturnCode.SUCCESS + assert found_user == USER1 + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_files_with_override_homedir(add_user_with_canary, + override_homedir_and_shell): + res, user = sssd_getpwnam_sync(USER1["name"]) + assert res == NssReturnCode.SUCCESS + assert user["dir"] == USER1["dir"] + + +@pytest.mark.skipif(not have_files_provider(), + reason="'files provider' disabled, skipping") +def test_files_with_override_shell(add_user_with_canary, + override_homedir_and_shell): + res, user = sssd_getpwnam_sync(USER1["name"]) + assert res == NssReturnCode.SUCCESS + assert user["shell"] == USER1["shell"] diff --git a/src/tests/intg/test_infopipe.py b/src/tests/intg/test_infopipe.py new file mode 100644 index 0000000..f1f6c83 --- /dev/null +++ b/src/tests/intg/test_infopipe.py @@ -0,0 +1,855 @@ +# +# Infopipe integration test +# +# Copyright (c) 2017 Red Hat, Inc. +# Author: Lukas Slebodnik <lslebodn@redhat.com> +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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/>. +# + +from __future__ import print_function + +import os +import stat +import pwd +import signal +import subprocess +import errno +import time +import ldap +import ldap.modlist +import pytest +import dbus +import shutil + +import config +import ds_openldap +import ldap_ent +from util import unindent, get_call_output + +LDAP_BASE_DN = "dc=example,dc=com" +INTERACTIVE_TIMEOUT = 4 + + +class DbusDaemon(object): + def __init__(self): + self.pid = 0 + + def start(self): + """Start the SSSD process""" + assert self.pid == 0 + + dbus_config_path = config.SYSCONFDIR + "/dbus-1/cwrap-dbus-system.conf" + dbus_commands = [ + ["dbus-daemon", "--config-file", dbus_config_path, + "--nosyslog", "--fork"], + ["dbus-daemon", "--config-file", dbus_config_path, "--fork"], + ] + dbus_started = False + for dbus_command in dbus_commands: + try: + if subprocess.call(dbus_command) == 0: + dbus_started = True + break + else: + print("start failed for %s" % " ".join(dbus_command)) + except OSError as ex: + if ex.errno == errno.ENOENT: + print("%s does not exist" % (dbus_command[0])) + pass + + if not dbus_started: + raise Exception("dbus-daemon start failed") + dbus_pid_path = config.RUNSTATEDIR + "/dbus/messagebus.pid" + # wait 10 seconds for pidfile + wait_time = 10 + for _ in range(wait_time * 10): + if os.path.isfile(dbus_pid_path): + break + time.sleep(.1) + + assert os.path.isfile(dbus_pid_path) + with open(dbus_pid_path, "r") as pid_file: + self.pid = int(pid_file.read()) + + def stop(self): + """Stop the SSSD process and remove its state""" + + # stop process only if running + if self.pid != 0: + try: + os.kill(self.pid, signal.SIGTERM) + while True: + try: + os.kill(self.pid, signal.SIGCONT) + except OSError: + break + time.sleep(.1) + except OSError: + pass + + # clean pid so we can start service one more time + self.pid = 0 + + # dbus-daemon 1.2.24 does not clean pid file after itself + try: + os.unlink(config.RUNSTATEDIR + "/dbus/messagebus.pid") + except OSError as ex: + if ex.errno != errno.ENOENT: + raise + + +@pytest.fixture(scope="module") +def dbus_system_bus(request): + dbus_daemon = DbusDaemon() + dbus_daemon.start() + + def cleanup_dbus_process(): + dbus_daemon.stop() + request.addfinalizer(cleanup_dbus_process) + + return dbus.SystemBus() + + +@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 Exception: + ds_inst.teardown() + raise + request.addfinalizer(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(ldap_conn.unbind_s) + return ldap_conn + + +def create_ldap_entries(ldap_conn, ent_list=None): + """Add LDAP entries from ent_list""" + if ent_list is not None: + for entry in ent_list: + ldap_conn.add_s(entry[0], entry[1]) + + +def cleanup_ldap_entries(ldap_conn, ent_list=None): + """Remove LDAP entries added by create_ldap_entries""" + if ent_list is None: + for ou in ("Users", "Groups", "Netgroups", "Services", "Policies"): + for entry in ldap_conn.search_s(f"ou={ou}," + f"{ldap_conn.ds_inst.base_dn}", + ldap.SCOPE_ONELEVEL, + attrlist=[]): + ldap_conn.delete_s(entry[0]) + else: + for entry in ent_list: + ldap_conn.delete_s(entry[0]) + + +def create_ldap_cleanup(request, ldap_conn, ent_list=None): + """Add teardown for removing all user/group LDAP entries""" + request.addfinalizer(lambda: cleanup_ldap_entries(ldap_conn, ent_list)) + + +def create_ldap_fixture(request, ldap_conn, ent_list=None): + """Add LDAP entries and add teardown for removing them""" + create_ldap_entries(ldap_conn, ent_list) + create_ldap_cleanup(request, ldap_conn, ent_list) + + +SCHEMA_RFC2307 = "rfc2307" +SCHEMA_RFC2307_BIS = "rfc2307bis" + + +def format_basic_conf(ldap_conn, schema, config): + """Format a basic SSSD configuration""" + schema_conf = "ldap_schema = " + schema + "\n" + if schema == SCHEMA_RFC2307_BIS: + schema_conf += "ldap_group_object_class = groupOfNames\n" + + return unindent("""\ + [sssd] + debug_level = 0xffff + domains = LDAP, app + services = nss, ifp + enable_files_domain = false + + [nss] + memcache_timeout = 0 + + [ifp] + debug_level = 0xffff + user_attributes = +extraName + ca_db = {config.PAM_CERT_DB_PATH} + + [domain/LDAP] + {schema_conf} + id_provider = ldap + ldap_uri = {ldap_conn.ds_inst.ldap_url} + ldap_search_base = {ldap_conn.ds_inst.base_dn} + ldap_user_extra_attrs = extraName:uid + ldap_user_certificate = userCert + + [application/app] + inherit_from = LDAP + """).format(**locals()) + + +def format_certificate_conf(ldap_conn, schema, config): + """Format an SSSD configuration with all caches refreshing in 4 seconds""" + return \ + format_basic_conf(ldap_conn, schema, config) + \ + unindent(""" + [certmap/LDAP/user1] + matchrule = <SUBJECT>.*CN = SSSD test cert 0001.* + """).format(**locals()) + + +def create_conf_file(contents): + """Create sssd.conf with specified contents""" + with open(config.CONF_PATH, "w") as conf: + conf.write(contents) + os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR) + + +def cleanup_conf_file(): + """Remove sssd.conf, if it exists""" + if os.path.lexists(config.CONF_PATH): + os.unlink(config.CONF_PATH) + + +def create_conf_cleanup(request): + """Add teardown for removing sssd.conf""" + request.addfinalizer(cleanup_conf_file) + + +def create_conf_fixture(request, contents): + """ + Create sssd.conf with specified contents and add teardown for removing it + """ + create_conf_file(contents) + create_conf_cleanup(request) + + +def create_sssd_process(): + """Start the SSSD process""" + if subprocess.call(["sssd", "-D", "--logger=files"]) != 0: + raise Exception("sssd start failed") + + +def cleanup_sssd_process(): + """Stop the SSSD process and remove its state""" + try: + with open(config.PIDFILE_PATH, "r") as pid_file: + pid = int(pid_file.read()) + os.kill(pid, signal.SIGTERM) + while True: + try: + os.kill(pid, signal.SIGCONT) + except OSError: + break + time.sleep(1) + except OSError: + 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) + + +def create_sssd_cleanup(request): + """Add teardown for stopping SSSD and removing its state""" + request.addfinalizer(cleanup_sssd_process) + + +def create_sssd_fixture(request): + """Start SSSD and add teardown for stopping it and removing its state""" + create_sssd_process() + create_sssd_cleanup(request) + + +def backup_ca_db(): + """Create backup file for ca db""" + src = os.path.dirname(config.PAM_CERT_DB_PATH) + "/SSSD_test_CA.pem" + dst = os.path.dirname(config.PAM_CERT_DB_PATH) + "/SSSD_test_CA.pem.bp" + shutil.copyfile(src, dst) + + +def restore_ca_db(): + """Restore backup file for ca db""" + src = os.path.dirname(config.PAM_CERT_DB_PATH) + "/SSSD_test_CA.pem.bp" + dst = os.path.dirname(config.PAM_CERT_DB_PATH) + "/SSSD_test_CA.pem" + shutil.copyfile(src, dst) + os.remove(src) + + +def create_restore_ca_db(request): + """Add teardown for restoring ca_db""" + request.addfinalizer(restore_ca_db) + + +def create_ca_db_fixture(request): + """ + Create backup for ca_db and add teardown for restoring it + """ + backup_ca_db() + create_restore_ca_db(request) + + +@pytest.fixture +def sanity_rfc2307(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + ent_list.add_user("user2", 1002, 2002) + ent_list.add_user("user3", 1003, 2003) + + ent_list.add_group("group1", 2001) + ent_list.add_group("group2", 2002) + ent_list.add_group("group3", 2003) + + ent_list.add_group("empty_group", 2010) + + ent_list.add_group("single_user_group", 2011, ["user1"]) + ent_list.add_group("two_user_group", 2012, ["user1", "user2"]) + + create_ldap_fixture(request, ldap_conn, ent_list) + + config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH'] + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307, config) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + create_ca_db_fixture(request) + return None + + +@pytest.fixture +def simple_rfc2307(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user('usr\\\\001', 181818, 181818) + ent_list.add_group("group1", 181818) + create_ldap_fixture(request, ldap_conn, ent_list) + config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH'] + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307, config) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + create_ca_db_fixture(request) + return None + + +@pytest.fixture +def auto_private_groups_rfc2307(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + + ent_list.add_group("group1", 2001) + ent_list.add_group("single_user_group", 2011, ["user1"]) + ent_list.add_group("two_user_group", 2012, ["user1"]) + + create_ldap_fixture(request, ldap_conn, ent_list) + + config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH'] + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307, config) + \ + unindent(""" + [domain/LDAP] + auto_private_groups = True + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + create_ca_db_fixture(request) + return None + + +@pytest.fixture +def add_user_with_cert(request, ldap_conn): + config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH'] + + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + + create_ldap_fixture(request, ldap_conn, ent_list) + + der_path = os.path.dirname(config.PAM_CERT_DB_PATH) + der_path += "/SSSD_test_cert_x509_0001.der" + with open(der_path, 'rb') as f: + val = f.read() + dn = "uid=user1,ou=Users," + LDAP_BASE_DN + ''' + Using 'userCert' instead of 'userCertificate' to hold the user certificate + because the default OpenLDAP has syntax and matching rules which are not + used in other LDAP servers. + ''' + ldap_conn.modify_s(dn, [(ldap.MOD_ADD, 'userCert', val)]) + + conf = format_certificate_conf(ldap_conn, SCHEMA_RFC2307_BIS, config) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + create_ca_db_fixture(request) + + return None + + +def test_ping_raw(dbus_system_bus, ldap_conn, simple_rfc2307): + # test with disabled introspection + sssd_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe', + '/org/freedesktop/sssd/infopipe', + introspect=False) + sssd_interface = dbus.Interface(sssd_obj, 'org.freedesktop.sssd.infopipe') + + # test missing parameter + with pytest.raises(dbus.exceptions.DBusException) as exc_info: + sssd_interface.Ping() + assert exc_info.errisinstance(dbus.exceptions.DBusException) + + ex = exc_info.value + assert ex.get_dbus_name() == 'sbus.Error.Errno' + assert 'Unexpected argument type provided' in ex.get_dbus_message() + + # test wrong parameter type + with pytest.raises(dbus.exceptions.DBusException) as exc_info: + sssd_interface.Ping(1) + assert exc_info.errisinstance(dbus.exceptions.DBusException) + + ex = exc_info.value + assert ex.get_dbus_name() == 'sbus.Error.Errno' + assert 'Unexpected argument type provided' in ex.get_dbus_message() + + # test wrong parameter value + with pytest.raises(dbus.exceptions.DBusException) as exc_info: + sssd_interface.Ping('test') + assert exc_info.errisinstance(dbus.exceptions.DBusException) + + ex = exc_info.value + assert ex.get_dbus_name() == 'org.freedesktop.DBus.Error.InvalidArgs' + assert ex.get_dbus_message() == 'Invalid argument' + + # positive test + ret = sssd_interface.Ping('ping') + assert ret == "PONG" + + # test case insensitive input + ret = sssd_interface.Ping('PinG') + assert ret == "PONG" + + ret = sssd_interface.Ping('PING') + assert ret == "PONG" + + +def test_ping_introspection(dbus_system_bus, ldap_conn, simple_rfc2307): + sssd_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe', + '/org/freedesktop/sssd/infopipe') + sssd_interface = dbus.Interface(sssd_obj, 'org.freedesktop.sssd.infopipe') + + # test missing parameter + with pytest.raises(TypeError) as exc_info: + sssd_interface.Ping() + assert exc_info.errisinstance(TypeError) + + ex = exc_info.value + assert str(ex) == 'More items found in D-Bus signature than in Python ' \ + 'arguments' + + # test wrong parameter type + with pytest.raises(TypeError) as exc_info: + sssd_interface.Ping(1) + assert exc_info.errisinstance(TypeError) + + ex = exc_info.value + assert str(ex) == 'Expected a string or unicode object' + + # test wrong parameter value + with pytest.raises(dbus.exceptions.DBusException) as exc_info: + sssd_interface.Ping('test') + assert exc_info.errisinstance(dbus.exceptions.DBusException) + + ex = exc_info.value + assert ex.get_dbus_name() == 'org.freedesktop.DBus.Error.InvalidArgs' + assert ex.get_dbus_message() == 'Invalid argument' + + # positive test + ret = sssd_interface.Ping('ping') + assert ret == "PONG" + + # test case insensitive input + ret = sssd_interface.Ping('PinG') + assert ret == "PONG" + + ret = sssd_interface.Ping('PING') + assert ret == "PONG" + + +def test_special_characters(dbus_system_bus, ldap_conn, simple_rfc2307): + sssd_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe', + '/org/freedesktop/sssd/infopipe') + sssd_interface = dbus.Interface(sssd_obj, 'org.freedesktop.sssd.infopipe') + + attributes = ['name', 'uidNumber', 'gidNumber', 'gecos', 'homeDirectory', + 'loginShell'] + expected = dict(name='usr\\001', uidNumber='181818', gidNumber='181818', + gecos='181818', homeDirectory='/home/usr\\\\001', + loginShell='/bin/bash') + + user_attrs = sssd_interface.GetUserAttr('usr\\001', attributes) + assert user_attrs.signature == 'sv' + assert user_attrs.variant_level == 0 + + assert len(attributes) == len(user_attrs) + assert sorted(attributes) == sorted(user_attrs.keys()) + + # check values of attributes + for attr in user_attrs: + assert user_attrs[attr].signature == 's' + assert user_attrs[attr].variant_level == 1 + assert user_attrs[attr][0] == expected[attr] + + +def test_get_user_attr(dbus_system_bus, ldap_conn, sanity_rfc2307): + sssd_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe', + '/org/freedesktop/sssd/infopipe') + sssd_interface = dbus.Interface(sssd_obj, 'org.freedesktop.sssd.infopipe') + + # negative test + with pytest.raises(dbus.exceptions.DBusException) as exc_info: + sssd_interface.GetUserAttr('non_existent_user', ['name']) + assert exc_info.errisinstance(dbus.exceptions.DBusException) + + ex = exc_info.value + assert ex.get_dbus_name() == 'sbus.Error.NotFound' + assert ex.get_dbus_message() == 'No such file or directory' + + # test 0 attributes + user_attrs = sssd_interface.GetUserAttr('user1', []) + + assert user_attrs.signature == 'sv' + assert user_attrs.variant_level == 0 + + # expect empty sequence; len(user_attrs) == 0 + assert not user_attrs + + # positive test + attributes = ['name', 'uidNumber', 'gidNumber', 'gecos', 'homeDirectory', + 'loginShell'] + expected = dict(name='user1', uidNumber='1001', gidNumber='2001', + gecos='1001', homeDirectory='/home/user1', + loginShell='/bin/bash') + user_attrs = sssd_interface.GetUserAttr('user1', attributes) + + assert user_attrs.signature == 'sv' + assert user_attrs.variant_level == 0 + + assert len(attributes) == len(user_attrs) + assert sorted(attributes) == sorted(user_attrs.keys()) + + # check values of attributes + for attr in user_attrs: + assert user_attrs[attr].signature == 's' + assert user_attrs[attr].variant_level == 1 + assert user_attrs[attr][0] == expected[attr] + + +def test_get_user_groups(dbus_system_bus, ldap_conn, sanity_rfc2307): + sssd_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe', + '/org/freedesktop/sssd/infopipe') + sssd_interface = dbus.Interface(sssd_obj, 'org.freedesktop.sssd.infopipe') + + # negative test + with pytest.raises(dbus.exceptions.DBusException) as exc_info: + sssd_interface.GetUserGroups('non_existent_user') + assert exc_info.errisinstance(dbus.exceptions.DBusException) + + ex = exc_info.value + assert ex.get_dbus_name() == 'sbus.Error.NotFound' + assert ex.get_dbus_message() == 'No such file or directory' + + # the same test via nss responder + with pytest.raises(KeyError): + pwd.getpwnam("non_existent_user") + + # 0 groups + res = sssd_interface.GetUserGroups('user3') + assert res.signature == 's' + assert res.variant_level == 0 + + # expect empty sequence; len(res) == 0 + assert not res + + # single group + res = sssd_interface.GetUserGroups('user2') + assert res.signature == 's' + assert res.variant_level == 0 + + assert len(res) == 1 + assert res[0] == 'two_user_group' + + # more groups + res = sssd_interface.GetUserGroups('user1') + assert res.signature == 's' + assert res.variant_level == 0 + + assert len(res) == 2 + assert sorted(res) == ['single_user_group', 'two_user_group'] + + +''' +Given auto_private_groups is enabled +When GetUserGroups is called +Then the origPrimaryGroupGidNumber is returned as part of the group memberships +''' + + +def test_get_user_groups_given_auto_private_groups_enabled( + dbus_system_bus, + ldap_conn, auto_private_groups_rfc2307): + sssd_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe', + '/org/freedesktop/sssd/infopipe') + sssd_interface = dbus.Interface(sssd_obj, 'org.freedesktop.sssd.infopipe') + + res = sssd_interface.GetUserGroups('user1') + + assert sorted(res) == ['group1', 'single_user_group', 'two_user_group'] + + +def get_user_property(dbus_system_bus, username, prop_name): + users_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe', + '/org/freedesktop/sssd/infopipe/Users') + + users_iface = dbus.Interface(users_obj, + "org.freedesktop.sssd.infopipe.Users") + + user_path = users_iface.FindByName(username) + user_object = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe', + user_path) + + prop_iface = dbus.Interface(user_object, 'org.freedesktop.DBus.Properties') + return prop_iface.Get('org.freedesktop.sssd.infopipe.Users.User', + prop_name) + + +def get_user_by_attr(dbus_system_bus, attribute, filter): + users_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe', + '/org/freedesktop/sssd/infopipe/Users') + + users_iface = dbus.Interface(users_obj, + "org.freedesktop.sssd.infopipe.Users") + + return users_iface.ListByAttr(attribute, filter, 0) + + +def get_user_by_name(dbus_system_bus, filter): + users_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe', + '/org/freedesktop/sssd/infopipe/Users') + + users_iface = dbus.Interface(users_obj, + "org.freedesktop.sssd.infopipe.Users") + + return users_iface.ListByName(filter, 0) + + +def test_get_extra_attributes_empty(dbus_system_bus, + ldap_conn, + sanity_rfc2307): + """ + Make sure the extraAttributes property can be retrieved + """ + extra_attrs = get_user_property(dbus_system_bus, + 'user1', + 'extraAttributes') + assert extra_attrs['extraName'][0] == 'user1' + + +def test_sssctl_domain_list_app_domain(dbus_system_bus, + ldap_conn, + sanity_rfc2307): + output = get_call_output(["sssctl", "domain-list"], subprocess.STDOUT) + + assert "Error" not in output + assert output.find("LDAP") != -1 + assert output.find("app") != -1 + + +def test_update_member_list_and_get_all(dbus_system_bus, + ldap_conn, + sanity_rfc2307): + ''' + Test that UpdateMemberList() and GetAll() return the correct users that are + members of a group + ''' + sssd_obj = dbus_system_bus.get_object( + 'org.freedesktop.sssd.infopipe', + '/org/freedesktop/sssd/infopipe/Groups') + groups_iface = dbus.Interface(sssd_obj, + 'org.freedesktop.sssd.infopipe.Groups') + group_id = 2011 + expected_user_result = "/org/freedesktop/sssd/infopipe/Users/LDAP/1001" + + group_path = groups_iface.FindByName('single_user_group') + + group_object = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe', + group_path) + group_iface = dbus.Interface(group_object, + 'org.freedesktop.sssd.infopipe.Groups.Group') + + # update local cache for group + try: + group_iface.UpdateMemberList(group_id) + except dbus.exceptions.DBusException as ex: + assert False, "Unexpected DBusException raised: " + ex + + # check members of group + prop_iface = dbus.Interface(group_object, + 'org.freedesktop.DBus.Properties') + res = prop_iface.GetAll('org.freedesktop.sssd.infopipe.Groups.Group') + assert str(res.get("users")[0]) == expected_user_result + + # delete group (there's no other way of removing a user from a group) and + # wait change to propagate + ldap_conn.delete("cn=single_user_group,ou=Groups,dc=example,dc=com") + time.sleep(INTERACTIVE_TIMEOUT) + + # add group back but this time without any member + ldap_group = ldap_ent.group("dc=example,dc=com", "single_user_group", 2011) + ldap_conn.add_s(ldap_group[0], ldap_group[1]) + + # invalidate cache + subprocess.call(["sss_cache", "-E"]) + + # check that group has no members + group_iface.UpdateMemberList(group_id) + prop_interface = dbus.Interface(group_object, + 'org.freedesktop.DBus.Properties') + res = prop_interface.GetAll('org.freedesktop.sssd.infopipe.Groups.Group') + assert not res.get("users") + + +def test_find_by_valid_certificate(dbus_system_bus, + ldap_conn, + add_user_with_cert): + """test_find_by_valid_certificate + + :id: 3f212e6e-00ce-44ac-95d4-59925cb5a14a + :title: SSSD-TC: Infopipe: Find by valid certificate + :casecomponent: sssd + :subsystemteam: sst_idm_sssd + """ + users_obj = dbus_system_bus.get_object( + 'org.freedesktop.sssd.infopipe', + '/org/freedesktop/sssd/infopipe/Users') + users_iface = dbus.Interface(users_obj, + 'org.freedesktop.sssd.infopipe.Users') + cert_path = os.path.dirname(config.PAM_CERT_DB_PATH) + + # Valid certificate with user + cert_file = cert_path + "/SSSD_test_cert_x509_0001.pem" + with open(cert_file, "r") as f: + cert = f.read() + res = users_iface.FindByValidCertificate(cert) + assert res == "/org/freedesktop/sssd/infopipe/Users/app/user1_40app" + + # Valid certificate without user + cert_file = cert_path + "/SSSD_test_cert_x509_0002.pem" + with open(cert_file, "r") as f: + cert = f.read() + try: + res = users_iface.FindByValidCertificate(cert) + assert False, "Previous call should raise an exception" + except dbus.exceptions.DBusException as ex: + assert str(ex) == "sbus.Error.NotFound: No such file or directory" + + # Valid certificate from another CA + cert_file = os.environ['ABS_SRCDIR'] + \ + "/../test_ECC_CA/SSSD_test_ECC_cert_key_0001.pem" + with open(cert_file, "r") as f: + cert = f.read() + try: + res = users_iface.FindByValidCertificate(cert) + assert False, "Previous call should raise an exception" + except dbus.exceptions.DBusException as ex: + assert str(ex) == \ + "org.freedesktop.DBus.Error.IOError: Input/output error" + + # Invalid certificate + cert = "Invalid cert" + try: + res = users_iface.FindByValidCertificate(cert) + assert False, "Previous call should raise an exception" + except dbus.exceptions.DBusException as ex: + error = "org.freedesktop.DBus.Error.IOError: Input/output error" + assert str(ex) == error + + # Remove certificate db + cert_db = cert_path + "/SSSD_test_CA.pem" + os.remove(cert_db) + cert_file = cert_path + "/SSSD_test_cert_x509_0002.pem" + with open(cert_file, "r") as f: + cert = f.read() + try: + res = users_iface.FindByValidCertificate(cert) + assert False, "Previous call should raise an exception" + except dbus.exceptions.DBusException as ex: + assert str(ex) == \ + "sbus.Error.NoCA: Certificate authority file not found" + + +def test_list_by_attr(dbus_system_bus, ldap_conn, sanity_rfc2307): + users = get_user_by_attr(dbus_system_bus, "extraName", "user2") + assert len(users) == 2 + assert '/org/freedesktop/sssd/infopipe/Users/LDAP/1002' in users + assert '/org/freedesktop/sssd/infopipe/Users/app/user2_40app' in users + + users = get_user_by_attr(dbus_system_bus, "extraName", "user*") + assert len(users) == 6 + assert '/org/freedesktop/sssd/infopipe/Users/LDAP/1001' in users + assert '/org/freedesktop/sssd/infopipe/Users/LDAP/1002' in users + assert '/org/freedesktop/sssd/infopipe/Users/LDAP/1003' in users + assert '/org/freedesktop/sssd/infopipe/Users/app/user1_40app' in users + assert '/org/freedesktop/sssd/infopipe/Users/app/user2_40app' in users + assert '/org/freedesktop/sssd/infopipe/Users/app/user3_40app' in users + + users = get_user_by_attr(dbus_system_bus, "extraName", "nouser*") + assert len(users) == 0 + + users = get_user_by_attr(dbus_system_bus, "noattr", "*") + assert len(users) == 0 + + +def test_list_by_name(dbus_system_bus, ldap_conn, sanity_rfc2307): + users = get_user_by_name(dbus_system_bus, "user2") + assert len(users) == 2 + assert '/org/freedesktop/sssd/infopipe/Users/LDAP/1002' in users + assert '/org/freedesktop/sssd/infopipe/Users/app/user2_40app' in users + + users = get_user_by_name(dbus_system_bus, "user*") + assert len(users) == 6 + assert '/org/freedesktop/sssd/infopipe/Users/LDAP/1001' in users + assert '/org/freedesktop/sssd/infopipe/Users/LDAP/1002' in users + assert '/org/freedesktop/sssd/infopipe/Users/LDAP/1003' in users + assert '/org/freedesktop/sssd/infopipe/Users/app/user1_40app' in users + assert '/org/freedesktop/sssd/infopipe/Users/app/user2_40app' in users + assert '/org/freedesktop/sssd/infopipe/Users/app/user3_40app' in users + + users = get_user_by_name(dbus_system_bus, "nouser*") + assert len(users) == 0 diff --git a/src/tests/intg/test_kcm.py b/src/tests/intg/test_kcm.py new file mode 100644 index 0000000..370e2a9 --- /dev/null +++ b/src/tests/intg/test_kcm.py @@ -0,0 +1,559 @@ +# +# KCM responder integration tests +# +# Copyright (c) 2016 Red Hat, Inc. +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 os.path +import stat +import subprocess +import pytest +import socket +import time +import signal +import sys +from datetime import datetime + +import kdc +import krb5utils +import config +from util import unindent + +MAX_SECRETS = 10 + + +class KcmTestEnv(object): + def __init__(self, k5kdc, k5util): + self.k5kdc = k5kdc + self.k5util = k5util + self.counter = 0 + + def my_uid(self): + s_myuid = os.environ['NON_WRAPPED_UID'] + return int(s_myuid) + + def ccname(self, my_uid=None): + if my_uid is None: + my_uid = self.my_uid() + + return "KCM:%d" % my_uid + + +def have_kcm_renewal(): + return os.environ['KCM_RENEW'] == "enabled" + + +@pytest.fixture(scope="module") +def kdc_instance(request): + """Kerberos server instance fixture""" + kdc_instance = kdc.KDC(config.PREFIX, "KCMTEST") + try: + kdc_instance.set_up() + kdc_instance.start_kdc() + except Exception: + kdc_instance.teardown() + raise + request.addfinalizer(kdc_instance.teardown) + return kdc_instance + + +def create_conf_fixture(request, contents): + """Generate sssd.conf and add teardown for removing it""" + with open(config.CONF_PATH, "w") as conf: + conf.write(contents) + os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR) + request.addfinalizer(lambda: os.unlink(config.CONF_PATH)) + + +def create_sssd_kcm_fixture(sock_path, krb5_conf_path, request): + if subprocess.call(['sssd', "--genconf"]) != 0: + raise Exception("failed to regenerate confdb") + + resp_path = os.path.join(config.LIBEXEC_PATH, "sssd", "sssd_kcm") + if not os.access(resp_path, os.X_OK): + # It would be cleaner to use pytest.mark.skipif on the package level + # but upstream insists on supporting RHEL-6.. + pytest.skip("No KCM responder, skipping") + + kcm_pid = os.fork() + assert kcm_pid >= 0 + + if kcm_pid == 0: + my_env = os.environ.copy() + my_env["KRB5_CONFIG"] = krb5_conf_path + if subprocess.call([resp_path, "--uid=0", "--gid=0"], env=my_env) != 0: + print("sssd_kcm failed to start") + sys.exit(99) + else: + abs_sock_path = os.path.join(config.RUNSTATEDIR, sock_path) + sck = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + for _ in range(1, 100): + try: + sck.connect(abs_sock_path) + except Exception: + time.sleep(0.1) + else: + break + sck.close() + assert os.path.exists(abs_sock_path) + + def kcm_teardown(): + if kcm_pid == 0: + return + os.kill(kcm_pid, signal.SIGTERM) + try: + os.unlink(os.path.join(config.SECDB_PATH, "secrets.ldb")) + except OSError as osex: + if osex.errno == 2: + pass + + request.addfinalizer(kcm_teardown) + return kcm_pid + + +def create_sssd_conf(kcm_path, ccache_storage, max_secrets=MAX_SECRETS): + return unindent("""\ + [sssd] + domains = files + services = nss + + [domain/files] + id_provider = proxy + proxy_lib_name = files + + [kcm] + socket_path = {kcm_path} + ccache_storage = {ccache_storage} + """).format(**locals()) + + +def create_sssd_conf_renewals(kcm_path, ccache_storage, renew_lifetime, + lifetime, renew_interval, + max_secrets=MAX_SECRETS): + return unindent("""\ + [sssd] + domains = files + services = nss + + [domain/files] + id_provider = proxy + proxy_lib_name = files + + [kcm] + socket_path = {kcm_path} + ccache_storage = {ccache_storage} + tgt_renewal = true + krb5_renewable_lifetime = {renew_lifetime} + krb5_lifetime = {lifetime} + krb5_renew_interval = {renew_interval} + """).format(**locals()) + + +def common_setup_for_kcm_mem(request, kdc_instance, kcm_path, sssd_conf): + kcm_socket_include = unindent(""" + [libdefaults] + default_ccache_name = KCM: + kcm_socket = {kcm_path} + """).format(**locals()) + kdc_instance.add_config({'kcm_socket': kcm_socket_include}) + + create_conf_fixture(request, sssd_conf) + create_sssd_kcm_fixture(kcm_path, kdc_instance.krb5_conf_path, request) + + k5util = krb5utils.Krb5Utils(kdc_instance.krb5_conf_path) + + return KcmTestEnv(kdc_instance, k5util) + + +@pytest.fixture +def setup_for_kcm_mem(request, kdc_instance): + """ + Just set up the files provider for tests and enable the KCM + responder + """ + kcm_path = os.path.join(config.RUNSTATEDIR, "kcm.socket") + sssd_conf = create_sssd_conf(kcm_path, "memory") + return common_setup_for_kcm_mem(request, kdc_instance, kcm_path, sssd_conf) + + +@pytest.fixture +def setup_for_kcm_secdb(request, kdc_instance): + """ + Set up the KCM responder backed by libsss_secrets + """ + kcm_path = os.path.join(config.RUNSTATEDIR, "kcm.socket") + sssd_conf = create_sssd_conf(kcm_path, "secdb") + return common_setup_for_kcm_mem(request, kdc_instance, kcm_path, sssd_conf) + + +@pytest.fixture +def setup_for_kcm_renewals_secdb(passwd_ops_setup, request, kdc_instance): + """ + Set up the KCM renewals backed by libsss_secrets + """ + kcm_path = os.path.join(config.RUNSTATEDIR, "kcm.socket") + sssd_conf = create_sssd_conf_renewals(kcm_path, "secdb", + "10d", "60s", "10s") + + testenv = common_setup_for_kcm_mem(request, kdc_instance, kcm_path, sssd_conf) + + user = dict(name='user1', passwd='x', + uid=testenv.my_uid(), gid=testenv.my_uid(), + gecos='User for tests', + dir='/home/user1', + shell='/bin/bash') + + passwd_ops_setup.useradd(**user) + + return testenv + + +def kcm_init_list_destroy(testenv): + """ + Test that kinit, kdestroy and klist work with KCM + """ + testenv.k5kdc.add_principal("kcmtest", "Secret123") + + ok = testenv.k5util.has_principal("kcmtest@KCMTEST") + assert ok is False + nprincs = testenv.k5util.num_princs() + assert nprincs == 0 + + out, _, _ = testenv.k5util.kinit("kcmtest", "Secret123") + assert out == 0 + nprincs = testenv.k5util.num_princs() + assert nprincs == 1 + + exp_ccname = testenv.ccname() + ok = testenv.k5util.has_principal("kcmtest@KCMTEST", exp_ccname) + assert ok is True + + out = testenv.k5util.kdestroy() + assert out == 0 + + ok = testenv.k5util.has_principal("kcmtest@KCMTEST") + assert ok is False + nprincs = testenv.k5util.num_princs() + assert nprincs == 0 + + +def test_kcm_mem_init_list_destroy(setup_for_kcm_mem): + testenv = setup_for_kcm_mem + kcm_init_list_destroy(testenv) + + +def test_kcm_secdb_init_list_destroy(setup_for_kcm_secdb): + testenv = setup_for_kcm_secdb + kcm_init_list_destroy(testenv) + + +def kcm_overwrite(testenv): + """ + Test that reusing a ccache reinitializes the cache and doesn't + add the same principal twice + """ + testenv.k5kdc.add_principal("kcmtest", "Secret123") + exp_ccache = {'kcmtest@KCMTEST': ['krbtgt/KCMTEST@KCMTEST']} + + assert testenv.k5util.num_princs() == 0 + + out, _, _ = testenv.k5util.kinit("kcmtest", "Secret123") + assert out == 0 + assert exp_ccache == testenv.k5util.list_all_princs() + + out, _, _ = testenv.k5util.kinit("kcmtest", "Secret123") + assert out == 0 + assert exp_ccache == testenv.k5util.list_all_princs() + + +@pytest.mark.converted('test_kcm.py', 'test_kcm__kinit_overwrite') +def test_kcm_mem_overwrite(setup_for_kcm_mem): + testenv = setup_for_kcm_mem + kcm_overwrite(testenv) + + +@pytest.mark.converted('test_kcm.py', 'test_kcm__kinit_overwrite') +def test_kcm_secdb_overwrite(setup_for_kcm_secdb): + testenv = setup_for_kcm_secdb + kcm_overwrite(testenv) + + +def collection_init_list_destroy(testenv): + """ + Test that multiple principals and service tickets can be stored + in a collection. + """ + testenv.k5kdc.add_principal("alice", "alicepw") + testenv.k5kdc.add_principal("bob", "bobpw") + testenv.k5kdc.add_principal("carol", "carolpw") + testenv.k5kdc.add_principal("host/somehostname") + + assert testenv.k5util.num_princs() == 0 + + out, _, _ = testenv.k5util.kinit("alice", "alicepw") + assert out == 0 + assert testenv.k5util.default_principal() == 'alice@KCMTEST' + cc_coll = testenv.k5util.list_all_princs() + assert len(cc_coll) == 1 + assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST'] + assert 'bob@KCMTEST' not in cc_coll + assert 'carol@KCMTEST' not in cc_coll + + out, _, _ = testenv.k5util.kinit("bob", "bobpw") + assert out == 0 + assert testenv.k5util.default_principal() == 'bob@KCMTEST' + cc_coll = testenv.k5util.list_all_princs() + assert len(cc_coll) == 2 + assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST'] + assert cc_coll['bob@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST'] + assert 'carol@KCMTEST' not in cc_coll + + out, _, _ = testenv.k5util.kinit("carol", "carolpw") + assert out == 0 + assert testenv.k5util.default_principal() == 'carol@KCMTEST' + cc_coll = testenv.k5util.list_all_princs() + assert len(cc_coll) == 3 + assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST'] + assert cc_coll['bob@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST'] + assert cc_coll['carol@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST'] + + out, _, _ = testenv.k5util.kvno('host/somehostname') + assert out == 0 + cc_coll = testenv.k5util.list_all_princs() + assert len(cc_coll) == 3 + assert set(cc_coll['carol@KCMTEST']) == set(['krbtgt/KCMTEST@KCMTEST', + 'host/somehostname@KCMTEST']) + + out = testenv.k5util.kdestroy() + assert out == 0 + # If the default is removed, KCM just uses whetever is the first entry + # in the collection as the default. And sine the KCM back ends don't + # guarantee if they are FIFO or LIFO, just check for either alice or bob + assert testenv.k5util.default_principal() in \ + ['alice@KCMTEST', 'bob@KCMTEST'] + cc_coll = testenv.k5util.list_all_princs() + assert len(cc_coll) == 2 + assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST'] + assert cc_coll['bob@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST'] + assert 'carol@KCMTEST' not in cc_coll + + # Let's kinit a 3rd principal + out, _, _ = testenv.k5util.kinit("carol", "carolpw") + assert out == 0 + cc_coll = testenv.k5util.list_all_princs() + assert len(cc_coll) == 3 + assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST'] + assert cc_coll['bob@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST'] + assert cc_coll['carol@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST'] + + # Let's ensure `kdestroy -A` works with more than 2 principals + # https://github.com/SSSD/sssd/issues/4440 + out = testenv.k5util.kdestroy(all_ccaches=True) + assert out == 0 + assert testenv.k5util.num_princs() == 0 + + +@pytest.mark.converted('test_kcm.py', 'test_kcm__kinit_collection') +def test_kcm_mem_collection_init_list_destroy(setup_for_kcm_mem): + testenv = setup_for_kcm_mem + collection_init_list_destroy(testenv) + + +@pytest.mark.converted('test_kcm.py', 'test_kcm__kinit_collection') +def test_kcm_secdb_collection_init_list_destroy(setup_for_kcm_secdb): + testenv = setup_for_kcm_secdb + collection_init_list_destroy(testenv) + + +def exercise_kswitch(testenv): + """ + Test switching between principals + """ + testenv.k5kdc.add_principal("alice", "alicepw") + testenv.k5kdc.add_principal("bob", "bobpw") + testenv.k5kdc.add_principal("host/somehostname") + testenv.k5kdc.add_principal("host/differenthostname") + + out, _, _ = testenv.k5util.kinit("alice", "alicepw") + assert out == 0 + assert testenv.k5util.default_principal() == 'alice@KCMTEST' + + out, _, _ = testenv.k5util.kinit("bob", "bobpw") + assert out == 0 + assert testenv.k5util.default_principal() == 'bob@KCMTEST' + + cc_coll = testenv.k5util.list_all_princs() + assert len(cc_coll) == 2 + assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST'] + assert cc_coll['bob@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST'] + + out = testenv.k5util.kswitch("alice@KCMTEST") + assert testenv.k5util.default_principal() == 'alice@KCMTEST' + out, _, _ = testenv.k5util.kvno('host/somehostname') + assert out == 0 + cc_coll = testenv.k5util.list_all_princs() + assert len(cc_coll) == 2 + assert set(cc_coll['alice@KCMTEST']) == set(['krbtgt/KCMTEST@KCMTEST', + 'host/somehostname@KCMTEST']) + assert cc_coll['bob@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST'] + + out = testenv.k5util.kswitch("bob@KCMTEST") + assert testenv.k5util.default_principal() == 'bob@KCMTEST' + out, _, _ = testenv.k5util.kvno('host/differenthostname') + assert out == 0 + cc_coll = testenv.k5util.list_all_princs() + assert len(cc_coll) == 2 + assert set(cc_coll['alice@KCMTEST']) == set(['krbtgt/KCMTEST@KCMTEST', + 'host/somehostname@KCMTEST']) + assert set(cc_coll['bob@KCMTEST']) == set(['krbtgt/KCMTEST@KCMTEST', + 'host/differenthostname@KCMTEST']) + + +@pytest.mark.converted('test_kcm.py', 'test_kcm__kinit_switch') +def test_kcm_mem_kswitch(setup_for_kcm_mem): + testenv = setup_for_kcm_mem + exercise_kswitch(testenv) + + +@pytest.mark.converted('test_kcm.py', 'test_kcm__kinit_switch') +def test_kcm_secdb_kswitch(setup_for_kcm_secdb): + testenv = setup_for_kcm_secdb + exercise_kswitch(testenv) + + +def exercise_subsidiaries(testenv): + """ + Test that subsidiary caches are usable and KCM: without specifying UID + can be used to identify the collection + """ + testenv.k5kdc.add_principal("alice", "alicepw") + testenv.k5kdc.add_principal("bob", "bobpw") + testenv.k5kdc.add_principal("host/somehostname") + testenv.k5kdc.add_principal("host/differenthostname") + + out, _, _ = testenv.k5util.kinit("alice", "alicepw") + assert out == 0 + out, _, _ = testenv.k5util.kvno('host/somehostname') + + out, _, _ = testenv.k5util.kinit("bob", "bobpw") + assert out == 0 + out, _, _ = testenv.k5util.kvno('host/differenthostname') + + exp_cc_coll = dict() + exp_cc_coll['alice@KCMTEST'] = 'host/somehostname@KCMTEST' + exp_cc_coll['bob@KCMTEST'] = 'host/differenthostname@KCMTEST' + + klist_l = testenv.k5util.list_princs() + princ_ccache = dict() + for line in klist_l: + princ, subsidiary = line.split() + princ_ccache[princ] = subsidiary + + for princ, subsidiary in princ_ccache.items(): + env = {'KRB5CCNAME': subsidiary} + cc_coll = testenv.k5util.list_all_princs(env=env) + assert len(cc_coll) == 1 + assert princ in cc_coll + assert exp_cc_coll[princ] in cc_coll[princ] + + cc_coll = testenv.k5util.list_all_princs(env={'KRB5CCNAME': 'KCM:'}) + assert len(cc_coll) == 2 + assert set(cc_coll['alice@KCMTEST']) == set(['krbtgt/KCMTEST@KCMTEST', + 'host/somehostname@KCMTEST']) + assert set(cc_coll['bob@KCMTEST']) == set(['krbtgt/KCMTEST@KCMTEST', + 'host/differenthostname@KCMTEST']) + + +@pytest.mark.converted('test_kcm.py', 'test_kcm__subsidiaries') +def test_kcm_mem_subsidiaries(setup_for_kcm_mem): + testenv = setup_for_kcm_mem + exercise_subsidiaries(testenv) + + +@pytest.mark.converted('test_kcm.py', 'test_kcm__subsidiaries') +def test_kcm_secdb_subsidiaries(setup_for_kcm_secdb): + testenv = setup_for_kcm_secdb + exercise_subsidiaries(testenv) + + +def kdestroy_nocache(testenv): + """ + Destroying a non-existing ccache should not throw an error + """ + testenv.k5kdc.add_principal("alice", "alicepw") + out, _, _ = testenv.k5util.kinit("alice", "alicepw") + assert out == 0 + + testenv.k5util.kdestroy() + assert out == 0 + out = testenv.k5util.kdestroy() + assert out == 0 + + +@pytest.mark.converted('test_kcm.py', 'test_kcm__kdestroy_nocache') +def test_kcm_mem_kdestroy_nocache(setup_for_kcm_mem): + testenv = setup_for_kcm_mem + exercise_subsidiaries(testenv) + + +@pytest.mark.converted('test_kcm.py', 'test_kcm__kdestroy_nocache') +def test_kcm_secdb_kdestroy_nocache(setup_for_kcm_secdb): + testenv = setup_for_kcm_secdb + exercise_subsidiaries(testenv) + + +def get_secrets_socket(): + return os.path.join(config.RUNSTATEDIR, "secrets.socket") + + +@pytest.mark.converted('test_kcm.py', 'test_kcm__tgt_renewal') +@pytest.mark.skipif(not have_kcm_renewal(), + reason="KCM renewal disabled, skipping") +def test_kcm_renewals(setup_for_kcm_renewals_secdb): + """ + Test that basic KCM renewal works + """ + if "LC_TIME" in os.environ: + del os.environ["LC_TIME"] + testenv = setup_for_kcm_renewals_secdb + testenv.k5kdc.add_principal("user1", "Secret123") + + ok = testenv.k5util.has_principal("user1@KCMTEST") + assert ok is False + nprincs = testenv.k5util.num_princs() + assert nprincs == 0 + + # Renewal is only performed after half of lifetime exceeded, + # see kcm_renew_all_tgts() + options = ["-r", "15s", "-l", "15s"] + out, _, _ = testenv.k5util.kinit("user1", "Secret123", options) + assert out == 0 + nprincs = testenv.k5util.num_princs() + assert nprincs == 1 + + timestr_fmt = "%m/%d/%y %H:%M:%S" + initial_times = testenv.k5util.list_times() + + # Wait for renewal to trigger once, after renew interval + time.sleep(15) + + renewed_times = testenv.k5util.list_times() + + init_times = initial_times.split()[0] + ' ' + initial_times.split()[1] + renew_times = renewed_times.split()[0] + ' ' + renewed_times.split()[1] + dt_init = datetime.strptime(init_times, timestr_fmt) + dt_renew = datetime.strptime(renew_times, timestr_fmt) + assert dt_renew > dt_init diff --git a/src/tests/intg/test_ldap.py b/src/tests/intg/test_ldap.py new file mode 100644 index 0000000..81c4f31 --- /dev/null +++ b/src/tests/intg/test_ldap.py @@ -0,0 +1,2106 @@ +# +# LDAP integration test +# +# Copyright (c) 2015 Red Hat, Inc. +# Author: Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 stat +import pwd +import grp +import signal +import subprocess +import time +import ldap +import ldap.modlist +import pytest + +import config +import ds_openldap +import ent +import ldap_ent +import sssd_id +import sssd_ldb +from util import unindent +from sssd_nss import NssReturnCode +from sssd_passwd import call_sssd_getpwnam, call_sssd_getpwuid +from sssd_group import call_sssd_getgrnam, call_sssd_getgrgid + +LDAP_BASE_DN = "dc=example,dc=com" +INTERACTIVE_TIMEOUT = 4 + + +@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 Exception: + ds_inst.teardown() + raise + request.addfinalizer(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(ldap_conn.unbind_s) + return ldap_conn + + +def create_ldap_entries(ldap_conn, ent_list=None): + """Add LDAP entries from ent_list""" + if ent_list is not None: + for entry in ent_list: + ldap_conn.add_s(entry[0], entry[1]) + + +def cleanup_ldap_entries(ldap_conn, ent_list=None): + """Remove LDAP entries added by create_ldap_entries""" + if ent_list is None: + for ou in ("Users", "Groups", "Netgroups", "Services", "Policies"): + for entry in ldap_conn.search_s(f"ou={ou}," + f"{ldap_conn.ds_inst.base_dn}", + ldap.SCOPE_ONELEVEL, + attrlist=[]): + ldap_conn.delete_s(entry[0]) + else: + for entry in ent_list: + ldap_conn.delete_s(entry[0]) + + +def create_ldap_cleanup(request, ldap_conn, ent_list=None): + """Add teardown for removing all user/group LDAP entries""" + request.addfinalizer(lambda: cleanup_ldap_entries(ldap_conn, ent_list)) + + +def create_ldap_fixture(request, ldap_conn, ent_list=None, cleanup=True): + """Add LDAP entries and add teardown for removing them""" + create_ldap_entries(ldap_conn, ent_list) + if cleanup: + create_ldap_cleanup(request, ldap_conn, ent_list) + + +SCHEMA_RFC2307 = "rfc2307" +SCHEMA_RFC2307_BIS = "rfc2307bis" + + +def format_basic_conf(ldap_conn, schema): + """Format a basic SSSD configuration""" + schema_conf = "ldap_schema = " + schema + "\n" + if schema == SCHEMA_RFC2307_BIS: + schema_conf += "ldap_group_object_class = groupOfNames\n" + return unindent("""\ + [sssd] + debug_level = 0xffff + domains = LDAP + services = nss, pam + enable_files_domain = false + + [nss] + debug_level = 0xffff + memcache_timeout = 0 + entry_negative_timeout = 1 + + [pam] + debug_level = 0xffff + + [domain/LDAP] + ldap_auth_disable_tls_never_use_in_production = true + debug_level = 0xffff + {schema_conf} + id_provider = ldap + auth_provider = ldap + ldap_uri = {ldap_conn.ds_inst.ldap_url} + ldap_search_base = {ldap_conn.ds_inst.base_dn} + """).format(**locals()) + + +def format_interactive_conf(ldap_conn, schema): + """Format an SSSD configuration with all caches refreshing in 4 seconds""" + return \ + format_basic_conf(ldap_conn, schema) + \ + unindent(""" + [nss] + memcache_timeout = 0 + entry_negative_timeout = 0 + + [domain/LDAP] + ldap_purge_cache_timeout = 1 + entry_cache_timeout = {0} + """).format(INTERACTIVE_TIMEOUT) + + +def format_rfc2307bis_deref_conf(ldap_conn, schema): + """Format an SSSD configuration with all caches refreshing in 4 seconds""" + return \ + format_basic_conf(ldap_conn, schema) + \ + unindent(""" + [nss] + memcache_timeout = 0 + entry_negative_timeout = 0 + + [domain/LDAP] + entry_cache_timeout = {0} + ldap_deref_threshold = 1 + """).format(INTERACTIVE_TIMEOUT) + + +def create_conf_file(contents): + """Create sssd.conf with specified contents""" + conf = open(config.CONF_PATH, "w") + conf.write(contents) + conf.close() + os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR) + + +def cleanup_conf_file(): + """Remove sssd.conf, if it exists""" + if os.path.lexists(config.CONF_PATH): + os.unlink(config.CONF_PATH) + + +def create_conf_cleanup(request): + """Add teardown for removing sssd.conf""" + request.addfinalizer(cleanup_conf_file) + + +def create_conf_fixture(request, contents): + """ + Create sssd.conf with specified contents and add teardown for removing it + """ + create_conf_file(contents) + create_conf_cleanup(request) + + +def create_sssd_process(): + """Start the SSSD process""" + if subprocess.call(["sssd", "-D", "--logger=files"]) != 0: + raise Exception("sssd start failed") + + +def cleanup_sssd_process(): + """Stop the SSSD process and remove its state""" + try: + 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 OSError: + break + time.sleep(1) + except OSError: + 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) + + +def create_sssd_cleanup(request): + """Add teardown for stopping SSSD and removing its state""" + request.addfinalizer(cleanup_sssd_process) + + +def create_sssd_fixture(request): + """Start SSSD and add teardown for stopping it and removing its state""" + create_sssd_process() + create_sssd_cleanup(request) + + +@pytest.fixture +def sanity_rfc2307(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + ent_list.add_user("user2", 1002, 2002) + ent_list.add_user("user3", 1003, 2003) + + ent_list.add_group("group1", 2001) + ent_list.add_group("group2", 2002) + ent_list.add_group("group3", 2003) + + ent_list.add_group("empty_group", 2010) + + ent_list.add_group("two_user_group", 2012, ["user1", "user2"]) + + ent_list.add_user("t(u)ser", 5000, 5001) + ent_list.add_group("group(_u)ser1", 5001, ["t(u)ser"]) + create_ldap_fixture(request, ldap_conn, ent_list) + + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +@pytest.fixture +def simple_rfc2307(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user('usr\\\\001', 181818, 181818) + ent_list.add_group("group1", 181818) + create_ldap_fixture(request, ldap_conn, ent_list) + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +@pytest.fixture +def sanity_rfc2307_bis(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + ent_list.add_user("user2", 1002, 2002) + ent_list.add_user("user3", 1003, 2003) + + ent_list.add_group_bis("group1", 2001) + ent_list.add_group_bis("group2", 2002) + ent_list.add_group_bis("group3", 2003) + + ent_list.add_group_bis("empty_group1", 2010) + ent_list.add_group_bis("empty_group2", 2011) + + ent_list.add_group_bis("two_user_group", 2012, ["user1", "user2"]) + ent_list.add_group_bis("group_empty_group", 2013, [], ["empty_group1"]) + ent_list.add_group_bis("group_two_empty_groups", 2014, + [], ["empty_group1", "empty_group2"]) + ent_list.add_group_bis("one_user_group1", 2015, ["user1"]) + ent_list.add_group_bis("one_user_group2", 2016, ["user2"]) + ent_list.add_group_bis("group_one_user_group", 2017, + [], ["one_user_group1"]) + ent_list.add_group_bis("group_two_user_group", 2018, + [], ["two_user_group"]) + ent_list.add_group_bis("group_two_one_user_groups", 2019, + [], ["one_user_group1", "one_user_group2"]) + + create_ldap_fixture(request, ldap_conn, ent_list) + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +@pytest.fixture +def member_with_different_cases_rfc2307_bis(request, ldap_conn): + """ + Create a group where the user DN values of the RFC2307bis member attribute + differ in case from the original DN of the user object. + """ + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + ent_list.add_user("user2", 1002, 2002) + + ent_list.add_group_bis("two_user_group", 2012, ["USER1", "uSeR2"]) + + create_ldap_fixture(request, ldap_conn, ent_list) + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def expected_list_to_name_dict(entries): + return dict((u["name"], u) for u in entries) + + +def test_regression_ticket2163(ldap_conn, simple_rfc2307): + ent.assert_passwd_by_name( + 'usr\\001', + dict(name='usr\\001', passwd='*', uid=181818, gid=181818, + gecos='181818', shell='/bin/bash')) + + +def test_sanity_rfc2307(ldap_conn, sanity_rfc2307): + passwd_pattern = expected_list_to_name_dict([ + dict(name='user1', passwd='*', uid=1001, gid=2001, gecos='1001', + dir='/home/user1', shell='/bin/bash'), + dict(name='user2', passwd='*', uid=1002, gid=2002, gecos='1002', + dir='/home/user2', shell='/bin/bash'), + dict(name='user3', passwd='*', uid=1003, gid=2003, gecos='1003', + dir='/home/user3', shell='/bin/bash') + ]) + ent.assert_each_passwd_by_name(passwd_pattern) + + group_pattern = expected_list_to_name_dict([ + dict(name='group1', passwd='*', gid=2001, mem=ent.contains_only()), + dict(name='group2', passwd='*', gid=2002, mem=ent.contains_only()), + dict(name='group3', passwd='*', gid=2003, mem=ent.contains_only()), + dict(name='empty_group', passwd='*', gid=2010, + mem=ent.contains_only()), + dict(name='two_user_group', passwd='*', gid=2012, + mem=ent.contains_only("user1", "user2")) + ]) + ent.assert_each_group_by_name(group_pattern) + + with pytest.raises(KeyError): + pwd.getpwnam("non_existent_user") + with pytest.raises(KeyError): + pwd.getpwuid(1) + with pytest.raises(KeyError): + grp.getgrnam("non_existent_group") + with pytest.raises(KeyError): + grp.getgrgid(1) + + +def test_sanity_rfc2307_bis(ldap_conn, sanity_rfc2307_bis): + passwd_pattern = expected_list_to_name_dict([ + dict(name='user1', passwd='*', uid=1001, gid=2001, gecos='1001', + dir='/home/user1', shell='/bin/bash'), + dict(name='user2', passwd='*', uid=1002, gid=2002, gecos='1002', + dir='/home/user2', shell='/bin/bash'), + dict(name='user3', passwd='*', uid=1003, gid=2003, gecos='1003', + dir='/home/user3', shell='/bin/bash') + ]) + ent.assert_each_passwd_by_name(passwd_pattern) + + group_pattern = expected_list_to_name_dict([ + dict(name='group1', passwd='*', gid=2001, mem=ent.contains_only()), + dict(name='group2', passwd='*', gid=2002, mem=ent.contains_only()), + dict(name='group3', passwd='*', gid=2003, mem=ent.contains_only()), + dict(name='empty_group1', passwd='*', gid=2010, + mem=ent.contains_only()), + dict(name='empty_group2', passwd='*', gid=2011, + mem=ent.contains_only()), + dict(name='two_user_group', passwd='*', gid=2012, + mem=ent.contains_only("user1", "user2")), + dict(name='group_empty_group', passwd='*', gid=2013, + mem=ent.contains_only()), + dict(name='group_two_empty_groups', passwd='*', gid=2014, + mem=ent.contains_only()), + dict(name='one_user_group1', passwd='*', gid=2015, + mem=ent.contains_only("user1")), + dict(name='one_user_group2', passwd='*', gid=2016, + mem=ent.contains_only("user2")), + dict(name='group_one_user_group', passwd='*', gid=2017, + mem=ent.contains_only("user1")), + dict(name='group_two_user_group', passwd='*', gid=2018, + mem=ent.contains_only("user1", "user2")), + dict(name='group_two_one_user_groups', passwd='*', gid=2019, + mem=ent.contains_only("user1", "user2")) + ]) + ent.assert_each_group_by_name(group_pattern) + + with pytest.raises(KeyError): + pwd.getpwnam("non_existent_user") + with pytest.raises(KeyError): + pwd.getpwuid(1) + with pytest.raises(KeyError): + grp.getgrnam("non_existent_group") + with pytest.raises(KeyError): + grp.getgrgid(1) + + +def test_member_with_different_cases_rfc2307_bis( + ldap_conn, + member_with_different_cases_rfc2307_bis): + """ + Regression test for https://bugzilla.redhat.com/show_bug.cgi?id=1817122 + Make sure that group members are added properly to the group even if the + user DN in the RFC2307bis member attribute differs in case from the + original DN of the user object. + """ + group_pattern = expected_list_to_name_dict([ + dict(name='two_user_group', passwd='*', gid=2012, + mem=ent.contains_only("user1", "user2")), + ]) + ent.assert_each_group_by_name(group_pattern) + + +@pytest.fixture +def refresh_after_cleanup_task(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + + ent_list.add_group_bis("group1", 2001, ["user1"]) + ent_list.add_group_bis("group2", 2002, [], ["group1"]) + + create_ldap_fixture(request, ldap_conn, ent_list) + + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + \ + unindent(""" + [domain/LDAP] + entry_cache_user_timeout = 1 + entry_cache_group_timeout = 5000 + ldap_purge_cache_timeout = 3 + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_refresh_after_cleanup_task(ldap_conn, refresh_after_cleanup_task): + """ + Regression test for ticket: + https://fedorahosted.org/sssd/ticket/2676 + """ + ent.assert_group_by_name( + "group2", + dict(mem=ent.contains_only("user1"))) + + ent.assert_passwd_by_name( + 'user1', + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + + time.sleep(15) + + ent.assert_group_by_name( + "group2", + dict(mem=ent.contains_only("user1"))) + + +@pytest.fixture +def update_ts_after_cleanup_task(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + ent_list.add_user("user2", 1002, 2001) + + ent_list.add_group_bis("group1", 2001, ["user1", "user2"]) + + create_ldap_fixture(request, ldap_conn, ent_list) + + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + \ + unindent(""" + [domain/LDAP] + ldap_purge_cache_timeout = 3 + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_update_ts_cache_after_cleanup_task(ldap_conn, + update_ts_after_cleanup_task): + """ + Regression test for ticket: + https://fedorahosted.org/sssd/ticket/2676 + """ + ent.assert_group_by_name( + "group1", + dict(mem=ent.contains_only("user1", "user2"))) + + ent.assert_passwd_by_name( + 'user1', + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + + ent.assert_passwd_by_name( + 'user2', + dict(name='user2', passwd='*', uid=1002, gid=2001, + gecos='1002', shell='/bin/bash')) + + if subprocess.call(["sss_cache", "-u", "user1"]) != 0: + raise Exception("sssd_cache failed") + + # The cleanup task runs every 3 seconds, so sleep for 6 + # so that we know the cleanup task ran at least once + # even if we start sleeping during the first one + time.sleep(6) + + ent.assert_group_by_name( + "group1", + dict(mem=ent.contains_only("user1", "user2"))) + + +@pytest.fixture +def blank_rfc2307(request, ldap_conn): + """Create blank RFC2307 directory fixture with interactive SSSD conf""" + create_ldap_cleanup(request, ldap_conn) + create_conf_fixture(request, + format_interactive_conf(ldap_conn, SCHEMA_RFC2307)) + create_sssd_fixture(request) + + +@pytest.fixture +def blank_rfc2307_bis(request, ldap_conn): + """Create blank RFC2307bis directory fixture with interactive SSSD conf""" + create_ldap_cleanup(request, ldap_conn) + create_conf_fixture(request, + format_interactive_conf(ldap_conn, SCHEMA_RFC2307_BIS)) + create_sssd_fixture(request) + + +@pytest.fixture +def user_and_group_rfc2307(request, ldap_conn): + """ + Create an RFC2307 directory fixture with interactive SSSD conf, + one user and one group + """ + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user", 1001, 2000) + ent_list.add_group("group", 2001) + create_ldap_fixture(request, ldap_conn, ent_list) + create_conf_fixture(request, + format_interactive_conf(ldap_conn, SCHEMA_RFC2307)) + create_sssd_fixture(request) + return None + + +@pytest.fixture +def user_and_groups_rfc2307_bis(request, ldap_conn): + """ + Create an RFC2307bis directory fixture with interactive SSSD conf, + one user and two groups + """ + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user", 1001, 2000) + ent_list.add_group_bis("group1", 2001) + ent_list.add_group_bis("group2", 2002) + create_ldap_fixture(request, ldap_conn, ent_list) + create_conf_fixture(request, + format_interactive_conf(ldap_conn, SCHEMA_RFC2307_BIS)) + create_sssd_fixture(request) + return None + + +@pytest.fixture +def rfc2307bis_deref_group_with_users(request, ldap_conn): + """ + Create an RFC2307bis directory fixture with interactive SSSD conf, + one user and two groups + """ + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2000) + ent_list.add_user("user2", 1001, 2000) + ent_list.add_user("user3", 1001, 2000) + ent_list.add_group_bis("group1", 20000, member_uids=("user1", "user2")) + create_ldap_fixture(request, ldap_conn, ent_list) + create_conf_fixture(request, + format_rfc2307bis_deref_conf( + ldap_conn, + SCHEMA_RFC2307_BIS)) + create_sssd_fixture(request) + return None + + +def test_ldap_group_dereference(ldap_conn, rfc2307bis_deref_group_with_users): + ent.assert_group_by_name("group1", + dict(mem=ent.contains_only("user1", "user2"))) + + +@pytest.fixture +def override_homedir(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user_with_homedir_A", 1001, 2001, + homeDirectory="/home/A") + ent_list.add_user("user_with_homedir_B", 1002, 2002, + homeDirectory="/home/B") + ent_list.add_user("user_with_empty_homedir", 1003, 2003, + homeDirectory="") + create_ldap_fixture(request, ldap_conn, ent_list) + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [nss] + override_homedir = /home/B + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +def test_override_homedir(override_homedir): + """Test the effect of the "override_homedir" option""" + passwd_pattern = expected_list_to_name_dict([ + dict(name="user_with_homedir_A", uid=1001, dir="/home/B"), + dict(name="user_with_homedir_B", uid=1002, dir="/home/B"), + dict(name="user_with_empty_homedir", uid=1003, dir="/home/B") + ]) + + ent.assert_each_passwd_by_name(passwd_pattern) + + +@pytest.fixture +def fallback_homedir(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user_with_homedir_A", 1001, 2001, + homeDirectory="/home/A") + ent_list.add_user("user_with_homedir_B", 1002, 2002, + homeDirectory="/home/B") + ent_list.add_user("user_with_empty_homedir", 1003, 2003, + homeDirectory="") + create_ldap_fixture(request, ldap_conn, ent_list) + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [nss] + fallback_homedir = /home/B + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +def test_fallback_homedir(fallback_homedir): + """Test the effect of the "fallback_homedir" option""" + passwd_pattern = expected_list_to_name_dict([ + dict(name="user_with_homedir_A", uid=1001, dir="/home/A"), + dict(name="user_with_homedir_B", uid=1002, dir="/home/B"), + dict(name="user_with_empty_homedir", uid=1003, dir="/home/B") + ]) + + ent.assert_each_passwd_by_name(passwd_pattern) + + +@pytest.fixture +def override_shell(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user_with_shell_A", 1001, 2001, + loginShell="/bin/A") + ent_list.add_user("user_with_shell_B", 1002, 2002, + loginShell="/bin/B") + ent_list.add_user("user_with_empty_shell", 1003, 2003, + loginShell="") + create_ldap_fixture(request, ldap_conn, ent_list) + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [nss] + override_shell = /bin/B + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +def test_override_shell(override_shell): + """Test the effect of the "override_shell" option""" + passwd_pattern = expected_list_to_name_dict([ + dict(name="user_with_shell_A", uid=1001, shell="/bin/B"), + dict(name="user_with_shell_B", uid=1002, shell="/bin/B"), + dict(name="user_with_empty_shell", uid=1003, shell="/bin/B") + ]) + + ent.assert_each_passwd_by_name(passwd_pattern) + + +@pytest.fixture +def shell_fallback(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user_with_sh_shell", 1001, 2001, + loginShell="/bin/sh") + ent_list.add_user("user_with_not_installed_shell", 1002, 2002, + loginShell="/bin/not_installed") + ent_list.add_user("user_with_empty_shell", 1003, 2003, + loginShell="") + create_ldap_fixture(request, ldap_conn, ent_list) + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [nss] + shell_fallback = /bin/fallback + allowed_shells = /bin/not_installed + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +def test_shell_fallback(shell_fallback): + """Test the effect of the "shell_fallback" option""" + passwd_pattern = expected_list_to_name_dict([ + dict(name="user_with_sh_shell", uid=1001, shell="/bin/sh"), + dict(name="user_with_not_installed_shell", uid=1002, + shell="/bin/fallback"), + dict(name="user_with_empty_shell", uid=1003, shell="") + ]) + + ent.assert_each_passwd_by_name(passwd_pattern) + + +@pytest.fixture +def default_shell(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user_with_sh_shell", 1001, 2001, + loginShell="/bin/sh") + ent_list.add_user("user_with_not_installed_shell", 1002, 2002, + loginShell="/bin/not_installed") + ent_list.add_user("user_with_empty_shell", 1003, 2003, + loginShell="") + create_ldap_fixture(request, ldap_conn, ent_list) + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [nss] + default_shell = /bin/default + allowed_shells = /bin/default, /bin/not_installed + shell_fallback = /bin/fallback + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +def test_default_shell(default_shell): + """Test the effect of the "default_shell" option""" + passwd_pattern = expected_list_to_name_dict([ + dict(name="user_with_sh_shell", uid=1001, shell="/bin/sh"), + dict(name="user_with_not_installed_shell", uid=1002, + shell="/bin/fallback"), + dict(name="user_with_empty_shell", uid=1003, + shell="/bin/default") + ]) + + ent.assert_each_passwd_by_name(passwd_pattern) + + +@pytest.fixture +def vetoed_shells(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user_with_sh_shell", 1001, 2001, + loginShell="/bin/sh") + ent_list.add_user("user_with_vetoed_shell", 1002, 2002, + loginShell="/bin/vetoed") + ent_list.add_user("user_with_empty_shell", 1003, 2003, + loginShell="") + create_ldap_fixture(request, ldap_conn, ent_list) + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [nss] + default_shell = /bin/default + vetoed_shells = /bin/vetoed + shell_fallback = /bin/fallback + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +def test_vetoed_shells(vetoed_shells): + """Test the effect of the "vetoed_shells" option""" + passwd_pattern = expected_list_to_name_dict([ + dict(name="user_with_sh_shell", uid=1001, shell="/bin/sh"), + dict(name="user_with_vetoed_shell", uid=1002, + shell="/bin/fallback"), + dict(name="user_with_empty_shell", uid=1003, + shell="/bin/default") + ]) + + ent.assert_each_passwd_by_name(passwd_pattern) + + +def test_user_2307bis_nested_groups(ldap_conn, + sanity_rfc2307_bis): + """ + Test nested groups. + + Regression test for ticket: + https://fedorahosted.org/sssd/ticket/3093 + """ + primary_gid = 2001 + # group1, two_user_group, one_user_group1, group_one_user_group, + # group_two_user_group, group_two_one_user_groups + expected_gids = [2001, 2012, 2015, 2017, 2018, 2019] + + ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001, + gid=primary_gid)) + + (res, errno, gids) = sssd_id.call_sssd_initgroups("user1", primary_gid) + assert res == sssd_id.NssReturnCode.SUCCESS + + assert sorted(gids) == sorted(expected_gids), \ + "result: %s\n expected %s" % ( + ", ".join(["%s" % s for s in sorted(gids)]), + ", ".join(["%s" % s for s in sorted(expected_gids)]) + ) + + +def test_special_characters_in_names(ldap_conn, sanity_rfc2307): + """ + Test special characters which could cause malformed filter + in ldb_seach. + + Regression test for ticket: + https://fedorahosted.org/sssd/ticket/3121 + """ + ent.assert_passwd_by_name( + "t(u)ser", + dict(name="t(u)ser", passwd="*", uid=5000, gid=5001, + gecos="5000", shell="/bin/bash")) + + ent.assert_group_by_name( + "group(_u)ser1", + dict(name="group(_u)ser1", passwd="*", gid=5001, + mem=ent.contains_only("t(u)ser"))) + + +@pytest.fixture +def extra_attributes(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user", 2001, 2000) + ent_list.add_group("group", 2000) + create_ldap_fixture(request, ldap_conn, ent_list) + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [domain/LDAP] + ldap_user_extra_attrs = mail, name:uid, givenName + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +def test_extra_attribute_already_exists(ldap_conn, extra_attributes): + """Test the effect of the "vetoed_shells" option""" + + user = 'user' + extra_attribute = 'givenName' + given_name = b'unix_user' + + user_dn = "uid=" + user + ",ou=Users," + ldap_conn.ds_inst.base_dn + + old = {'objectClass': [b'top', b'inetOrgPerson', b'posixAccount']} + new = {'objectClass': [b'top', b'inetOrgPerson', b'posixAccount', + b'extensibleObject']} + ldif = ldap.modlist.modifyModlist(old, new) + + ldap_conn.modify_s(user_dn, ldif) + ldap_conn.modify_s(user_dn, [(ldap.MOD_ADD, extra_attribute, given_name)]) + + ent.assert_passwd_by_name( + user, + dict(name="user", uid=2001, gid=2000, shell="/bin/bash"), + ) + + domain = 'LDAP' + ldb_conn = sssd_ldb.SssdLdb('LDAP') + val = ldb_conn.get_entry_attr(sssd_ldb.CacheType.sysdb, + sssd_ldb.TsCacheEntry.user, + user, domain, extra_attribute) + + assert val == given_name + + +@pytest.fixture +def add_user_to_group(request, ldap_conn): + """ + Adding user to group + """ + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + ent_list.add_group_bis("group1", 20001, member_uids=["user1"]) + create_ldap_fixture(request, ldap_conn, ent_list) + create_conf_fixture(request, + format_rfc2307bis_deref_conf( + ldap_conn, + SCHEMA_RFC2307_BIS)) + create_sssd_fixture(request) + return None + + +def test_add_user_to_group(ldap_conn, add_user_to_group): + ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001, gid=2001)) + ent.assert_group_by_name("group1", dict(mem=ent.contains_only("user1"))) + + +@pytest.fixture +def remove_user_from_group(request, ldap_conn): + """ + Adding user to group + """ + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + ent_list.add_user("user2", 1002, 2002) + ent_list.add_group_bis("group1", 20001, member_uids=["user1", "user2"]) + create_ldap_fixture(request, ldap_conn, ent_list) + create_conf_fixture(request, + format_rfc2307bis_deref_conf( + ldap_conn, + SCHEMA_RFC2307_BIS)) + create_sssd_fixture(request) + return None + + +def test_remove_user_from_group(ldap_conn, remove_user_from_group): + """ + Removing two users from group, step by step + """ + group1_dn = 'cn=group1,ou=Groups,' + ldap_conn.ds_inst.base_dn + + ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001, gid=2001)) + ent.assert_passwd_by_name("user2", dict(name="user2", uid=1002, gid=2002)) + ent.assert_group_by_name("group1", + dict(mem=ent.contains_only("user1", "user2"))) + + # removing of user2 from group1 + old = {'member': [b"uid=user1,ou=Users,dc=example,dc=com", + b"uid=user2,ou=Users,dc=example,dc=com"]} + new = {'member': [b"uid=user1,ou=Users,dc=example,dc=com"]} + + ldif = ldap.modlist.modifyModlist(old, new) + ldap_conn.modify_s(group1_dn, ldif) + + if subprocess.call(["sss_cache", "-GU"]) != 0: + raise Exception("sssd_cache failed") + + ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001, gid=2001)) + ent.assert_passwd_by_name("user2", dict(name="user2", uid=1002, gid=2002)) + ent.assert_group_by_name("group1", dict(mem=ent.contains_only("user1"))) + + # removing of user1 from group1 + old = {'member': [b"uid=user1,ou=Users,dc=example,dc=com"]} + new = {'member': []} + + ldif = ldap.modlist.modifyModlist(old, new) + ldap_conn.modify_s(group1_dn, ldif) + + if subprocess.call(["sss_cache", "-GU"]) != 0: + raise Exception("sssd_cache failed") + + ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001, gid=2001)) + ent.assert_passwd_by_name("user2", dict(name="user2", uid=1002, gid=2002)) + ent.assert_group_by_name("group1", dict(mem=ent.contains_only())) + + +@pytest.fixture +def remove_user_from_nested_group(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + ent_list.add_user("user2", 1002, 2002) + ent_list.add_group_bis("group1", 20001, member_uids=["user1"]) + ent_list.add_group_bis("group2", 20002, member_uids=["user2"]) + ent_list.add_group_bis("group3", 20003, member_gids=["group1", "group2"]) + create_ldap_fixture(request, ldap_conn, ent_list) + create_conf_fixture(request, + format_rfc2307bis_deref_conf( + ldap_conn, + SCHEMA_RFC2307_BIS)) + create_sssd_fixture(request) + return None + + +def test_remove_user_from_nested_group(ldap_conn, + remove_user_from_nested_group): + + group3_dn = 'cn=group3,ou=Groups,' + ldap_conn.ds_inst.base_dn + + ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001, gid=2001)) + ent.assert_passwd_by_name("user2", dict(name="user2", uid=1002, gid=2002)) + + ent.assert_group_by_name("group1", + dict(mem=ent.contains_only("user1"))) + ent.assert_group_by_name("group2", + dict(mem=ent.contains_only("user2"))) + + ent.assert_group_by_name("group3", + dict(mem=ent.contains_only("user1", + "user2"))) + + # removing of group2 from group3 + old = {'member': [b"cn=group1,ou=Groups,dc=example,dc=com", + b"cn=group2,ou=Groups,dc=example,dc=com"]} + new = {'member': [b"cn=group1,ou=Groups,dc=example,dc=com"]} + + ldif = ldap.modlist.modifyModlist(old, new) + ldap_conn.modify_s(group3_dn, ldif) + + if subprocess.call(["sss_cache", "-GU"]) != 0: + raise Exception("sssd_cache failed") + + ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001, gid=2001)) + ent.assert_passwd_by_name("user2", dict(name="user2", uid=1002, gid=2002)) + + ent.assert_group_by_name("group1", + dict(mem=ent.contains_only("user1"))) + ent.assert_group_by_name("group2", + dict(mem=ent.contains_only("user2"))) + ent.assert_group_by_name("group3", + dict(mem=ent.contains_only("user1"))) + + # removing of group1 from group3 + old = {'member': [b"cn=group1,ou=Groups,dc=example,dc=com"]} + new = {'member': []} + + ldif = ldap.modlist.modifyModlist(old, new) + ldap_conn.modify_s(group3_dn, ldif) + + if subprocess.call(["sss_cache", "-GU"]) != 0: + raise Exception("sssd_cache failed") + + ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001, gid=2001)) + ent.assert_passwd_by_name("user2", dict(name="user2", uid=1002, gid=2002)) + + ent.assert_group_by_name("group1", + dict(mem=ent.contains_only("user1"))) + ent.assert_group_by_name("group2", + dict(mem=ent.contains_only("user2"))) + ent.assert_group_by_name("group3", + dict(mem=ent.contains_only())) + + +def zero_nesting_sssd_conf(ldap_conn, schema): + """Format an SSSD configuration with group nesting disabled""" + return \ + format_basic_conf(ldap_conn, schema) + \ + unindent(""" + [domain/LDAP] + ldap_group_nesting_level = 0 + """).format(INTERACTIVE_TIMEOUT) + + +@pytest.fixture +def rfc2307bis_no_nesting(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + ent_list.add_group_bis("primarygroup", 2001) + ent_list.add_group_bis("parentgroup", 2010, member_uids=["user1"]) + ent_list.add_group_bis("nestedgroup", 2011, member_gids=["parentgroup"]) + create_ldap_fixture(request, ldap_conn, ent_list) + create_conf_fixture(request, + zero_nesting_sssd_conf( + ldap_conn, + SCHEMA_RFC2307_BIS)) + create_sssd_fixture(request) + return None + + +def test_zero_nesting_level(ldap_conn, rfc2307bis_no_nesting): + """ + Test initgroups operation with rfc2307bis schema asserting + only primary group and parent groups are included in group + list. No parent groups of groups should be returned with zero + group nesting level. + """ + ent.assert_group_by_name("parentgroup", + dict(mem=ent.contains_only("user1"))) + ent.assert_group_by_name("nestedgroup", + dict(mem=ent.contains_only())) + + (res, errno, grp_list) = sssd_id.get_user_groups("user1") + assert res == sssd_id.NssReturnCode.SUCCESS, \ + "Could not find groups for user1, %d" % errno + + # test nestedgroup is not returned in group list + assert sorted(grp_list) == sorted(["primarygroup", "parentgroup"]) + + +@pytest.fixture +def sanity_nss_filter(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + ent_list.add_user("user2", 1002, 2002) + ent_list.add_user("user3", 1003, 2003) + + ent_list.add_group_bis("group1", 2001) + ent_list.add_group_bis("group2", 2002) + ent_list.add_group_bis("group3", 2003) + + ent_list.add_group_bis("empty_group1", 2010) + ent_list.add_group_bis("empty_group2", 2011) + + ent_list.add_group_bis("two_user_group", 2012, ["user1", "user2"]) + ent_list.add_group_bis("group_empty_group", 2013, [], ["empty_group1"]) + ent_list.add_group_bis("group_two_empty_groups", 2014, + [], ["empty_group1", "empty_group2"]) + ent_list.add_group_bis("one_user_group1", 2015, ["user1"]) + ent_list.add_group_bis("one_user_group2", 2016, ["user2"]) + ent_list.add_group_bis("group_one_user_group", 2017, + [], ["one_user_group1"]) + ent_list.add_group_bis("group_two_user_group", 2018, + [], ["two_user_group"]) + ent_list.add_group_bis("group_two_one_user_groups", 2019, + [], ["one_user_group1", "one_user_group2"]) + + create_ldap_fixture(request, ldap_conn, ent_list) + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + \ + unindent(""" + [nss] + filter_users = user2 + filter_groups = group_two_one_user_groups + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_nss_filters(ldap_conn, sanity_nss_filter): + passwd_pattern = expected_list_to_name_dict([ + dict(name='user1', passwd='*', uid=1001, gid=2001, gecos='1001', + dir='/home/user1', shell='/bin/bash'), + dict(name='user3', passwd='*', uid=1003, gid=2003, gecos='1003', + dir='/home/user3', shell='/bin/bash') + ]) + + # test filtered user + ent.assert_each_passwd_by_name(passwd_pattern) + with pytest.raises(KeyError): + pwd.getpwnam("user2") + with pytest.raises(KeyError): + pwd.getpwuid(1002) + + group_pattern = expected_list_to_name_dict([ + dict(name='group1', passwd='*', gid=2001, mem=ent.contains_only()), + dict(name='group2', passwd='*', gid=2002, mem=ent.contains_only()), + dict(name='group3', passwd='*', gid=2003, mem=ent.contains_only()), + dict(name='empty_group1', passwd='*', gid=2010, + mem=ent.contains_only()), + dict(name='empty_group2', passwd='*', gid=2011, + mem=ent.contains_only()), + dict(name='two_user_group', passwd='*', gid=2012, + mem=ent.contains_only("user1")), + dict(name='group_empty_group', passwd='*', gid=2013, + mem=ent.contains_only()), + dict(name='group_two_empty_groups', passwd='*', gid=2014, + mem=ent.contains_only()), + dict(name='one_user_group1', passwd='*', gid=2015, + mem=ent.contains_only("user1")), + dict(name='one_user_group2', passwd='*', gid=2016, + mem=ent.contains_only()), + dict(name='group_one_user_group', passwd='*', gid=2017, + mem=ent.contains_only("user1")), + dict(name='group_two_user_group', passwd='*', gid=2018, + mem=ent.contains_only("user1")), + ]) + + # test filtered group + ent.assert_each_group_by_name(group_pattern) + with pytest.raises(KeyError): + grp.getgrnam("group_two_one_user_groups") + with pytest.raises(KeyError): + grp.getgrgid(2019) + + # test non-existing user/group + with pytest.raises(KeyError): + pwd.getpwnam("non_existent_user") + with pytest.raises(KeyError): + pwd.getpwuid(9) + with pytest.raises(KeyError): + grp.getgrnam("non_existent_group") + with pytest.raises(KeyError): + grp.getgrgid(14) + + # test initgroups - user1 is member of group_two_one_user_groups (2019) + # which is filtered out + (res, errno, gids) = sssd_id.call_sssd_initgroups("user1", 2001) + assert res == sssd_id.NssReturnCode.SUCCESS + + user_with_group_ids = [2001, 2012, 2015, 2017, 2018] + assert sorted(gids) == sorted(user_with_group_ids), \ + "result: %s\n expected %s" % ( + ", ".join(["%s" % s for s in sorted(gids)]), + ", ".join(["%s" % s for s in sorted(user_with_group_ids)]) + ) + + +@pytest.fixture +def sanity_nss_filter_cached(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + ent_list.add_user("user2", 1002, 2002) + ent_list.add_user("user3", 1003, 2003) + ent_list.add_user("root", 1004, 2004) + ent_list.add_user("zerouid", 0, 0) + + ent_list.add_group_bis("group1", 2001) + ent_list.add_group_bis("group2", 2002) + ent_list.add_group_bis("group3", 2003) + ent_list.add_group_bis("root", 2004) + ent_list.add_group_bis("zerogid", 0) + + create_ldap_fixture(request, ldap_conn, ent_list) + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + \ + unindent(""" + [nss] + filter_users = user2 + filter_groups = group2 + entry_negative_timeout = 1 + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_nss_filters_cached(ldap_conn, sanity_nss_filter_cached): + passwd_pattern = expected_list_to_name_dict([ + dict(name='user1', passwd='*', uid=1001, gid=2001, gecos='1001', + dir='/home/user1', shell='/bin/bash'), + dict(name='user3', passwd='*', uid=1003, gid=2003, gecos='1003', + dir='/home/user3', shell='/bin/bash') + ]) + ent.assert_each_passwd_by_name(passwd_pattern) + + # test filtered user + with pytest.raises(KeyError): + pwd.getpwuid(1002) + time.sleep(2) + with pytest.raises(KeyError): + pwd.getpwuid(1002) + + group_pattern = expected_list_to_name_dict([ + dict(name='group1', passwd='*', gid=2001, mem=ent.contains_only()), + dict(name='group3', passwd='*', gid=2003, mem=ent.contains_only()), + ]) + ent.assert_each_group_by_name(group_pattern) + + # test filtered group + with pytest.raises(KeyError): + grp.getgrgid(2002) + time.sleep(2) + with pytest.raises(KeyError): + grp.getgrgid(2002) + + # test that root is always filtered even if filter_users contains other + # entries. This is a regression test for upstream ticket #3460 + res, _ = call_sssd_getpwnam("root") + assert res == NssReturnCode.NOTFOUND + + res, _ = call_sssd_getgrnam("root") + assert res == NssReturnCode.NOTFOUND + + res, _ = call_sssd_getpwuid(0) + assert res == NssReturnCode.NOTFOUND + + res, _ = call_sssd_getgrgid(0) + assert res == NssReturnCode.NOTFOUND + + +@pytest.fixture +def mpg_setup(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + ent_list.add_user("user2", 1002, 2002) + ent_list.add_user("user3", 1003, 2003) + + ent_list.add_group_bis("group1", 2001) + ent_list.add_group_bis("group2", 2002) + ent_list.add_group_bis("group3", 2003) + + ent_list.add_group_bis("two_user_group", 2012, ["user1", "user2"]) + ent_list.add_group_bis("one_user_group1", 2015, ["user1"]) + ent_list.add_group_bis("one_user_group2", 2016, ["user2"]) + + create_ldap_entries(ldap_conn, ent_list) + create_ldap_cleanup(request, ldap_conn, None) + + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + \ + unindent(""" + [domain/LDAP] + auto_private_groups = True + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_ldap_auto_private_groups_direct(ldap_conn, mpg_setup): + """ + Integration test for auto_private_groups + + See also ticket https://github.com/SSSD/sssd/issues/2914 + """ + # Make sure the user's GID is taken from their uidNumber + ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001, gid=1001)) + # Make sure the private group is resolvable by name and by GID + ent.assert_group_by_name("user1", dict(gid=1001, mem=ent.contains_only())) + ent.assert_group_by_gid(1001, dict(name="user1", mem=ent.contains_only())) + + # The group referenced in user's gidNumber attribute should be still + # visible, but it's fine that it doesn't contain the user as a member + # as the group is currently added during the initgroups operation only + ent.assert_group_by_name("group1", dict(gid=2001, mem=ent.contains_only())) + ent.assert_group_by_gid(2001, dict(name="group1", mem=ent.contains_only())) + + # The user's secondary groups list must be correct as well + # Note that the original GID is listed as well -- this is correct and + # expected because we save the original GID in the + # SYSDB_PRIMARY_GROUP_GIDNUM attribute + user1_expected_gids = [1001, 2001, 2012, 2015] + (res, errno, gids) = sssd_id.call_sssd_initgroups("user1", 1001) + assert res == sssd_id.NssReturnCode.SUCCESS + + assert sorted(gids) == sorted(user1_expected_gids), \ + "result: %s\n expected %s" % ( + ", ".join(["%s" % s for s in sorted(gids)]), + ", ".join(["%s" % s for s in sorted(user1_expected_gids)]) + ) + + # Request user2's private group by GID without resolving the user first. + # This must trigger user resolution through by-GID resolution, since the + # GID doesn't exist on its own in LDAP + ent.assert_group_by_gid(1002, dict(name="user2", mem=ent.contains_only())) + + # Test supplementary groups for user2 as well + user1_expected_gids = [1002, 2002, 2012, 2016] + (res, errno, gids) = sssd_id.call_sssd_initgroups("user2", 1002) + assert res == sssd_id.NssReturnCode.SUCCESS + + assert sorted(gids) == sorted(user1_expected_gids), \ + "result: %s\n expected %s" % ( + ", ".join(["%s" % s for s in sorted(gids)]), + ", ".join(["%s" % s for s in sorted(user1_expected_gids)]) + ) + + # Request user3's private group by name without resolving the user first + # This must trigger user resolution through by-name resolution, since the + # name doesn't exist on its own in LDAP + ent.assert_group_by_name("user3", dict(gid=1003, mem=ent.contains_only())) + + # Remove entries and request them again to make sure they are not + # resolvable anymore + cleanup_ldap_entries(ldap_conn, None) + + if subprocess.call(["sss_cache", "-GU"]) != 0: + raise Exception("sssd_cache failed") + + with pytest.raises(KeyError): + pwd.getpwnam("user1") + with pytest.raises(KeyError): + grp.getgrnam("user1") + with pytest.raises(KeyError): + grp.getgrgid(1002) + with pytest.raises(KeyError): + grp.getgrnam("user3") + + +@pytest.fixture +def mpg_setup_conflict(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + ent_list.add_user("user2", 1002, 2002) + ent_list.add_user("user3", 1003, 1003) + ent_list.add_group_bis("group1", 1001) + ent_list.add_group_bis("group2", 1002) + ent_list.add_group_bis("group3", 1003) + ent_list.add_group_bis("supp_group", 2015, ["user3"]) + create_ldap_fixture(request, ldap_conn, ent_list) + + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + \ + unindent(""" + [domain/LDAP] + auto_private_groups = True + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_ldap_auto_private_groups_conflict(ldap_conn, mpg_setup_conflict): + """ + Make sure that conflicts between groups that are auto-created with the + help of the auto_private_groups option and between 'real' LDAP groups + are handled in a predictable manner. + """ + # Make sure the user's GID is taken from their uidNumber + ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001, gid=1001)) + # Make sure the private group is resolvable by name and by GID + ent.assert_group_by_name("user1", dict(gid=1001, mem=ent.contains_only())) + ent.assert_group_by_gid(1001, dict(name="user1", mem=ent.contains_only())) + + # Let's request the group with the same ID as user2's private group + # The request should match the 'real' group + ent.assert_group_by_gid(1002, dict(name="group2", mem=ent.contains_only())) + # But because of the GID conflict, the user cannot be resolved + with pytest.raises(KeyError): + pwd.getpwnam("user2") + + # This user's GID is the same as the UID in this entry. The most important + # thing here is that the supplementary groups are correct and the GID + # resolves to the private group (as long as the user was requested first) + user3_expected_gids = [1003, 2015] + ent.assert_passwd_by_name("user3", dict(name="user3", uid=1003, gid=1003)) + (res, errno, gids) = sssd_id.call_sssd_initgroups("user3", 1003) + assert res == sssd_id.NssReturnCode.SUCCESS + + assert sorted(gids) == sorted(user3_expected_gids), \ + "result: %s\n expected %s" % ( + ", ".join(["%s" % s for s in sorted(gids)]), + ", ".join(["%s" % s for s in sorted(user3_expected_gids)]) + ) + # Make sure the private group is resolvable by name and by GID + ent.assert_group_by_gid(1003, dict(name="user3", mem=ent.contains_only())) + ent.assert_group_by_name("user3", dict(gid=1003, mem=ent.contains_only())) + + +@pytest.fixture +def mpg_setup_no_gid(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + + ent_list.add_group_bis("group1", 2001) + ent_list.add_group_bis("one_user_group1", 2015, ["user1"]) + + create_ldap_entries(ldap_conn, ent_list) + create_ldap_cleanup(request, ldap_conn, None) + + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + \ + unindent(""" + [domain/LDAP] + auto_private_groups = True + ldap_user_gid_number = no_such_attribute + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_ldap_auto_private_groups_direct_no_gid(ldap_conn, mpg_setup_no_gid): + """ + Integration test for auto_private_groups - test that even a user with + no GID assigned at all can be resolved including their autogenerated + primary group. + + See also ticket https://github.com/SSSD/sssd/issues/2914 + """ + # Make sure the user's GID is taken from their uidNumber + ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001, gid=1001)) + # Make sure the private group is resolvable by name and by GID + ent.assert_group_by_name("user1", dict(gid=1001, mem=ent.contains_only())) + ent.assert_group_by_gid(1001, dict(name="user1", mem=ent.contains_only())) + + # The group referenced in user's gidNumber attribute should be still + # visible, but shouldn't have any relation to the user + ent.assert_group_by_name("group1", dict(gid=2001, mem=ent.contains_only())) + ent.assert_group_by_gid(2001, dict(name="group1", mem=ent.contains_only())) + + # The user's secondary groups list must be correct as well. This time only + # the generated group and the explicit secondary group are added, since + # there is no original GID + user1_expected_gids = [1001, 2015] + (res, errno, gids) = sssd_id.call_sssd_initgroups("user1", 1001) + assert res == sssd_id.NssReturnCode.SUCCESS + + assert sorted(gids) == sorted(user1_expected_gids), \ + "result: %s\n expected %s" % ( + ", ".join(["%s" % s for s in sorted(gids)]), + ", ".join(["%s" % s for s in sorted(user1_expected_gids)]) + ) + + +@pytest.fixture +def mpg_setup_hybrid(request, ldap_conn): + """ + This setup creates two users - one with a GID that corresponds to + a group and another with GID that does not. + """ + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + + ent_list.add_user("user_with_group", 1001, 2001) + ent_list.add_group_bis("user_with_group_pvt", 2001) + ent_list.add_group_bis("with_group_group1", 10010, ["user_with_group"]) + ent_list.add_group_bis("with_group_group2", 10011, ["user_with_group"]) + + ent_list.add_user("user_with_no_group", 1002, 1002) + # Note - there is no group for the GID 1002 + ent_list.add_group_bis("no_group_group1", 10020, ["user_with_no_group"]) + ent_list.add_group_bis("no_group_group2", 10021, ["user_with_no_group"]) + + # This user has their gid different from the UID, but there is + # no group with gid 2003 + ent_list.add_user("user_with_unresolvable_gid", 1003, 2003) + ent_list.add_group_bis("unresolvable_group1", + 10030, + ["user_with_unresolvable_gid"]) + ent_list.add_group_bis("unresolvable_group2", + 10031, + ["user_with_unresolvable_gid"]) + + # This user's autogenerated private group should be shadowed + # by the real one + ent_list.add_user("user_with_real_group", 1004, 1004) + ent_list.add_user("user_in_pvt_group", 1005, 1005) + ent_list.add_group_bis("user_with_real_group_pvt", + 1004, + ['user_in_pvt_group']) + ent_list.add_group_bis("with_real_group_group1", + 10040, + ["user_with_real_group"]) + ent_list.add_group_bis("with_real_group_group2", + 10041, + ["user_with_real_group"]) + + # Test shadowing again, but this time with the same name + ent_list.add_user("u_g_same_name", 1006, 1006) + ent_list.add_group_bis("u_g_same_name", + 1006, + ['user_in_pvt_group']) + ent_list.add_group_bis("u_g_same_g1", + 10060, + ["u_g_same_name"]) + ent_list.add_group_bis("u_g_same_g2", + 10061, + ["u_g_same_name"]) + + create_ldap_entries(ldap_conn, ent_list) + create_ldap_cleanup(request, ldap_conn, None) + + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + \ + unindent(""" + [domain/LDAP] + auto_private_groups = hybrid + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_ldap_auto_private_groups_hybrid_direct(ldap_conn, mpg_setup_hybrid): + """ + Integration test for auto_private_groups=hybrid. This test checks the + resolution of the users and their groups. + + See also ticket https://github.com/SSSD/sssd/issues/2914 + """ + # Make sure the user's GID is taken from their gidNumber, if available + ent.assert_passwd_by_name("user_with_group", + dict(name="user_with_group", uid=1001, gid=2001)) + + # The user's secondary groups list must be correct as well and include + # the primary gid, too + user_with_group_ids = [2001, 10010, 10011] + (res, errno, gids) = sssd_id.call_sssd_initgroups("user_with_group", 2001) + assert res == sssd_id.NssReturnCode.SUCCESS + + assert sorted(gids) == sorted(user_with_group_ids), \ + "result: %s\n expected %s" % ( + ", ".join(["%s" % s for s in sorted(gids)]), + ", ".join(["%s" % s for s in sorted(user_with_group_ids)]) + ) + + # On the other hand, if the gidNumber is the same as UID, SSSD should + # just autogenerate the private group on its own + ent.assert_passwd_by_name("user_with_no_group", + dict(name="user_with_no_group", + uid=1002, gid=1002)) + + # The user's secondary groups list must be correct as well. Since there was + # no original GID, it is not added to the list + user_without_group_ids = [1002, 10020, 10021] + (res, errno, gids) = sssd_id.call_sssd_initgroups("user_with_no_group", + 1002) + assert res == sssd_id.NssReturnCode.SUCCESS + + assert sorted(gids) == sorted(user_without_group_ids), \ + "result: %s\n expected %s" % ( + ", ".join(["%s" % s for s in sorted(gids)]), + ", ".join(["%s" % s for s in sorted(user_without_group_ids)]) + ) + + ent.assert_passwd_by_name("user_with_unresolvable_gid", + dict(name="user_with_unresolvable_gid", + uid=1003, gid=2003)) + unresolvable_group_ids = [2003, 10030, 10031] + (res, errno, gids) = sssd_id.call_sssd_initgroups("user_with_unresolvable_gid", 2003) + assert res == sssd_id.NssReturnCode.SUCCESS + + assert sorted(gids) == sorted(unresolvable_group_ids), \ + "result: %s\n expected %s" % ( + ", ".join(["%s" % s for s in sorted(gids)]), + ", ".join(["%s" % s for s in sorted(unresolvable_group_ids)]) + ) + + ent.assert_passwd_by_name("user_with_real_group", + dict(name="user_with_real_group", + uid=1004, gid=1004)) + with_real_group_ids = [1004, 10040, 10041] + (res, errno, gids) = sssd_id.call_sssd_initgroups("user_with_real_group", 1004) + assert res == sssd_id.NssReturnCode.SUCCESS + + assert sorted(gids) == sorted(with_real_group_ids), \ + "result: %s\n expected %s" % ( + ", ".join(["%s" % s for s in sorted(gids)]), + ", ".join(["%s" % s for s in sorted(with_real_group_ids)]) + ) + + +def test_ldap_auto_private_groups_hybrid_priv_group_byname(ldap_conn, + mpg_setup_hybrid): + """ + Integration test for auto_private_groups=hybrid. This test checks the + resolution of user private groups by name. + + See also ticket https://github.com/SSSD/sssd/issues/2914 + """ + # gidNumber is resolvable by name.. + ent.assert_group_by_name("user_with_group_pvt", + dict(gid=2001, + mem=ent.contains_only())) + + # ..but since this user /has/ a gidNumber set, their autogenerated group + # should not be resolvable + with pytest.raises(KeyError): + grp.getgrnam("user_with_group") + + # Finally, the autogenerated group for the user with + # uidNumber==gidNumber must be resolvable + ent.assert_group_by_name("user_with_no_group", + dict(gid=1002, + mem=ent.contains_only())) + + # A gid that is different from an UID must not resolve to a private + # group even if the private group does not exist + with pytest.raises(KeyError): + grp.getgrnam("user_with_unresolvable_gid") + + # If there is a user with the same UID and GID but there is a real + # group corresponding to the primary GID, the real group should take + # precedence and the automatic group should not be resolvable + ent.assert_group_by_name("user_with_real_group_pvt", + dict(gid=1004, + mem=ent.contains_only('user_in_pvt_group',))) + + # getgrnam should not return + with pytest.raises(KeyError): + grp.getgrnam("user_with_real_group") + + +def test_ldap_auto_private_groups_hybrid_priv_group_byid(ldap_conn, + mpg_setup_hybrid): + """ + Integration test for auto_private_groups=hybrid. This test checks the + resolution of user private groups by name. + + See also ticket https://github.com/SSSD/sssd/issues/2914 + """ + # Make sure the private group of user who has this group set in their + # gidNumber is resolvable by ID + ent.assert_group_by_gid(2001, + dict(name="user_with_group_pvt", + mem=ent.contains_only())) + + # ..but since this user /has/ a gidNumber set different from the uidNumber, + # their autogenerated group + # should not be resolvable + with pytest.raises(KeyError): + grp.getgrgid(1001) + + # Finally, the autogenerated group for the user with + # uidNumber==gidNumber must be resolvable + ent.assert_group_by_gid(1002, + dict(name="user_with_no_group", + mem=ent.contains_only())) + + # A gid that is different from an UID must not resolve to a private + # group even if the private group does not exist + with pytest.raises(KeyError): + grp.getgrgid(2003) + + # Conversely, a GID that corresponds to a group must not resolve to + # the autogenerated group (IOW, the autogenerated group should not + # shadow the real one + ent.assert_group_by_gid(1004, + dict(name="user_with_real_group_pvt", + mem=ent.contains_only('user_in_pvt_group'))) + + +def test_ldap_auto_private_groups_hybrid_name_gid_identical(ldap_conn, + mpg_setup_hybrid): + """ + See also ticket https://github.com/SSSD/sssd/issues/2914 + """ + ent.assert_passwd_by_name("u_g_same_name", + dict(name="u_g_same_name", + uid=1006, gid=1006)) + user_without_group_ids = [1006, 10060, 10061] + (res, errno, gids) = sssd_id.call_sssd_initgroups("u_g_same_name", + 1006) + assert res == sssd_id.NssReturnCode.SUCCESS + + assert sorted(gids) == sorted(user_without_group_ids), \ + "result: %s\n expected %s" % ( + ", ".join(["%s" % s for s in sorted(gids)]), + ", ".join(["%s" % s for s in sorted(user_without_group_ids)]) + ) + ent.assert_group_by_gid(1006, + dict(name="u_g_same_name", + mem=ent.contains_only('user_in_pvt_group'))) + + +def test_ldap_auto_private_groups_hybrid_initgr(ldap_conn, mpg_setup_hybrid): + """ + See also ticket https://github.com/SSSD/sssd/issues/2914 + """ + user_without_group_ids = [1004, 10040, 10041] + (res, errno, gids) = sssd_id.call_sssd_initgroups("user_with_real_group", + 1004) + assert res == sssd_id.NssReturnCode.SUCCESS + + assert sorted(gids) == sorted(user_without_group_ids), \ + "result: %s\n expected %s" % ( + ", ".join(["%s" % s for s in sorted(gids)]), + ", ".join(["%s" % s for s in sorted(user_without_group_ids)]) + ) + + ent.assert_group_by_gid(1004, + dict(name="user_with_real_group_pvt", + mem=ent.contains_only('user_in_pvt_group'))) + + +def rename_setup_no_cleanup(request, ldap_conn, cleanup_ent=None): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + ent_list.add_group_bis("user1_private", 2001) + + ent_list.add_user("user2", 1002, 2002) + ent_list.add_group_bis("user2_private", 2002) + + ent_list.add_group_bis("group1", 2015, ["user1", "user2"]) + + if cleanup_ent is None: + create_ldap_fixture(request, ldap_conn, ent_list) + else: + # Since the entries were renamed, we need to clean up + # the renamed entries.. + create_ldap_fixture(request, ldap_conn, ent_list, cleanup=False) + create_ldap_cleanup(request, ldap_conn, None) + + +@pytest.fixture +def rename_setup_cleanup(request, ldap_conn): + cleanup_ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + cleanup_ent_list.add_user("user1", 1001, 2001) + cleanup_ent_list.add_group_bis("new_user1_private", 2001) + + cleanup_ent_list.add_user("user2", 1002, 2002) + cleanup_ent_list.add_group_bis("new_user2_private", 2002) + + cleanup_ent_list.add_group_bis("new_group1", 2015, ["user1", "user2"]) + + rename_setup_no_cleanup(request, ldap_conn, cleanup_ent_list) + + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +@pytest.fixture +def rename_setup_with_name(request, ldap_conn): + rename_setup_no_cleanup(request, ldap_conn) + + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + \ + unindent(""" + [nss] + [domain/LDAP] + ldap_group_name = name + timeout = 3000 + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_rename_incomplete_group_same_dn(ldap_conn, rename_setup_with_name): + """ + Test that if a group's name attribute changes, but the DN stays the same, + the incomplete group object will be renamed. + + Because the RDN attribute must be present in the entry, we add another + attribute "name" that is purposefully different from the CN and make + sure the group names are reflected in name + + Regression test for https://github.com/SSSD/sssd/issues/4315 + """ + pvt_dn1 = 'cn=user1_private,ou=Groups,' + ldap_conn.ds_inst.base_dn + pvt_dn2 = 'cn=user2_private,ou=Groups,' + ldap_conn.ds_inst.base_dn + group1_dn = 'cn=group1,ou=Groups,' + ldap_conn.ds_inst.base_dn + + # Add the name we want for both private and secondary group + old = {'name': []} + new = {'name': [b"user1_group1"]} + ldif = ldap.modlist.modifyModlist(old, new) + ldap_conn.modify_s(group1_dn, ldif) + + new = {'name': [b"pvt_user1"]} + ldif = ldap.modlist.modifyModlist(old, new) + ldap_conn.modify_s(pvt_dn1, ldif) + + new = {'name': [b"pvt_user2"]} + ldif = ldap.modlist.modifyModlist(old, new) + ldap_conn.modify_s(pvt_dn2, ldif) + + # Make sure the old name shows up in the id output + (res, errno, grp_list) = sssd_id.get_user_groups("user1") + assert res == sssd_id.NssReturnCode.SUCCESS, \ + "Could not find groups for user1, %d" % errno + + assert sorted(grp_list) == sorted(["pvt_user1", "user1_group1"]) + + # Rename the group by changing the cn attribute, but keep the DN the same + old = {'name': [b"user1_group1"]} + new = {'name': [b"new_user1_group1"]} + ldif = ldap.modlist.modifyModlist(old, new) + ldap_conn.modify_s(group1_dn, ldif) + + (res, errno, grp_list) = sssd_id.get_user_groups("user2") + assert res == sssd_id.NssReturnCode.SUCCESS, \ + "Could not find groups for user2, %d" % errno + + assert sorted(grp_list) == sorted(["pvt_user2", "new_user1_group1"]) + + (res, errno, grp_list) = sssd_id.get_user_groups("user1") + assert res == sssd_id.NssReturnCode.SUCCESS, \ + "Could not find groups for user1, %d" % errno + + assert sorted(grp_list) == sorted(["pvt_user1", "new_user1_group1"]) + + +def test_rename_incomplete_group_rdn_changed(ldap_conn, rename_setup_cleanup): + """ + If a group is renamed and also some attributes changed and gid remains the + same then existing group in the cache is overridden with the new attributes + and name. + """ + pvt_dn = 'cn=user1_private,ou=Groups,' + ldap_conn.ds_inst.base_dn + group1_dn = 'cn=group1,ou=Groups,' + ldap_conn.ds_inst.base_dn + + # Make sure the old name shows up in the id output + (res, errno, grp_list) = sssd_id.get_user_groups("user1") + assert res == sssd_id.NssReturnCode.SUCCESS, \ + "Could not find groups for user1, %d" % errno + + assert sorted(grp_list) == sorted(["user1_private", "group1"]) + + # Rename the groups, changing the RDN + ldap_conn.rename_s(group1_dn, "cn=new_group1") + ldap_conn.rename_s(pvt_dn, "cn=new_user1_private") + + (res, errno, grp_list) = sssd_id.get_user_groups("user2") + assert res == sssd_id.NssReturnCode.SUCCESS, \ + "Could not find groups for user2, %d" % errno + + # The initgroups succeeds, but because saving the new group fails, + # SSSD will revert to the cache contents and return what's in the cache + assert sorted(grp_list) == sorted(["user2_private", "new_group1"]) + + +@pytest.fixture +def find_local_user_and_group(): + f = open("/etc/passwd") + for line in f: + passwd_user = line.split(':') + passwd_user[2] = int(passwd_user[2]) + if passwd_user[2] != 0: + break + f.close() + assert passwd_user[2] != 0 + + f = open("/etc/group") + for line in f: + passwd_group = line.split(':') + passwd_group[2] = int(passwd_group[2]) + if passwd_group[2] != 0: + break + f.close() + assert passwd_group[2] != 0 + + return (passwd_user, passwd_group) + + +@pytest.fixture +def user_and_group_rfc2307_lcl(find_local_user_and_group, + user_and_group_rfc2307): + return find_local_user_and_group + + +def test_local_negative_timeout_enabled_by_default(ldap_conn, + user_and_group_rfc2307_lcl): + """ + Test that with the default local_negative_timeout value, a user who can't + be resolved through SSSD but can be resolved in LDAP is negatively cached + """ + # sanity check - try resolving an LDAP user + ent.assert_passwd_by_name("user", dict(name="user", uid=1001, gid=2000)) + + passwd_user, passwd_group = user_and_group_rfc2307_lcl + + # resolve a user who is not in LDAP, but exists locally + res, _ = call_sssd_getpwnam(passwd_user[0]) + assert res == NssReturnCode.NOTFOUND + # Do the same by UID + res, _ = call_sssd_getpwuid(passwd_user[2]) + assert res == NssReturnCode.NOTFOUND + + # Do the same for a group both by name and by ID + res, _ = call_sssd_getgrnam(passwd_group[0]) + assert res == NssReturnCode.NOTFOUND + res, _ = call_sssd_getgrgid(passwd_group[2]) + assert res == NssReturnCode.NOTFOUND + + # add the user and the group to LDAP + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user(passwd_user[0], passwd_user[2], 2000) + ent_list.add_group(passwd_group[0], passwd_group[2]) + create_ldap_entries(ldap_conn, ent_list) + + # Make sure the negative cache would expire if global timeout was used + time.sleep(2) + + # The user is now negatively cached and can't be resolved by either + # name or UID + res, _ = call_sssd_getpwnam(passwd_group[0]) + assert res == NssReturnCode.NOTFOUND + res, _ = call_sssd_getpwuid(passwd_group[2]) + assert res == NssReturnCode.NOTFOUND + + res, _ = call_sssd_getgrnam(passwd_group[0]) + assert res == NssReturnCode.NOTFOUND + res, _ = call_sssd_getgrgid(passwd_group[2]) + assert res == NssReturnCode.NOTFOUND + + cleanup_ldap_entries(ldap_conn, ent_list) + + +@pytest.fixture +def usr_and_grp_rfc2307_no_local_ncache(request, find_local_user_and_group, + ldap_conn): + """ + Create an RFC2307 directory fixture with interactive SSSD conf, + one user and one group but with the local negative timeout + disabled + """ + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user", 1001, 2000) + ent_list.add_group("group", 2001) + create_ldap_fixture(request, ldap_conn, ent_list) + conf = format_interactive_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent(""" + [nss] + local_negative_timeout = 0 + """) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return find_local_user_and_group + + +def test_local_negative_timeout_disabled(ldap_conn, + usr_and_grp_rfc2307_no_local_ncache): + """ + Test that with the local negative cache disabled, a user who is in both + LDAP and files can be resolved once the negative cache expires + """ + # sanity check - try resolving an LDAP user + ent.assert_passwd_by_name("user", dict(name="user", uid=1001, gid=2000)) + + passwd_user, passwd_group = usr_and_grp_rfc2307_no_local_ncache + + # resolve a user who is not in LDAP, but exists locally + res, _ = call_sssd_getpwnam(passwd_user[0]) + assert res == NssReturnCode.NOTFOUND + # Do the same by UID + res, _ = call_sssd_getpwuid(passwd_user[2]) + assert res == NssReturnCode.NOTFOUND + + # Do the same for a group both by name and by ID + res, _ = call_sssd_getgrnam(passwd_group[0]) + assert res == NssReturnCode.NOTFOUND + res, _ = call_sssd_getgrgid(passwd_group[2]) + assert res == NssReturnCode.NOTFOUND + + # add the user and the group to LDAP + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user(passwd_user[0], passwd_user[2], 2000) + ent_list.add_group(passwd_group[0], passwd_group[2]) + create_ldap_entries(ldap_conn, ent_list) + + # Make sure the negative cache expired + time.sleep(2) + + # The user can now be resolved + res, _ = call_sssd_getpwnam(passwd_user[0]) + assert res == NssReturnCode.SUCCESS + # Do the same by UID + res, _ = call_sssd_getpwuid(passwd_user[2]) + assert res == NssReturnCode.SUCCESS + + res, _ = call_sssd_getgrnam(passwd_group[0]) + assert res == NssReturnCode.SUCCESS + res, _ = call_sssd_getgrgid(passwd_group[2]) + assert res == NssReturnCode.SUCCESS + + cleanup_ldap_entries(ldap_conn, ent_list) + + +def users_with_email_setup(request, ldap_conn, cache_first): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001, mail="user1.email@LDAP") + + ent_list.add_user("emailuser", 1002, 2002) + ent_list.add_user("emailuser2", 1003, 2003, mail="emailuser@LDAP") + + ent_list.add_user("userx", 1004, 2004, mail="userxy@LDAP") + ent_list.add_user("usery", 1005, 2005, mail="userxy@LDAP") + + create_ldap_fixture(request, ldap_conn, ent_list) + + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + + conf += unindent(""" + [nss] + cache_first = {0} + """).format(str(cache_first)) + + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +@pytest.mark.parametrize('cache_first', [True, False]) +def test_lookup_by_email(request, ldap_conn, cache_first): + """ + Test the simple case of looking up a user by e-mail + """ + users_with_email_setup(request, ldap_conn, cache_first) + ent.assert_passwd_by_name("user1.email@LDAP", + dict(name="user1", uid=1001, gid=2001)) + + +@pytest.mark.parametrize('cache_first', [True, False]) +def test_conflicting_mail_addresses_and_fqdn(request, ldap_conn, cache_first): + """ + Test that we handle the case where one user's mail address is the + same as another user's FQDN + + This is a regression test for https://github.com/SSSD/sssd/issues/4630 + """ + users_with_email_setup(request, ldap_conn, cache_first) + # With #3607 unfixed, these two lookups would prime the cache with + # nameAlias: emailuser@LDAP for both entries.. + ent.assert_passwd_by_name("emailuser@LDAP", + dict(name="emailuser", uid=1002, gid=2002)) + ent.assert_passwd_by_name("emailuser2@LDAP", + dict(name="emailuser2", uid=1003, gid=2003)) + + # ..and subsequently, emailuser would not be returned because the cache + # lookup would have had returned two entries which is an error + ent.assert_passwd_by_name("emailuser@LDAP", + dict(name="emailuser", uid=1002, gid=2002)) + ent.assert_passwd_by_name("emailuser2@LDAP", + dict(name="emailuser2", uid=1003, gid=2003)) + + +@pytest.mark.parametrize('cache_first', [True, False]) +def test_conflicting_mail_addresses(request, ldap_conn, cache_first): + """ + Negative test: looking up a user by e-mail which belongs to more than + one account fails in the back end. + """ + users_with_email_setup(request, ldap_conn, cache_first) + with pytest.raises(KeyError): + pwd.getpwnam("userxy@LDAP") + + # However resolving the users on their own must work + ent.assert_passwd_by_name("userx", dict(name="userx", uid=1004, gid=2004)) + ent.assert_passwd_by_name("usery", dict(name="usery", uid=1005, gid=2005)) diff --git a/src/tests/intg/test_memory_cache.py b/src/tests/intg/test_memory_cache.py new file mode 100644 index 0000000..b30c902 --- /dev/null +++ b/src/tests/intg/test_memory_cache.py @@ -0,0 +1,1215 @@ +# +# LDAP integration test +# +# Copyright (c) 2015 Red Hat, Inc. +# Author: Lukas Slebodnik <lslebodn@redhat.com> +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 stat +import ent +import grp +import pwd +import config +import random +import signal +import string +import struct +import subprocess +import time +import pytest +import pysss_murmur + +import ds_openldap +import ldap_ent +import sssd_id +from util import unindent + +LDAP_BASE_DN = "dc=example,dc=com" + + +@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 Exception: + 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: + ldap_conn.delete_s(entry[0]) + 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 OSError: + 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", "--logger=files"]) != 0: + raise Exception("sssd start failed") + + def teardown(): + try: + stop_sssd() + except Exception: + 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) + # force sss_client libs to realize mem-cache files were deleted + try: + sssd_id.call_sssd_initgroups("user1", 2001) + except Exception: + pass + try: + grp.getgrnam("group1") + except Exception: + pass + try: + pwd.getpwnam("user1") + except Exception: + pass + request.addfinalizer(teardown) + + +def load_data_to_ldap(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + ent_list.add_user("user2", 1002, 2002) + ent_list.add_user("user3", 1003, 2003) + ent_list.add_user("user11", 1011, 2001) + ent_list.add_user("user12", 1012, 2002) + ent_list.add_user("user13", 1013, 2003) + ent_list.add_user("user21", 1021, 2001) + ent_list.add_user("user22", 1022, 2002) + ent_list.add_user("user23", 1023, 2003) + + ent_list.add_group("group1", 2001, ["user1", "user11", "user21"]) + ent_list.add_group("group2", 2002, ["user2", "user12", "user22"]) + ent_list.add_group("group3", 2003, ["user3", "user13", "user23"]) + + ent_list.add_group("group0x", 2000, ["user1", "user2", "user3"]) + ent_list.add_group("group1x", 2010, ["user11", "user12", "user13"]) + ent_list.add_group("group2x", 2020, ["user21", "user22", "user23"]) + create_ldap_fixture(request, ldap_conn, ent_list) + + +@pytest.fixture +def disable_memcache_rfc2307(request, ldap_conn): + load_data_to_ldap(request, ldap_conn) + + conf = unindent("""\ + [sssd] + domains = LDAP + services = nss + + [nss] + memcache_size_group = 0 + memcache_size_passwd = 0 + memcache_size_initgroups = 0 + + [domain/LDAP] + ldap_auth_disable_tls_never_use_in_production = true + ldap_schema = rfc2307 + id_provider = ldap + auth_provider = ldap + sudo_provider = ldap + 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 + + +@pytest.fixture +def disable_pwd_mc_rfc2307(request, ldap_conn): + load_data_to_ldap(request, ldap_conn) + + conf = unindent("""\ + [sssd] + domains = LDAP + services = nss + + [nss] + memcache_size_passwd = 0 + + [domain/LDAP] + ldap_auth_disable_tls_never_use_in_production = true + ldap_schema = rfc2307 + id_provider = ldap + auth_provider = ldap + sudo_provider = ldap + 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 + + +@pytest.fixture +def disable_grp_mc_rfc2307(request, ldap_conn): + load_data_to_ldap(request, ldap_conn) + + conf = unindent("""\ + [sssd] + domains = LDAP + services = nss + + [nss] + memcache_size_group = 0 + + [domain/LDAP] + ldap_auth_disable_tls_never_use_in_production = true + ldap_schema = rfc2307 + id_provider = ldap + auth_provider = ldap + sudo_provider = ldap + 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 + + +@pytest.fixture +def disable_initgr_mc_rfc2307(request, ldap_conn): + load_data_to_ldap(request, ldap_conn) + + conf = unindent("""\ + [sssd] + domains = LDAP + services = nss + + [nss] + memcache_size_initgroups = 0 + + [domain/LDAP] + ldap_auth_disable_tls_never_use_in_production = true + ldap_schema = rfc2307 + id_provider = ldap + auth_provider = ldap + sudo_provider = ldap + 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 + + +@pytest.fixture +def sanity_rfc2307(request, ldap_conn): + load_data_to_ldap(request, ldap_conn) + + conf = unindent("""\ + [sssd] + domains = LDAP + services = nss + + [nss] + + [domain/LDAP] + ldap_auth_disable_tls_never_use_in_production = true + ldap_schema = rfc2307 + id_provider = ldap + auth_provider = ldap + sudo_provider = ldap + 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 + + +@pytest.fixture +def fqname_rfc2307(request, ldap_conn): + load_data_to_ldap(request, ldap_conn) + + conf = unindent("""\ + [sssd] + domains = LDAP + services = nss + + [nss] + + [domain/LDAP] + ldap_auth_disable_tls_never_use_in_production = true + ldap_schema = rfc2307 + id_provider = ldap + auth_provider = ldap + sudo_provider = ldap + ldap_uri = {ldap_conn.ds_inst.ldap_url} + ldap_search_base = {ldap_conn.ds_inst.base_dn} + use_fully_qualified_names = true + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +@pytest.fixture +def fqname_case_insensitive_rfc2307(request, ldap_conn): + load_data_to_ldap(request, ldap_conn) + + conf = unindent("""\ + [sssd] + domains = LDAP + services = nss + + [nss] + + [domain/LDAP] + ldap_auth_disable_tls_never_use_in_production = true + ldap_schema = rfc2307 + id_provider = ldap + auth_provider = ldap + sudo_provider = ldap + ldap_uri = {ldap_conn.ds_inst.ldap_url} + ldap_search_base = {ldap_conn.ds_inst.base_dn} + use_fully_qualified_names = true + case_sensitive = false + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +@pytest.fixture +def zero_timeout_rfc2307(request, ldap_conn): + load_data_to_ldap(request, ldap_conn) + + conf = unindent("""\ + [sssd] + domains = LDAP + services = nss + + [nss] + memcache_timeout = 0 + + [domain/LDAP] + ldap_auth_disable_tls_never_use_in_production = true + ldap_schema = rfc2307 + id_provider = ldap + auth_provider = ldap + sudo_provider = ldap + 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 + + +@pytest.mark.converted('test_id.py', 'test_id__getpwuid') +@pytest.mark.converted('test_id.py', 'test_id__getpwnam') +def test_getpwnam(ldap_conn, sanity_rfc2307): + ent.assert_passwd_by_name( + 'user1', + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + ent.assert_passwd_by_uid( + 1001, + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + + ent.assert_passwd_by_name( + 'user2', + dict(name='user2', passwd='*', uid=1002, gid=2002, + gecos='1002', shell='/bin/bash')) + ent.assert_passwd_by_uid( + 1002, + dict(name='user2', passwd='*', uid=1002, gid=2002, + gecos='1002', shell='/bin/bash')) + + ent.assert_passwd_by_name( + 'user3', + dict(name='user3', passwd='*', uid=1003, gid=2003, + gecos='1003', shell='/bin/bash')) + ent.assert_passwd_by_uid( + 1003, + dict(name='user3', passwd='*', uid=1003, gid=2003, + gecos='1003', shell='/bin/bash')) + + ent.assert_passwd_by_name( + 'user11', + dict(name='user11', passwd='*', uid=1011, gid=2001, + gecos='1011', shell='/bin/bash')) + ent.assert_passwd_by_uid( + 1011, + dict(name='user11', passwd='*', uid=1011, gid=2001, + gecos='1011', shell='/bin/bash')) + + ent.assert_passwd_by_name( + 'user12', + dict(name='user12', passwd='*', uid=1012, gid=2002, + gecos='1012', shell='/bin/bash')) + ent.assert_passwd_by_uid( + 1012, + dict(name='user12', passwd='*', uid=1012, gid=2002, + gecos='1012', shell='/bin/bash')) + + ent.assert_passwd_by_name( + 'user13', + dict(name='user13', passwd='*', uid=1013, gid=2003, + gecos='1013', shell='/bin/bash')) + ent.assert_passwd_by_uid( + 1013, + dict(name='user13', passwd='*', uid=1013, gid=2003, + gecos='1013', shell='/bin/bash')) + + ent.assert_passwd_by_name( + 'user21', + dict(name='user21', passwd='*', uid=1021, gid=2001, + gecos='1021', shell='/bin/bash')) + ent.assert_passwd_by_uid( + 1021, + dict(name='user21', passwd='*', uid=1021, gid=2001, + gecos='1021', shell='/bin/bash')) + + ent.assert_passwd_by_name( + 'user22', + dict(name='user22', passwd='*', uid=1022, gid=2002, + gecos='1022', shell='/bin/bash')) + ent.assert_passwd_by_uid( + 1022, + dict(name='user22', passwd='*', uid=1022, gid=2002, + gecos='1022', shell='/bin/bash')) + + ent.assert_passwd_by_name( + 'user23', + dict(name='user23', passwd='*', uid=1023, gid=2003, + gecos='1023', shell='/bin/bash')) + ent.assert_passwd_by_uid( + 1023, + dict(name='user23', passwd='*', uid=1023, gid=2003, + gecos='1023', shell='/bin/bash')) + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__getpwnam') +def test_getpwnam_with_mc(ldap_conn, sanity_rfc2307): + test_getpwnam(ldap_conn, sanity_rfc2307) + stop_sssd() + test_getpwnam(ldap_conn, sanity_rfc2307) + + +@pytest.mark.converted('test_id.py', 'test_id__getgrgid') +@pytest.mark.converted('test_id.py', 'test_id__getgrnam') +def test_getgrnam_simple(ldap_conn, sanity_rfc2307): + ent.assert_group_by_name("group1", dict(name="group1", gid=2001)) + ent.assert_group_by_gid(2001, dict(name="group1", gid=2001)) + + ent.assert_group_by_name("group2", dict(name="group2", gid=2002)) + ent.assert_group_by_gid(2002, dict(name="group2", gid=2002)) + + ent.assert_group_by_name("group3", dict(name="group3", gid=2003)) + ent.assert_group_by_gid(2003, dict(name="group3", gid=2003)) + + ent.assert_group_by_name("group0x", dict(name="group0x", gid=2000)) + ent.assert_group_by_gid(2000, dict(name="group0x", gid=2000)) + + ent.assert_group_by_name("group1x", dict(name="group1x", gid=2010)) + ent.assert_group_by_gid(2010, dict(name="group1x", gid=2010)) + + ent.assert_group_by_name("group2x", dict(name="group2x", gid=2020)) + ent.assert_group_by_gid(2020, dict(name="group2x", gid=2020)) + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__getgrnam') +def test_getgrnam_simple_with_mc(ldap_conn, sanity_rfc2307): + test_getgrnam_simple(ldap_conn, sanity_rfc2307) + stop_sssd() + test_getgrnam_simple(ldap_conn, sanity_rfc2307) + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__disabled_passwd_getgrnam') +def test_getgrnam_simple_disabled_pwd_mc(ldap_conn, disable_pwd_mc_rfc2307): + test_getgrnam_simple(ldap_conn, disable_pwd_mc_rfc2307) + stop_sssd() + test_getgrnam_simple(ldap_conn, disable_pwd_mc_rfc2307) + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__disabled_intitgroups_getgrnam') +def test_getgrnam_simple_disabled_intitgr_mc(ldap_conn, + disable_initgr_mc_rfc2307): + test_getgrnam_simple(ldap_conn, disable_initgr_mc_rfc2307) + stop_sssd() + test_getgrnam_simple(ldap_conn, disable_initgr_mc_rfc2307) + + +@pytest.mark.converted('test_id.py', 'test_id__membership_by_group_id') +@pytest.mark.converted('test_id.py', 'test_id__membership_by_group_name') +def test_getgrnam_membership(ldap_conn, sanity_rfc2307): + ent.assert_group_by_name( + "group1", + dict(mem=ent.contains_only("user1", "user11", "user21"))) + ent.assert_group_by_gid( + 2001, + dict(mem=ent.contains_only("user1", "user11", "user21"))) + + ent.assert_group_by_name( + "group2", + dict(mem=ent.contains_only("user2", "user12", "user22"))) + ent.assert_group_by_gid( + 2002, + dict(mem=ent.contains_only("user2", "user12", "user22"))) + + ent.assert_group_by_name( + "group3", + dict(mem=ent.contains_only("user3", "user13", "user23"))) + ent.assert_group_by_gid( + 2003, + dict(mem=ent.contains_only("user3", "user13", "user23"))) + + ent.assert_group_by_name( + "group0x", + dict(mem=ent.contains_only("user1", "user2", "user3"))) + ent.assert_group_by_gid( + 2000, + dict(mem=ent.contains_only("user1", "user2", "user3"))) + + ent.assert_group_by_name( + "group1x", + dict(mem=ent.contains_only("user11", "user12", "user13"))) + ent.assert_group_by_gid( + 2010, + dict(mem=ent.contains_only("user11", "user12", "user13"))) + + ent.assert_group_by_name( + "group2x", + dict(mem=ent.contains_only("user21", "user22", "user23"))) + ent.assert_group_by_gid( + 2020, + dict(mem=ent.contains_only("user21", "user22", "user23"))) + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__membership_by_group_id') +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__membership_by_group_name') +def test_getgrnam_membership_with_mc(ldap_conn, sanity_rfc2307): + test_getgrnam_membership(ldap_conn, sanity_rfc2307) + stop_sssd() + test_getgrnam_membership(ldap_conn, sanity_rfc2307) + + +def assert_user_gids_equal(user, expected_gids): + (res, errno, gids) = sssd_id.get_user_gids(user) + assert res == sssd_id.NssReturnCode.SUCCESS, \ + "Could not find groups for user %s, %d" % (user, errno) + + assert sorted(gids) == sorted(expected_gids), \ + "result: %s\n expected %s" % ( + ", ".join(["%s" % s for s in sorted(gids)]), + ", ".join(["%s" % s for s in sorted(expected_gids)]) + ) + + +@pytest.mark.converted('test_id.py', 'test_id__initgroups') +def test_initgroups(ldap_conn, sanity_rfc2307): + assert_user_gids_equal('user1', [2000, 2001]) + assert_user_gids_equal('user2', [2000, 2002]) + assert_user_gids_equal('user3', [2000, 2003]) + + assert_user_gids_equal('user11', [2010, 2001]) + assert_user_gids_equal('user12', [2010, 2002]) + assert_user_gids_equal('user13', [2010, 2003]) + + assert_user_gids_equal('user21', [2020, 2001]) + assert_user_gids_equal('user22', [2020, 2002]) + assert_user_gids_equal('user23', [2020, 2003]) + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__user_gids') +def test_initgroups_with_mc(ldap_conn, sanity_rfc2307): + test_initgroups(ldap_conn, sanity_rfc2307) + stop_sssd() + test_initgroups(ldap_conn, sanity_rfc2307) + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__getpwnam_fully_qualified_names') +@pytest.mark.converted('test_id.py', 'test_id__getpwnam_fully_qualified_names') +def test_initgroups_fqname_with_mc(ldap_conn, fqname_rfc2307): + assert_user_gids_equal('user1@LDAP', [2000, 2001]) + stop_sssd() + assert_user_gids_equal('user1@LDAP', [2000, 2001]) + + +def assert_initgroups_equal(user, primary_gid, expected_gids): + (res, errno, gids) = sssd_id.call_sssd_initgroups(user, primary_gid) + assert res == sssd_id.NssReturnCode.SUCCESS, \ + "Could not find groups for user %s, %d" % (user, errno) + + assert sorted(gids) == sorted(expected_gids), \ + "result: %s\n expected %s" % ( + ", ".join(["%s" % s for s in sorted(gids)]), + ", ".join(["%s" % s for s in sorted(expected_gids)]) + ) + + +def assert_stored_last_initgroups(user1_case1, user1_case2, user1_case_last, + primary_gid, expected_gids): + + assert_initgroups_equal(user1_case1, primary_gid, expected_gids) + assert_initgroups_equal(user1_case2, primary_gid, expected_gids) + assert_initgroups_equal(user1_case_last, primary_gid, expected_gids) + stop_sssd() + + user = user1_case1 + (res, errno, _) = sssd_id.call_sssd_initgroups(user, primary_gid) + assert res == sssd_id.NssReturnCode.UNAVAIL, \ + "Initgroups for user should fail user %s, %d, %d" % (user, res, errno) + + user = user1_case2 + (res, errno, _) = sssd_id.call_sssd_initgroups(user, primary_gid) + assert res == sssd_id.NssReturnCode.UNAVAIL, \ + "Initgroups for user should fail user %s, %d, %d" % (user, res, errno) + + # Just last invocation of initgroups should PASS + # Otherwise, we would not be able to invalidate it + assert_initgroups_equal(user1_case_last, primary_gid, expected_gids) + + +@pytest.mark.converted('test_id.py', 'test_id__fq_names_case_insensitive') +@pytest.mark.converted('test_id.py', 'test_id__case_insensitive') +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__fq_names_case_insensitive') +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__case_insensitive') +def test_initgroups_case_insensitive_with_mc1(ldap_conn, + fqname_case_insensitive_rfc2307): + user1_case1 = 'User1@LDAP' + user1_case2 = 'uSer1@LDAP' + user1_case_last = 'usEr1@LDAP' + primary_gid = 2001 + expected_gids = [2000, 2001] + + assert_stored_last_initgroups(user1_case1, user1_case2, user1_case_last, + primary_gid, expected_gids) + + +@pytest.mark.converted('test_id.py', 'test_id__fq_names_case_insensitive') +@pytest.mark.converted('test_id.py', 'test_id__case_insensitive') +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__fq_names_case_insensitive') +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__case_insensitive') +def test_initgroups_case_insensitive_with_mc2(ldap_conn, + fqname_case_insensitive_rfc2307): + user1_case1 = 'usEr1@LDAP' + user1_case2 = 'User1@LDAP' + user1_case_last = 'uSer1@LDAP' + primary_gid = 2001 + expected_gids = [2000, 2001] + + assert_stored_last_initgroups(user1_case1, user1_case2, user1_case_last, + primary_gid, expected_gids) + + +@pytest.mark.converted('test_id.py', 'test_id__fq_names_case_insensitive') +@pytest.mark.converted('test_id.py', 'test_id__case_insensitive') +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__fq_names_case_insensitive') +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__case_insensitive') +def test_initgroups_case_insensitive_with_mc3(ldap_conn, + fqname_case_insensitive_rfc2307): + user1_case1 = 'uSer1@LDAP' + user1_case2 = 'usEr1@LDAP' + user1_case_last = 'User1@LDAP' + primary_gid = 2001 + expected_gids = [2000, 2001] + + assert_stored_last_initgroups(user1_case1, user1_case2, user1_case_last, + primary_gid, expected_gids) + + +def run_simple_test_with_initgroups(): + ent.assert_passwd_by_name( + 'user1', + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + ent.assert_passwd_by_uid( + 1001, + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + + ent.assert_group_by_name( + "group1", + dict(mem=ent.contains_only("user1", "user11", "user21"))) + ent.assert_group_by_gid( + 2001, + dict(mem=ent.contains_only("user1", "user11", "user21"))) + + # unrelated group to user1 + ent.assert_group_by_name( + "group2", + dict(mem=ent.contains_only("user2", "user12", "user22"))) + ent.assert_group_by_gid( + 2002, + dict(mem=ent.contains_only("user2", "user12", "user22"))) + + assert_initgroups_equal("user1", 2001, [2000, 2001]) + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__invalidatation_of_gids_after_initgroups') +def test_invalidation_of_gids_after_initgroups(ldap_conn, sanity_rfc2307): + + # the sssd cache was empty and not all user's group were + # resolved with getgr{nm,gid}. Therefore there is a change in + # group membership => user groups should be invalidated + run_simple_test_with_initgroups() + assert_initgroups_equal("user1", 2001, [2000, 2001]) + + stop_sssd() + + ent.assert_passwd_by_name( + 'user1', + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + ent.assert_passwd_by_uid( + 1001, + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + + # unrelated group to user1 must be returned + ent.assert_group_by_name( + "group2", + dict(mem=ent.contains_only("user2", "user12", "user22"))) + ent.assert_group_by_gid( + 2002, + dict(mem=ent.contains_only("user2", "user12", "user22"))) + + assert_initgroups_equal("user1", 2001, [2000, 2001]) + + # user groups must be invalidated + for group in ["group1", "group0x"]: + with pytest.raises(KeyError): + grp.getgrnam(group) + + for gid in [2000, 2001]: + with pytest.raises(KeyError): + grp.getgrgid(gid) + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__initgroups_without_change_in_membership') +def test_initgroups_without_change_in_membership(ldap_conn, sanity_rfc2307): + + # the sssd cache was empty and not all user's group were + # resolved with getgr{nm,gid}. Therefore there is a change in + # group membership => user groups should be invalidated + run_simple_test_with_initgroups() + + # invalidate cache + subprocess.call(["sss_cache", "-E"]) + + # all users and groups will be just refreshed from LDAP + # but there will not be a change in group membership + # user groups should not be invlaidated + run_simple_test_with_initgroups() + + stop_sssd() + + # everything should be in memory cache + run_simple_test_with_initgroups() + + +def assert_mc_records_for_user1(): + ent.assert_passwd_by_name( + 'user1', + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + ent.assert_passwd_by_uid( + 1001, + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + + ent.assert_group_by_name( + "group1", + dict(mem=ent.contains_only("user1", "user11", "user21"))) + ent.assert_group_by_gid( + 2001, + dict(mem=ent.contains_only("user1", "user11", "user21"))) + ent.assert_group_by_name( + "group0x", + dict(mem=ent.contains_only("user1", "user2", "user3"))) + ent.assert_group_by_gid( + 2000, + dict(mem=ent.contains_only("user1", "user2", "user3"))) + + assert_initgroups_equal("user1", 2001, [2000, 2001]) + + +def assert_missing_mc_records_for_user1(): + with pytest.raises(KeyError): + pwd.getpwnam("user1") + with pytest.raises(KeyError): + pwd.getpwuid(1001) + + for gid in [2000, 2001]: + with pytest.raises(KeyError): + grp.getgrgid(gid) + for group in ["group0x", "group1"]: + with pytest.raises(KeyError): + grp.getgrnam(group) + + (res, err, _) = sssd_id.call_sssd_initgroups("user1", 2001) + assert res == sssd_id.NssReturnCode.UNAVAIL, \ + "Initgroups should not find anything after invalidation of mc.\n" \ + "User user1, errno:%d" % err + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__invalidate_user_before_stop') +def test_invalidate_user_before_stop(ldap_conn, sanity_rfc2307): + # initialize cache with full ID + (res, errno, _) = sssd_id.get_user_groups("user1") + assert res == sssd_id.NssReturnCode.SUCCESS, \ + "Could not find groups for user1, %d" % errno + assert_mc_records_for_user1() + + subprocess.call(["sss_cache", "-u", "user1"]) + stop_sssd() + + assert_missing_mc_records_for_user1() + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__invalidate_user_after_stop') +def test_invalidate_user_after_stop(ldap_conn, sanity_rfc2307): + # initialize cache with full ID + (res, errno, _) = sssd_id.get_user_groups("user1") + assert res == sssd_id.NssReturnCode.SUCCESS, \ + "Could not find groups for user1, %d" % errno + assert_mc_records_for_user1() + + stop_sssd() + subprocess.call(["sss_cache", "-u", "user1"]) + + assert_missing_mc_records_for_user1() + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__invalidate_users_before_stop') +def test_invalidate_users_before_stop(ldap_conn, sanity_rfc2307): + # initialize cache with full ID + (res, errno, _) = sssd_id.get_user_groups("user1") + assert res == sssd_id.NssReturnCode.SUCCESS, \ + "Could not find groups for user1, %d" % errno + assert_mc_records_for_user1() + + subprocess.call(["sss_cache", "-U"]) + stop_sssd() + + assert_missing_mc_records_for_user1() + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__invalidate_users_after_stop') +def test_invalidate_users_after_stop(ldap_conn, sanity_rfc2307): + # initialize cache with full ID + (res, errno, _) = sssd_id.get_user_groups("user1") + assert res == sssd_id.NssReturnCode.SUCCESS, \ + "Could not find groups for user1, %d" % errno + assert_mc_records_for_user1() + + stop_sssd() + subprocess.call(["sss_cache", "-U"]) + + assert_missing_mc_records_for_user1() + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__invalidate_group_before_stop') +def test_invalidate_group_before_stop(ldap_conn, sanity_rfc2307): + # initialize cache with full ID + (res, errno, _) = sssd_id.get_user_groups("user1") + assert res == sssd_id.NssReturnCode.SUCCESS, \ + "Could not find groups for user1, %d" % errno + assert_mc_records_for_user1() + + subprocess.call(["sss_cache", "-g", "group1"]) + stop_sssd() + + assert_missing_mc_records_for_user1() + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__invalidate_group_after_stop') +def test_invalidate_group_after_stop(ldap_conn, sanity_rfc2307): + # initialize cache with full ID + (res, errno, _) = sssd_id.get_user_groups("user1") + assert res == sssd_id.NssReturnCode.SUCCESS, \ + "Could not find groups for user1, %d" % errno + assert_mc_records_for_user1() + + stop_sssd() + subprocess.call(["sss_cache", "-g", "group1"]) + + assert_missing_mc_records_for_user1() + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__invalidate_groups_before_stop') +def test_invalidate_groups_before_stop(ldap_conn, sanity_rfc2307): + # initialize cache with full ID + (res, errno, _) = sssd_id.get_user_groups("user1") + assert res == sssd_id.NssReturnCode.SUCCESS, \ + "Could not find groups for user1, %d" % errno + assert_mc_records_for_user1() + + subprocess.call(["sss_cache", "-G"]) + stop_sssd() + + assert_missing_mc_records_for_user1() + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__invalidate_groups_after_stop') +def test_invalidate_groups_after_stop(ldap_conn, sanity_rfc2307): + # initialize cache with full ID + (res, errno, _) = sssd_id.get_user_groups("user1") + assert res == sssd_id.NssReturnCode.SUCCESS, \ + "Could not find groups for user1, %d" % errno + assert_mc_records_for_user1() + + stop_sssd() + subprocess.call(["sss_cache", "-G"]) + + assert_missing_mc_records_for_user1() + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__invalidate_everything_before_stop') +def test_invalidate_everything_before_stop(ldap_conn, sanity_rfc2307): + # initialize cache with full ID + (res, errno, _) = sssd_id.get_user_groups("user1") + assert res == sssd_id.NssReturnCode.SUCCESS, \ + "Could not find groups for user1, %d" % errno + assert_mc_records_for_user1() + + subprocess.call(["sss_cache", "-E"]) + stop_sssd() + + assert_missing_mc_records_for_user1() + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__invalidate_everything_after_stop') +def test_invalidate_everything_after_stop(ldap_conn, sanity_rfc2307): + # initialize cache with full ID + (res, errno, _) = sssd_id.get_user_groups("user1") + assert res == sssd_id.NssReturnCode.SUCCESS, \ + "Could not find groups for user1, %d" % errno + assert_mc_records_for_user1() + + stop_sssd() + subprocess.call(["sss_cache", "-E"]) + + assert_missing_mc_records_for_user1() + + +def get_random_string(length): + return ''.join([random.choice(string.ascii_letters + string.digits) + for n in range(length)]) + + +class MemoryCache(object): + SIZEOF_UINT32_T = 4 + + def __init__(self, path): + with open(path, "rb") as fin: + fin.seek(4 * self.SIZEOF_UINT32_T) + self.seed = struct.unpack('i', fin.read(4))[0] + self.data_size = struct.unpack('i', fin.read(4))[0] + self.ft_size = struct.unpack('i', fin.read(4))[0] + hash_len = struct.unpack('i', fin.read(4))[0] + self.hash_size = hash_len / self.SIZEOF_UINT32_T + + def sss_nss_mc_hash(self, key): + input_key = key + '\0' + input_len = len(key) + 1 + + murmur_hash = pysss_murmur.murmurhash3(input_key, input_len, self.seed) + return murmur_hash % self.hash_size + + +def test_colliding_hashes(ldap_conn, sanity_rfc2307): + """ + Regression test for ticket: + https://github.com/SSSD/sssd/issues/4595 + """ + + first_user = 'user1' + + # initialize data in memcache + ent.assert_passwd_by_name( + first_user, + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + + mem_cache = MemoryCache(config.MCACHE_PATH + '/passwd') + + colliding_hash = mem_cache.sss_nss_mc_hash(first_user) + + while True: + # string for colliding hash need to be longer then data for user1 + # stored in memory cache (almost equivalent to: + # `getent passwd user1 | wc -c` ==> 45 + second_user = get_random_string(80) + val = mem_cache.sss_nss_mc_hash(second_user) + if val == colliding_hash: + break + + # add new user to LDAP + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user(second_user, 5001, 5001) + ldap_conn.add_s(ent_list[0][0], ent_list[0][1]) + + ent.assert_passwd_by_name( + second_user, + dict(name=second_user, passwd='*', uid=5001, gid=5001, + gecos='5001', shell='/bin/bash')) + + stop_sssd() + + # check that both users are stored in cache + ent.assert_passwd_by_name( + first_user, + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + + ent.assert_passwd_by_name( + second_user, + dict(name=second_user, passwd='*', uid=5001, gid=5001, + gecos='5001', shell='/bin/bash')) + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__removed_cache_without_invalidation') +def test_removed_mc(ldap_conn, sanity_rfc2307): + """ + Regression test for ticket: + https://fedorahosted.org/sssd/ticket/2726 + """ + + ent.assert_passwd_by_name( + 'user1', + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + ent.assert_passwd_by_uid( + 1001, + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + + ent.assert_group_by_name("group1", dict(name="group1", gid=2001)) + ent.assert_group_by_gid(2001, dict(name="group1", gid=2001)) + stop_sssd() + + # remove cache without invalidation + for path in os.listdir(config.MCACHE_PATH): + os.unlink(config.MCACHE_PATH + "/" + path) + + # sssd is stopped; so the memory cache should not be used + # in long living clients (py.test in this case) + with pytest.raises(KeyError): + pwd.getpwnam('user1') + with pytest.raises(KeyError): + pwd.getpwuid(1001) + + with pytest.raises(KeyError): + grp.getgrnam('group1') + with pytest.raises(KeyError): + grp.getgrgid(2001) + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__memcache_timeout_zero') +def test_mc_zero_timeout(ldap_conn, zero_timeout_rfc2307): + """ + Test that the memory cache is not created at all with memcache_timeout=0 + """ + # No memory cache files must be created + assert len(os.listdir(config.MCACHE_PATH)) == 0 + + ent.assert_passwd_by_name( + 'user1', + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + ent.assert_passwd_by_uid( + 1001, + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + + ent.assert_group_by_name("group1", dict(name="group1", gid=2001)) + ent.assert_group_by_gid(2001, dict(name="group1", gid=2001)) + stop_sssd() + + # sssd is stopped; so the memory cache should not be used + # in long living clients (py.test in this case) + with pytest.raises(KeyError): + pwd.getpwnam('user1') + with pytest.raises(KeyError): + pwd.getpwuid(1001) + + with pytest.raises(KeyError): + grp.getgrnam('group1') + with pytest.raises(KeyError): + grp.getgrgid(2001) + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__disabled_cache') +def test_disabled_mc(ldap_conn, disable_memcache_rfc2307): + ent.assert_passwd_by_name( + 'user1', + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + ent.assert_passwd_by_uid( + 1001, + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + + ent.assert_group_by_name("group1", dict(name="group1", gid=2001)) + ent.assert_group_by_gid(2001, dict(name="group1", gid=2001)) + + assert_user_gids_equal('user1', [2000, 2001]) + + stop_sssd() + + # sssd is stopped and the memory cache is disabled; + # so pytest should not be able to find anything + with pytest.raises(KeyError): + pwd.getpwnam('user1') + with pytest.raises(KeyError): + pwd.getpwuid(1001) + + with pytest.raises(KeyError): + grp.getgrnam('group1') + with pytest.raises(KeyError): + grp.getgrgid(2001) + + with pytest.raises(KeyError): + (res, errno, gids) = sssd_id.get_user_gids('user1') + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__disabled_passwd_getpwnam') +def test_disabled_passwd_mc(ldap_conn, disable_pwd_mc_rfc2307): + ent.assert_passwd_by_name( + 'user1', + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + ent.assert_passwd_by_uid( + 1001, + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + + assert_user_gids_equal('user1', [2000, 2001]) + + stop_sssd() + + # passwd cache is disabled + with pytest.raises(KeyError): + pwd.getpwnam('user1') + with pytest.raises(KeyError): + pwd.getpwuid(1001) + + # Initgroups looks up the user first, hence KeyError from the + # passwd database even if the initgroups cache is active. + with pytest.raises(KeyError): + (res, errno, gids) = sssd_id.get_user_gids('user1') + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__disabled_group') +def test_disabled_group_mc(ldap_conn, disable_grp_mc_rfc2307): + ent.assert_passwd_by_name( + 'user1', + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + ent.assert_passwd_by_uid( + 1001, + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + + ent.assert_group_by_name("group1", dict(name="group1", gid=2001)) + ent.assert_group_by_gid(2001, dict(name="group1", gid=2001)) + + assert_user_gids_equal('user1', [2000, 2001]) + + stop_sssd() + + # group cache is disabled, other caches should work + ent.assert_passwd_by_name( + 'user1', + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + ent.assert_passwd_by_uid( + 1001, + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + + with pytest.raises(KeyError): + grp.getgrnam('group1') + with pytest.raises(KeyError): + grp.getgrgid(2001) + + assert_user_gids_equal('user1', [2000, 2001]) + + +@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__disabled_intitgroups_getpwnam') +def test_disabled_initgr_mc(ldap_conn, disable_initgr_mc_rfc2307): + # Even if initgroups is disabled, passwd should work + ent.assert_passwd_by_name( + 'user1', + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + ent.assert_passwd_by_uid( + 1001, + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + + stop_sssd() + + ent.assert_passwd_by_name( + 'user1', + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + ent.assert_passwd_by_uid( + 1001, + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) diff --git a/src/tests/intg/test_netgroup.py b/src/tests/intg/test_netgroup.py new file mode 100644 index 0000000..56c742c --- /dev/null +++ b/src/tests/intg/test_netgroup.py @@ -0,0 +1,538 @@ +# +# Netgroup integration test +# +# Copyright (c) 2016 Red Hat, Inc. +# Author: Petr Cech <pcech@redhat.com> +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 stat +import signal +import subprocess +import time +import ldap +import ldap.modlist +import pytest + +import config +import ds_openldap +import ldap_ent +from util import unindent +from sssd_nss import NssReturnCode +from sssd_netgroup import get_sssd_netgroups + +LDAP_BASE_DN = "dc=example,dc=com" + + +@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 Exception: + ds_inst.teardown() + raise + request.addfinalizer(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(ldap_conn.unbind_s) + return ldap_conn + + +def create_ldap_entries(ldap_conn, ent_list=None): + """Add LDAP entries from ent_list""" + if ent_list is not None: + for entry in ent_list: + ldap_conn.add_s(entry[0], entry[1]) + + +def cleanup_ldap_entries(ldap_conn, ent_list=None): + """Remove LDAP entries added by create_ldap_entries""" + if ent_list is None: + for ou in ("Users", "Groups", "Netgroups", "Services", "Policies"): + for entry in ldap_conn.search_s(f"ou={ou}," + f"{ldap_conn.ds_inst.base_dn}", + ldap.SCOPE_ONELEVEL, + attrlist=[]): + ldap_conn.delete_s(entry[0]) + else: + for entry in ent_list: + ldap_conn.delete_s(entry[0]) + + +def create_ldap_cleanup(request, ldap_conn, ent_list=None): + """Add teardown for removing all user/group LDAP entries""" + request.addfinalizer(lambda: cleanup_ldap_entries(ldap_conn, ent_list)) + + +def create_ldap_fixture(request, ldap_conn, ent_list=None): + """Add LDAP entries and add teardown for removing them""" + create_ldap_entries(ldap_conn, ent_list) + create_ldap_cleanup(request, ldap_conn, ent_list) + + +SCHEMA_RFC2307_BIS = "rfc2307bis" + + +def format_basic_conf(ldap_conn, schema): + """Format a basic SSSD configuration""" + schema_conf = "ldap_schema = " + schema + "\n" + schema_conf += "ldap_group_object_class = groupOfNames\n" + return unindent("""\ + [sssd] + domains = LDAP + services = nss + disable_netlink = true + + [nss] + + [domain/LDAP] + {schema_conf} + id_provider = ldap + auth_provider = ldap + ldap_uri = {ldap_conn.ds_inst.ldap_url} + ldap_search_base = {ldap_conn.ds_inst.base_dn} + ldap_netgroup_search_base = ou=Netgroups,{ldap_conn.ds_inst.base_dn} + """).format(**locals()) + + +def create_conf_file(contents): + """Create sssd.conf with specified contents""" + conf = open(config.CONF_PATH, "w") + conf.write(contents) + conf.close() + os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR) + + +def cleanup_conf_file(): + """Remove sssd.conf, if it exists""" + if os.path.lexists(config.CONF_PATH): + os.unlink(config.CONF_PATH) + + +def create_conf_cleanup(request): + """Add teardown for removing sssd.conf""" + request.addfinalizer(cleanup_conf_file) + + +def create_conf_fixture(request, contents): + """ + Create sssd.conf with specified contents and add teardown for removing it + """ + create_conf_file(contents) + create_conf_cleanup(request) + + +def create_sssd_process(): + """Start the SSSD process""" + if subprocess.call(["sssd", "-D", "--logger=files"]) != 0: + raise Exception("sssd start failed") + + +def get_sssd_pid(): + pid_file = open(config.PIDFILE_PATH, "r") + pid = int(pid_file.read()) + return pid + + +def cleanup_sssd_process(): + """Stop the SSSD process and remove its state""" + try: + pid = get_sssd_pid() + os.kill(pid, signal.SIGTERM) + while True: + try: + os.kill(pid, signal.SIGCONT) + except OSError: + break + time.sleep(1) + except OSError: + 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) + + +def create_sssd_cleanup(request): + """Add teardown for stopping SSSD and removing its state""" + request.addfinalizer(cleanup_sssd_process) + + +def simulate_offline(): + pid = get_sssd_pid() + os.kill(pid, signal.SIGUSR1) + + +def create_sssd_fixture(request): + """Start SSSD and add teardown for stopping it and removing its state""" + create_sssd_process() + create_sssd_cleanup(request) + + +@pytest.fixture +def add_empty_netgroup(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + + ent_list.add_netgroup("empty_netgroup") + + create_ldap_fixture(request, ldap_conn, ent_list) + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_add_empty_netgroup(add_empty_netgroup): + """ + Adding empty netgroup. + """ + + res, _, netgroups = get_sssd_netgroups("empty_netgroup") + assert res == NssReturnCode.SUCCESS + assert netgroups == [] + + +@pytest.fixture +def add_tripled_netgroup(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + + ent_list.add_netgroup("tripled_netgroup", ["(host,user,domain)"]) + + ent_list.add_netgroup("adv_tripled_netgroup", ["(host1,user1,domain1)", + "(host2,user2,domain2)"]) + + ent_list.add_netgroup("tripled_netgroup_no_domain", ["(host,user,)"]) + + ent_list.add_netgroup("tripled_netgroup_no_user", ["(host,,domain)"]) + + ent_list.add_netgroup("tripled_netgroup_no_host", ["(,user,domain)"]) + + ent_list.add_netgroup("tripled_netgroup_none", ["(,,)"]) + + create_ldap_fixture(request, ldap_conn, ent_list) + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_add_tripled_netgroup(add_tripled_netgroup): + """ + Adding netgroup with triplet. + """ + + res, _, netgrps = get_sssd_netgroups("tripled_netgroup") + assert res == NssReturnCode.SUCCESS + assert netgrps == [("host", "user", "domain")] + + res, _, netgrps = get_sssd_netgroups("adv_tripled_netgroup") + assert res == NssReturnCode.SUCCESS + assert sorted(netgrps) == sorted([("host1", "user1", "domain1"), + ("host2", "user2", "domain2")]) + + res, _, netgrps = get_sssd_netgroups("tripled_netgroup_no_domain") + assert res == NssReturnCode.SUCCESS + assert netgrps == [("host", "user", "")] + + res, _, netgrps = get_sssd_netgroups("tripled_netgroup_no_user") + assert res == NssReturnCode.SUCCESS + assert netgrps == [("host", "", "domain")] + + res, _, netgrps = get_sssd_netgroups("tripled_netgroup_no_host") + assert res == NssReturnCode.SUCCESS + assert netgrps == [("", "user", "domain")] + + res, _, netgrps = get_sssd_netgroups("tripled_netgroup_none") + assert res == NssReturnCode.SUCCESS + assert netgrps == [("", "", "")] + + +@pytest.fixture +def add_mixed_netgroup(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + + ent_list.add_netgroup("mixed_netgroup1") + ent_list.add_netgroup("mixed_netgroup2", members=["mixed_netgroup1"]) + + ent_list.add_netgroup("mixed_netgroup3", ["(host1,user1,domain1)"]) + ent_list.add_netgroup("mixed_netgroup4", + ["(host2,user2,domain2)", "(host3,user3,domain3)"]) + + ent_list.add_netgroup("mixed_netgroup5", + ["(host4,user4,domain4)"], + ["mixed_netgroup1"]) + ent_list.add_netgroup("mixed_netgroup6", + ["(host5,user5,domain5)"], + ["mixed_netgroup2"]) + + ent_list.add_netgroup("mixed_netgroup7", members=["mixed_netgroup3"]) + ent_list.add_netgroup("mixed_netgroup8", + members=["mixed_netgroup3", "mixed_netgroup4"]) + + ent_list.add_netgroup("mixed_netgroup9", + ["(host6,user6,domain6)"], + ["mixed_netgroup3", "mixed_netgroup4"]) + + create_ldap_fixture(request, ldap_conn, ent_list) + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_add_mixed_netgroup(add_mixed_netgroup): + """ + Adding many netgroups of different type. + """ + + res, _, netgroups = get_sssd_netgroups("mixed_netgroup1") + assert res == NssReturnCode.SUCCESS + assert netgroups == [] + + res, _, netgroups = get_sssd_netgroups("mixed_netgroup2") + assert res == NssReturnCode.SUCCESS + assert netgroups == [] + + res, _, netgroups = get_sssd_netgroups("mixed_netgroup3") + assert res == NssReturnCode.SUCCESS + assert netgroups == [("host1", "user1", "domain1")] + + res, _, netgroups = get_sssd_netgroups("mixed_netgroup4") + assert res == NssReturnCode.SUCCESS + assert sorted(netgroups) == sorted([("host2", "user2", "domain2"), + ("host3", "user3", "domain3")]) + + res, _, netgroups = get_sssd_netgroups("mixed_netgroup5") + assert res == NssReturnCode.SUCCESS + assert netgroups == [("host4", "user4", "domain4")] + + res, _, netgroups = get_sssd_netgroups("mixed_netgroup6") + assert res == NssReturnCode.SUCCESS + assert netgroups == [("host5", "user5", "domain5")] + + res, _, netgroups = get_sssd_netgroups("mixed_netgroup7") + assert res == NssReturnCode.SUCCESS + assert netgroups == [("host1", "user1", "domain1")] + + res, _, netgroups = get_sssd_netgroups("mixed_netgroup8") + assert res == NssReturnCode.SUCCESS + assert sorted(netgroups) == sorted([("host1", "user1", "domain1"), + ("host2", "user2", "domain2"), + ("host3", "user3", "domain3")]) + + res, _, netgroups = get_sssd_netgroups("mixed_netgroup9") + assert res == NssReturnCode.SUCCESS + assert sorted(netgroups) == sorted([("host1", "user1", "domain1"), + ("host2", "user2", "domain2"), + ("host3", "user3", "domain3"), + ("host6", "user6", "domain6")]) + + +@pytest.fixture +def remove_step_by_step(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + + ent_list.add_netgroup("rm_empty_netgroup1", ["(host1,user1,domain1)"]) + ent_list.add_netgroup("rm_empty_netgroup2", + ["(host2,user2,domain2)"], + ["rm_empty_netgroup1"]) + + create_ldap_fixture(request, ldap_conn, ent_list) + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return ent_list + + +def test_remove_step_by_step(remove_step_by_step, ldap_conn): + """ + Removing netgroups step by step. + """ + + ent_list = remove_step_by_step + + res, _, netgroups = get_sssd_netgroups("rm_empty_netgroup1") + assert res == NssReturnCode.SUCCESS + assert netgroups == [('host1', 'user1', 'domain1')] + + res, _, netgroups = get_sssd_netgroups("rm_empty_netgroup2") + assert res == NssReturnCode.SUCCESS + assert sorted(netgroups) == sorted([('host1', 'user1', 'domain1'), + ('host2', 'user2', 'domain2')]) + + # removing of rm_empty_netgroup1 + ldap_conn.delete_s(ent_list[0][0]) + ent_list.remove(ent_list[0]) + + if subprocess.call(["sss_cache", "-N"]) != 0: + raise Exception("sssd_cache failed") + + res, _, netgroups = get_sssd_netgroups("rm_empty_netgroup1") + assert res == NssReturnCode.NOTFOUND + assert netgroups == [] + + res, _, netgroups = get_sssd_netgroups("rm_empty_netgroup2") + assert res == NssReturnCode.SUCCESS + assert netgroups == [('host2', 'user2', 'domain2')] + + # removing of rm_empty_netgroup2 + ldap_conn.delete_s(ent_list[0][0]) + ent_list.remove(ent_list[0]) + + if subprocess.call(["sss_cache", "-N"]) != 0: + raise Exception("sssd_cache failed") + + res, _, netgroups = get_sssd_netgroups("rm_empty_netgroup1") + assert res == NssReturnCode.NOTFOUND + assert netgroups == [] + + res, _, netgroups = get_sssd_netgroups("rm_empty_netgroup2") + assert res == NssReturnCode.NOTFOUND + assert netgroups == [] + + +@pytest.fixture +def removing_nested_netgroups(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + + ent_list.add_netgroup("t2841_netgroup1", ["(host1,user1,domain1)"]) + ent_list.add_netgroup("t2841_netgroup2", ["(host2,user2,domain2)"]) + ent_list.add_netgroup("t2841_netgroup3", + members=["t2841_netgroup1", "t2841_netgroup2"]) + + create_ldap_fixture(request, ldap_conn, ent_list) + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_removing_nested_netgroups(removing_nested_netgroups, ldap_conn): + """ + Regression test for ticket 2841. + https://fedorahosted.org/sssd/ticket/2841 + """ + + netgrp_dn = 'cn=t2841_netgroup3,ou=Netgroups,' + ldap_conn.ds_inst.base_dn + + res, _, netgroups = get_sssd_netgroups("t2841_netgroup1") + assert res == NssReturnCode.SUCCESS + assert netgroups == [('host1', 'user1', 'domain1')] + + res, _, netgroups = get_sssd_netgroups("t2841_netgroup2") + assert res == NssReturnCode.SUCCESS + assert netgroups == [('host2', 'user2', 'domain2')] + + res, _, netgroups = get_sssd_netgroups("t2841_netgroup3") + assert res == NssReturnCode.SUCCESS + assert sorted(netgroups) == sorted([('host1', 'user1', 'domain1'), + ('host2', 'user2', 'domain2')]) + + # removing of t2841_netgroup1 from t2841_netgroup3 + old = {'memberNisNetgroup': [b"t2841_netgroup1", b"t2841_netgroup2"]} + new = {'memberNisNetgroup': [b"t2841_netgroup2"]} + + ldif = ldap.modlist.modifyModlist(old, new) + ldap_conn.modify_s(netgrp_dn, ldif) + + if subprocess.call(["sss_cache", "-N"]) != 0: + raise Exception("sssd_cache failed") + + res, _, netgroups = get_sssd_netgroups("t2841_netgroup1") + assert res == NssReturnCode.SUCCESS + assert netgroups == [('host1', 'user1', 'domain1')] + + res, _, netgroups = get_sssd_netgroups("t2841_netgroup2") + assert res == NssReturnCode.SUCCESS + assert netgroups == [('host2', 'user2', 'domain2')] + + res, _, netgroups = get_sssd_netgroups("t2841_netgroup3") + assert res == NssReturnCode.SUCCESS + assert netgroups == [('host2', 'user2', 'domain2')] + + # removing of t2841_netgroup2 from t2841_netgroup3 + old = {'memberNisNetgroup': [b"t2841_netgroup2"]} + new = {'memberNisNetgroup': []} + + ldif = ldap.modlist.modifyModlist(old, new) + ldap_conn.modify_s(netgrp_dn, ldif) + + if subprocess.call(["sss_cache", "-N"]) != 0: + raise Exception("sssd_cache failed") + + res, _, netgroups = get_sssd_netgroups("t2841_netgroup1") + assert res == NssReturnCode.SUCCESS + assert netgroups == [('host1', 'user1', 'domain1')] + + res, _, netgroups = get_sssd_netgroups("t2841_netgroup2") + assert res == NssReturnCode.SUCCESS + assert netgroups == [('host2', 'user2', 'domain2')] + + res, _, netgroups = get_sssd_netgroups("t2841_netgroup3") + assert res == NssReturnCode.SUCCESS + assert netgroups == [] + + +def test_offline_netgroups(add_tripled_netgroup): + res, _, netgrps = get_sssd_netgroups("tripled_netgroup") + assert res == NssReturnCode.SUCCESS + assert netgrps == [("host", "user", "domain")] + + subprocess.check_call(["sss_cache", "-N"]) + + simulate_offline() + + res, _, netgrps = get_sssd_netgroups("tripled_netgroup") + assert res == NssReturnCode.SUCCESS + assert netgrps == [("host", "user", "domain")] + + +@pytest.fixture +def add_thread_test_netgroup(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + + triple_list = [] + for i in range(1, 999): + triple_list.append("(host1,user" + str(i) + ",domain1)") + ent_list.add_netgroup("ng1", triple_list) + + triple_list = [] + for i in range(1, 999): + triple_list.append("(host2,user" + str(i) + ",domain2)") + ent_list.add_netgroup("ng2", triple_list) + + create_ldap_fixture(request, ldap_conn, ent_list) + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_innetgr_with_threads(add_thread_test_netgroup): + + subprocess.check_call(["sss_netgroup_thread_test"]) diff --git a/src/tests/intg/test_pac_responder.py b/src/tests/intg/test_pac_responder.py new file mode 100644 index 0000000..640f547 --- /dev/null +++ b/src/tests/intg/test_pac_responder.py @@ -0,0 +1,129 @@ +# +# SSSD PAC responder tests +# +# Copyright (c) 2017 Red Hat, Inc. +# Author: Sumit Bose <sbose@redhat.com> +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 stat +import time +import config +import signal +import subprocess +import pytest +from util import unindent + + +def have_sssd_responder(responder_name): + responder_path = os.path.join(config.LIBEXEC_PATH, "sssd", responder_name) + + return os.access(responder_path, os.X_OK) + + +def stop_sssd(): + with open(config.PIDFILE_PATH, "r") as pid_file: + pid = int(pid_file.read()) + os.kill(pid, signal.SIGTERM) + while True: + try: + os.kill(pid, signal.SIGCONT) + except OSError: + break + time.sleep(1) + + +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 create_sssd_fixture(request): + """Start sssd and add teardown for stopping it and removing state""" + if subprocess.call(["sssd", "-D", "--logger=files"]) != 0: + raise Exception("sssd start failed") + + def teardown(): + try: + stop_sssd() + except Exception: + 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) + + +@pytest.fixture +def files_domain_only(request): + conf = unindent("""\ + [sssd] + services = nss, pac + domains = files + + [nss] + memcache_timeout = 0 + + [domain/files] + id_provider = proxy + proxy_lib_name = files + auth_provider = none + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +@pytest.fixture +def sssd_pac_test_client(request): + path = os.path.join(config.ABS_BUILDDIR, + "..", "..", "..", "sssd_pac_test_client") + if os.access(path, os.X_OK): + return path + + return None + + +def timeout_handler(signum, frame): + raise Exception("Timeout") + + +@pytest.mark.skipif(not have_sssd_responder("sssd_pac"), + reason="No PAC responder, skipping") +def test_multithreaded_pac_client(files_domain_only, sssd_pac_test_client): + """ + Test for ticket + https://github.com/SSSD/sssd/issues/4544 + """ + + if not sssd_pac_test_client: + pytest.skip("The sssd_pac_test_client is not available, skipping test") + + signal.signal(signal.SIGALRM, timeout_handler) + signal.alarm(10) + + try: + subprocess.check_call(sssd_pac_test_client) + except Exception: + # cancel alarm + signal.alarm(0) + raise Exception("sssd_pac_test_client failed") + + signal.alarm(0) diff --git a/src/tests/intg/test_pam_responder.py b/src/tests/intg/test_pam_responder.py new file mode 100644 index 0000000..1fc3937 --- /dev/null +++ b/src/tests/intg/test_pam_responder.py @@ -0,0 +1,926 @@ +# +# Test for the PAM responder +# +# Copyright (c) 2018 Red Hat, Inc. +# Author: Sumit Bose <sbose@redhat.com> +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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/>. +# + +""" +Tests for the PAM responder +""" +import os +import stat +import signal +import errno +import subprocess +import time + +import config +import intg.ds_openldap +import kdc + +import pytest + +from intg.util import unindent + +LDAP_BASE_DN = "dc=example,dc=com" + +def provider_list(): + if os.environ['FILES_PROVIDER'] == "enabled": + return ('files', 'files_with_policy', 'proxy') + else: + # The comma is required to indicate a list with the string 'proxy' as + # only item, without it the string 'proxy' will be interpreted as list + # with five letters. + return ('proxy',) + + +class provider_switch: + def __init__(self, p): + if p == 'files': + self.p = "id_provider = files\nfallback_to_nss = False\n" + elif p == 'files_with_policy': + self.p = "id_provider = files\nfallback_to_nss = False\nlocal_auth_policy = enable:smartcard\n" + elif p == 'proxy': + self.p = "id_provider = proxy\nlocal_auth_policy = only\nproxy_lib_name = call\n" + elif p == 'proxy_password': + self.p = "id_provider = proxy\nproxy_lib_name = call\nproxy_pam_target = sssd-shadowutils\n" + elif p == 'proxy_password_with_sc': + self.p = "id_provider = proxy\nlocal_auth_policy = enable:smartcard\nproxy_lib_name = call\nproxy_pam_target = sssd-shadowutils\n" + else: + self.p = none + + +@pytest.fixture(scope="module") +def ad_inst(request): + """Fake AD server instance fixture""" + instance = intg.ds_openldap.FakeAD( + config.PREFIX, 10389, LDAP_BASE_DN, + "cn=admin", "Secret123" + ) + + try: + instance.setup() + except Exception: + instance.teardown() + raise + request.addfinalizer(instance.teardown) + return instance + + +@pytest.fixture(scope="module") +def ldap_conn(request, ad_inst): + """LDAP server connection fixture""" + ldap_conn = ad_inst.bind() + ldap_conn.ad_inst = ad_inst + request.addfinalizer(ldap_conn.unbind_s) + return ldap_conn + + +def format_basic_conf(ldap_conn): + """Format a basic SSSD configuration""" + return unindent("""\ + [sssd] + domains = FakeAD + services = pam, nss + + [nss] + + [pam] + debug_level = 10 + + [domain/FakeAD] + debug_level = 10 + ldap_search_base = {ldap_conn.ad_inst.base_dn} + ldap_referrals = false + + id_provider = ldap + auth_provider = ldap + chpass_provider = ldap + access_provider = ldap + + ldap_uri = {ldap_conn.ad_inst.ldap_url} + ldap_default_bind_dn = {ldap_conn.ad_inst.admin_dn} + ldap_default_authtok_type = password + ldap_default_authtok = {ldap_conn.ad_inst.admin_pw} + + ldap_schema = ad + ldap_id_mapping = true + ldap_idmap_default_domain_sid = S-1-5-21-1305200397-2901131868-73388776 + case_sensitive = False + + [prompting/password] + password_prompt = My global prompt + + [prompting/password/pam_sss_alt_service] + password_prompt = My alt service prompt + """).format(**locals()) + + +USER1 = dict(name='user1', passwd='x', uid=10001, gid=20001, + gecos='User for tests', + dir='/home/user1', + shell='/bin/bash') + +USER2 = dict(name='user2', passwd='x', uid=10002, gid=20002, + gecos='User with no Smartcard mapping', + dir='/home/user2', + shell='/bin/bash') + + +def format_pam_cert_auth_conf(config, provider): + """Format a basic SSSD configuration""" + return unindent("""\ + [sssd] + debug_level = 10 + domains = auth_only + services = pam, nss + + [nss] + debug_level = 10 + + [pam] + pam_cert_auth = True + pam_p11_allowed_services = +pam_sss_service, +pam_sss_sc_required, \ + +pam_sss_try_sc, +pam_sss_allow_missing_name + pam_cert_db_path = {config.PAM_CERT_DB_PATH} + p11_uri = pkcs11:manufacturer=SoftHSM%20project; \ + token=SSSD%20Test%20Token + p11_child_timeout = 5 + p11_wait_for_card_timeout = 5 + debug_level = 10 + + [domain/auth_only] + debug_level = 10 + {provider.p} + + [certmap/auth_only/user1] + matchrule = <SUBJECT>.*CN=SSSD test cert 0001.* + """).format(**locals()) + + +def format_pam_cert_auth_conf_name_format(config, provider): + """Format SSSD configuration with full_name_format""" + return unindent("""\ + [sssd] + debug_level = 10 + domains = auth_only + services = pam, nss + + [nss] + debug_level = 10 + + [pam] + pam_cert_auth = True + pam_p11_allowed_services = +pam_sss_service, +pam_sss_sc_required, \ + +pam_sss_try_sc, +pam_sss_allow_missing_name + pam_cert_db_path = {config.PAM_CERT_DB_PATH} + p11_uri = pkcs11:manufacturer=SoftHSM%20project; \ + token=SSSD%20Test%20Token + p11_child_timeout = 5 + p11_wait_for_card_timeout = 5 + debug_level = 10 + + [domain/auth_only] + use_fully_qualified_names = True + full_name_format = %2$s\\%1$s + debug_level = 10 + {provider.p} + + [certmap/auth_only/user1] + matchrule = <SUBJECT>.*CN=SSSD test cert 0001.* + """).format(**locals()) + + +def format_pam_krb5_auth(config, kdc_instance): + """Format SSSD configuration for krb5 authentication""" + return unindent("""\ + [sssd] + debug_level = 10 + domains = krb5_auth + services = pam, nss + + [nss] + debug_level = 10 + + [pam] + debug_level = 10 + + [domain/krb5_auth] + debug_level = 10 + id_provider = proxy + proxy_lib_name = call + auth_provider = krb5 + + krb5_realm = PAMKRB5TEST + krb5_server = localhost:{kdc_instance.kdc_port} + """).format(**locals()) + + +def format_pam_krb5_auth_domains(config, kdc_instance): + """Format SSSD configuration for krb5 authentication""" + return unindent("""\ + [sssd] + debug_level = 10 + domains = wrong.dom1, wrong.dom2, krb5_auth, wrong.dom3 + services = pam, nss + + [nss] + debug_level = 10 + + [pam] + debug_level = 10 + + [domain/wrong.dom1] + debug_level = 10 + id_provider = proxy + proxy_lib_name = call + auth_provider = krb5 + + krb5_realm = WRONG1REALM + krb5_server = localhost:{kdc_instance.kdc_port} + + [domain/wrong.dom2] + debug_level = 10 + id_provider = proxy + proxy_lib_name = call + auth_provider = krb5 + + krb5_realm = WRONG2REALM + krb5_server = localhost:{kdc_instance.kdc_port} + + [domain/wrong.dom3] + debug_level = 10 + id_provider = proxy + proxy_lib_name = call + auth_provider = krb5 + + krb5_realm = WRONG3REALM + krb5_server = localhost:{kdc_instance.kdc_port} + + [domain/krb5_auth] + debug_level = 10 + id_provider = proxy + proxy_lib_name = call + auth_provider = krb5 + + krb5_realm = PAMKRB5TEST + krb5_server = localhost:{kdc_instance.kdc_port} + """).format(**locals()) + + +def create_conf_file(contents): + """Create sssd.conf with specified contents""" + conf = open(config.CONF_PATH, "w") + conf.write(contents) + conf.close() + os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR) + + +def create_conf_fixture(request, contents): + """ + Create sssd.conf with specified contents and add teardown for removing it + """ + create_conf_file(contents) + + def cleanup_conf_file(): + """Remove sssd.conf, if it exists""" + if os.path.lexists(config.CONF_PATH): + os.unlink(config.CONF_PATH) + + request.addfinalizer(cleanup_conf_file) + + +def create_sssd_process(krb5_conf_path=None): + """Start the SSSD process""" + my_env = os.environ.copy() + my_env["SSS_FILES_PASSWD"] = os.environ["NSS_WRAPPER_PASSWD"] + my_env["SSS_FILES_GROUP"] = os.environ["NSS_WRAPPER_GROUP"] + if krb5_conf_path is not None: + my_env['KRB5_CONFIG'] = krb5_conf_path + if subprocess.call(["sssd", "-D", "--logger=files"], env=my_env) != 0: + raise Exception("sssd start failed") + + +def cleanup_sssd_process(): + """Stop the SSSD process and remove its state""" + try: + with open(config.PIDFILE_PATH, "r") as pid_file: + pid = int(pid_file.read()) + os.kill(pid, signal.SIGTERM) + while True: + try: + os.kill(pid, signal.SIGCONT) + except OSError: + break + time.sleep(1) + except OSError: + 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) + + # make sure that the indicator file is removed during shutdown + try: + assert not os.stat(config.PUBCONF_PATH + "/pam_preauth_available") + except OSError as ex: + if ex.errno != errno.ENOENT: + raise ex + + +def create_sssd_fixture(request, krb5_conf_path=None): + """Start SSSD and add teardown for stopping it and removing its state""" + create_sssd_process(krb5_conf_path) + request.addfinalizer(cleanup_sssd_process) + + +@pytest.fixture +def simple_pam_cert_auth(request, passwd_ops_setup): + """Setup SSSD with pam_cert_auth=True""" + config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH'] + conf = format_pam_cert_auth_conf(config, provider_switch(request.param)) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + passwd_ops_setup.useradd(**USER1) + passwd_ops_setup.useradd(**USER2) + return None + + +@pytest.fixture +def simple_pam_cert_auth_no_cert(request, passwd_ops_setup): + """Setup SSSD with pam_cert_auth=True""" + config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH'] + + old_softhsm2_conf = os.environ['SOFTHSM2_CONF'] + del os.environ['SOFTHSM2_CONF'] + + conf = format_pam_cert_auth_conf(config, provider_switch(request.param)) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + os.environ['SOFTHSM2_CONF'] = old_softhsm2_conf + + passwd_ops_setup.useradd(**USER1) + passwd_ops_setup.useradd(**USER2) + + return None + + +@pytest.fixture +def simple_pam_cert_auth_name_format(request, passwd_ops_setup): + """Setup SSSD with pam_cert_auth=True and full_name_format""" + config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH'] + conf = format_pam_cert_auth_conf_name_format(config, provider_switch(request.param)) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + passwd_ops_setup.useradd(**USER1) + passwd_ops_setup.useradd(**USER2) + return None + +@pytest.mark.parametrize('simple_pam_cert_auth', provider_list(), indirect=True) +def test_preauth_indicator(simple_pam_cert_auth): + """Check if preauth indicator file is created""" + statinfo = os.stat(config.PUBCONF_PATH + "/pam_preauth_available") + assert stat.S_ISREG(statinfo.st_mode) + + +@pytest.fixture +def pam_prompting_config(request, ldap_conn): + """Setup SSSD with PAM prompting config""" + conf = format_basic_conf(ldap_conn) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_password_prompting_config_global(ldap_conn, pam_prompting_config, + env_for_sssctl): + """Check global change of the password prompt""" + + sssctl = subprocess.Popen(["sssctl", "user-checks", "user1_dom1-19661", + "--action=auth", "--service=pam_sss_service"], + universal_newlines=True, + env=env_for_sssctl, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + try: + out, err = sssctl.communicate(input="111") + except Exception: + sssctl.kill() + out, err = sssctl.communicate() + + sssctl.stdin.close() + sssctl.stdout.close() + + if sssctl.wait() != 0: + raise Exception("sssctl failed") + + assert err.find("My global prompt") != -1 + + +def test_password_prompting_config_srv(ldap_conn, pam_prompting_config, + env_for_sssctl): + """Check change of the password prompt for dedicated service""" + + sssctl = subprocess.Popen(["sssctl", "user-checks", "user1_dom1-19661", + "--action=auth", + "--service=pam_sss_alt_service"], + universal_newlines=True, + env=env_for_sssctl, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + try: + out, err = sssctl.communicate(input="111") + except Exception: + sssctl.kill() + out, err = sssctl.communicate() + + sssctl.stdin.close() + sssctl.stdout.close() + + if sssctl.wait() != 0: + raise Exception("sssctl failed") + + assert err.find("My alt service prompt") != -1 + + +@pytest.fixture +def env_for_sssctl(request): + pwrap_runtimedir = os.getenv("PAM_WRAPPER_SERVICE_DIR") + if pwrap_runtimedir is None: + raise ValueError("The PAM_WRAPPER_SERVICE_DIR variable is unset\n") + + env_for_sssctl = os.environ.copy() + env_for_sssctl['PAM_WRAPPER'] = "1" + env_for_sssctl['SSSD_INTG_PEER_UID'] = "0" + env_for_sssctl['SSSD_INTG_PEER_GID'] = "0" + env_for_sssctl['LD_PRELOAD'] += ':' + os.environ['PAM_WRAPPER_PATH'] + + return env_for_sssctl + + +@pytest.mark.parametrize('simple_pam_cert_auth', provider_list(), indirect=True) +def test_sc_auth_wrong_pin(simple_pam_cert_auth, env_for_sssctl): + + sssctl = subprocess.Popen(["sssctl", "user-checks", "user1", + "--action=auth", "--service=pam_sss_service"], + universal_newlines=True, + env=env_for_sssctl, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + try: + out, err = sssctl.communicate(input="111") + except Exception: + sssctl.kill() + out, err = sssctl.communicate() + + sssctl.stdin.close() + sssctl.stdout.close() + + if sssctl.wait() != 0: + raise Exception("sssctl failed") + + assert err.find("pam_authenticate for user [user1]: " + "Authentication failure") != -1 + + +@pytest.mark.parametrize('simple_pam_cert_auth', provider_list(), indirect=True) +def test_sc_auth(simple_pam_cert_auth, env_for_sssctl): + + sssctl = subprocess.Popen(["sssctl", "user-checks", "user1", + "--action=auth", "--service=pam_sss_service"], + universal_newlines=True, + env=env_for_sssctl, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + try: + out, err = sssctl.communicate(input="123456") + except Exception: + sssctl.kill() + out, err = sssctl.communicate() + + sssctl.stdin.close() + sssctl.stdout.close() + + if sssctl.wait() != 0: + raise Exception("sssctl failed") + + assert err.find("pam_authenticate for user [user1]: Success") != -1 + + +@pytest.mark.parametrize('simple_pam_cert_auth', ['proxy_password'], indirect=True) +def test_sc_proxy_password_fallback(simple_pam_cert_auth, env_for_sssctl): + """ + Check that there will be a password prompt if another proxy auth module is + configured and Smartcard authentication is not allowed but a Smartcard is + present. + """ + + sssctl = subprocess.Popen(["sssctl", "user-checks", "user1", + "--action=auth", "--service=pam_sss_service"], + universal_newlines=True, + env=env_for_sssctl, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + out, err = sssctl.communicate() + + sssctl.stdin.close() + sssctl.stdout.close() + + assert err.find("Password:") != -1 + + +@pytest.mark.parametrize('simple_pam_cert_auth', ['proxy_password_with_sc'], + indirect=True) +def test_sc_proxy_no_password_fallback(simple_pam_cert_auth, env_for_sssctl): + """ + Use the same environ as for test_sc_proxy_password_fallback but now allow + local Smartcard authentication. Here we expect that there will be a prompt + for the Smartcard PIN and that Smartcard authentication is successful. + """ + + sssctl = subprocess.Popen(["sssctl", "user-checks", "user1", + "--action=auth", "--service=pam_sss_service"], + universal_newlines=True, + env=env_for_sssctl, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + try: + out, err = sssctl.communicate(input="123456") + except Exception: + sssctl.kill() + out, err = sssctl.communicate() + + sssctl.stdin.close() + sssctl.stdout.close() + + if sssctl.wait() != 0: + raise Exception("sssctl failed") + + assert err.find("pam_authenticate for user [user1]: Success") != -1 + + +@pytest.mark.parametrize('simple_pam_cert_auth', provider_list(), indirect=True) +def test_require_sc_auth(simple_pam_cert_auth, env_for_sssctl): + + sssctl = subprocess.Popen(["sssctl", "user-checks", "user1", + "--action=auth", + "--service=pam_sss_sc_required"], + universal_newlines=True, + env=env_for_sssctl, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + try: + out, err = sssctl.communicate(input="123456") + except Exception: + sssctl.kill() + out, err = sssctl.communicate() + + sssctl.stdin.close() + sssctl.stdout.close() + + if sssctl.wait() != 0: + raise Exception("sssctl failed") + + assert err.find("pam_authenticate for user [user1]: Success") != -1 + + +@pytest.mark.parametrize('simple_pam_cert_auth_no_cert', provider_list(), indirect=True) +def test_require_sc_auth_no_cert(simple_pam_cert_auth_no_cert, env_for_sssctl): + + # We have to wait about 20s before the command returns because there will + # be 2 run since retry=1 in the PAM configuration and both + # p11_child_timeout and p11_wait_for_card_timeout are 5s in sssd.conf, + # so 2*(5+5)=20. */ + start_time = time.time() + sssctl = subprocess.Popen(["sssctl", "user-checks", "user1", + "--action=auth", + "--service=pam_sss_sc_required"], + universal_newlines=True, + env=env_for_sssctl, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + try: + out, err = sssctl.communicate(input="123456") + except Exception: + sssctl.kill() + out, err = sssctl.communicate() + + sssctl.stdin.close() + sssctl.stdout.close() + + if sssctl.wait() != 0: + raise Exception("sssctl failed") + + end_time = time.time() + assert end_time > start_time and \ + (end_time - start_time) >= 20 and \ + (end_time - start_time) < 40 + assert out.find("Please insert smart card\nPlease insert smart card") != -1 + assert err.find("pam_authenticate for user [user1]: Authentication " + "service cannot retrieve authentication info") != -1 + + +@pytest.mark.parametrize('simple_pam_cert_auth', provider_list(), indirect=True) +def test_try_sc_auth_no_map(simple_pam_cert_auth, env_for_sssctl): + + sssctl = subprocess.Popen(["sssctl", "user-checks", "user2", + "--action=auth", + "--service=pam_sss_try_sc"], + universal_newlines=True, + env=env_for_sssctl, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + try: + out, err = sssctl.communicate(input="123456") + except Exception: + sssctl.kill() + out, err = sssctl.communicate() + + sssctl.stdin.close() + sssctl.stdout.close() + + if sssctl.wait() != 0: + raise Exception("sssctl failed") + + assert err.find("pam_authenticate for user [user2]: Authentication " + "service cannot retrieve authentication info") != -1 + + +@pytest.mark.parametrize('simple_pam_cert_auth', provider_list(), indirect=True) +def test_try_sc_auth(simple_pam_cert_auth, env_for_sssctl): + + sssctl = subprocess.Popen(["sssctl", "user-checks", "user1", + "--action=auth", + "--service=pam_sss_try_sc"], + universal_newlines=True, + env=env_for_sssctl, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + try: + out, err = sssctl.communicate(input="123456") + except Exception: + sssctl.kill() + out, err = sssctl.communicate() + + sssctl.stdin.close() + sssctl.stdout.close() + + if sssctl.wait() != 0: + raise Exception("sssctl failed") + + assert err.find("pam_authenticate for user [user1]: Success") != -1 + + +@pytest.mark.parametrize('simple_pam_cert_auth', provider_list(), indirect=True) +def test_try_sc_auth_root(simple_pam_cert_auth, env_for_sssctl): + """ + Make sure pam_sss returns PAM_AUTHINFO_UNAVAIL even for root if + try_cert_auth is set. + """ + sssctl = subprocess.Popen(["sssctl", "user-checks", "root", + "--action=auth", + "--service=pam_sss_try_sc"], + universal_newlines=True, + env=env_for_sssctl, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + try: + out, err = sssctl.communicate(input="123456") + except Exception: + sssctl.kill() + out, err = sssctl.communicate() + + sssctl.stdin.close() + sssctl.stdout.close() + + if sssctl.wait() != 0: + raise Exception("sssctl failed") + + assert err.find("pam_authenticate for user [root]: Authentication " + "service cannot retrieve authentication info") != -1 + + +@pytest.mark.parametrize('simple_pam_cert_auth', provider_list(), indirect=True) +def test_sc_auth_missing_name(simple_pam_cert_auth, env_for_sssctl): + """ + Test pam_sss allow_missing_name feature. + """ + + sssctl = subprocess.Popen(["sssctl", "user-checks", "", + "--action=auth", + "--service=pam_sss_allow_missing_name"], + universal_newlines=True, + env=env_for_sssctl, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + try: + out, err = sssctl.communicate(input="123456") + except Exception: + sssctl.kill() + out, err = sssctl.communicate() + + sssctl.stdin.close() + sssctl.stdout.close() + + if sssctl.wait() != 0: + raise Exception("sssctl failed") + + assert err.find("pam_authenticate for user [user1]: Success") != -1 + + +@pytest.mark.parametrize('simple_pam_cert_auth', provider_list(), indirect=True) +def test_sc_auth_missing_name_whitespace(simple_pam_cert_auth, env_for_sssctl): + """ + Test pam_sss allow_missing_name feature. + """ + + sssctl = subprocess.Popen(["sssctl", "user-checks", " ", + "--action=auth", + "--service=pam_sss_allow_missing_name"], + universal_newlines=True, + env=env_for_sssctl, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + try: + out, err = sssctl.communicate(input="123456") + except Exception: + sssctl.kill() + out, err = sssctl.communicate() + + sssctl.stdin.close() + sssctl.stdout.close() + + if sssctl.wait() != 0: + raise Exception("sssctl failed") + + assert err.find("pam_authenticate for user [user1]: Success") != -1 + + +@pytest.mark.parametrize('simple_pam_cert_auth_name_format', provider_list(), indirect=True) +def test_sc_auth_name_format(simple_pam_cert_auth_name_format, env_for_sssctl): + """ + Test that full_name_format is respected with pam_sss allow_missing_name + option. + """ + + sssctl = subprocess.Popen(["sssctl", "user-checks", "", + "--action=auth", + "--service=pam_sss_allow_missing_name"], + universal_newlines=True, + env=env_for_sssctl, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + try: + out, err = sssctl.communicate(input="123456") + except Exception: + sssctl.kill() + out, err = sssctl.communicate() + + sssctl.stdin.close() + sssctl.stdout.close() + + if sssctl.wait() != 0: + raise Exception("sssctl failed") + + assert err.find(r"pam_authenticate for user [auth_only\user1]: " + "Success") != -1 + + +@pytest.fixture +def kdc_instance(request): + """Kerberos server instance fixture""" + kdc_instance = kdc.KDC(config.PREFIX, "PAMKRB5TEST") + try: + kdc_instance.set_up() + kdc_instance.start_kdc() + except Exception: + kdc_instance.teardown() + raise + request.addfinalizer(kdc_instance.teardown) + return kdc_instance + + +@pytest.fixture +def setup_krb5(request, kdc_instance, passwd_ops_setup): + """ + Setup SSSD for Kerberos authentication with 2 users with different + passwords + """ + conf = format_pam_krb5_auth(config, kdc_instance) + create_conf_fixture(request, conf) + create_sssd_fixture(request, kdc_instance.krb5_conf_path) + + passwd_ops_setup.useradd(**USER1) + passwd_ops_setup.useradd(**USER2) + kdc_instance.add_principal("user1", "Secret123User1") + kdc_instance.add_principal("user2", "Secret123User2") + return None + + +def test_krb5_auth(setup_krb5, env_for_sssctl): + """ + Test basic Kerberos authentication, check for authentication failure when + a wrong password is used + """ + sssctl = subprocess.Popen(["sssctl", "user-checks", "user1", + "--action=auth", + "--service=pam_sss_service"], + universal_newlines=True, + env=env_for_sssctl, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + try: + out, err = sssctl.communicate(input="Secret123User1") + except Exception: + sssctl.kill() + out, err = sssctl.communicate() + + sssctl.stdin.close() + sssctl.stdout.close() + + if sssctl.wait() != 0: + raise Exception("sssctl failed") + + assert err.find(r"pam_authenticate for user [user1]: Success") != -1 + + sssctl = subprocess.Popen(["sssctl", "user-checks", "user2", + "--action=auth", + "--service=pam_sss_service"], + universal_newlines=True, + env=env_for_sssctl, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + try: + out, err = sssctl.communicate(input="Secret123User1") + except Exception: + sssctl.kill() + out, err = sssctl.communicate() + + sssctl.stdin.close() + sssctl.stdout.close() + + if sssctl.wait() != 0: + raise Exception("sssctl failed") + + assert err.find(r"pam_authenticate for user [user2]: " + "Authentication failure") != -1 + + +@pytest.fixture +def setup_krb5_domains(request, kdc_instance, passwd_ops_setup): + """ + Setup SSSD for Kerberos authentication with 2 users with different + passwords and multiple domains configured in sssd.conf + """ + conf = format_pam_krb5_auth_domains(config, kdc_instance) + create_conf_fixture(request, conf) + create_sssd_fixture(request, kdc_instance.krb5_conf_path) + + passwd_ops_setup.useradd(**USER1) + passwd_ops_setup.useradd(**USER2) + kdc_instance.add_principal("user1", "Secret123User1") + kdc_instance.add_principal("user2", "Secret123User2") + return None + + +def test_krb5_auth_domains(setup_krb5_domains, env_for_sssctl): + """ + Test basic Kerberos authentication with pam_sss 'domains' option, make + sure not-matching domains are skipped even if the user exists in that + domain + """ + sssctl = subprocess.Popen(["sssctl", "user-checks", "user1", + "--action=auth", + "--service=pam_sss_domains"], + universal_newlines=True, + env=env_for_sssctl, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + try: + out, err = sssctl.communicate(input="Secret123User1") + except Exception: + sssctl.kill() + out, err = sssctl.communicate() + + sssctl.stdin.close() + sssctl.stdout.close() + + if sssctl.wait() != 0: + raise Exception("sssctl failed") + + assert err.find(r"pam_authenticate for user [user1]: Success") != -1 diff --git a/src/tests/intg/test_pysss_nss_idmap.py b/src/tests/intg/test_pysss_nss_idmap.py new file mode 100644 index 0000000..fd71420 --- /dev/null +++ b/src/tests/intg/test_pysss_nss_idmap.py @@ -0,0 +1,360 @@ +# +# LDAP integration test +# +# Copyright (c) 2017 Red Hat, Inc. +# Author: Lukas Slebodnik <lslebodn@redhat.com> +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 stat +import pwd +import grp +import signal +import subprocess +import time +import pytest +import ldb +import pysss_nss_idmap + +import config +import ds_openldap + +from .util import unindent + +LDAP_BASE_DN = "dc=example,dc=com" + + +@pytest.fixture(scope="module") +def ad_inst(request): + """Fake AD server instance fixture""" + instance = ds_openldap.FakeAD( + config.PREFIX, 10389, LDAP_BASE_DN, + "cn=admin", "Secret123" + ) + + try: + instance.setup() + except Exception: + instance.teardown() + raise + request.addfinalizer(instance.teardown) + return instance + + +@pytest.fixture(scope="module") +def ldap_conn(request, ad_inst): + """LDAP server connection fixture""" + ldap_conn = ad_inst.bind() + ldap_conn.ad_inst = ad_inst + request.addfinalizer(ldap_conn.unbind_s) + return ldap_conn + + +def format_basic_conf(ldap_conn, ignore_unreadable_refs): + """Format a basic SSSD configuration""" + + ignore_unreadable_refs_conf = "false" + if ignore_unreadable_refs: + ignore_unreadable_refs_conf = "true" + + return unindent("""\ + [sssd] + domains = FakeAD + services = nss + + [nss] + + [pam] + + [domain/FakeAD] + ldap_search_base = {ldap_conn.ad_inst.base_dn} + ldap_referrals = false + + id_provider = ldap + auth_provider = ldap + chpass_provider = ldap + access_provider = ldap + + ldap_uri = {ldap_conn.ad_inst.ldap_url} + ldap_default_bind_dn = {ldap_conn.ad_inst.admin_dn} + ldap_default_authtok_type = password + ldap_default_authtok = {ldap_conn.ad_inst.admin_pw} + + ldap_schema = ad + ldap_id_mapping = true + ldap_idmap_default_domain_sid = S-1-5-21-1305200397-2901131868-73388776 + case_sensitive = False + + ldap_ignore_unreadable_references = {ignore_unreadable_refs_conf} + """).format(**locals()) + + +def create_conf_file(contents): + """Create sssd.conf with specified contents""" + conf = open(config.CONF_PATH, "w") + conf.write(contents) + conf.close() + os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR) + + +def create_conf_fixture(request, contents): + """ + Create sssd.conf with specified contents and add teardown for removing it + """ + create_conf_file(contents) + + def cleanup_conf_file(): + """Remove sssd.conf, if it exists""" + if os.path.lexists(config.CONF_PATH): + os.unlink(config.CONF_PATH) + + request.addfinalizer(cleanup_conf_file) + + +def create_sssd_process(): + """Start the SSSD process""" + if subprocess.call(["sssd", "-D", "--logger=files"]) != 0: + raise Exception("sssd start failed") + + +def cleanup_sssd_process(): + """Stop the SSSD process and remove its state""" + try: + with open(config.PIDFILE_PATH, "r") as pid_file: + pid = int(pid_file.read()) + os.kill(pid, signal.SIGTERM) + while True: + try: + os.kill(pid, signal.SIGCONT) + except OSError: + break + time.sleep(1) + except OSError: + 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) + + +def create_sssd_fixture(request): + """Start SSSD and add teardown for stopping it and removing its state""" + create_sssd_process() + request.addfinalizer(cleanup_sssd_process) + + +def sysdb_sed_domainid(domain_name, domain_id): + sssd_cache = "{0}/cache_{1}.ldb".format(config.DB_PATH, domain_name) + domain_ldb = ldb.Ldb(sssd_cache) + + msg = ldb.Message() + msg.dn = ldb.Dn(domain_ldb, "cn=sysdb") + msg["cn"] = "sysdb" + msg["description"] = "base object" + msg["version"] = "0.17" + domain_ldb.add(msg) + + # Set domainID for fake AD domain + msg = ldb.Message() + msg.dn = ldb.Dn(domain_ldb, "cn={0},cn=sysdb".format(domain_name)) + msg["cn"] = domain_name + msg["domainID"] = domain_id + msg["distinguishedName"] = "cn={0},cn=sysdb".format(domain_name) + domain_ldb.add(msg) + + msg = ldb.Message() + msg.dn = ldb.Dn(domain_ldb, "@ATTRIBUTES") + msg["distinguishedName"] = "@ATTRIBUTES" + for attr in ['cn', 'dc', 'dn', 'objectclass', 'originalDN', + 'userPrincipalName']: + msg[attr] = "CASE_INSENSITIVE" + domain_ldb.add(msg) + + msg = ldb.Message() + msg.dn = ldb.Dn(domain_ldb, "@INDEXLIST") + msg["distinguishedName"] = "@INDEXLIST" + msg["@IDXONE"] = "1" + for attr in ['cn', 'objectclass', 'member', 'memberof', 'name', + 'uidNumber', 'gidNumber', 'lastUpdate', 'dataExpireTimestamp', + 'originalDN', 'nameAlias', 'servicePort', 'serviceProtocol', + 'sudoUser', 'sshKnownHostsExpire', 'objectSIDString']: + msg["@IDXATTR"] = attr + domain_ldb.add(msg) + + msg = ldb.Message() + msg.dn = ldb.Dn(domain_ldb, "@MODULES") + msg["distinguishedName"] = "@MODULES" + msg["@LIST"] = "asq,memberof" + domain_ldb.add(msg) + + +@pytest.fixture +def simple_ad(request, ldap_conn): + conf = format_basic_conf(ldap_conn, ignore_unreadable_refs=False) + sysdb_sed_domainid("FakeAD", "S-1-5-21-1305200397-2901131868-73388776") + + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_user_operations(ldap_conn, simple_ad): + user = 'user1_dom1-19661' + user_id = pwd.getpwnam(user).pw_uid + user_sid = 'S-1-5-21-1305200397-2901131868-73388776-82809' + + output = pysss_nss_idmap.getsidbyname(user)[user] + assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_USER + assert output[pysss_nss_idmap.SID_KEY] == user_sid + + output = pysss_nss_idmap.getsidbyusername(user)[user] + assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_USER + assert output[pysss_nss_idmap.SID_KEY] == user_sid + + output = pysss_nss_idmap.getsidbygroupname(user) + assert len(output) == 0 + + output = pysss_nss_idmap.getsidbyid(user_id)[user_id] + assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_USER + assert output[pysss_nss_idmap.SID_KEY] == user_sid + + output = pysss_nss_idmap.getsidbyuid(user_id)[user_id] + assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_USER + assert output[pysss_nss_idmap.SID_KEY] == user_sid + + output = pysss_nss_idmap.getsidbygid(user_id) + assert len(output) == 0 + + output = pysss_nss_idmap.getidbysid(user_sid)[user_sid] + assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_USER + assert output[pysss_nss_idmap.ID_KEY] == user_id + + output = pysss_nss_idmap.getnamebysid(user_sid)[user_sid] + assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_USER + assert output[pysss_nss_idmap.NAME_KEY] == user + + +def test_group_operations(ldap_conn, simple_ad): + group = 'group1_dom1-19661' + group_id = grp.getgrnam(group).gr_gid + group_sid = 'S-1-5-21-1305200397-2901131868-73388776-82810' + + output = pysss_nss_idmap.getsidbyname(group)[group] + assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP + assert output[pysss_nss_idmap.SID_KEY] == group_sid + + output = pysss_nss_idmap.getsidbygroupname(group)[group] + assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP + assert output[pysss_nss_idmap.SID_KEY] == group_sid + + output = pysss_nss_idmap.getsidbyusername(group) + assert len(output) == 0 + + output = pysss_nss_idmap.getsidbyid(group_id)[group_id] + assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP + assert output[pysss_nss_idmap.SID_KEY] == group_sid + + output = pysss_nss_idmap.getsidbygid(group_id)[group_id] + assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP + assert output[pysss_nss_idmap.SID_KEY] == group_sid + + output = pysss_nss_idmap.getsidbyuid(group_id) + assert len(output) == 0 + + output = pysss_nss_idmap.getidbysid(group_sid)[group_sid] + assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP + assert output[pysss_nss_idmap.ID_KEY] == group_id + + output = pysss_nss_idmap.getnamebysid(group_sid)[group_sid] + assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP + assert output[pysss_nss_idmap.NAME_KEY] == group + + +def test_case_insensitive(ldap_conn, simple_ad): + # resolve group and also member of this group + group = 'Domain Users' + group_id = grp.getgrnam(group).gr_gid + group_sid = 'S-1-5-21-1305200397-2901131868-73388776-513' + + output = pysss_nss_idmap.getsidbyname(group)[group] + assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP + assert output[pysss_nss_idmap.SID_KEY] == group_sid + + output = pysss_nss_idmap.getsidbyid(group_id)[group_id] + assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP + assert output[pysss_nss_idmap.SID_KEY] == group_sid + + output = pysss_nss_idmap.getsidbygid(group_id)[group_id] + assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP + assert output[pysss_nss_idmap.SID_KEY] == group_sid + + output = pysss_nss_idmap.getsidbyuid(group_id) + assert len(output) == 0 + + output = pysss_nss_idmap.getidbysid(group_sid)[group_sid] + assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP + assert output[pysss_nss_idmap.ID_KEY] == group_id + + output = pysss_nss_idmap.getnamebysid(group_sid)[group_sid] + assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP + assert output[pysss_nss_idmap.NAME_KEY] == group.lower() + + +@pytest.fixture +def simple_ad_ignore_unrdbl_refs(request, ldap_conn): + conf = format_basic_conf(ldap_conn, ignore_unreadable_refs=True) + sysdb_sed_domainid("FakeAD", "S-1-5-21-1305200397-2901131868-73388776") + + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_ignore_unreadable_references(ldap_conn, simple_ad_ignore_unrdbl_refs): + group = 'group3_dom1-17775' + group_id = grp.getgrnam(group).gr_gid + group_sid = 'S-1-5-21-1305200397-2901131868-73388776-82764' + + output = pysss_nss_idmap.getsidbyname(group)[group] + assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP + assert output[pysss_nss_idmap.SID_KEY] == group_sid + + output = pysss_nss_idmap.getsidbyid(group_id)[group_id] + assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP + assert output[pysss_nss_idmap.SID_KEY] == group_sid + + output = pysss_nss_idmap.getsidbygid(group_id)[group_id] + assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP + assert output[pysss_nss_idmap.SID_KEY] == group_sid + + output = pysss_nss_idmap.getsidbyuid(group_id) + assert len(output) == 0 + + output = pysss_nss_idmap.getidbysid(group_sid)[group_sid] + assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP + assert output[pysss_nss_idmap.ID_KEY] == group_id + + output = pysss_nss_idmap.getnamebysid(group_sid)[group_sid] + assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP + assert output[pysss_nss_idmap.NAME_KEY] == group + + +def test_no_ignore_unreadable_references(ldap_conn, simple_ad): + group = 'group3_dom1-17775' + + # This group has a member attribute referencing to a user in other + # domain + with pytest.raises(KeyError): + grp.getgrnam(group) diff --git a/src/tests/intg/test_resolver.py b/src/tests/intg/test_resolver.py new file mode 100644 index 0000000..1b1a494 --- /dev/null +++ b/src/tests/intg/test_resolver.py @@ -0,0 +1,326 @@ +# +# Resolver integration test +# +# Authors: +# Samuel Cabrero <scabrero@suse.com> +# +# Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 stat +import signal +import subprocess +import time +import ldap +import pytest +import socket +import config +import ds_openldap +import ldap_ent +from util import unindent +from sssd_nss import NssReturnCode, HostError +from sssd_hosts import call_sssd_gethostbyname +from sssd_nets import call_sssd_getnetbyname, call_sssd_getnetbyaddr + +LDAP_BASE_DN = "dc=example,dc=com" + + +@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 Exception: + ds_inst.teardown() + raise + request.addfinalizer(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(ldap_conn.unbind_s) + return ldap_conn + + +def create_ldap_entries(ldap_conn, ent_list=None): + """Add LDAP entries from ent_list""" + if ent_list is not None: + for entry in ent_list: + ldap_conn.add_s(entry[0], entry[1]) + + +def cleanup_ldap_entries(ldap_conn, ent_list=None): + """Remove LDAP entries added by create_ldap_entries""" + if ent_list is None: + for ou in ("Users", "Groups", "Netgroups", "Services", "Policies", + "Hosts", "Networks"): + for entry in ldap_conn.search_s(f"ou={ou}," + f"{ldap_conn.ds_inst.base_dn}", + ldap.SCOPE_ONELEVEL, + attrlist=[]): + ldap_conn.delete_s(entry[0]) + else: + for entry in ent_list: + ldap_conn.delete_s(entry[0]) + + +def create_ldap_cleanup(request, ldap_conn, ent_list=None): + """Add teardown for removing all user/group LDAP entries""" + request.addfinalizer(lambda: cleanup_ldap_entries(ldap_conn, ent_list)) + + +def create_ldap_fixture(request, ldap_conn, ent_list=None): + """Add LDAP entries and add teardown for removing them""" + create_ldap_entries(ldap_conn, ent_list) + create_ldap_cleanup(request, ldap_conn, ent_list) + + +SCHEMA_RFC2307 = "rfc2307" +SCHEMA_RFC2307_BIS = "rfc2307bis" + + +def format_basic_conf(ldap_conn, schema): + """Format a basic SSSD configuration""" + schema_conf = "ldap_schema = " + schema + "\n" + if schema == SCHEMA_RFC2307_BIS: + schema_conf += "ldap_group_object_class = groupOfNames\n" + iphost_search_base = "ou=Hosts," + ldap_conn.ds_inst.base_dn + ipnetwork_search_base = "ou=Networks," + ldap_conn.ds_inst.base_dn + return unindent("""\ + [sssd] + debug_level = 0xffff + domains = LDAP + services = nss + + [nss] + debug_level = 0xffff + memcache_timeout = 0 + entry_negative_timeout = 1 + + [domain/LDAP] + ldap_auth_disable_tls_never_use_in_production = true + debug_level = 0xffff + {schema_conf} + id_provider = ldap + auth_provider = ldap + resolver_provider = ldap + ldap_uri = {ldap_conn.ds_inst.ldap_url} + ldap_search_base = {ldap_conn.ds_inst.base_dn} + ldap_iphost_search_base = {iphost_search_base} + ldap_ipnetwork_search_base = {ipnetwork_search_base} + """).format(**locals()) + + +def create_conf_file(contents): + """Create sssd.conf with specified contents""" + conf = open(config.CONF_PATH, "w") + conf.write(contents) + conf.close() + os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR) + + +def cleanup_conf_file(): + """Remove sssd.conf, if it exists""" + if os.path.lexists(config.CONF_PATH): + os.unlink(config.CONF_PATH) + + +def create_conf_cleanup(request): + """Add teardown for removing sssd.conf""" + request.addfinalizer(cleanup_conf_file) + + +def create_conf_fixture(request, contents): + """ + Create sssd.conf with specified contents and add teardown for removing it + """ + create_conf_file(contents) + create_conf_cleanup(request) + + +def create_sssd_process(): + """Start the SSSD process""" + if subprocess.call(["sssd", "-D", "--logger=files"]) != 0: + raise Exception("sssd start failed") + + +def get_sssd_pid(): + pid_file = open(config.PIDFILE_PATH, "r") + pid = int(pid_file.read()) + return pid + + +def cleanup_sssd_process(): + """Stop the SSSD process and remove its state""" + try: + pid = get_sssd_pid() + os.kill(pid, signal.SIGTERM) + while True: + try: + os.kill(pid, signal.SIGCONT) + except OSError: + break + time.sleep(1) + except OSError: + 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) + + +def create_sssd_fixture(request): + """Start SSSD and add teardown for stopping it and removing its state""" + create_sssd_process() + create_sssd_cleanup(request) + + +def create_sssd_cleanup(request): + """Add teardown for stopping SSSD and removing its state""" + request.addfinalizer(cleanup_sssd_process) + + +@pytest.fixture +def add_hosts(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + + ent_list.add_host("host1", + aliases=["host1_alias1", "host1_alias2"], + addresses=["192.168.1.1", "192.168.1.2", + "2001:db8:1::1", "2001:db8:1::2"]) + ent_list.add_host("host2.example.com", + aliases=["host2_alias1.example.com", + "host2_alias2.example.com"], + addresses=["192.168.2.1", "192.168.2.2", + "2001:db8:2::1", "2001:db8:2::2"]) + + create_ldap_fixture(request, ldap_conn, ent_list) + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +@pytest.fixture +def add_nets(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + + ent_list.add_ipnet("net1", "192.168.1.1", + aliases=["net1_alias1", "net1_alias2"]) + ent_list.add_ipnet("net2", "10.2.2.2", + aliases=["net2_alias1", "net2_alias2"]) + + create_ldap_fixture(request, ldap_conn, ent_list) + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_hostbyname(add_hosts): + (res, hres, _) = call_sssd_gethostbyname("invalid") + + assert res == NssReturnCode.NOTFOUND + assert hres == HostError.HOST_NOT_FOUND + + (res, hres, _) = call_sssd_gethostbyname("example.com") + assert res == NssReturnCode.NOTFOUND + assert hres == HostError.HOST_NOT_FOUND + + (res, hres, _) = call_sssd_gethostbyname("invalid.example.com") + assert res == NssReturnCode.NOTFOUND + assert hres == HostError.HOST_NOT_FOUND + + (res, hres, _) = call_sssd_gethostbyname("host1") + assert res == NssReturnCode.SUCCESS + assert hres == 0 + + (res, hres, _) = call_sssd_gethostbyname("host1_alias1") + assert res == NssReturnCode.SUCCESS + assert hres == 0 + + (res, hres, _) = call_sssd_gethostbyname("host1_alias2") + assert res == NssReturnCode.SUCCESS + assert hres == 0 + + (res, hres, _) = call_sssd_gethostbyname("host2.example.com") + assert res == NssReturnCode.SUCCESS + assert hres == 0 + + (res, hres, _) = call_sssd_gethostbyname("host2_alias1.example.com") + assert res == NssReturnCode.SUCCESS + assert hres == 0 + + (res, hres, _) = call_sssd_gethostbyname("host2_alias2.example.com") + assert res == NssReturnCode.SUCCESS + assert hres == 0 + + +def test_netbyname(add_nets): + (res, hres, _) = call_sssd_getnetbyname("invalid") + assert res == NssReturnCode.NOTFOUND + assert hres == HostError.HOST_NOT_FOUND + + (res, hres, _) = call_sssd_getnetbyname("net1") + assert res == NssReturnCode.SUCCESS + assert hres == 0 + + (res, hres, _) = call_sssd_getnetbyname("net1_alias1") + assert res == NssReturnCode.SUCCESS + assert hres == 0 + + (res, hres, _) = call_sssd_getnetbyname("net1_alias2") + assert res == NssReturnCode.SUCCESS + assert hres == 0 + + (res, hres, _) = call_sssd_getnetbyname("net2") + assert res == NssReturnCode.SUCCESS + assert hres == 0 + + (res, hres, _) = call_sssd_getnetbyname("net2_alias1") + assert res == NssReturnCode.SUCCESS + assert hres == 0 + + (res, hres, _) = call_sssd_getnetbyname("net2_alias2") + assert res == NssReturnCode.SUCCESS + assert hres == 0 + + +def test_netbyaddr(add_nets): + (res, hres, _) = call_sssd_getnetbyaddr("10.2.2.1", socket.AF_INET) + assert res == NssReturnCode.NOTFOUND + assert hres == HostError.HOST_NOT_FOUND + + (res, hres, _) = call_sssd_getnetbyaddr("10.2.2.1", socket.AF_UNSPEC) + assert res == NssReturnCode.NOTFOUND + assert hres == HostError.HOST_NOT_FOUND + + (res, hres, _) = call_sssd_getnetbyaddr("10.2.2.2", socket.AF_INET) + assert res == NssReturnCode.SUCCESS + assert hres == 0 + + (res, hres, _) = call_sssd_getnetbyaddr("10.2.2.2", socket.AF_UNSPEC) + assert res == NssReturnCode.SUCCESS + assert hres == 0 diff --git a/src/tests/intg/test_session_recording.py b/src/tests/intg/test_session_recording.py new file mode 100644 index 0000000..15faf12 --- /dev/null +++ b/src/tests/intg/test_session_recording.py @@ -0,0 +1,1165 @@ +# +# Session Recording tests +# +# Copyright (c) 2016 Red Hat, Inc. +# Author: Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 stat +import ent +import config +import signal +import subprocess +import time +import ldap +import pytest +import ds_openldap +import ldap_ent +from util import unindent + +LDAP_BASE_DN = "dc=example,dc=com" +INTERACTIVE_TIMEOUT = 4 + + +def stop_sssd(): + """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 OSError: + break + time.sleep(1) + + +def start_sssd(): + """Start sssd""" + if subprocess.call(["sssd", "-D", "--logger=files"]) != 0: + raise Exception("sssd start failed") + + +def restart_sssd(): + """Restart sssd""" + stop_sssd() + start_sssd() + + +@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 Exception: + 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_entries(ldap_conn, ent_list=None): + """Add LDAP entries from ent_list""" + if ent_list is not None: + for entry in ent_list: + ldap_conn.add_s(entry[0], entry[1]) + + +def cleanup_ldap_entries(ldap_conn, ent_list=None): + """Remove LDAP entries added by create_ldap_entries""" + if ent_list is None: + for ou in ("Users", "Groups", "Netgroups", "Services", "Policies"): + for entry in ldap_conn.search_s(f"ou={ou}," + f"{ldap_conn.ds_inst.base_dn}", + ldap.SCOPE_ONELEVEL, + attrlist=[]): + ldap_conn.delete_s(entry[0]) + else: + for entry in ent_list: + ldap_conn.delete_s(entry[0]) + + +def create_ldap_cleanup(request, ldap_conn, ent_list=None): + """Add teardown for removing all user/group LDAP entries""" + request.addfinalizer(lambda: cleanup_ldap_entries(ldap_conn, ent_list)) + + +def create_ldap_fixture(request, ldap_conn, ent_list=None): + """Add LDAP entries and add teardown for removing them""" + create_ldap_entries(ldap_conn, ent_list) + create_ldap_cleanup(request, ldap_conn, ent_list) + + +SCHEMA_RFC2307 = "rfc2307" +SCHEMA_RFC2307_BIS = "rfc2307bis" + + +def format_basic_conf(ldap_conn, schema): + """ + Format a basic SSSD configuration. + + The files domain is defined but not enabled in order to avoid enumerating + users from the files domain that would otherwise by implicitly enabled. + """ + schema_conf = "ldap_schema = " + schema + "\n" + if schema == SCHEMA_RFC2307_BIS: + schema_conf += "ldap_group_object_class = groupOfNames\n" + return unindent("""\ + [sssd] + debug_level = 0xffff + domains = LDAP + services = nss, pam + + [nss] + debug_level = 0xffff + memcache_timeout = 0 + + [pam] + debug_level = 0xffff + + [domain/files] + id_provider = files + + [domain/LDAP] + ldap_auth_disable_tls_never_use_in_production = true + debug_level = 0xffff + enumerate = true + {schema_conf} + id_provider = ldap + auth_provider = ldap + ldap_uri = {ldap_conn.ds_inst.ldap_url} + ldap_search_base = {ldap_conn.ds_inst.base_dn} + ldap_enumeration_refresh_offset = 0 + """).format(**locals()) + + +def create_conf_file(contents): + """Create sssd.conf with specified contents""" + conf = open(config.CONF_PATH, "w") + conf.write(contents) + conf.close() + os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR) + + +def cleanup_conf_file(): + """Remove sssd.conf, if it exists""" + if os.path.lexists(config.CONF_PATH): + os.unlink(config.CONF_PATH) + + +def create_conf_cleanup(request): + """Add teardown for removing sssd.conf""" + request.addfinalizer(cleanup_conf_file) + + +def create_conf_fixture(request, contents): + """ + Create sssd.conf with specified contents and add teardown for removing it. + """ + create_conf_file(contents) + create_conf_cleanup(request) + + +def create_sssd_process(): + """Start the SSSD process""" + if subprocess.call(["sssd", "-D", "--logger=files"]) != 0: + raise Exception("sssd start failed") + + +def cleanup_sssd_process(): + """Stop the SSSD process and remove its state""" + try: + 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 OSError: + break + time.sleep(1) + except OSError: + 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) + + +def create_sssd_cleanup(request): + """Add teardown for stopping SSSD and removing its state""" + request.addfinalizer(cleanup_sssd_process) + + +def create_sssd_fixture(request): + """Start SSSD and add teardown for stopping it and removing its state""" + create_sssd_process() + create_sssd_cleanup(request) + + +@pytest.fixture +def users_and_groups(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001, loginShell="/bin/sh1") + ent_list.add_user("user2", 1002, 2002, loginShell="/bin/sh2") + ent_list.add_user("user3", 1003, 2003, loginShell="/bin/sh3") + # User without primary group + ent_list.add_user("user4", 1004, 2004, loginShell="/bin/sh4") + ent_list.add_group("group1", 2001) + ent_list.add_group("group2", 2002) + ent_list.add_group("group3", 2003) + ent_list.add_group("empty_group", 2010) + ent_list.add_group("one_user_group", 2011, ["user1"]) + ent_list.add_group("two_user_group", 2012, ["user1", "user2"]) + ent_list.add_group("three_user_group", 2013, ["user1", "user2", "user3"]) + # Supplementary group for a user without primary group + ent_list.add_group("groupless_user_group", 2014, ["user4"]) + create_ldap_fixture(request, ldap_conn, ent_list) + + +@pytest.fixture +def none(request, ldap_conn, users_and_groups): + """ + Fixture with scope "none". + """ + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [session_recording] + scope = none + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +def test_none(none): + """Test "none" scope""" + ent.assert_passwd( + ent.contains_only( + dict(name="user1", uid=1001, shell="/bin/sh1"), + dict(name="user2", uid=1002, shell="/bin/sh2"), + dict(name="user3", uid=1003, shell="/bin/sh3"), + dict(name="user4", uid=1004, shell="/bin/sh4"), + ) + ) + + +@pytest.fixture +def all(request, ldap_conn, users_and_groups): + """ + Fixture with scope "all". + """ + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [session_recording] + scope = all + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +def test_all_nam(all): + """Test "all" scope with getpwnam""" + ent.assert_each_passwd_by_name(dict( + user1=dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + user2=dict(name="user2", uid=1002, + shell=config.SESSION_RECORDING_SHELL), + user3=dict(name="user3", uid=1003, + shell=config.SESSION_RECORDING_SHELL), + user4=dict(name="user4", uid=1004, + shell=config.SESSION_RECORDING_SHELL), + )) + + +def test_all_uid(all): + """Test "all" scope with getpwuid""" + ent.assert_each_passwd_by_uid({ + 1001: dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + 1002: dict(name="user2", uid=1002, + shell=config.SESSION_RECORDING_SHELL), + 1003: dict(name="user3", uid=1003, + shell=config.SESSION_RECORDING_SHELL), + 1004: dict(name="user4", uid=1004, + shell=config.SESSION_RECORDING_SHELL), + }) + + +def test_all_ent(all): + """Test "all" scope with getpwent""" + ent.assert_passwd_list( + ent.contains_only( + dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL), + dict(name="user2", uid=1002, shell=config.SESSION_RECORDING_SHELL), + dict(name="user3", uid=1003, shell=config.SESSION_RECORDING_SHELL), + dict(name="user4", uid=1004, shell=config.SESSION_RECORDING_SHELL), + ) + ) + + +@pytest.fixture +def some_empty(request, ldap_conn, users_and_groups): + """ + Fixture with scope "some", but no users or groups listed. + """ + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [session_recording] + scope = some + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +def test_some_empty(some_empty): + """Test "some" scope with no users or groups""" + ent.assert_passwd( + ent.contains_only( + dict(name="user1", uid=1001, shell="/bin/sh1"), + dict(name="user2", uid=1002, shell="/bin/sh2"), + dict(name="user3", uid=1003, shell="/bin/sh3"), + dict(name="user4", uid=1004, shell="/bin/sh4"), + ) + ) + + +@pytest.fixture +def some_users(request, ldap_conn, users_and_groups): + """ + Fixture with scope "some", and some users listed. + """ + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [session_recording] + scope = some + users = user1, user2 + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +def test_some_users_nam(some_users): + """Test "some" scope with user list and getpwnam""" + ent.assert_each_passwd_by_name(dict( + user1=dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + user2=dict(name="user2", uid=1002, + shell=config.SESSION_RECORDING_SHELL), + user3=dict(name="user3", uid=1003, shell="/bin/sh3"), + user4=dict(name="user4", uid=1004, shell="/bin/sh4"), + )) + + +def test_some_users_uid(some_users): + """Test "some" scope with user list and getpwuid""" + ent.assert_each_passwd_by_uid({ + 1001: dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + 1002: dict(name="user2", uid=1002, + shell=config.SESSION_RECORDING_SHELL), + 1003: dict(name="user3", uid=1003, shell="/bin/sh3"), + 1004: dict(name="user4", uid=1004, shell="/bin/sh4"), + }) + + +def test_some_users_ent(some_users): + """Test "some" scope with user list and getpwent""" + ent.assert_passwd_list( + ent.contains_only( + dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL), + dict(name="user2", uid=1002, shell=config.SESSION_RECORDING_SHELL), + dict(name="user3", uid=1003, shell="/bin/sh3"), + dict(name="user4", uid=1004, shell="/bin/sh4"), + ) + ) + + +@pytest.fixture +def some_users_overridden(request, ldap_conn, users_and_groups): + """ + Fixture with scope "some", specifying two users with + overridden names, but one listed with the original name. + """ + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [session_recording] + scope = some + users = overridden_user1, user2 + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + subprocess.check_call(["sss_override", "user-add", "user1", + "-n", "overridden_user1"]) + subprocess.check_call(["sss_override", "user-add", "user2", + "-n", "overridden_user2"]) + restart_sssd() + + +def test_some_users_overridden_nam(some_users_overridden): + """ + Test "some" scope with user list containing some + overridden users, requested with getpwnam. + """ + ent.assert_each_passwd_by_name(dict( + overridden_user1=dict(name="overridden_user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + overridden_user2=dict(name="overridden_user2", uid=1002, + shell="/bin/sh2"), + user3=dict(name="user3", uid=1003, shell="/bin/sh3"), + user4=dict(name="user4", uid=1004, shell="/bin/sh4"), + )) + + +def test_some_users_overridden_uid(some_users_overridden): + """ + Test "some" scope with user list containing some + overridden users, requested with getpwuid. + """ + ent.assert_each_passwd_by_uid({ + 1001: dict(name="overridden_user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + 1002: dict(name="overridden_user2", uid=1002, + shell="/bin/sh2"), + 1003: dict(name="user3", uid=1003, shell="/bin/sh3"), + 1004: dict(name="user4", uid=1004, shell="/bin/sh4"), + }) + + +def test_some_users_overridden_ent(some_users_overridden): + """ + Test "some" scope with user list containing some + overridden users, requested with getpwent. + """ + ent.assert_passwd_list( + ent.contains_only( + dict(name="overridden_user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + dict(name="overridden_user2", uid=1002, + shell="/bin/sh2"), + dict(name="user3", uid=1003, shell="/bin/sh3"), + dict(name="user4", uid=1004, shell="/bin/sh4"), + ) + ) + + +@pytest.fixture +def some_groups1(request, ldap_conn, users_and_groups): + """ + Fixture with scope "some", specifying a single-user supplementary group, + and a two-user supplementary group intersecting with the first one. + """ + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [session_recording] + scope = some + groups = one_user_group, two_user_group + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +@pytest.fixture +def some_groups2(request, ldap_conn, users_and_groups): + """ + Fixture with scope "some", specifying a three-user supplementary group. + """ + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [session_recording] + scope = some + groups = three_user_group + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +@pytest.fixture +def some_groups3(request, ldap_conn, users_and_groups): + """ + Fixture with scope "some", specifying a group with a user with + non-existent primary group. + """ + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [session_recording] + scope = some + groups = groupless_user_group + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +@pytest.fixture +def some_groups4(request, ldap_conn, users_and_groups): + """ + Fixture with scope "some", specifying two primary groups. + """ + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [session_recording] + scope = some + groups = group1, group3 + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +def test_some_groups1_nam(some_groups1): + """Test "some" scope with group list and getpwnam""" + ent.assert_each_passwd_by_name(dict( + user1=dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + user2=dict(name="user2", uid=1002, + shell=config.SESSION_RECORDING_SHELL), + user3=dict(name="user3", uid=1003, shell="/bin/sh3"), + user4=dict(name="user4", uid=1004, shell="/bin/sh4"), + )) + + +def test_some_groups1_uid(some_groups1): + """Test "some" scope with group list and getpwuid""" + ent.assert_each_passwd_by_uid({ + 1001: dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + 1002: dict(name="user2", uid=1002, + shell=config.SESSION_RECORDING_SHELL), + 1003: dict(name="user3", uid=1003, shell="/bin/sh3"), + 1004: dict(name="user4", uid=1004, shell="/bin/sh4"), + }) + + +def test_some_groups1_ent(some_groups1): + """Test "some" scope with group list and getpwent""" + ent.assert_passwd_list( + ent.contains_only( + dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL), + dict(name="user2", uid=1002, shell=config.SESSION_RECORDING_SHELL), + dict(name="user3", uid=1003, shell="/bin/sh3"), + dict(name="user4", uid=1004, shell="/bin/sh4"), + ) + ) + + +def test_some_groups2_nam(some_groups2): + """Test "some" scope with group list and getpwnam""" + ent.assert_each_passwd_by_name(dict( + user1=dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + user2=dict(name="user2", uid=1002, + shell=config.SESSION_RECORDING_SHELL), + user3=dict(name="user3", uid=1003, + shell=config.SESSION_RECORDING_SHELL), + user4=dict(name="user4", uid=1004, shell="/bin/sh4"), + )) + + +def test_some_groups2_uid(some_groups2): + """Test "some" scope with group list and getpwuid""" + ent.assert_each_passwd_by_uid({ + 1001: dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + 1002: dict(name="user2", uid=1002, + shell=config.SESSION_RECORDING_SHELL), + 1003: dict(name="user3", uid=1003, + shell=config.SESSION_RECORDING_SHELL), + 1004: dict(name="user4", uid=1004, shell="/bin/sh4"), + }) + + +def test_some_groups2_ent(some_groups2): + """Test "some" scope with group list and getpwent""" + ent.assert_passwd_list( + ent.contains_only( + dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL), + dict(name="user2", uid=1002, shell=config.SESSION_RECORDING_SHELL), + dict(name="user3", uid=1003, shell=config.SESSION_RECORDING_SHELL), + dict(name="user4", uid=1004, shell="/bin/sh4"), + ) + ) + + +def test_some_groups3_nam(some_groups3): + """Test "some" scope with group list and getpwnam""" + ent.assert_each_passwd_by_name(dict( + user1=dict(name="user1", uid=1001, shell="/bin/sh1"), + user2=dict(name="user2", uid=1002, shell="/bin/sh2"), + user3=dict(name="user3", uid=1003, shell="/bin/sh3"), + user4=dict(name="user4", uid=1004, + shell=config.SESSION_RECORDING_SHELL), + )) + + +def test_some_groups3_uid(some_groups3): + """Test "some" scope with group list and getpwuid""" + ent.assert_each_passwd_by_uid({ + 1001: dict(name="user1", uid=1001, shell="/bin/sh1"), + 1002: dict(name="user2", uid=1002, shell="/bin/sh2"), + 1003: dict(name="user3", uid=1003, shell="/bin/sh3"), + 1004: dict(name="user4", uid=1004, + shell=config.SESSION_RECORDING_SHELL), + }) + + +def test_some_groups3_ent(some_groups3): + """Test "some" scope with group list and getpwent""" + ent.assert_passwd_list( + ent.contains_only( + dict(name="user1", uid=1001, shell="/bin/sh1"), + dict(name="user2", uid=1002, shell="/bin/sh2"), + dict(name="user3", uid=1003, shell="/bin/sh3"), + dict(name="user4", uid=1004, shell=config.SESSION_RECORDING_SHELL), + ) + ) + + +def test_some_groups4_nam(some_groups4): + """Test "some" scope with group list and getpwnam""" + ent.assert_each_passwd_by_name(dict( + user1=dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + user2=dict(name="user2", uid=1002, shell="/bin/sh2"), + user3=dict(name="user3", uid=1003, + shell=config.SESSION_RECORDING_SHELL), + user4=dict(name="user4", uid=1004, shell="/bin/sh4"), + )) + + +def test_some_groups4_uid(some_groups4): + """Test "some" scope with group list and getpwuid""" + ent.assert_each_passwd_by_uid({ + 1001: dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + 1002: dict(name="user2", uid=1002, shell="/bin/sh2"), + 1003: dict(name="user3", uid=1003, + shell=config.SESSION_RECORDING_SHELL), + 1004: dict(name="user4", uid=1004, shell="/bin/sh4"), + }) + + +def test_some_groups4_ent(some_groups4): + """Test "some" scope with group list and getpwent""" + ent.assert_passwd_list( + ent.contains_only( + dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL), + dict(name="user2", uid=1002, shell="/bin/sh2"), + dict(name="user3", uid=1003, shell=config.SESSION_RECORDING_SHELL), + dict(name="user4", uid=1004, shell="/bin/sh4"), + ) + ) + + +@pytest.fixture +def some_groups_overridden1(request, ldap_conn, users_and_groups): + """ + Fixture with scope "some", specifying two primary groups with + overridden names, but one listed with the original name. + """ + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [session_recording] + scope = some + groups = overridden_group1, group2 + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + subprocess.check_call(["sss_override", "group-add", "group1", + "-n", "overridden_group1"]) + subprocess.check_call(["sss_override", "group-add", "group2", + "-n", "overridden_group2"]) + restart_sssd() + + +def test_some_groups_overridden1_nam(some_groups_overridden1): + """ + Test "some" scope with group list containing some + overridden groups, and users requested with getpwnam. + """ + ent.assert_each_passwd_by_name(dict( + user1=dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + user2=dict(name="user2", uid=1002, shell="/bin/sh2"), + user3=dict(name="user3", uid=1003, shell="/bin/sh3"), + user4=dict(name="user4", uid=1004, shell="/bin/sh4"), + )) + + +def test_some_groups_overridden1_uid(some_groups_overridden1): + """ + Test "some" scope with group list containing some + overridden groups, and users requested with getpwuid. + """ + ent.assert_each_passwd_by_uid({ + 1001: dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + 1002: dict(name="user2", uid=1002, shell="/bin/sh2"), + 1003: dict(name="user3", uid=1003, shell="/bin/sh3"), + 1004: dict(name="user4", uid=1004, shell="/bin/sh4"), + }) + + +def test_some_groups_overridden1_ent(some_groups_overridden1): + """ + Test "some" scope with group list containing some + overridden groups, and users requested with getpwent. + """ + ent.assert_passwd_list( + ent.contains_only( + dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL), + dict(name="user2", uid=1002, shell="/bin/sh2"), + dict(name="user3", uid=1003, shell="/bin/sh3"), + dict(name="user4", uid=1004, shell="/bin/sh4"), + ) + ) + + +@pytest.fixture +def some_groups_overridden2(request, ldap_conn, users_and_groups): + """ + Fixture with scope "some", specifying two supplementary groups with + overridden names, but one listed with the original name. + """ + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [session_recording] + scope = some + groups = one_user_group_overridden, two_user_group + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + subprocess.check_call(["sss_override", "group-add", "one_user_group", + "-n", "one_user_group_overridden"]) + subprocess.check_call(["sss_override", "group-add", "two_user_group", + "-n", "two_user_group_overridden"]) + restart_sssd() + + +def test_some_groups_overridden2_nam(some_groups_overridden2): + """ + Test "some" scope with group list containing some + overridden groups, and users requested with getpwnam. + """ + ent.assert_each_passwd_by_name(dict( + user1=dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + user2=dict(name="user2", uid=1002, shell="/bin/sh2"), + user3=dict(name="user3", uid=1003, shell="/bin/sh3"), + user4=dict(name="user4", uid=1004, shell="/bin/sh4"), + )) + + +def test_some_groups_overridden2_uid(some_groups_overridden2): + """ + Test "some" scope with group list containing some + overridden groups, and users requested with getpwuid. + """ + ent.assert_each_passwd_by_uid({ + 1001: dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + 1002: dict(name="user2", uid=1002, shell="/bin/sh2"), + 1003: dict(name="user3", uid=1003, shell="/bin/sh3"), + 1004: dict(name="user4", uid=1004, shell="/bin/sh4"), + }) + + +def test_some_groups_overridden2_ent(some_groups_overridden2): + """ + Test "some" scope with group list containing some + overridden groups, and users requested with getpwent. + """ + ent.assert_passwd_list( + ent.contains_only( + dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL), + dict(name="user2", uid=1002, shell="/bin/sh2"), + dict(name="user3", uid=1003, shell="/bin/sh3"), + dict(name="user4", uid=1004, shell="/bin/sh4"), + ) + ) + + +@pytest.fixture +def some_groups_overridden3(request, ldap_conn, users_and_groups): + """ + Fixture with scope "some", having two primary groups with + IDs swapped via overriding, but only one of them listed. + """ + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [session_recording] + scope = some + groups = group2 + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + subprocess.check_call(["sss_override", "group-add", "group1", + "-g", "2002"]) + subprocess.check_call(["sss_override", "group-add", "group2", + "-g", "2001"]) + restart_sssd() + + +def test_some_groups_overridden3_nam(some_groups_overridden3): + """ + Test "some" scope with group list containing some + overridden group, and users requested with getpwnam. + """ + ent.assert_each_passwd_by_name(dict( + user1=dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + user2=dict(name="user2", uid=1002, shell="/bin/sh2"), + user3=dict(name="user3", uid=1003, shell="/bin/sh3"), + user4=dict(name="user4", uid=1004, shell="/bin/sh4"), + )) + + +def test_some_groups_overridden3_uid(some_groups_overridden3): + """ + Test "some" scope with group list containing some + overridden group, and users requested with getpwuid. + """ + ent.assert_each_passwd_by_uid({ + 1001: dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + 1002: dict(name="user2", uid=1002, shell="/bin/sh2"), + 1003: dict(name="user3", uid=1003, shell="/bin/sh3"), + 1004: dict(name="user4", uid=1004, shell="/bin/sh4"), + }) + + +def test_some_groups_overridden3_ent(some_groups_overridden3): + """ + Test "some" scope with group list containing some + overridden group, and users requested with getpwent. + """ + ent.assert_passwd_list( + ent.contains_only( + dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL), + dict(name="user2", uid=1002, shell="/bin/sh2"), + dict(name="user3", uid=1003, shell="/bin/sh3"), + dict(name="user4", uid=1004, shell="/bin/sh4"), + ) + ) + + +@pytest.fixture +def some_groups_overridden4(request, ldap_conn, users_and_groups): + """ + Fixture with scope "some", two users with GIDs swapped via overridding, + and one of their primary groups listed. + """ + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [session_recording] + scope = some + groups = group2 + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + subprocess.check_call(["sss_override", "user-add", "user1", + "-g", "2002"]) + subprocess.check_call(["sss_override", "user-add", "user2", + "-g", "2001"]) + restart_sssd() + + +def test_some_groups_overridden4_nam(some_groups_overridden3): + """ + Test "some" scope with group list containing some + overridden group, and users requested with getpwnam. + """ + ent.assert_each_passwd_by_name(dict( + user1=dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + user2=dict(name="user2", uid=1002, shell="/bin/sh2"), + user3=dict(name="user3", uid=1003, shell="/bin/sh3"), + user4=dict(name="user4", uid=1004, shell="/bin/sh4"), + )) + + +def test_some_groups_overridden4_uid(some_groups_overridden3): + """ + Test "some" scope with group list containing some + overridden group, and users requested with getpwuid. + """ + ent.assert_each_passwd_by_uid({ + 1001: dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + 1002: dict(name="user2", uid=1002, shell="/bin/sh2"), + 1003: dict(name="user3", uid=1003, shell="/bin/sh3"), + 1004: dict(name="user4", uid=1004, shell="/bin/sh4"), + }) + + +def test_some_groups_overridden4_ent(some_groups_overridden3): + """ + Test "some" scope with group list containing some + overridden group, and users requested with getpwent. + """ + ent.assert_passwd_list( + ent.contains_only( + dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL), + dict(name="user2", uid=1002, shell="/bin/sh2"), + dict(name="user3", uid=1003, shell="/bin/sh3"), + dict(name="user4", uid=1004, shell="/bin/sh4"), + ) + ) + + +@pytest.fixture +def some_users_and_groups(request, ldap_conn, users_and_groups): + """ + Fixture with scope "some", listing some users and groups. + """ + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [session_recording] + scope = some + users = user3 + groups = one_user_group + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +def test_some_users_and_groups_nam(some_users_and_groups): + """ + Test "some" scope with user and group lists and getpwnam. + """ + ent.assert_each_passwd_by_name(dict( + user1=dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + user2=dict(name="user2", uid=1002, shell="/bin/sh2"), + user3=dict(name="user3", uid=1003, + shell=config.SESSION_RECORDING_SHELL), + user4=dict(name="user4", uid=1004, shell="/bin/sh4"), + )) + + +def test_some_users_and_groups_uid(some_users_and_groups): + """ + Test "some" scope with user and group lists and getpwuid. + """ + ent.assert_each_passwd_by_uid({ + 1001: dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + 1002: dict(name="user2", uid=1002, shell="/bin/sh2"), + 1003: dict(name="user3", uid=1003, + shell=config.SESSION_RECORDING_SHELL), + 1004: dict(name="user4", uid=1004, shell="/bin/sh4"), + }) + + +def test_some_users_and_groups_ent(some_users_and_groups): + """ + Test "some" scope with user and group lists and getpwent. + """ + ent.assert_passwd_list( + ent.contains_only( + dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL), + dict(name="user2", uid=1002, shell="/bin/sh2"), + dict(name="user3", uid=1003, shell=config.SESSION_RECORDING_SHELL), + dict(name="user4", uid=1004, shell="/bin/sh4"), + ) + ) + + +@pytest.fixture +def all_exclude_users(request, ldap_conn, users_and_groups): + """ + Test "all" scope with a simple excludes user list + """ + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [session_recording] + scope = all + exclude_users = user1, user3 + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +def test_all_exclude_users_nam(all_exclude_users): + """Test "all" scope with exclude users list and getpwnam""" + ent.assert_each_passwd_by_name(dict( + user1=dict(name="user1", uid=1001, shell="/bin/sh1"), + user2=dict(name="user2", uid=1002, + shell=config.SESSION_RECORDING_SHELL), + user3=dict(name="user3", uid=1003, shell="/bin/sh3"), + user4=dict(name="user4", uid=1004, + shell=config.SESSION_RECORDING_SHELL), + )) + + +def test_all_exclude_users_uid(all_exclude_users): + """Test "all" scope with exclude users list and getpwuid""" + ent.assert_each_passwd_by_uid({ + 1001: dict(name="user1", uid=1001, shell="/bin/sh1"), + 1002: dict(name="user2", uid=1002, + shell=config.SESSION_RECORDING_SHELL), + 1003: dict(name="user3", uid=1003, shell="/bin/sh3"), + 1004: dict(name="user4", uid=1004, + shell=config.SESSION_RECORDING_SHELL), + }) + + +def test_all_exclude_users_ent(all_exclude_users): + """Test "all" scope with exclude users list and getpwent""" + ent.assert_passwd_list( + ent.contains_only( + dict(name="user1", uid=1001, shell="/bin/sh1"), + dict(name="user2", uid=1002, shell=config.SESSION_RECORDING_SHELL), + dict(name="user3", uid=1003, shell="/bin/sh3"), + dict(name="user4", uid=1004, shell=config.SESSION_RECORDING_SHELL), + ) + ) + + +@pytest.fixture +def all_exclude_groups(request, ldap_conn, users_and_groups): + """ + Fixture with scope "all", specifying two primary exclude + groups and one supplementary exclude group. + """ + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [session_recording] + scope = all + exclude_groups = group1, group3, two_user_group + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +def test_all_exclude_groups_nam(all_exclude_groups): + """Test "all" scope with exclude groups list and getpwnam""" + ent.assert_each_passwd_by_name(dict( + user1=dict(name="user1", uid=1001, shell="/bin/sh1"), + user2=dict(name="user2", uid=1002, shell="/bin/sh2"), + user3=dict(name="user3", uid=1003, shell="/bin/sh3"), + user4=dict(name="user4", uid=1004, + shell=config.SESSION_RECORDING_SHELL), + )) + + +def test_all_exclude_groups_uid(all_exclude_groups): + """Test "all" scope with group list and getpwuid""" + ent.assert_each_passwd_by_uid({ + 1001: dict(name="user1", uid=1001, shell="/bin/sh1"), + 1002: dict(name="user2", uid=1002, shell="/bin/sh2"), + 1003: dict(name="user3", uid=1003, shell="/bin/sh3"), + 1004: dict(name="user4", uid=1004, + shell=config.SESSION_RECORDING_SHELL), + }) + + +def test_all_exclude_groups_ent(all_exclude_groups): + """Test "all" scope with group list and getpwent""" + ent.assert_passwd_list( + ent.contains_only( + dict(name="user1", uid=1001, shell="/bin/sh1"), + dict(name="user2", uid=1002, shell="/bin/sh2"), + dict(name="user3", uid=1003, shell="/bin/sh3"), + dict(name="user4", uid=1004, + shell=config.SESSION_RECORDING_SHELL), + ) + ) + + +@pytest.fixture +def some_users_and_exclude_groups(request, ldap_conn, users_and_groups): + """ + Fixture with scope "some" containing users to + enable recording, and exclude_* options to be ignored + intentionally + """ + conf = \ + format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \ + unindent("""\ + [session_recording] + scope = some + users = user1, user2 + exclude_users = user1, user2, user3 + exclude_groups = group1, group3, two_user_group + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + +def test_some_users_and_exclude_groups_nam(some_users_and_exclude_groups): + """Test "some" scope with exclude users and groups list and getpwnam""" + ent.assert_each_passwd_by_name(dict( + user1=dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + user2=dict(name="user2", uid=1002, + shell=config.SESSION_RECORDING_SHELL), + user3=dict(name="user3", uid=1003, shell="/bin/sh3"), + user4=dict(name="user4", uid=1004, shell="/bin/sh4"), + )) + + +def test_some_users_and_exclude_groups_uid(some_users_and_exclude_groups): + """Test "some" scope with exclude users and groups list and getpwuid""" + ent.assert_each_passwd_by_uid({ + 1001: dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + 1002: dict(name="user2", uid=1002, + shell=config.SESSION_RECORDING_SHELL), + 1003: dict(name="user3", uid=1003, shell="/bin/sh3"), + 1004: dict(name="user4", uid=1004, shell="/bin/sh4"), + }) + + +def test_some_users_and_exclude_groups_ent(some_users_and_exclude_groups): + """Test "some" scope with exclude users and group list and getpwent""" + ent.assert_passwd_list( + ent.contains_only( + dict(name="user1", uid=1001, + shell=config.SESSION_RECORDING_SHELL), + dict(name="user2", uid=1002, + shell=config.SESSION_RECORDING_SHELL), + dict(name="user3", uid=1003, shell="/bin/sh3"), + dict(name="user4", uid=1004, shell="/bin/sh4"), + ) + ) diff --git a/src/tests/intg/test_ssh_pubkey.py b/src/tests/intg/test_ssh_pubkey.py new file mode 100644 index 0000000..34bde1b --- /dev/null +++ b/src/tests/intg/test_ssh_pubkey.py @@ -0,0 +1,340 @@ +# +# ssh public key integration test +# +# Copyright (c) 2018 Red Hat, Inc. +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 stat +import signal +import subprocess +import time +import string +import random +import pytest + +import ds_openldap +import ldap_ent +import ldap +import ldap.modlist +import config + +from util import unindent, get_call_output + +LDAP_BASE_DN = "dc=example,dc=com" + +USER1_PUBKEY1 = "ssh-dss AAAAB3NzaC1kc3MAAACBAPMkvcU53RVhBtjwiC3IqeRIWR9Qwdv8\ +DmZzEsDD3Csd6jYxMsPZoXcPrHqwYcEj1s5MVqhdSFS0Cjz13e7gO6OMLInO3xMBSSFHjfp9RE1H\ +pgc4WisazzyJaW9EMkQo/DqvkFkKh31oqAmxcSbLAFJRg4TTIqm18qu8IRKS6m/RAAAAFQC97TA5\ +JSsMsaX1bRszC7y4PhMBvQAAAIEAt9Yo9v/h9W4nDbzUdkGwNRszlPEK+T12bJv0O9Fk6subD3Do\ +6A4Qru/Nr6voXoq8b018Wb7iFWvKOoz5uT/plWBKLXL2NN7ovTR+dUJIzvwurQZroukmU1EghNey\ +lkSHmDlxSoMK6Nh21uGu6l+b6x5pXNaZHMpsywG4kY8SoC0AAACAAWLHneEGvqkYA8La4Eob+Hjj\ +mAKilx8byxm3Kfb1XO+ZrR6XxadofZOaUYRMpPKgFjKAKPxJftPLiDjWM7lSe6h8df0dUMLVXt6m\ +eA83kE0uK5JOOGJfJDqmRed2YnfxUDNNFQGT4xFWGrNtYNbGyw9BWKbkooAsLqaO04zP3Rs= \ +user1@LDAP" + +USER1_PUBKEY2 = "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAwHUUF3HPH+DkU6j8k7Q1wHG\ +RJY9NeLqSav3h95mTSCQYPSC7I9RTJ4OORgqCbEzrP/DYrrn4TtQ9dhRJar3ZY+F36SH5yFIXORb\ +lAIbFU+/anahBuFS9vHi1MqFPckGmwJ4QCpjQhdYxo1ro0e1RuGSaQNp/w9N6S/fDz4Cj4I99xDz\ +SeQeGHxYv0e60plQ8dUajmnaGmYRJHF9a6Ban7IWySActCja7eQP2zIRXEZMpuhl1E0U4y+gHTFI\ +gD3zQai3QrXm8RUrQURIJ0u6BlGS910OPbHqLpLTFWG08L8sNUcYzC+DY6yoCSO0n/Df3pVRS4C9\ +5Krf3FqppMTjdfQ== user1@LDAP" + + +@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 Exception: + ds_inst.teardown() + raise + request.addfinalizer(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(ldap_conn.unbind_s) + return ldap_conn + + +def create_ldap_entries(ldap_conn, ent_list=None): + """Add LDAP entries from ent_list""" + if ent_list is not None: + for entry in ent_list: + ldap_conn.add_s(entry[0], entry[1]) + + +def cleanup_ldap_entries(ldap_conn, ent_list=None): + """Remove LDAP entries added by create_ldap_entries""" + if ent_list is None: + for ou in ("Users", "Groups", "Netgroups", "Services", "Policies"): + for entry in ldap_conn.search_s(f"ou={ou}," + f"{ldap_conn.ds_inst.base_dn}", + ldap.SCOPE_ONELEVEL, + attrlist=[]): + ldap_conn.delete_s(entry[0]) + else: + for entry in ent_list: + ldap_conn.delete_s(entry[0]) + + +def create_ldap_cleanup(request, ldap_conn, ent_list=None): + """Add teardown for removing all user/group LDAP entries""" + request.addfinalizer(lambda: cleanup_ldap_entries(ldap_conn, ent_list)) + + +def create_ldap_fixture(request, ldap_conn, ent_list=None): + """Add LDAP entries and add teardown for removing them""" + create_ldap_entries(ldap_conn, ent_list) + create_ldap_cleanup(request, ldap_conn, ent_list) + + +SCHEMA_RFC2307_BIS = "rfc2307bis" + + +def format_basic_conf(ldap_conn, schema, config): + """Format a basic SSSD configuration""" + schema_conf = "ldap_schema = " + schema + "\n" + schema_conf += "ldap_group_object_class = groupOfNames\n" + return unindent("""\ + [sssd] + domains = LDAP + services = nss, ssh + + [nss] + + [ssh] + debug_level=10 + ca_db = {config.PAM_CERT_DB_PATH} + + [pam] + pam_cert_auth = True + + [domain/LDAP] + {schema_conf} + id_provider = ldap + auth_provider = ldap + ldap_uri = {ldap_conn.ds_inst.ldap_url} + ldap_search_base = {ldap_conn.ds_inst.base_dn} + ldap_sudo_use_host_filter = false + debug_level=10 + ldap_user_certificate = userCertificate;binary + """).format(**locals()) + + +def create_conf_file(contents): + """Create sssd.conf with specified contents""" + conf = open(config.CONF_PATH, "w") + conf.write(contents) + conf.close() + os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR) + + +def cleanup_conf_file(): + """Remove sssd.conf, if it exists""" + if os.path.lexists(config.CONF_PATH): + os.unlink(config.CONF_PATH) + + +def create_conf_cleanup(request): + """Add teardown for removing sssd.conf""" + request.addfinalizer(cleanup_conf_file) + + +def create_conf_fixture(request, contents): + """ + Create sssd.conf with specified contents and add teardown for removing it + """ + create_conf_file(contents) + create_conf_cleanup(request) + + +def create_sssd_process(): + """Start the SSSD process""" + if subprocess.call(["sssd", "-D", "--logger=files"]) != 0: + raise Exception("sssd start failed") + + +def get_sssd_pid(): + pid_file = open(config.PIDFILE_PATH, "r") + pid = int(pid_file.read()) + return pid + + +def cleanup_sssd_process(): + """Stop the SSSD process and remove its state""" + try: + pid = get_sssd_pid() + os.kill(pid, signal.SIGTERM) + while True: + try: + os.kill(pid, signal.SIGCONT) + except OSError: + break + time.sleep(1) + except OSError: + 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) + + +def create_sssd_fixture(request): + """Start SSSD and add teardown for stopping it and removing its state""" + create_sssd_process() + create_sssd_cleanup(request) + + +def create_sssd_cleanup(request): + """Add teardown for stopping SSSD and removing its state""" + request.addfinalizer(cleanup_sssd_process) + + +@pytest.fixture +def add_user_with_ssh_key(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001, + sshPubKey=(USER1_PUBKEY1, USER1_PUBKEY2)) + ent_list.add_user("user2", 1002, 2001) + create_ldap_fixture(request, ldap_conn, ent_list) + + config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH'] + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS, config) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_ssh_pubkey_retrieve(add_user_with_ssh_key): + """ + Test that we can retrieve an SSH public key for a user who has one + and can't retrieve a key for a user who does not have one. + """ + sshpubkey = get_call_output(["sss_ssh_authorizedkeys", "user1"]) + assert sshpubkey == USER1_PUBKEY1 + '\n' + USER1_PUBKEY2 + '\n' + + sshpubkey = get_call_output(["sss_ssh_authorizedkeys", "user2"]) + assert len(sshpubkey) == 0 + + +def test_ssh_pubkey_retrieve_cert(add_user_with_ssh_cert): + """ + Test that we can retrieve an SSH public key derived from a cert in ldap. + Compare with the sshpubkey derived via ssh-keygen, they should match. + """ + for u in [1, 7]: + pubsshkey_path = os.path.dirname(config.PAM_CERT_DB_PATH) + pubsshkey_path += "/SSSD_test_cert_pubsshkey_000%s.pub" % u + with open(pubsshkey_path, 'r') as f: + pubsshkey = f.read() + sshpubkey = get_call_output(["sss_ssh_authorizedkeys", "user%s" % u]) + print(sshpubkey) + print(pubsshkey) + assert sshpubkey == pubsshkey + + +@pytest.fixture() +def sighup_client(request): + test_ssh_cli_path = os.path.join(config.ABS_BUILDDIR, + "..", "..", "..", "test_ssh_client") + assert os.access(test_ssh_cli_path, os.X_OK) + return test_ssh_cli_path + + +@pytest.fixture +def add_user_with_many_keys(request, ldap_conn): + # Generate a large list of unique ssh pubkeys + pubkey_list = [] + while len(pubkey_list) < 50: + new_pubkey = list(USER1_PUBKEY1) + new_pubkey[10] = random.choice(string.ascii_uppercase) + new_pubkey[11] = random.choice(string.ascii_uppercase) + new_pubkey[12] = random.choice(string.ascii_uppercase) + str_new_pubkey = ''.join(c for c in new_pubkey) + if str_new_pubkey in pubkey_list: + continue + pubkey_list.append(str_new_pubkey) + + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001, sshPubKey=pubkey_list) + create_ldap_fixture(request, ldap_conn, ent_list) + + config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH'] + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS, config) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +@pytest.fixture +def add_user_with_ssh_cert(request, ldap_conn): + # Add a certificate to ldap, to manually test a cert from a smartcard. + config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH'] + + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + ent_list.add_user("user7", 1007, 2001) + create_ldap_fixture(request, ldap_conn, ent_list) + + for u in [1, 7]: + der_path = os.path.dirname(config.PAM_CERT_DB_PATH) + der_path += "/SSSD_test_cert_x509_000%s.der" % u + with open(der_path, 'rb') as f: + val = f.read() + + dn = "uid=user%s,ou=Users," % u + LDAP_BASE_DN + ldap_conn.modify_s(dn, [(ldap.MOD_ADD, 'usercertificate;binary', val)]) + + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS, config) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + + return None + + +def test_ssh_sighup(add_user_with_many_keys, sighup_client): + """ + A regression test for https://github.com/SSSD/sssd/issues/4754 + + OpenSSH can close its end of the pipe towards sss_ssh_authorizedkeys + before all of the output is read. In that case, older versions + of sss_ssh_authorizedkeys were receiving a SIGPIPE + """ + cli_path = sighup_client + + # python actually does the sensible, but unexpected (for a C programmer) + # thing and handles SIGPIPE. In order to reproduce the bug, we need + # to unset the SIGPIPE handler + signal.signal(signal.SIGPIPE, signal.SIG_DFL) + + process = subprocess.Popen([cli_path, "user1"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + _, _ = process.communicate() + # If the test tool detects that sss_ssh_authorizedkeys was killed with a + # signal, it would have returned 1 + assert process.returncode == 0 diff --git a/src/tests/intg/test_sss_cache.py b/src/tests/intg/test_sss_cache.py new file mode 100644 index 0000000..22f12f0 --- /dev/null +++ b/src/tests/intg/test_sss_cache.py @@ -0,0 +1,34 @@ +# +# SSSD files domain tests +# +# Copyright (c) 2019 Red Hat, Inc. +# Author: Lukas Slebodnik <lslebodn@redhat.com> +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 subprocess + + +def test_missing_domains(): + # Utilities in shadow-utils call sss_cache but it might fail in case + # sssd has never been started on such host. + ret = subprocess.call(["sss_cache", "-U"]) + assert ret == 0 + + ret = subprocess.call(["sss_cache", "-G"]) + assert ret == 0 + + ret = subprocess.call(["sss_cache", "-E"]) + assert ret == 0 diff --git a/src/tests/intg/test_sssctl.py b/src/tests/intg/test_sssctl.py new file mode 100644 index 0000000..60c2167 --- /dev/null +++ b/src/tests/intg/test_sssctl.py @@ -0,0 +1,532 @@ +# +# sssctl tool integration test +# +# Copyright (c) 2016 Red Hat, Inc. +# Author: Michal Zidek <mzidek@redhat.com> +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 subprocess +import pytest +import stat +import time +import signal +import ds_openldap +import ldap_ent +import config +from util import unindent, get_call_output +import sssd_netgroup + +LDAP_BASE_DN = "dc=example,dc=com" + + +@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 Exception: + 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: + ldap_conn.delete_s(entry[0]) + request.addfinalizer(teardown) + + +def create_conf_fixture(request, contents, snippet=None): + """Generate sssd.conf and add teardown for removing it""" + if contents is not None: + 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)) + if snippet is not None: + conf = open(config.CONF_SNIPPET_PATH, "w") + conf.write(snippet) + conf.close() + os.chmod(config.CONF_SNIPPET_PATH, stat.S_IRUSR | stat.S_IWUSR) + request.addfinalizer(lambda: os.unlink(config.CONF_SNIPPET_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 OSError: + 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", "--logger=files"]) != 0: + raise Exception("sssd start failed") + + def teardown(): + try: + stop_sssd() + except Exception: + 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) + + +@pytest.fixture +def portable_LC_ALL(request): + os.environ["LC_ALL"] = "C" + return None + + +def load_data_to_ldap(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + ent_list.add_user("CamelCaseUser1", 1002, 2002) + + ent_list.add_group("group1", 2001, ["user1"]) + ent_list.add_group("CamelCaseGroup1", 2002, ["CamelCaseUser1"]) + + create_ldap_fixture(request, ldap_conn, ent_list) + + +@pytest.fixture +def sanity_rfc2307(request, ldap_conn): + load_data_to_ldap(request, ldap_conn) + + conf = unindent("""\ + [sssd] + domains = LDAP + services = nss + + [nss] + + [domain/LDAP] + ldap_auth_disable_tls_never_use_in_production = true + ldap_schema = rfc2307 + id_provider = ldap + auth_provider = ldap + sudo_provider = ldap + ldap_uri = {ldap_conn.ds_inst.ldap_url} + ldap_search_base = {ldap_conn.ds_inst.base_dn} + ldap_netgroup_search_base = ou=Netgroups,{ldap_conn.ds_inst.base_dn} + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +@pytest.fixture +def fqname_rfc2307(request, ldap_conn): + load_data_to_ldap(request, ldap_conn) + + conf = unindent("""\ + [sssd] + domains = LDAP + services = nss + + [nss] + + [domain/LDAP] + ldap_auth_disable_tls_never_use_in_production = true + ldap_schema = rfc2307 + id_provider = ldap + auth_provider = ldap + sudo_provider = ldap + ldap_uri = {ldap_conn.ds_inst.ldap_url} + ldap_search_base = {ldap_conn.ds_inst.base_dn} + use_fully_qualified_names = true + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +@pytest.fixture +def fqname_case_insensitive_rfc2307(request, ldap_conn): + load_data_to_ldap(request, ldap_conn) + + conf = unindent("""\ + [sssd] + domains = LDAP + services = nss + + [nss] + + [domain/LDAP] + ldap_auth_disable_tls_never_use_in_production = true + ldap_schema = rfc2307 + id_provider = ldap + auth_provider = ldap + sudo_provider = ldap + ldap_uri = {ldap_conn.ds_inst.ldap_url} + ldap_search_base = {ldap_conn.ds_inst.base_dn} + use_fully_qualified_names = true + case_sensitive = false + """).format(**locals()) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +def test_user_show_basic_sanity(ldap_conn, sanity_rfc2307, portable_LC_ALL): + # Fill the cache first + ent.assert_passwd_by_name( + 'user1', + dict(name='user1', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + ent.assert_passwd_by_name( + 'CamelCaseUser1', + dict(name='CamelCaseUser1', passwd='*', uid=1002, gid=2002, + gecos='1002', shell='/bin/bash')) + + output = get_call_output(["sssctl", "user-show", "user1"]) + assert output.find("Name: user1") != -1 + assert output.find("Initgroups expiration time: Initgroups were not yet " + "performed") != -1 + assert output.find("Cached in InfoPipe: No") != -1 + + output = get_call_output(["sssctl", "user-show", + "CamelCaseUser1"]) + assert output.find("Name: CamelCaseUser1") != -1 + assert output.find("Initgroups expiration time: Initgroups were not yet " + "performed") != -1 + assert output.find("Cached in InfoPipe: No") != -1 + + output = get_call_output(["sssctl", "user-show", "camelcaseuser1"]) + assert output.find("User camelcaseuser1 is not present in cache.") != -1 + + +def test_user_show_basic_fqname(ldap_conn, fqname_rfc2307, portable_LC_ALL): + # Fill the cache first + ent.assert_passwd_by_name( + 'user1@LDAP', + dict(name='user1@LDAP', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + ent.assert_passwd_by_name( + 'CamelCaseUser1@LDAP', + dict(name='CamelCaseUser1@LDAP', passwd='*', uid=1002, gid=2002, + gecos='1002', shell='/bin/bash')) + + output = get_call_output(["sssctl", "user-show", "user1@LDAP"]) + assert output.find("Name: user1@LDAP") != -1 + assert output.find("Initgroups expiration time: Initgroups were not yet " + "performed") != -1 + assert output.find("Cached in InfoPipe: No") != -1 + + output = get_call_output(["sssctl", "user-show", "CamelCaseUser1@LDAP"]) + assert output.find("Name: CamelCaseUser1@LDAP") != -1 + assert output.find("Initgroups expiration time: Initgroups were not yet " + "performed") != -1 + assert output.find("Cached in InfoPipe: No") != -1 + + output = get_call_output(["sssctl", "user-show", "camelcaseuser1@LDAP"]) + assert output.find("User camelcaseuser1 is not present in cache.") != -1 + + +def test_user_show_basic_fqname_insensitive(ldap_conn, + fqname_case_insensitive_rfc2307, + portable_LC_ALL): + # Fill the cache first + ent.assert_passwd_by_name( + 'user1@LDAP', + dict(name='user1@LDAP', passwd='*', uid=1001, gid=2001, + gecos='1001', shell='/bin/bash')) + ent.assert_passwd_by_name( + 'CamelCaseUser1@LDAP', + dict(name='camelcaseuser1@LDAP', passwd='*', uid=1002, gid=2002, + gecos='1002', shell='/bin/bash')) + + output = get_call_output(["sssctl", "user-show", "user1@LDAP"]) + assert output.find("Name: user1@LDAP") != -1 + assert output.find("Initgroups expiration time: Initgroups were not yet " + "performed") != -1 + assert output.find("Cached in InfoPipe: No") != -1 + + output = get_call_output(["sssctl", "user-show", "CamelCaseUser1@LDAP"]) + assert output.find("Name: camelcaseuser1@LDAP") != -1 + assert output.find("Initgroups expiration time: Initgroups were not yet " + "performed") != -1 + assert output.find("Cached in InfoPipe: No") != -1 + + output = get_call_output(["sssctl", "user-show", "camelcaseuser1@LDAP"]) + assert output.find("Name: camelcaseuser1@LDAP") != -1 + assert output.find("Initgroups expiration time: Initgroups were not yet " + "performed") != -1 + assert output.find("Cached in InfoPipe: No") != -1 + + +def test_group_show_basic_sanity(ldap_conn, sanity_rfc2307, portable_LC_ALL): + # Fill the cache first + ent.assert_group_by_name( + "group1", + dict(mem=ent.contains_only("user1"))) + ent.assert_group_by_name( + "CamelCaseGroup1", + dict(mem=ent.contains_only("CamelCaseUser1"))) + + output = get_call_output(["sssctl", "group-show", "group1"]) + assert output.find("Name: group1") != -1 + assert output.find("Cached in InfoPipe: No") != -1 + + output = get_call_output(["sssctl", "group-show", "CamelCaseGroup1"]) + assert output.find("Name: CamelCaseGroup1") != -1 + assert output.find("Cached in InfoPipe: No") != -1 + + output = get_call_output(["sssctl", "group-show", "camelcasegroup1"]) + assert output.find("Group camelcasegroup1 is not present in cache.") != -1 + + +def test_group_show_basic_fqname(ldap_conn, fqname_rfc2307, portable_LC_ALL): + # Fill the cache first + ent.assert_group_by_name( + "group1@LDAP", + dict(mem=ent.contains_only("user1@LDAP"))) + ent.assert_group_by_name( + "CamelCaseGroup1@LDAP", + dict(mem=ent.contains_only("CamelCaseUser1@LDAP"))) + + output = get_call_output(["sssctl", "group-show", "group1@LDAP"]) + assert output.find("Name: group1@LDAP") != -1 + assert output.find("Cached in InfoPipe: No") != -1 + + output = get_call_output(["sssctl", "group-show", "CamelCaseGroup1@LDAP"]) + assert output.find("Name: CamelCaseGroup1@LDAP") != -1 + assert output.find("Cached in InfoPipe: No") != -1 + + output = get_call_output(["sssctl", "group-show", "camelcasegroup1@LDAP"]) + assert output.find("Group camelcasegroup1 is not present in cache.") != -1 + + +def test_group_show_basic_fqname_insensitive(ldap_conn, + fqname_case_insensitive_rfc2307, + portable_LC_ALL): + # Fill the cache first + ent.assert_group_by_name( + "group1@LDAP", + dict(mem=ent.contains_only("user1@LDAP"))) + ent.assert_group_by_name( + "camelcasegroup1@LDAP", + dict(mem=ent.contains_only("camelcaseuser1@LDAP"))) + + output = get_call_output(["sssctl", "group-show", "group1@LDAP"]) + assert output.find("Name: group1@LDAP") != -1 + assert output.find("Cached in InfoPipe: No") != -1 + + output = get_call_output(["sssctl", "group-show", "CamelCaseGroup1@LDAP"]) + assert output.find("Name: camelcasegroup1@LDAP") != -1 + assert output.find("Cached in InfoPipe: No") != -1 + + output = get_call_output(["sssctl", "group-show", "camelcasegroup1@LDAP"]) + assert output.find("Name: camelcasegroup1@LDAP") != -1 + assert output.find("Cached in InfoPipe: No") != -1 + + +@pytest.fixture +def add_tripled_netgroup(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + + ent_list.add_netgroup("tripled_netgroup", ["(host,user,domain)"]) + + create_ldap_fixture(request, ldap_conn, ent_list) + return None + + +def test_netgroup_show(ldap_conn, + sanity_rfc2307, + portable_LC_ALL, + add_tripled_netgroup): + output = get_call_output(["sssctl", "netgroup-show", "tripled_netgroup"]) + assert "Name: tripled_netgroup" not in output + + res, _, netgrps = sssd_netgroup.get_sssd_netgroups("tripled_netgroup") + assert res == sssd_netgroup.NssReturnCode.SUCCESS + assert netgrps == [("host", "user", "domain")] + + output = get_call_output(["sssctl", "netgroup-show", "tripled_netgroup"]) + assert "Name: tripled_netgroup" in output + + +@pytest.fixture +def conf_snippets_only(request): + snip = unindent("""\ + [sssd] + services = nss, pam, ssh + [nss] + [pam] + [ssh] + """) + create_conf_fixture(request, None, snip) + return None + + +@pytest.fixture +def conf_stub_domain(request): + snip = unindent("""\ + [sssd] + services = nss + domains = files + [nss] + [domain/files] + id_provider = files + """) + create_conf_fixture(request, None, snip) + return None + + +def test_sssctl_snippets_only(conf_snippets_only, portable_LC_ALL): + output = get_call_output(["sssctl", "config-check"]) + assert "There is no configuration" not in output + assert config.CONF_SNIPPET_PATH in output + + +def test_sssctl_no_config(portable_LC_ALL): + output = get_call_output(["sssctl", "config-check"]) + assert "There is no configuration" in output + + +def test_debug_level_sanity(ldap_conn, sanity_rfc2307, portable_LC_ALL): + output = get_call_output(["sssctl", "debug-level", "0x00F0"], + check=True) + assert output.strip() == "" + output = get_call_output(["sssctl", "debug-level"], + check=True) + for line in output.splitlines(): + elems = line.split() + assert elems[0] in ["sssd", "nss", "domain/LDAP", "domain/implicit_files"] + assert elems[1] == "0x00f0" + + output = get_call_output(["sssctl", "debug-level", "--sssd", "0x0270"], + check=True) + assert output.strip() == "" + output = get_call_output(["sssctl", "debug-level", "--sssd"], + check=True) + assert "sssd " in output + assert "0x0270" in output + + output = get_call_output(["sssctl", "debug-level", "--nss", "0x0370"], + check=True) + assert output.strip() == "" + output = get_call_output(["sssctl", "debug-level", "--nss"], + check=True) + assert "nss " in output + assert "0x0370" in output + + output = get_call_output(["sssctl", "debug-level", "--domain=LDAP", "8"], + check=True) + assert output.strip() == "" + output = get_call_output(["sssctl", "debug-level", "--domain=LDAP"], + check=True) + assert "domain/LDAP " in output + assert "0x37f0" in output + + try: + get_call_output(["sssctl", "debug-level", "--domain=FAKE"], + check=True) + except subprocess.CalledProcessError as cpe: + assert cpe.returncode == 1 + assert "domain/FAKE " in cpe.output + assert " Unknown domain" in cpe.output + + try: + get_call_output(["sssctl", "debug-level", "--pac"], + check=True) + except subprocess.CalledProcessError as cpe: + assert cpe.returncode == 1 + assert "pac " in cpe.output + assert " Unreachable service" in cpe.output + + try: + get_call_output(["sssctl", "debug-level", "--domain=FAKE", "8"], + check=True) + except subprocess.CalledProcessError as cpe: + assert cpe.returncode == 1 + assert cpe.output.strip() == "" + + +def test_debug_level_no_sssd(conf_stub_domain, portable_LC_ALL): + # Once we are sure all tests run using Python 3.5 or newer, + # we can remove the redirections STDOUT > STDERR and check cpe.stderr. + + try: + get_call_output(["sssctl", "debug-level"], check=True, + stderr_output=subprocess.STDOUT) + except subprocess.CalledProcessError as cpe: + assert cpe.returncode == 1 + assert "SSSD is not running" in cpe.output + + try: + get_call_output(["sssctl", "debug-level", "0x70"], check=True, + stderr_output=subprocess.STDOUT) + except subprocess.CalledProcessError as cpe: + assert cpe.returncode == 1 + assert "SSSD is not running" in cpe.output + + try: + get_call_output(["sssctl", "debug-level", "--nss"], check=True, + stderr_output=subprocess.STDOUT) + except subprocess.CalledProcessError as cpe: + assert cpe.returncode == 1 + assert "SSSD is not running" in cpe.output + + try: + get_call_output(["sssctl", "debug-level", "--nss", "0x70"], check=True, + stderr_output=subprocess.STDOUT) + except subprocess.CalledProcessError as cpe: + assert cpe.returncode == 1 + assert "SSSD is not running" in cpe.output + + +def test_invalidate_missing_specific_entry(ldap_conn, sanity_rfc2307, portable_LC_ALL): + # Ensure we will fail when invalidating missing specific entry + ret = subprocess.call(["sssctl", "cache-expire", "-u", "non-existing"]) + assert ret == 1 + + ret = subprocess.call(["sssctl", "cache-expire", "-d", "non-existing", "-u", "dummy"]) + assert ret == 1 + + ret = subprocess.call(["sssctl", "cache-expire", "-g", "non-existing"]) + assert ret == 1 + + ret = subprocess.call(["sssctl", "cache-expire", "-d", "non-existing", "-g", "dummy"]) + assert ret == 1 diff --git a/src/tests/intg/test_sudo.py b/src/tests/intg/test_sudo.py new file mode 100644 index 0000000..ec85511 --- /dev/null +++ b/src/tests/intg/test_sudo.py @@ -0,0 +1,284 @@ +# +# Sudo integration test +# +# Copyright (c) 2018 Red Hat, Inc. +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 stat +import signal +import subprocess +import time +import ldap +import pytest +import json + +import config +import ds_openldap +import ldap_ent +from util import unindent, get_call_output + +LDAP_BASE_DN = "dc=example,dc=com" + + +class SudoReplyElement: + def __init__(self, retval, rules): + self.retval = retval + self.rules = rules + + +class SudoReply: + def __init__(self, json_string): + self.jres = json.loads(json_string) + for reply_elem in self.jres: + el = SudoReplyElement(reply_elem['retval'], + reply_elem['result']['rules']) + if reply_elem['type'] == 'default': + self.defaults = el + if reply_elem['type'] == 'rules': + self.sudo_rules = el + + +@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 Exception: + ds_inst.teardown() + raise + request.addfinalizer(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(ldap_conn.unbind_s) + return ldap_conn + + +def create_ldap_entries(ldap_conn, ent_list=None): + """Add LDAP entries from ent_list""" + if ent_list is not None: + for entry in ent_list: + ldap_conn.add_s(entry[0], entry[1]) + + +def cleanup_ldap_entries(ldap_conn, ent_list=None): + """Remove LDAP entries added by create_ldap_entries""" + if ent_list is None: + for ou in ("Users", "Groups", "Netgroups", "Services", "Policies"): + for entry in ldap_conn.search_s(f"ou={ou}," + f"{ldap_conn.ds_inst.base_dn}", + ldap.SCOPE_ONELEVEL, + attrlist=[]): + ldap_conn.delete_s(entry[0]) + else: + for entry in ent_list: + ldap_conn.delete_s(entry[0]) + + +def create_ldap_cleanup(request, ldap_conn, ent_list=None): + """Add teardown for removing all user/group LDAP entries""" + request.addfinalizer(lambda: cleanup_ldap_entries(ldap_conn, ent_list)) + + +def create_ldap_fixture(request, ldap_conn, ent_list=None): + """Add LDAP entries and add teardown for removing them""" + create_ldap_entries(ldap_conn, ent_list) + create_ldap_cleanup(request, ldap_conn, ent_list) + + +SCHEMA_RFC2307_BIS = "rfc2307bis" + + +def format_basic_conf(ldap_conn, schema): + """Format a basic SSSD configuration""" + schema_conf = "ldap_schema = " + schema + "\n" + schema_conf += "ldap_group_object_class = groupOfNames\n" + return unindent("""\ + [sssd] + domains = LDAP + services = nss, sudo + + [nss] + + [sudo] + debug_level=10 + + [domain/LDAP] + {schema_conf} + id_provider = ldap + auth_provider = ldap + ldap_uri = {ldap_conn.ds_inst.ldap_url} + ldap_search_base = {ldap_conn.ds_inst.base_dn} + ldap_sudo_use_host_filter = false + ldap_sudo_random_offset = 0 + debug_level=10 + """).format(**locals()) + + +def create_conf_file(contents): + """Create sssd.conf with specified contents""" + conf = open(config.CONF_PATH, "w") + conf.write(contents) + conf.close() + os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR) + + +def cleanup_conf_file(): + """Remove sssd.conf, if it exists""" + if os.path.lexists(config.CONF_PATH): + os.unlink(config.CONF_PATH) + + +def create_conf_cleanup(request): + """Add teardown for removing sssd.conf""" + request.addfinalizer(cleanup_conf_file) + + +def create_conf_fixture(request, contents): + """ + Create sssd.conf with specified contents and add teardown for removing it + """ + create_conf_file(contents) + create_conf_cleanup(request) + + +def create_sssd_process(): + """Start the SSSD process""" + if subprocess.call(["sssd", "-D", "--logger=files"]) != 0: + raise Exception("sssd start failed") + + +def get_sssd_pid(): + pid_file = open(config.PIDFILE_PATH, "r") + pid = int(pid_file.read()) + return pid + + +def cleanup_sssd_process(): + """Stop the SSSD process and remove its state""" + try: + pid = get_sssd_pid() + os.kill(pid, signal.SIGTERM) + while True: + try: + os.kill(pid, signal.SIGCONT) + except OSError: + break + time.sleep(1) + except OSError: + 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) + + +def create_sssd_fixture(request): + """Start SSSD and add teardown for stopping it and removing its state""" + create_sssd_process() + create_sssd_cleanup(request) + + +def create_sssd_cleanup(request): + """Add teardown for stopping SSSD and removing its state""" + request.addfinalizer(cleanup_sssd_process) + + +@pytest.fixture() +def sudocli_tool(request): + sudocli_path = os.path.join(config.ABS_BUILDDIR, + "..", "..", "..", "sss_sudo_cli") + assert os.access(sudocli_path, os.X_OK) + return sudocli_path + + +@pytest.fixture +def add_common_rules(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + ent_list.add_user("user2", 1001, 2001) + ent_list.add_sudo_rule("user1_allow_less_shadow", + users=("user1",), + hosts=("ALL",), + commands=("/usr/bin/less /etc/shadow", "/bin/ls")) + create_ldap_fixture(request, ldap_conn, ent_list) + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +@pytest.mark.converted('test_sudo.py', 'test_sudo__user_allowed') +def test_sudo_rule_for_user(add_common_rules, sudocli_tool): + """ + Test that user1 is allowed in the rule but user2 is not + """ + user1_rules = get_call_output([sudocli_tool, "user1"]) + reply = SudoReply(user1_rules) + assert len(reply.sudo_rules.rules) == 1 + assert reply.sudo_rules.rules[0]['cn'] == 'user1_allow_less_shadow' + + user2_rules = get_call_output([sudocli_tool, "user2"]) + reply = SudoReply(user2_rules) + assert len(reply.sudo_rules.rules) == 0 + + +@pytest.fixture +def add_double_qualified_rules(request, ldap_conn): + ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn) + ent_list.add_user("user1", 1001, 2001) + ent_list.add_user("user2", 1002, 2001) + ent_list.add_user("user3", 1003, 2001) + ent_list.add_user("user4", 1004, 2001) + ent_list.add_sudo_rule("user1_allow_less_shadow", + users=("user1", "user2", "user2@LDAP", "user3"), + hosts=("ALL",), + commands=("/usr/bin/less /etc/shadow", "/bin/ls")) + create_ldap_fixture(request, ldap_conn, ent_list) + conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + create_conf_fixture(request, conf) + create_sssd_fixture(request) + return None + + +@pytest.mark.converted('test_sudo.py', 'test_sudo__duplicate_sudo_user') +def test_sudo_rule_duplicate_sudo_user(add_double_qualified_rules, + sudocli_tool): + """ + Test that despite user1 and user1@LDAP meaning the same user, + the rule is still usable + """ + # Try several users to make sure we don't mangle the list + for u in ["user1", "user2", "user3"]: + user_rules = get_call_output([sudocli_tool, u]) + reply = SudoReply(user_rules) + assert len(reply.sudo_rules.rules) == 1 + assert reply.sudo_rules.rules[0]['cn'] == 'user1_allow_less_shadow' + + user4_rules = get_call_output([sudocli_tool, "user4"]) + reply = SudoReply(user4_rules) + assert len(reply.sudo_rules.rules) == 0 diff --git a/src/tests/intg/test_ts_cache.py b/src/tests/intg/test_ts_cache.py new file mode 100644 index 0000000..dbade2e --- /dev/null +++ b/src/tests/intg/test_ts_cache.py @@ -0,0 +1,679 @@ +# +# LDAP integration test - test updating the sysdb and timestamp +# cache +# +# Copyright (c) 2016 Red Hat, Inc. +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 stat +import ent +import grp +import pwd +import config +import signal +import subprocess +import time +import ldap +import pytest +import ds_openldap +import ldap_ent +import sssd_ldb +import sssd_id +from util import unindent + +LDAP_BASE_DN = "dc=example,dc=com" +SSSD_DOMAIN = "LDAP" + +SCHEMA_RFC2307 = "rfc2307" +SCHEMA_RFC2307_BIS = "rfc2307bis" + +TS_ATTRLIST = ("dataExpireTimestamp", "originalModifyTimestamp") + + +@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 Exception: + 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 OSError: + 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", "--logger=files"]) != 0: + raise Exception("sssd start failed") + + def teardown(): + try: + stop_sssd() + except Exception: + 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) + ent_list.add_user("user11", 1011, 2001) + ent_list.add_user("user21", 1021, 2001) + + if schema == SCHEMA_RFC2307_BIS: + ent_list.add_group_bis("group1", 2001, ("user1", "user11", "user21")) + elif schema == SCHEMA_RFC2307: + ent_list.add_group("group1", 2001, ("user1", "user11", "user21")) + 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) + + +def load_2307_data_to_ldap(request, ldap_conn): + return load_data_to_ldap(request, ldap_conn, SCHEMA_RFC2307) + + +@pytest.fixture +def setup_rfc2307bis(request, ldap_conn): + load_2307bis_data_to_ldap(request, ldap_conn) + + conf = unindent("""\ + [sssd] + domains = LDAP + services = nss + + [nss] + memcache_timeout = 1 + + [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 + + +@pytest.fixture +def setup_rfc2307(request, ldap_conn): + load_2307_data_to_ldap(request, ldap_conn) + + conf = unindent("""\ + [sssd] + domains = LDAP + services = nss + + [nss] + memcache_timeout = 1 + + [domain/LDAP] + ldap_schema = rfc2307 + id_provider = ldap + auth_provider = ldap + sudo_provider = ldap + 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 + + +@pytest.fixture +def ldb_examine(request): + ldb_conn = sssd_ldb.SssdLdb('LDAP') + return ldb_conn + + +def invalidate_group(ldb_conn, name): + ldb_conn.invalidate_entry(name, sssd_ldb.TsCacheEntry.group, SSSD_DOMAIN) + + +def invalidate_user(ldb_conn, name): + ldb_conn.invalidate_entry(name, sssd_ldb.TsCacheEntry.user, SSSD_DOMAIN) + + +def get_attrs(ldb_conn, type, name, domain, attr_list): + sysdb_attrs = dict() + ts_attrs = dict() + + for attr in attr_list: + val = ldb_conn.get_entry_attr(sssd_ldb.CacheType.sysdb, + type, name, domain, attr) + if val: + val = val.decode('utf-8') + sysdb_attrs[attr] = val + + val = ldb_conn.get_entry_attr(sssd_ldb.CacheType.timestamps, + type, name, domain, attr) + if val: + val = val.decode('utf-8') + ts_attrs[attr] = val + return (sysdb_attrs, ts_attrs) + + +def get_group_attrs(ldb_conn, name, domain, attr_list): + return get_attrs(ldb_conn, sssd_ldb.TsCacheEntry.group, + name, domain, attr_list) + + +def get_user_attrs(ldb_conn, name, domain, attr_list): + return get_attrs(ldb_conn, sssd_ldb.TsCacheEntry.user, + name, domain, attr_list) + + +def assert_same_attrval(adict1, adict2, attr_name): + assert adict1.get(attr_name) is not None and \ + adict1.get(attr_name) == adict2.get(attr_name) + + +def assert_diff_attrval(adict1, adict2, attr_name): + assert adict1.get(attr_name) is not None and \ + adict1.get(attr_name) != adict2.get(attr_name) + + +def prime_cache_group(ldb_conn, name, members): + ent.assert_group_by_name( + name, + dict(mem=ent.contains_only(*members))) + sysdb_attrs, ts_attrs = get_group_attrs(ldb_conn, name, + SSSD_DOMAIN, TS_ATTRLIST) + assert_same_attrval(sysdb_attrs, ts_attrs, "dataExpireTimestamp") + assert_same_attrval(sysdb_attrs, ts_attrs, "originalModifyTimestamp") + + # just to force different stamps and make sure memcache is gone + time.sleep(1) + invalidate_group(ldb_conn, name) + + return sysdb_attrs, ts_attrs + + +def prime_cache_user(ldb_conn, name, primary_gid): + # calling initgroups would add the initgExpire timestamp attribute and + # make sure that sss_cache doesn't add it with a value of 1, + # triggering a sysdb update + (res, errno, gids) = sssd_id.call_sssd_initgroups(name, primary_gid) + assert res == sssd_id.NssReturnCode.SUCCESS + + sysdb_attrs, ts_attrs = get_user_attrs(ldb_conn, name, + SSSD_DOMAIN, TS_ATTRLIST) + assert_same_attrval(sysdb_attrs, ts_attrs, "dataExpireTimestamp") + assert_same_attrval(sysdb_attrs, ts_attrs, "originalModifyTimestamp") + + # just to force different stamps and make sure memcache is gone + time.sleep(1) + invalidate_user(ldb_conn, name) + + return sysdb_attrs, ts_attrs + + +def test_group_2307bis_update_same_modstamp(ldap_conn, + ldb_examine, + setup_rfc2307bis): + """ + Test that a group update with the same modifyTimestamp does not trigger + sysdb cache update + """ + ldb_conn = ldb_examine + old_sysdb_attrs, old_ts_attrs = prime_cache_group( + ldb_conn, "group1", + ("user1", "user11", "user21")) + + ent.assert_group_by_name( + "group1", + dict(mem=ent.contains_only("user1", "user11", "user21"))) + sysdb_attrs, ts_attrs = get_group_attrs(ldb_conn, "group1", + SSSD_DOMAIN, TS_ATTRLIST) + + assert_same_attrval(sysdb_attrs, old_sysdb_attrs, "dataExpireTimestamp") + assert_same_attrval(sysdb_attrs, old_sysdb_attrs, + "originalModifyTimestamp") + + assert_diff_attrval(ts_attrs, old_ts_attrs, "dataExpireTimestamp") + assert_same_attrval(ts_attrs, old_ts_attrs, "originalModifyTimestamp") + + +def test_group_2307bis_update_same_attrs(ldap_conn, + ldb_examine, + setup_rfc2307bis): + """ + Test that a group update with a different modifyTimestamp but the same + attrs does not trigger sysdb cache update + """ + ldb_conn = ldb_examine + old_sysdb_attrs, old_ts_attrs = prime_cache_group( + ldb_conn, "group1", + ("user1", "user11", "user21")) + + # modify an argument we don't save to the cache. This will bump the + # modifyTimestamp attribute, but the attributes themselves will be the same + # from sssd's point of view + ldap_conn.modify_s("cn=group1,ou=Groups," + ldap_conn.ds_inst.base_dn, + [(ldap.MOD_ADD, "description", b"group one")]) + # wait for slapd to change its database + time.sleep(1) + + ent.assert_group_by_name( + "group1", + dict(mem=ent.contains_only("user1", "user11", "user21"))) + sysdb_attrs, ts_attrs = get_group_attrs(ldb_conn, "group1", + SSSD_DOMAIN, TS_ATTRLIST) + + assert_same_attrval(sysdb_attrs, old_sysdb_attrs, "dataExpireTimestamp") + assert_same_attrval(sysdb_attrs, old_sysdb_attrs, + "originalModifyTimestamp") + + assert_diff_attrval(ts_attrs, old_ts_attrs, "dataExpireTimestamp") + assert_diff_attrval(ts_attrs, old_ts_attrs, "originalModifyTimestamp") + + +def test_group_2307bis_update_diff_attrs(ldap_conn, + ldb_examine, + setup_rfc2307bis): + """ + Test that a group update with different attribute triggers cache update + """ + ldb_conn = ldb_examine + old_sysdb_attrs, old_ts_attrs = prime_cache_group( + ldb_conn, "group1", + ("user1", "user11", "user21")) + + user_dn = "uid=user1,ou=Users," + ldap_conn.ds_inst.base_dn + ldap_conn.modify_s("cn=group1,ou=Groups," + ldap_conn.ds_inst.base_dn, + [(ldap.MOD_DELETE, "member", user_dn.encode('utf-8'))]) + # wait for slapd to change its database + time.sleep(1) + + ent.assert_group_by_name( + "group1", + dict(mem=ent.contains_only("user11", "user21"))) + sysdb_attrs, ts_attrs = get_group_attrs(ldb_conn, "group1", + SSSD_DOMAIN, TS_ATTRLIST) + + assert_diff_attrval(sysdb_attrs, old_sysdb_attrs, "dataExpireTimestamp") + assert_diff_attrval(sysdb_attrs, old_sysdb_attrs, + "originalModifyTimestamp") + + assert_diff_attrval(ts_attrs, old_ts_attrs, "dataExpireTimestamp") + assert_diff_attrval(ts_attrs, old_ts_attrs, "originalModifyTimestamp") + + +def test_group_2307bis_delete_group(ldap_conn, + ldb_examine, + setup_rfc2307bis): + """ + Test that deleting a group removes it from both caches + """ + ldb_conn = ldb_examine + old_sysdb_attrs, old_ts_attrs = prime_cache_group( + ldb_conn, "group1", + ("user1", "user11", "user21")) + + e = ldap_ent.group_bis(ldap_conn.ds_inst.base_dn, "group1", 2001) + ldap_conn.delete_s(e[0]) + # wait for slapd to change its database + time.sleep(1) + + with pytest.raises(KeyError): + grp.getgrnam("group1") + + sysdb_attrs, ts_attrs = get_group_attrs(ldb_conn, "group1", + SSSD_DOMAIN, TS_ATTRLIST) + assert sysdb_attrs.get("dataExpireTimestamp") is None + assert sysdb_attrs.get("originalModifyTimestamp") is None + assert ts_attrs.get("dataExpireTimestamp") is None + assert ts_attrs.get("originalModifyTimestamp") is None + + +def test_group_2307_update_same_modstamp(ldap_conn, + ldb_examine, + setup_rfc2307): + """ + Test that a group update with the same modifyTimestamp does not trigger + sysdb cache update + """ + ldb_conn = ldb_examine + old_sysdb_attrs, old_ts_attrs = prime_cache_group( + ldb_conn, "group1", + ("user1", "user11", "user21")) + + ent.assert_group_by_name( + "group1", + dict(mem=ent.contains_only("user1", "user11", "user21"))) + sysdb_attrs, ts_attrs = get_group_attrs(ldb_conn, "group1", + SSSD_DOMAIN, TS_ATTRLIST) + + assert_same_attrval(sysdb_attrs, old_sysdb_attrs, "dataExpireTimestamp") + assert_same_attrval(sysdb_attrs, old_sysdb_attrs, + "originalModifyTimestamp") + + assert_diff_attrval(ts_attrs, old_ts_attrs, "dataExpireTimestamp") + assert_same_attrval(ts_attrs, old_ts_attrs, "originalModifyTimestamp") + + +def test_group_2307_update_same_attrs(ldap_conn, + ldb_examine, + setup_rfc2307): + """ + Test that a group update with a different modifyTimestamp but the same + attrs does not trigger sysdb cache update + """ + ldb_conn = ldb_examine + old_sysdb_attrs, old_ts_attrs = prime_cache_group( + ldb_conn, "group1", + ("user1", "user11", "user21")) + + # modify an argument we don't save to the cache. This will bump the + # modifyTimestamp attribute, but the attributes themselves will be the same + # from sssd's point of view + ldap_conn.modify_s("cn=group1,ou=Groups," + ldap_conn.ds_inst.base_dn, + [(ldap.MOD_ADD, "description", b"group one")]) + # wait for slapd to change its database + time.sleep(1) + + ent.assert_group_by_name( + "group1", + dict(mem=ent.contains_only("user1", "user11", "user21"))) + sysdb_attrs, ts_attrs = get_group_attrs(ldb_conn, "group1", + SSSD_DOMAIN, TS_ATTRLIST) + + assert_same_attrval(sysdb_attrs, old_sysdb_attrs, "dataExpireTimestamp") + assert_same_attrval(sysdb_attrs, old_sysdb_attrs, + "originalModifyTimestamp") + + assert_diff_attrval(ts_attrs, old_ts_attrs, "dataExpireTimestamp") + assert_diff_attrval(ts_attrs, old_ts_attrs, "originalModifyTimestamp") + + +def test_group_2307_update_diff_attrs(ldap_conn, + ldb_examine, + setup_rfc2307): + """ + Test that a group update with different attribute triggers cache update + """ + ldb_conn = ldb_examine + old_sysdb_attrs, old_ts_attrs = prime_cache_group( + ldb_conn, "group1", + ("user1", "user11", "user21")) + + ldap_conn.modify_s("cn=group1,ou=Groups," + ldap_conn.ds_inst.base_dn, + [(ldap.MOD_DELETE, "memberUid", b"user1")]) + # wait for slapd to change its database + time.sleep(1) + + ent.assert_group_by_name( + "group1", + dict(mem=ent.contains_only("user11", "user21"))) + sysdb_attrs, ts_attrs = get_group_attrs(ldb_conn, "group1", + SSSD_DOMAIN, TS_ATTRLIST) + + assert_diff_attrval(sysdb_attrs, old_sysdb_attrs, "dataExpireTimestamp") + assert_diff_attrval(sysdb_attrs, old_sysdb_attrs, + "originalModifyTimestamp") + + assert_diff_attrval(ts_attrs, old_ts_attrs, "dataExpireTimestamp") + assert_diff_attrval(ts_attrs, old_ts_attrs, "originalModifyTimestamp") + + +def test_group_2307_delete_group(ldap_conn, + ldb_examine, + setup_rfc2307): + """ + Test that deleting a group removes it from both caches + """ + ldb_conn = ldb_examine + old_sysdb_attrs, old_ts_attrs = prime_cache_group( + ldb_conn, "group1", + ("user1", "user11", "user21")) + + e = ldap_ent.group_bis(ldap_conn.ds_inst.base_dn, "group1", 2001) + ldap_conn.delete_s(e[0]) + # wait for slapd to change its database + time.sleep(1) + + with pytest.raises(KeyError): + grp.getgrnam("group1") + + sysdb_attrs, ts_attrs = get_group_attrs(ldb_conn, "group1", + SSSD_DOMAIN, TS_ATTRLIST) + assert sysdb_attrs.get("dataExpireTimestamp") is None + assert sysdb_attrs.get("originalModifyTimestamp") is None + assert ts_attrs.get("dataExpireTimestamp") is None + assert ts_attrs.get("originalModifyTimestamp") is None + + +def test_user_update_same_modstamp(ldap_conn, + ldb_examine, + setup_rfc2307bis): + """ + Test that a user update with the same modifyTimestamp does not trigger + sysdb cache update + """ + ldb_conn = ldb_examine + old_sysdb_attrs, old_ts_attrs = prime_cache_user(ldb_conn, "user1", 2001) + + ent.assert_passwd_by_name("user1", dict(name="user1")) + + sysdb_attrs, ts_attrs = get_user_attrs(ldb_conn, "user1", + SSSD_DOMAIN, TS_ATTRLIST) + assert_same_attrval(sysdb_attrs, old_sysdb_attrs, "dataExpireTimestamp") + assert_same_attrval(sysdb_attrs, old_sysdb_attrs, + "originalModifyTimestamp") + + assert_diff_attrval(ts_attrs, old_ts_attrs, "dataExpireTimestamp") + assert_same_attrval(ts_attrs, old_ts_attrs, "originalModifyTimestamp") + + +def test_user_update_same_attrs(ldap_conn, + ldb_examine, + setup_rfc2307bis): + """ + Test that a user update with the same modifyTimestamp does not trigger + sysdb cache update + """ + ldb_conn = ldb_examine + old_sysdb_attrs, old_ts_attrs = prime_cache_user(ldb_conn, "user1", 2001) + + # modify an argument we don't save to the cache. This will bump the + # modifyTimestamp attribute, but the attributes themselves will be the same + # from sssd's point of view + ldap_conn.modify_s("uid=user1,ou=Users," + ldap_conn.ds_inst.base_dn, + [(ldap.MOD_ADD, "description", b"user one")]) + # wait for slapd to change its database + time.sleep(1) + + ent.assert_passwd_by_name("user1", dict(name="user1")) + + sysdb_attrs, ts_attrs = get_user_attrs(ldb_conn, "user1", + SSSD_DOMAIN, TS_ATTRLIST) + assert_same_attrval(sysdb_attrs, old_sysdb_attrs, "dataExpireTimestamp") + assert_same_attrval(sysdb_attrs, old_sysdb_attrs, + "originalModifyTimestamp") + + assert_diff_attrval(ts_attrs, old_ts_attrs, "dataExpireTimestamp") + assert_diff_attrval(ts_attrs, old_ts_attrs, "originalModifyTimestamp") + + +def test_user_update_diff_attrs(ldap_conn, + ldb_examine, + setup_rfc2307bis): + """ + Test that a user update with the same modifyTimestamp does not trigger + sysdb cache update + """ + ldb_conn = ldb_examine + old_sysdb_attrs, old_ts_attrs = prime_cache_user(ldb_conn, "user1", 2001) + + # modify an argument we don't save to the cache. This will bump the + # modifyTimestamp attribute, but the attributes themselves will be the same + # from sssd's point of view + ldap_conn.modify_s("uid=user1,ou=Users," + ldap_conn.ds_inst.base_dn, + [(ldap.MOD_REPLACE, "loginShell", b"/bin/zsh")]) + # wait for slapd to change its database + time.sleep(1) + + ent.assert_passwd_by_name("user1", dict(name="user1")) + sysdb_attrs, ts_attrs = get_user_attrs(ldb_conn, "user1", + SSSD_DOMAIN, TS_ATTRLIST) + assert_diff_attrval(sysdb_attrs, old_sysdb_attrs, "dataExpireTimestamp") + assert_diff_attrval(sysdb_attrs, old_sysdb_attrs, + "originalModifyTimestamp") + + assert_diff_attrval(ts_attrs, old_ts_attrs, "dataExpireTimestamp") + assert_diff_attrval(ts_attrs, old_ts_attrs, "originalModifyTimestamp") + + +def test_user_2307bis_delete_user(ldap_conn, + ldb_examine, + setup_rfc2307bis): + """ + Test that deleting a user removes it from both caches + """ + ldb_conn = ldb_examine + old_sysdb_attrs, old_ts_attrs = prime_cache_user(ldb_conn, "user1", 2001) + + e = ldap_ent.user(ldap_conn.ds_inst.base_dn, "user1", 1001, 2001) + + ldap_conn.delete_s(e[0]) + # wait for slapd to change its database + time.sleep(1) + + with pytest.raises(KeyError): + pwd.getpwnam("user1") + sysdb_attrs, ts_attrs = get_user_attrs(ldb_conn, "user1", + SSSD_DOMAIN, TS_ATTRLIST) + assert sysdb_attrs.get("dataExpireTimestamp") is None + assert sysdb_attrs.get("originalModifyTimestamp") is None + assert ts_attrs.get("dataExpireTimestamp") is None + assert ts_attrs.get("originalModifyTimestamp") is None + + +def test_sss_cache_invalidate_user(ldap_conn, + ldb_examine, + setup_rfc2307bis): + """ + Test that sss_cache invalidate user in both caches + """ + + ldb_conn = ldb_examine + old_sysdb_attrs, old_ts_attrs = prime_cache_user(ldb_conn, "user1", 2001) + + subprocess.call(["sss_cache", "-u", "user1"]) + + sysdb_attrs, ts_attrs = get_user_attrs(ldb_conn, "user1", + SSSD_DOMAIN, TS_ATTRLIST) + + assert sysdb_attrs.get("dataExpireTimestamp") == '1' + assert ts_attrs.get("dataExpireTimestamp") == '1' + + time.sleep(1) + pwd.getpwnam("user1") + sysdb_attrs, ts_attrs = get_user_attrs(ldb_conn, "user1", + SSSD_DOMAIN, TS_ATTRLIST) + + assert sysdb_attrs.get("dataExpireTimestamp") == '1' + assert_diff_attrval(ts_attrs, sysdb_attrs, "dataExpireTimestamp") + + +def test_sss_cache_invalidate_group(ldap_conn, + ldb_examine, + setup_rfc2307bis): + """ + Test that sss_cache invalidate group in both caches + """ + + ldb_conn = ldb_examine + old_sysdb_attrs, old_ts_attrs = prime_cache_group( + ldb_conn, "group1", + ("user1", "user11", "user21")) + + subprocess.call(["sss_cache", "-g", "group1"]) + + sysdb_attrs, ts_attrs = get_group_attrs(ldb_conn, "group1", + SSSD_DOMAIN, TS_ATTRLIST) + + assert sysdb_attrs.get("dataExpireTimestamp") == '1' + assert ts_attrs.get("dataExpireTimestamp") == '1' + + time.sleep(1) + grp.getgrnam("group1") + sysdb_attrs, ts_attrs = get_group_attrs(ldb_conn, "group1", + SSSD_DOMAIN, TS_ATTRLIST) + + assert sysdb_attrs.get("dataExpireTimestamp") == '1' + assert_diff_attrval(ts_attrs, sysdb_attrs, "dataExpireTimestamp") diff --git a/src/tests/intg/util.py b/src/tests/intg/util.py new file mode 100644 index 0000000..9814ec0 --- /dev/null +++ b/src/tests/intg/util.py @@ -0,0 +1,106 @@ +# +# Various functions +# +# Copyright (c) 2015 Red Hat, Inc. +# Author: Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> +# +# This program 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 re +import os +import sys +import subprocess +import config +import shutil + +UNINDENT_RE = re.compile("^ +", re.MULTILINE) + + +def unindent(text): + """ + Unindent text by removing at most the number of spaces present in + the first non-empty line from the beginning of every line. + """ + indent_ref = [0] + + def replace(match): + if indent_ref[0] == 0: + indent_ref[0] = len(match.group()) + return match.group()[indent_ref[0]:] + return UNINDENT_RE.sub(replace, text) + + +def run_shell(): + """ + Execute an interactive shell under "screen", preserving environment. + For use as a breakpoint for debugging. + """ + my_env = os.environ.copy() + my_env["ROOT_DIR"] = config.PREFIX + + # screen filter out LD_* evniroment varibles. + # Back-up them and set them later in screenrc + my_env["_LD_LIBRARY_PATH"] = os.getenv("LD_LIBRARY_PATH", "") + my_env["_LD_PRELOAD"] = os.getenv("LD_PRELOAD", "") + + subprocess.call([ + "screen", "-DAm", "-S", "sssd_cwrap_session", "-c", + ".config/screenrc"], + env=my_env + ) + + +def first_dir(*args): + """Return first argument that points to an existing directory.""" + for arg in args: + if os.path.isdir(arg): + return arg + + +def backup_envvar_file(name): + path = os.environ[name] + backup_path = path + ".bak" + shutil.copyfile(path, backup_path) + return path + + +def restore_envvar_file(name): + path = os.environ[name] + backup_path = path + ".bak" + os.rename(backup_path, path) + + +def get_call_output(cmd, stderr_output=subprocess.PIPE, check=False): + """ + Executes the provided command. + When check is set to True, this function will throw an exception + if the command returns with a non-zero value. + """ + + if (sys.version_info.major < 3 + or (sys.version_info.major == 3 and sys.version_info.minor < 7)): + try: + output = subprocess.check_output(cmd, universal_newlines=True, + stderr=stderr_output) + except subprocess.CalledProcessError as err: + if (not check): + output = err.output + else: + raise err + return output + + process = subprocess.run(cmd, check=check, text=True, + stdout=subprocess.PIPE, stderr=stderr_output) + return process.stdout |