Blame SOURCES/0028-TESTS-Add-integration-tests-for-the-KCM-responder.patch

bb7cd1
From ad820beebae89c886f1ba4f0d2ddac4ca36857b7 Mon Sep 17 00:00:00 2001
bb7cd1
From: Jakub Hrozek <jhrozek@redhat.com>
bb7cd1
Date: Tue, 13 Dec 2016 17:17:16 +0100
bb7cd1
Subject: [PATCH 28/36] TESTS: Add integration tests for the KCM responder
bb7cd1
MIME-Version: 1.0
bb7cd1
Content-Type: text/plain; charset=UTF-8
bb7cd1
Content-Transfer-Encoding: 8bit
bb7cd1
bb7cd1
Reviewed-by: Michal Židek <mzidek@redhat.com>
bb7cd1
Reviewed-by: Simo Sorce <simo@redhat.com>
bb7cd1
Reviewed-by: Lukáš Slebodník <lslebodn@redhat.com>
bb7cd1
---
bb7cd1
 contrib/ci/configure.sh     |   7 +
bb7cd1
 contrib/ci/deps.sh          |   6 +
bb7cd1
 src/tests/intg/Makefile.am  |   4 +
bb7cd1
 src/tests/intg/kdc.py       | 175 +++++++++++++++++++++
bb7cd1
 src/tests/intg/krb5utils.py | 156 +++++++++++++++++++
bb7cd1
 src/tests/intg/test_kcm.py  | 361 ++++++++++++++++++++++++++++++++++++++++++++
bb7cd1
 6 files changed, 709 insertions(+)
bb7cd1
 create mode 100644 src/tests/intg/kdc.py
bb7cd1
 create mode 100644 src/tests/intg/krb5utils.py
bb7cd1
 create mode 100644 src/tests/intg/test_kcm.py
bb7cd1
bb7cd1
diff --git a/contrib/ci/configure.sh b/contrib/ci/configure.sh
bb7cd1
index 8e779cfe634a7555e0e8e3b52f42c07e62980fbc..7590743c2aa5fe31bcdf1a3e92a3f482dbec699b 100644
bb7cd1
--- a/contrib/ci/configure.sh
bb7cd1
+++ b/contrib/ci/configure.sh
bb7cd1
@@ -38,6 +38,13 @@ if [[ "$DISTRO_BRANCH" == -redhat-redhatenterprise*-6.*- ||
bb7cd1
         "--disable-cifs-idmap-plugin"
bb7cd1
         "--with-syslog=syslog"
bb7cd1
         "--without-python3-bindings"
bb7cd1
+        "--without-kcm"
bb7cd1
+    )
bb7cd1
+fi
bb7cd1
+
bb7cd1
+if [[ "$DISTRO_BRANCH" == -redhat-fedora-2[0-2]* ]]; then
bb7cd1
+    CONFIGURE_ARG_LIST+=(
bb7cd1
+        "--without-kcm"
bb7cd1
     )
bb7cd1
 fi
bb7cd1
 
bb7cd1
diff --git a/contrib/ci/deps.sh b/contrib/ci/deps.sh
bb7cd1
index c525e62e8c1d5b9fa042dee4ad03790dbceba242..4467e117c3a896a7f01ef7cb9e94fe28c2ea2838 100644
bb7cd1
--- a/contrib/ci/deps.sh
bb7cd1
+++ b/contrib/ci/deps.sh
bb7cd1
@@ -47,6 +47,8 @@ if [[ "$DISTRO_BRANCH" == -redhat-* ]]; then
bb7cd1
         uid_wrapper
bb7cd1
         python-requests
bb7cd1
         curl-devel
bb7cd1
+        krb5-server
bb7cd1
+        krb5-workstation
bb7cd1
     )
bb7cd1
     _DEPS_LIST_SPEC=`
bb7cd1
         sed -e 's/@PACKAGE_VERSION@/0/g' \
bb7cd1
@@ -122,6 +124,10 @@ if [[ "$DISTRO_BRANCH" == -debian-* ]]; then
bb7cd1
         libhttp-parser-dev
bb7cd1
         libjansson-dev
bb7cd1
         libcurl4-openssl-dev
bb7cd1
+        krb5-kdc
bb7cd1
+        krb5-admin-server
bb7cd1
+        krb5-user
bb7cd1
+        uuid-dev
bb7cd1
     )
bb7cd1
     DEPS_INTGCHECK_SATISFIED=true
bb7cd1
 fi
bb7cd1
diff --git a/src/tests/intg/Makefile.am b/src/tests/intg/Makefile.am
bb7cd1
index 1d36fa0d2d50307fbc871f5b2a6f0cb1cc95db81..8526beace09b15c99aa27ac98d5038d1980f6a71 100644
bb7cd1
--- a/src/tests/intg/Makefile.am
bb7cd1
+++ b/src/tests/intg/Makefile.am
bb7cd1
@@ -26,6 +26,9 @@ dist_noinst_DATA = \
bb7cd1
     files_ops.py \
bb7cd1
     test_files_ops.py \
bb7cd1
     test_files_provider.py \
bb7cd1
+    kdc.py \
bb7cd1
+    krb5utils.py \
bb7cd1
+    test_kcm.py \
bb7cd1
     $(NULL)
bb7cd1
 
bb7cd1
 config.py: config.py.m4
bb7cd1
@@ -80,5 +83,6 @@ intgcheck-installed: config.py passwd group
bb7cd1
 	NSS_WRAPPER_MODULE_FN_PREFIX="sss" \
bb7cd1
 	UID_WRAPPER=1 \
bb7cd1
 	UID_WRAPPER_ROOT=1 \
bb7cd1
+	NON_WRAPPED_UID=$$(echo $$UID) \
bb7cd1
 	    fakeroot $(PYTHON2) $(PYTEST) -v --tb=native $(INTGCHECK_PYTEST_ARGS) .
bb7cd1
 	rm -f $(DESTDIR)$(logpath)/*
bb7cd1
diff --git a/src/tests/intg/kdc.py b/src/tests/intg/kdc.py
bb7cd1
new file mode 100644
bb7cd1
index 0000000000000000000000000000000000000000..dec33a979916c0979561afb22dc39d6eb8894ff3
bb7cd1
--- /dev/null
bb7cd1
+++ b/src/tests/intg/kdc.py
bb7cd1
@@ -0,0 +1,175 @@
bb7cd1
+#
bb7cd1
+# MIT Kerberos server class
bb7cd1
+#
bb7cd1
+# Copyright (c) 2016 Red Hat, Inc.
bb7cd1
+#
bb7cd1
+# This is free software; you can redistribute it and/or modify it
bb7cd1
+# under the terms of the GNU General Public License as published by
bb7cd1
+# the Free Software Foundation; version 2 only
bb7cd1
+#
bb7cd1
+# This program is distributed in the hope that it will be useful, but
bb7cd1
+# WITHOUT ANY WARRANTY; without even the implied warranty of
bb7cd1
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
bb7cd1
+# General Public License for more details.
bb7cd1
+#
bb7cd1
+# You should have received a copy of the GNU General Public License
bb7cd1
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
bb7cd1
+#
bb7cd1
+import os
bb7cd1
+import signal
bb7cd1
+import shutil
bb7cd1
+import subprocess
bb7cd1
+
bb7cd1
+from util import *
bb7cd1
+
bb7cd1
+
bb7cd1
+class KDC(object):
bb7cd1
+    """
bb7cd1
+    MIT Kerberos KDC instance
bb7cd1
+    """
bb7cd1
+
bb7cd1
+    def __init__(self, basedir, realm,
bb7cd1
+                 includedir=None,
bb7cd1
+                 kdc_port=10088,
bb7cd1
+                 kadmin_port=10749,
bb7cd1
+                 master_key='master'):
bb7cd1
+        self.basedir = basedir
bb7cd1
+        self.realm = realm
bb7cd1
+        self.kdc_port = kdc_port
bb7cd1
+        self.kadmin_port = kadmin_port
bb7cd1
+        self.master_key = master_key
bb7cd1
+
bb7cd1
+        self.kdc_basedir = self.basedir + "/var/krb5kdc"
bb7cd1
+        self.includedir = includedir or (self.kdc_basedir + "/include")
bb7cd1
+        self.kdc_logdir = self.kdc_basedir + "/log"
bb7cd1
+        self.kdc_conf_path = self.kdc_basedir + "/kdc.conf"
bb7cd1
+        self.krb5_conf_path = self.kdc_basedir + "/krb5.conf"
bb7cd1
+
bb7cd1
+        self.kdc_pid_file = self.kdc_basedir + "/kdc.pid"
bb7cd1
+
bb7cd1
+        self.acl_file = self.kdc_basedir + "/kadm5.acl"
bb7cd1
+
bb7cd1
+        self.admin_princ = "admin/admin@" + self.realm
bb7cd1
+
bb7cd1
+    def start_kdc(self, extra_args=[]):
bb7cd1
+        args = ["krb5kdc", '-P', self.kdc_pid_file] + extra_args
bb7cd1
+        return self._run_in_env(args, self.get_krb5_env())
bb7cd1
+
bb7cd1
+    def stop_kdc(self):
bb7cd1
+        try:
bb7cd1
+            with open(self.kdc_pid_file, "r") as pid_file:
bb7cd1
+                os.kill(int(pid_file.read()), signal.SIGTERM)
bb7cd1
+        except IOError as ioex:
bb7cd1
+            if ioex.errno == 2:
bb7cd1
+                pass
bb7cd1
+            else:
bb7cd1
+                raise ioex
bb7cd1
+
bb7cd1
+    def teardown(self):
bb7cd1
+        self.stop_kdc()
bb7cd1
+        shutil.rmtree(self.kdc_basedir)
bb7cd1
+
bb7cd1
+    def set_up(self):
bb7cd1
+        self._create_config()
bb7cd1
+        self._create_acl()
bb7cd1
+        self._create_kdb()
bb7cd1
+
bb7cd1
+    def get_krb5_env(self):
bb7cd1
+        my_env = os.environ
bb7cd1
+        my_env['KRB5_CONFIG'] = self.krb5_conf_path
bb7cd1
+        my_env['KRB5_KDC_PROFILE'] = self.kdc_conf_path
bb7cd1
+        return my_env
bb7cd1
+
bb7cd1
+    def add_config(self, include_files):
bb7cd1
+        for name, contents in include_files.items():
bb7cd1
+            include_fpath = os.path.join(self.includedir, name)
bb7cd1
+            with open(include_fpath, 'w') as include_file:
bb7cd1
+                include_file.write(contents)
bb7cd1
+
bb7cd1
+    def add_principal(self, princ, password=None):
bb7cd1
+        args = ["kadmin.local", "-q"]
bb7cd1
+        if password is None:
bb7cd1
+            args += ["addprinc -randkey %s" % (princ)]
bb7cd1
+        else:
bb7cd1
+            args += ["addprinc -pw %s %s" % (password, princ)]
bb7cd1
+        return self._run_in_env(args, self.get_krb5_env())
bb7cd1
+
bb7cd1
+    def _run_in_env(self, args, env):
bb7cd1
+        cmd = subprocess.Popen(args, env=env)
bb7cd1
+        out, err = cmd.communicate()
bb7cd1
+        return cmd.returncode, out, err
bb7cd1
+
bb7cd1
+    def _create_config(self):
bb7cd1
+        try:
bb7cd1
+            os.makedirs(self.kdc_basedir)
bb7cd1
+            os.makedirs(self.kdc_logdir)
bb7cd1
+            os.makedirs(self.includedir)
bb7cd1
+        except OSError as osex:
bb7cd1
+            if osex.errno == 17:
bb7cd1
+                pass
bb7cd1
+
bb7cd1
+        kdc_conf = self._format_kdc_conf()
bb7cd1
+        with open(self.kdc_conf_path, 'w') as kdc_conf_file:
bb7cd1
+            kdc_conf_file.write(kdc_conf)
bb7cd1
+
bb7cd1
+        krb5_conf = self._format_krb5_conf()
bb7cd1
+        with open(self.krb5_conf_path, 'w') as krb5_conf_file:
bb7cd1
+            krb5_conf_file.write(krb5_conf)
bb7cd1
+
bb7cd1
+    def _create_acl(self):
bb7cd1
+        with open(self.acl_file, 'w') as acl_fobject:
bb7cd1
+            acl_fobject.write(self.admin_princ)
bb7cd1
+
bb7cd1
+    def _create_kdb(self):
bb7cd1
+        self._run_in_env(
bb7cd1
+            ['kdb5_util', 'create', '-W', '-s', '-P', self.master_key],
bb7cd1
+            self.get_krb5_env()
bb7cd1
+        )
bb7cd1
+
bb7cd1
+    def _format_kdc_conf(self):
bb7cd1
+        database_path = self.kdc_basedir + "/principal"
bb7cd1
+        key_stash = self.kdc_basedir + "/stash." + self.realm
bb7cd1
+
bb7cd1
+        kdc_logfile = "FILE:" + self.kdc_logdir + "/krb5kdc.log"
bb7cd1
+        kadmin_logfile = "FILE:" + self.kdc_logdir + "/kadmin.log"
bb7cd1
+        libkrb5_logfile = "FILE:" + self.kdc_logdir + "/libkrb5.log"
bb7cd1
+
bb7cd1
+        kdc_conf = unindent("""
bb7cd1
+        [kdcdefaults]
bb7cd1
+        kdc_ports = {self.kdc_port}
bb7cd1
+        kdc_tcp_ports = {self.kdc_port}
bb7cd1
+
bb7cd1
+        [realms]
bb7cd1
+            {self.realm} = {{
bb7cd1
+                kadmind_port = {self.kadmin_port}
bb7cd1
+                database_name = {database_path}
bb7cd1
+                key_stash_file = {key_stash}
bb7cd1
+                acl_file = {self.acl_file}
bb7cd1
+            }}
bb7cd1
+
bb7cd1
+        [logging]
bb7cd1
+            kdc = {kdc_logfile}
bb7cd1
+            admin_server = {kadmin_logfile}
bb7cd1
+            default = {libkrb5_logfile}
bb7cd1
+        """).format(**locals())
bb7cd1
+        return kdc_conf
bb7cd1
+
bb7cd1
+    def _format_krb5_conf(self):
bb7cd1
+        kdc_uri = "localhost:%d" % self.kdc_port
bb7cd1
+        kadmin_uri = "localhost:%d" % self.kadmin_port
bb7cd1
+
bb7cd1
+        krb5_conf = unindent("""
bb7cd1
+        includedir {self.includedir}
bb7cd1
+
bb7cd1
+        [libdefaults]
bb7cd1
+        default_realm = {self.realm}
bb7cd1
+        dns_lookup_kdc = false
bb7cd1
+        dns_lookup_realm = false
bb7cd1
+
bb7cd1
+        [realms]
bb7cd1
+            {self.realm} = {{
bb7cd1
+                kdc = {kdc_uri}
bb7cd1
+                admin_server = {kadmin_uri}
bb7cd1
+            }}
bb7cd1
+        """).format(**locals())
bb7cd1
+        return krb5_conf
bb7cd1
diff --git a/src/tests/intg/krb5utils.py b/src/tests/intg/krb5utils.py
bb7cd1
new file mode 100644
bb7cd1
index 0000000000000000000000000000000000000000..775cffd0bbfa011f2d8ffc1169dccfef96d78fab
bb7cd1
--- /dev/null
bb7cd1
+++ b/src/tests/intg/krb5utils.py
bb7cd1
@@ -0,0 +1,156 @@
bb7cd1
+#
bb7cd1
+# MIT Kerberos server class
bb7cd1
+#
bb7cd1
+# Copyright (c) 2016 Red Hat, Inc.
bb7cd1
+#
bb7cd1
+# This is free software; you can redistribute it and/or modify it
bb7cd1
+# under the terms of the GNU General Public License as published by
bb7cd1
+# the Free Software Foundation; version 2 only
bb7cd1
+#
bb7cd1
+# This program is distributed in the hope that it will be useful, but
bb7cd1
+# WITHOUT ANY WARRANTY; without even the implied warranty of
bb7cd1
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
bb7cd1
+# General Public License for more details.
bb7cd1
+#
bb7cd1
+# You should have received a copy of the GNU General Public License
bb7cd1
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
bb7cd1
+#
bb7cd1
+import os
bb7cd1
+import subprocess
bb7cd1
+
bb7cd1
+
bb7cd1
+class NoPrincipals(Exception):
bb7cd1
+    def __init__(self):
bb7cd1
+        Exception.__init__(self, 'No principals in the collection')
bb7cd1
+
bb7cd1
+
bb7cd1
+class PrincNotFound(Exception):
bb7cd1
+    def __init__(self, principal):
bb7cd1
+        Exception.__init__(self, 'Principal %s not found' % principal)
bb7cd1
+
bb7cd1
+
bb7cd1
+class Krb5Utils(object):
bb7cd1
+    """
bb7cd1
+    Helper class to test Kerberos command line utilities
bb7cd1
+    """
bb7cd1
+    def __init__(self, krb5_conf_path):
bb7cd1
+        self.krb5_conf_path = krb5_conf_path
bb7cd1
+
bb7cd1
+    def _run_in_env(self, args, stdin=None, extra_env=None):
bb7cd1
+        my_env = os.environ
bb7cd1
+        my_env['KRB5_CONFIG'] = self.krb5_conf_path
bb7cd1
+
bb7cd1
+        if 'KRB5CCNAME' in my_env:
bb7cd1
+            del my_env['KRB5CCNAME']
bb7cd1
+        if extra_env is not None:
bb7cd1
+            my_env.update(extra_env)
bb7cd1
+
bb7cd1
+        cmd = subprocess.Popen(args,
bb7cd1
+                               env=my_env,
bb7cd1
+                               stdin=subprocess.PIPE,
bb7cd1
+                               stdout=subprocess.PIPE,
bb7cd1
+                               stderr=subprocess.PIPE)
bb7cd1
+        out, err = cmd.communicate(stdin)
bb7cd1
+        return cmd.returncode, out.decode('utf-8'), err.decode('utf-8')
bb7cd1
+
bb7cd1
+    def kinit(self, principal, password, env=None):
bb7cd1
+        args = ["kinit", principal]
bb7cd1
+        return self._run_in_env(args, password.encode('utf-8'), env)
bb7cd1
+
bb7cd1
+    def kvno(self, principal, env=None):
bb7cd1
+        args = ["kvno", principal]
bb7cd1
+        return self._run_in_env(args, env)
bb7cd1
+
bb7cd1
+    def kdestroy(self, all_ccaches=False, env=None):
bb7cd1
+        args = ["kdestroy"]
bb7cd1
+        if all_ccaches is True:
bb7cd1
+            args += ["-A"]
bb7cd1
+        retval, _, _ = self._run_in_env(args, env)
bb7cd1
+        return retval
bb7cd1
+
bb7cd1
+    def kswitch(self, principal, env=None):
bb7cd1
+        args = ["kswitch", '-p', principal]
bb7cd1
+        retval, _, _ = self._run_in_env(args, env)
bb7cd1
+        return retval
bb7cd1
+
bb7cd1
+    def _check_klist_l(self, line, exp_principal, exp_cache):
bb7cd1
+        try:
bb7cd1
+            princ, cache = line.split()
bb7cd1
+        except ValueError:
bb7cd1
+            return False
bb7cd1
+
bb7cd1
+        if exp_cache is not None and cache != exp_cache:
bb7cd1
+            return False
bb7cd1
+
bb7cd1
+        if exp_principal != princ:
bb7cd1
+            return False
bb7cd1
+
bb7cd1
+        return True
bb7cd1
+
bb7cd1
+    def num_princs(self, env=None):
bb7cd1
+        args = ["klist", "-l"]
bb7cd1
+        retval, out, err = self._run_in_env(args, extra_env=env)
bb7cd1
+        if retval != 0:
bb7cd1
+            return 0
bb7cd1
+
bb7cd1
+        outlines = [l for l in out.split('\n') if len(l) > 1]
bb7cd1
+        return len(outlines) - 2
bb7cd1
+
bb7cd1
+    def list_princs(self, env=None):
bb7cd1
+        args = ["klist", "-l"]
bb7cd1
+        retval, out, err = self._run_in_env(args, extra_env=env)
bb7cd1
+        if retval == 1:
bb7cd1
+            raise NoPrincipals
bb7cd1
+        elif retval != 0:
bb7cd1
+            raise Exception("klist failed: %d: %s\n", retval, err)
bb7cd1
+
bb7cd1
+        outlines = out.split('\n')
bb7cd1
+        if len(outlines) < 2:
bb7cd1
+            raise Exception("Not enough output from klist -l")
bb7cd1
+
bb7cd1
+        return [l for l in outlines[2:] if len(l) > 0]
bb7cd1
+
bb7cd1
+    def has_principal(self, exp_principal, exp_cache=None, env=None):
bb7cd1
+        try:
bb7cd1
+            princlist = self.list_princs(env)
bb7cd1
+        except NoPrincipals:
bb7cd1
+            return False
bb7cd1
+
bb7cd1
+        for line in princlist:
bb7cd1
+            matches = self._check_klist_l(line, exp_principal, exp_cache)
bb7cd1
+            if matches is True:
bb7cd1
+                return True
bb7cd1
+
bb7cd1
+        return False
bb7cd1
+
bb7cd1
+    def default_principal(self, env=None):
bb7cd1
+        principals = self.list_princs(env)
bb7cd1
+        return principals[0].split()[0]
bb7cd1
+
bb7cd1
+    def _parse_klist_a(self, out):
bb7cd1
+        dflprinc = None
bb7cd1
+        thisrealm = None
bb7cd1
+        ccache_dict = dict()
bb7cd1
+
bb7cd1
+        for line in [l for l in out.split('\n') if len(l) > 0]:
bb7cd1
+            if line.startswith("Default principal"):
bb7cd1
+                dflprinc = line.split()[2]
bb7cd1
+                thisrealm = '@' + dflprinc.split('@')[1]
bb7cd1
+            elif thisrealm is not None and line.endswith(thisrealm):
bb7cd1
+                svc = line.split()[-1]
bb7cd1
+                if dflprinc in ccache_dict:
bb7cd1
+                    ccache_dict[dflprinc].append(svc)
bb7cd1
+                else:
bb7cd1
+                    ccache_dict[dflprinc] = [svc]
bb7cd1
+
bb7cd1
+        return ccache_dict
bb7cd1
+
bb7cd1
+    def list_all_princs(self, env=None):
bb7cd1
+        args = ["klist", "-A"]
bb7cd1
+        retval, out, err = self._run_in_env(args, extra_env=env)
bb7cd1
+        if retval == 1:
bb7cd1
+            raise NoPrincipals
bb7cd1
+        elif retval != 0:
bb7cd1
+            raise Exception("klist -A failed: %d: %s\n", retval, err)
bb7cd1
+
bb7cd1
+        return self._parse_klist_a(out)
bb7cd1
diff --git a/src/tests/intg/test_kcm.py b/src/tests/intg/test_kcm.py
bb7cd1
new file mode 100644
bb7cd1
index 0000000000000000000000000000000000000000..ad1e4923bfe339cb040464757431d2ef3bf57ce1
bb7cd1
--- /dev/null
bb7cd1
+++ b/src/tests/intg/test_kcm.py
bb7cd1
@@ -0,0 +1,361 @@
bb7cd1
+#
bb7cd1
+# KCM responder integration tests
bb7cd1
+#
bb7cd1
+# Copyright (c) 2016 Red Hat, Inc.
bb7cd1
+#
bb7cd1
+# This is free software; you can redistribute it and/or modify it
bb7cd1
+# under the terms of the GNU General Public License as published by
bb7cd1
+# the Free Software Foundation; version 2 only
bb7cd1
+#
bb7cd1
+# This program is distributed in the hope that it will be useful, but
bb7cd1
+# WITHOUT ANY WARRANTY; without even the implied warranty of
bb7cd1
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
bb7cd1
+# General Public License for more details.
bb7cd1
+#
bb7cd1
+# You should have received a copy of the GNU General Public License
bb7cd1
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
bb7cd1
+#
bb7cd1
+import os
bb7cd1
+import os.path
bb7cd1
+import stat
bb7cd1
+import subprocess
bb7cd1
+import pytest
bb7cd1
+import socket
bb7cd1
+import time
bb7cd1
+import signal
bb7cd1
+
bb7cd1
+import kdc
bb7cd1
+import krb5utils
bb7cd1
+import config
bb7cd1
+from util import unindent, run_shell
bb7cd1
+
bb7cd1
+class KcmTestEnv(object):
bb7cd1
+    def __init__(self, k5kdc, k5util):
bb7cd1
+        self.k5kdc = k5kdc
bb7cd1
+        self.k5util = k5util
bb7cd1
+        self.counter = 0
bb7cd1
+
bb7cd1
+    def my_uid(self):
bb7cd1
+        s_myuid = os.environ['NON_WRAPPED_UID']
bb7cd1
+        return int(s_myuid)
bb7cd1
+
bb7cd1
+    def ccname(self, my_uid=None):
bb7cd1
+        if my_uid is None:
bb7cd1
+            my_uid = self.my_uid()
bb7cd1
+
bb7cd1
+        return "KCM:%d" % my_uid
bb7cd1
+
bb7cd1
+
bb7cd1
+@pytest.fixture(scope="module")
bb7cd1
+def kdc_instance(request):
bb7cd1
+    """Kerberos server instance fixture"""
bb7cd1
+    kdc_instance = kdc.KDC(config.PREFIX, "KCMTEST")
bb7cd1
+    try:
bb7cd1
+        kdc_instance.set_up()
bb7cd1
+        kdc_instance.start_kdc()
bb7cd1
+    except:
bb7cd1
+        kdc_instance.teardown()
bb7cd1
+        raise
bb7cd1
+    request.addfinalizer(kdc_instance.teardown)
bb7cd1
+    return kdc_instance
bb7cd1
+
bb7cd1
+
bb7cd1
+def create_conf_fixture(request, contents):
bb7cd1
+    """Generate sssd.conf and add teardown for removing it"""
bb7cd1
+    with open(config.CONF_PATH, "w") as conf:
bb7cd1
+        conf.write(contents)
bb7cd1
+    os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR)
bb7cd1
+    request.addfinalizer(lambda: os.unlink(config.CONF_PATH))
bb7cd1
+
bb7cd1
+
bb7cd1
+def create_sssd_kcm_fixture(sock_path, request):
bb7cd1
+    if subprocess.call(['sssd', "--genconf"]) != 0:
bb7cd1
+        raise Exception("failed to regenerate confdb")
bb7cd1
+
bb7cd1
+    resp_path = os.path.join(config.LIBEXEC_PATH, "sssd", "sssd_kcm")
bb7cd1
+    if not os.access(resp_path, os.X_OK):
bb7cd1
+        # It would be cleaner to use pytest.mark.skipif on the package level
bb7cd1
+        # but upstream insists on supporting RHEL-6..
bb7cd1
+        pytest.skip("No KCM responder, skipping")
bb7cd1
+
bb7cd1
+    kcm_pid = os.fork()
bb7cd1
+    assert kcm_pid >= 0
bb7cd1
+
bb7cd1
+    if kcm_pid == 0:
bb7cd1
+        if subprocess.call([resp_path, "--uid=0", "--gid=0"]) != 0:
bb7cd1
+            print("sssd_kcm failed to start")
bb7cd1
+            sys.exit(99)
bb7cd1
+    else:
bb7cd1
+        abs_sock_path = os.path.join(config.RUNSTATEDIR, sock_path)
bb7cd1
+        sck = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
bb7cd1
+        for _ in range(1, 10):
bb7cd1
+            try:
bb7cd1
+                sck.connect(abs_sock_path)
bb7cd1
+            except:
bb7cd1
+                time.sleep(0.1)
bb7cd1
+            else:
bb7cd1
+                break
bb7cd1
+        sck.close()
bb7cd1
+        assert os.path.exists(abs_sock_path)
bb7cd1
+
bb7cd1
+    def kcm_teardown():
bb7cd1
+        if kcm_pid == 0:
bb7cd1
+            return
bb7cd1
+        os.kill(kcm_pid, signal.SIGTERM)
bb7cd1
+
bb7cd1
+    request.addfinalizer(kcm_teardown)
bb7cd1
+    return kcm_pid
bb7cd1
+
bb7cd1
+
bb7cd1
+@pytest.fixture
bb7cd1
+def setup_for_kcm(request, kdc_instance):
bb7cd1
+    """
bb7cd1
+    Just set up the local provider for tests and enable the KCM
bb7cd1
+    responder
bb7cd1
+    """
bb7cd1
+    kcm_path = os.path.join(config.RUNSTATEDIR, "kcm.socket")
bb7cd1
+
bb7cd1
+    sssd_conf = unindent("""\
bb7cd1
+        [sssd]
bb7cd1
+        domains = local
bb7cd1
+        services = nss
bb7cd1
+
bb7cd1
+        [domain/local]
bb7cd1
+        id_provider = local
bb7cd1
+
bb7cd1
+        [kcm]
bb7cd1
+        socket_path = {kcm_path}
bb7cd1
+    """).format(**locals())
bb7cd1
+
bb7cd1
+    kcm_socket_include = unindent("""
bb7cd1
+    [libdefaults]
bb7cd1
+    default_ccache_name = KCM:
bb7cd1
+    kcm_socket = {kcm_path}
bb7cd1
+    """).format(**locals())
bb7cd1
+    kdc_instance.add_config({'kcm_socket': kcm_socket_include})
bb7cd1
+
bb7cd1
+    create_conf_fixture(request, sssd_conf)
bb7cd1
+    create_sssd_kcm_fixture(kcm_path, request)
bb7cd1
+
bb7cd1
+    k5util = krb5utils.Krb5Utils(kdc_instance.krb5_conf_path)
bb7cd1
+
bb7cd1
+    return KcmTestEnv(kdc_instance, k5util)
bb7cd1
+
bb7cd1
+
bb7cd1
+def test_kcm_init_list_destroy(setup_for_kcm):
bb7cd1
+    """
bb7cd1
+    Test that kinit, kdestroy and klist work with KCM
bb7cd1
+    """
bb7cd1
+    testenv = setup_for_kcm
bb7cd1
+    testenv.k5kdc.add_principal("kcmtest", "Secret123")
bb7cd1
+
bb7cd1
+    ok = testenv.k5util.has_principal("kcmtest@KCMTEST")
bb7cd1
+    assert ok is False
bb7cd1
+    nprincs = testenv.k5util.num_princs()
bb7cd1
+    assert nprincs == 0
bb7cd1
+
bb7cd1
+    out, _, _ = testenv.k5util.kinit("kcmtest", "Secret123")
bb7cd1
+    assert out == 0
bb7cd1
+    nprincs = testenv.k5util.num_princs()
bb7cd1
+    assert nprincs == 1
bb7cd1
+
bb7cd1
+    exp_ccname = testenv.ccname()
bb7cd1
+    ok = testenv.k5util.has_principal("kcmtest@KCMTEST", exp_ccname)
bb7cd1
+    assert ok is True
bb7cd1
+
bb7cd1
+    out = testenv.k5util.kdestroy()
bb7cd1
+    assert out == 0
bb7cd1
+
bb7cd1
+    ok = testenv.k5util.has_principal("kcmtest@KCMTEST")
bb7cd1
+    assert ok is False
bb7cd1
+    nprincs = testenv.k5util.num_princs()
bb7cd1
+    assert nprincs == 0
bb7cd1
+
bb7cd1
+
bb7cd1
+def test_kcm_overwrite(setup_for_kcm):
bb7cd1
+    """
bb7cd1
+    That that reusing a ccache reinitializes the cache and doesn't
bb7cd1
+    add the same principal twice
bb7cd1
+    """
bb7cd1
+    testenv = setup_for_kcm
bb7cd1
+    testenv.k5kdc.add_principal("kcmtest", "Secret123")
bb7cd1
+    exp_ccache = {'kcmtest@KCMTEST': ['krbtgt/KCMTEST@KCMTEST']}
bb7cd1
+
bb7cd1
+    assert testenv.k5util.num_princs() == 0
bb7cd1
+
bb7cd1
+    out, _, _ = testenv.k5util.kinit("kcmtest", "Secret123")
bb7cd1
+    assert out == 0
bb7cd1
+    assert exp_ccache == testenv.k5util.list_all_princs()
bb7cd1
+
bb7cd1
+    out, _, _ = testenv.k5util.kinit("kcmtest", "Secret123")
bb7cd1
+    assert out == 0
bb7cd1
+    assert exp_ccache == testenv.k5util.list_all_princs()
bb7cd1
+
bb7cd1
+
bb7cd1
+def test_collection_init_list_destroy(setup_for_kcm):
bb7cd1
+    """
bb7cd1
+    Test that multiple principals and service tickets can be stored
bb7cd1
+    in a collection.
bb7cd1
+    """
bb7cd1
+    testenv = setup_for_kcm
bb7cd1
+    testenv.k5kdc.add_principal("alice", "alicepw")
bb7cd1
+    testenv.k5kdc.add_principal("bob", "bobpw")
bb7cd1
+    testenv.k5kdc.add_principal("carol", "carolpw")
bb7cd1
+    testenv.k5kdc.add_principal("host/somehostname")
bb7cd1
+
bb7cd1
+    assert testenv.k5util.num_princs() == 0
bb7cd1
+
bb7cd1
+    out, _, _ = testenv.k5util.kinit("alice", "alicepw")
bb7cd1
+    assert out == 0
bb7cd1
+    assert testenv.k5util.default_principal() == 'alice@KCMTEST'
bb7cd1
+    cc_coll = testenv.k5util.list_all_princs()
bb7cd1
+    assert len(cc_coll) == 1
bb7cd1
+    assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
bb7cd1
+    assert 'bob@KCMTEST' not in cc_coll
bb7cd1
+    assert 'carol@KCMTEST' not in cc_coll
bb7cd1
+
bb7cd1
+    out, _, _ = testenv.k5util.kinit("bob", "bobpw")
bb7cd1
+    assert out == 0
bb7cd1
+    assert testenv.k5util.default_principal() == 'bob@KCMTEST'
bb7cd1
+    cc_coll = testenv.k5util.list_all_princs()
bb7cd1
+    assert len(cc_coll) == 2
bb7cd1
+    assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
bb7cd1
+    assert cc_coll['bob@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
bb7cd1
+    assert 'carol@KCMTEST' not in cc_coll
bb7cd1
+
bb7cd1
+    out, _, _ = testenv.k5util.kinit("carol", "carolpw")
bb7cd1
+    assert out == 0
bb7cd1
+    assert testenv.k5util.default_principal() == 'carol@KCMTEST'
bb7cd1
+    cc_coll = testenv.k5util.list_all_princs()
bb7cd1
+    assert len(cc_coll) == 3
bb7cd1
+    assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
bb7cd1
+    assert cc_coll['bob@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
bb7cd1
+    assert cc_coll['carol@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
bb7cd1
+
bb7cd1
+    out, _, _ = testenv.k5util.kvno('host/somehostname')
bb7cd1
+    assert out == 0
bb7cd1
+    cc_coll = testenv.k5util.list_all_princs()
bb7cd1
+    assert len(cc_coll) == 3
bb7cd1
+    assert set(cc_coll['carol@KCMTEST']) == set(['krbtgt/KCMTEST@KCMTEST',
bb7cd1
+                                                 'host/somehostname@KCMTEST'])
bb7cd1
+
bb7cd1
+    out = testenv.k5util.kdestroy()
bb7cd1
+    assert out == 0
bb7cd1
+    assert testenv.k5util.default_principal() == 'bob@KCMTEST'
bb7cd1
+    cc_coll = testenv.k5util.list_all_princs()
bb7cd1
+    assert len(cc_coll) == 2
bb7cd1
+    assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
bb7cd1
+    assert cc_coll['bob@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
bb7cd1
+    assert 'carol@KCMTEST' not in cc_coll
bb7cd1
+
bb7cd1
+    # FIXME - a bug in libkrb5?
bb7cd1
+    #out = testenv.k5util.kdestroy(all_ccaches=True)
bb7cd1
+    #assert out == 0
bb7cd1
+    #cc_coll = testenv.k5util.list_all_princs()
bb7cd1
+    #assert len(cc_coll) == 0
bb7cd1
+
bb7cd1
+
bb7cd1
+def test_kswitch(setup_for_kcm):
bb7cd1
+    """
bb7cd1
+    Test switching between principals
bb7cd1
+    """
bb7cd1
+    testenv = setup_for_kcm
bb7cd1
+    testenv.k5kdc.add_principal("alice", "alicepw")
bb7cd1
+    testenv.k5kdc.add_principal("bob", "bobpw")
bb7cd1
+    testenv.k5kdc.add_principal("host/somehostname")
bb7cd1
+    testenv.k5kdc.add_principal("host/differenthostname")
bb7cd1
+
bb7cd1
+    out, _, _ = testenv.k5util.kinit("alice", "alicepw")
bb7cd1
+    assert out == 0
bb7cd1
+    assert testenv.k5util.default_principal() == 'alice@KCMTEST'
bb7cd1
+
bb7cd1
+    out, _, _ = testenv.k5util.kinit("bob", "bobpw")
bb7cd1
+    assert out == 0
bb7cd1
+    assert testenv.k5util.default_principal() == 'bob@KCMTEST'
bb7cd1
+
bb7cd1
+    cc_coll = testenv.k5util.list_all_princs()
bb7cd1
+    assert len(cc_coll) == 2
bb7cd1
+    assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
bb7cd1
+    assert cc_coll['bob@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
bb7cd1
+
bb7cd1
+    out = testenv.k5util.kswitch("alice@KCMTEST")
bb7cd1
+    assert testenv.k5util.default_principal() == 'alice@KCMTEST'
bb7cd1
+    out, _, _ = testenv.k5util.kvno('host/somehostname')
bb7cd1
+    assert out == 0
bb7cd1
+    cc_coll = testenv.k5util.list_all_princs()
bb7cd1
+    assert len(cc_coll) == 2
bb7cd1
+    assert set(cc_coll['alice@KCMTEST']) == set(['krbtgt/KCMTEST@KCMTEST',
bb7cd1
+                                                 'host/somehostname@KCMTEST'])
bb7cd1
+    assert cc_coll['bob@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
bb7cd1
+
bb7cd1
+    out = testenv.k5util.kswitch("bob@KCMTEST")
bb7cd1
+    assert testenv.k5util.default_principal() == 'bob@KCMTEST'
bb7cd1
+    out, _, _ = testenv.k5util.kvno('host/differenthostname')
bb7cd1
+    assert out == 0
bb7cd1
+    cc_coll = testenv.k5util.list_all_princs()
bb7cd1
+    assert len(cc_coll) == 2
bb7cd1
+    assert set(cc_coll['alice@KCMTEST']) == set(['krbtgt/KCMTEST@KCMTEST',
bb7cd1
+                                                 'host/somehostname@KCMTEST'])
bb7cd1
+    assert set(cc_coll['bob@KCMTEST']) == set([
bb7cd1
+                                    'krbtgt/KCMTEST@KCMTEST',
bb7cd1
+                                    'host/differenthostname@KCMTEST'])
bb7cd1
+
bb7cd1
+
bb7cd1
+def test_subsidiaries(setup_for_kcm):
bb7cd1
+    """
bb7cd1
+    Test that subsidiary caches are usable and KCM: without specifying UID
bb7cd1
+    can be used to identify the collection
bb7cd1
+    """
bb7cd1
+    testenv = setup_for_kcm
bb7cd1
+    testenv.k5kdc.add_principal("alice", "alicepw")
bb7cd1
+    testenv.k5kdc.add_principal("bob", "bobpw")
bb7cd1
+    testenv.k5kdc.add_principal("host/somehostname")
bb7cd1
+    testenv.k5kdc.add_principal("host/differenthostname")
bb7cd1
+
bb7cd1
+    out, _, _ = testenv.k5util.kinit("alice", "alicepw")
bb7cd1
+    assert out == 0
bb7cd1
+    out, _, _ = testenv.k5util.kvno('host/somehostname')
bb7cd1
+
bb7cd1
+    out, _, _ = testenv.k5util.kinit("bob", "bobpw")
bb7cd1
+    assert out == 0
bb7cd1
+    out, _, _ = testenv.k5util.kvno('host/differenthostname')
bb7cd1
+
bb7cd1
+    exp_cc_coll = dict()
bb7cd1
+    exp_cc_coll['alice@KCMTEST'] = 'host/somehostname@KCMTEST'
bb7cd1
+    exp_cc_coll['bob@KCMTEST'] = 'host/differenthostname@KCMTEST'
bb7cd1
+
bb7cd1
+    klist_l = testenv.k5util.list_princs()
bb7cd1
+    princ_ccache = dict()
bb7cd1
+    for line in klist_l:
bb7cd1
+        princ, subsidiary = line.split()
bb7cd1
+        princ_ccache[princ] = subsidiary
bb7cd1
+
bb7cd1
+    for princ, subsidiary in princ_ccache.items():
bb7cd1
+        env = {'KRB5CCNAME': subsidiary}
bb7cd1
+        cc_coll = testenv.k5util.list_all_princs(env=env)
bb7cd1
+        assert len(cc_coll) == 1
bb7cd1
+        assert princ in cc_coll
bb7cd1
+        assert exp_cc_coll[princ] in cc_coll[princ]
bb7cd1
+
bb7cd1
+    cc_coll = testenv.k5util.list_all_princs(env={'KRB5CCNAME': 'KCM:'})
bb7cd1
+    assert len(cc_coll) == 2
bb7cd1
+    assert set(cc_coll['alice@KCMTEST']) == set(['krbtgt/KCMTEST@KCMTEST',
bb7cd1
+                                                 'host/somehostname@KCMTEST'])
bb7cd1
+    assert set(cc_coll['bob@KCMTEST']) == set([
bb7cd1
+                                            'krbtgt/KCMTEST@KCMTEST',
bb7cd1
+                                            'host/differenthostname@KCMTEST'])
bb7cd1
+
bb7cd1
+
bb7cd1
+def test_kdestroy_nocache(setup_for_kcm):
bb7cd1
+    """
bb7cd1
+    Destroying a non-existing ccache should not throw an error
bb7cd1
+    """
bb7cd1
+    testenv = setup_for_kcm
bb7cd1
+    testenv.k5kdc.add_principal("alice", "alicepw")
bb7cd1
+    out, _, _ = testenv.k5util.kinit("alice", "alicepw")
bb7cd1
+    assert out == 0
bb7cd1
+
bb7cd1
+    testenv.k5util.kdestroy()
bb7cd1
+    assert out == 0
bb7cd1
+    out = testenv.k5util.kdestroy()
bb7cd1
+    assert out == 0
bb7cd1
-- 
bb7cd1
2.9.3
bb7cd1