[SSSD] [PATCH] Back end patches for running SSSD as a non-root user

Jakub Hrozek jhrozek at redhat.com
Mon Oct 20 14:13:20 UTC 2014


Hi,

I'll send patches that help SSSD run as a non-root user to this thread.
I'm still chasing some bugs in the krb5_child changes, but the attached
patches are ready for review.

If other developers don't like the idea of a Python-based unit test that
spawns a KDC, I'm equally fine with keeping those patches in my tree --
they served their purpose, after all.
-------------- next part --------------
>From a7d9e32cbcf3fc79c677632063bff7a0d1518ff1 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Sat, 11 Oct 2014 20:22:42 +0200
Subject: [PATCH 1/6] BUILD: Install ldap_child and krb5_child as setuid if
 running under non-privileged user

The krb5_child and ldap_child permissions should be 4750, owned by root.sssd,
to make sure only root and sssd can execute the child and if executed by
sssd, the child will run as root.
---
 Makefile.am | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/Makefile.am b/Makefile.am
index 9fe0880109ff6fa72cf5b428f0ba7936cc5702f0..083837e4894c0bf3875ab09893cdc69903c10e06 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2806,6 +2806,13 @@ else
 	$(MKDIR_P) $(DESTDIR)$(initdir)
 endif
 
+if SSSD_USER
+	chgrp $(SSSD_USER) $(sssdlibexecdir)/ldap_child
+	chgrp $(SSSD_USER) $(sssdlibexecdir)/krb5_child
+	chmod 4750 $(sssdlibexecdir)/ldap_child
+	chmod 4750 $(sssdlibexecdir)/krb5_child
+endif
+
 install-data-hook:
 	rm $(DESTDIR)/$(nsslibdir)/libnss_sss.so.2 \
        $(DESTDIR)/$(nsslibdir)/libnss_sss.so
-- 
1.9.3

-------------- next part --------------
>From 1bb1ec210685acb6a3dd0bba1763e6466a000cf6 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Sat, 11 Oct 2014 17:39:21 +0200
Subject: [PATCH 2/6] LDAP: Move sss_krb5_verify_keytab_ex to ldap_child

The function was called from one place only, so it makes no sense to
keep it in a shared module. Moreover, the function should only be
called from code that runs as root.
---
 src/providers/ldap/ldap_child.c | 80 ++++++++++++++++++++++++++++++++++++++++-
 src/util/sss_krb5.c             | 76 ---------------------------------------
 src/util/sss_krb5.h             |  3 --
 3 files changed, 79 insertions(+), 80 deletions(-)

diff --git a/src/providers/ldap/ldap_child.c b/src/providers/ldap/ldap_child.c
index e5779b70906d90ab855677f04a154e179f2163c6..faa6f027ba52f55985c63e8055292e616b5bb6bf 100644
--- a/src/providers/ldap/ldap_child.c
+++ b/src/providers/ldap/ldap_child.c
@@ -160,6 +160,84 @@ set_child_debugging(krb5_context ctx)
     return EOK;
 }
 
+static int lc_verify_keytab_ex(const char *principal,
+                               const char *keytab_name,
+                               krb5_context context,
+                               krb5_keytab keytab)
+{
+    bool found;
+    char *kt_principal;
+    krb5_error_code krberr;
+    krb5_kt_cursor cursor;
+    krb5_keytab_entry entry;
+
+    krberr = krb5_kt_start_seq_get(context, keytab, &cursor);
+    if (krberr) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              "Cannot read keytab [%s].\n", KEYTAB_CLEAN_NAME);
+
+        sss_log(SSS_LOG_ERR, "Error reading keytab file [%s]: [%d][%s]. "
+                             "Unable to create GSSAPI-encrypted LDAP "
+                             "connection.",
+                             KEYTAB_CLEAN_NAME, krberr,
+                             sss_krb5_get_error_message(context, krberr));
+
+        return EIO;
+    }
+
+    found = false;
+    while((krb5_kt_next_entry(context, keytab, &entry, &cursor)) == 0){
+        krberr = krb5_unparse_name(context, entry.principal, &kt_principal);
+        if (krberr) {
+            DEBUG(SSSDBG_FATAL_FAILURE,
+                  "Could not parse keytab entry\n");
+            sss_log(SSS_LOG_ERR, "Could not parse keytab entry\n");
+            return EIO;
+        }
+
+        if (strcmp(principal, kt_principal) == 0) {
+            found = true;
+        }
+        free(kt_principal);
+        krberr = sss_krb5_free_keytab_entry_contents(context, &entry);
+        if (krberr) {
+            /* This should never happen. The API docs for this function
+             * specify only success for this function
+             */
+            DEBUG(SSSDBG_CRIT_FAILURE,"Could not free keytab entry contents\n");
+            /* This is non-fatal, so we'll continue here */
+        }
+
+        if (found) {
+            break;
+        }
+    }
+
+    krberr = krb5_kt_end_seq_get(context, keytab, &cursor);
+    if (krberr) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "Could not close keytab.\n");
+        sss_log(SSS_LOG_ERR, "Could not close keytab file [%s].",
+                             KEYTAB_CLEAN_NAME);
+        return EIO;
+    }
+
+    if (!found) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              "Principal [%s] not found in keytab [%s]\n",
+               principal,
+               KEYTAB_CLEAN_NAME);
+        sss_log(SSS_LOG_ERR, "Error processing keytab file [%s]: "
+                             "Principal [%s] was not found. "
+                             "Unable to create GSSAPI-encrypted LDAP connection.",
+                             KEYTAB_CLEAN_NAME, principal);
+
+        return EFAULT;
+    }
+
+    return EOK;
+}
+
+
 static krb5_error_code ldap_child_get_tgt_sync(TALLOC_CTX *memctx,
                                                const char *realm_str,
                                                const char *princ_str,
@@ -287,7 +365,7 @@ static krb5_error_code ldap_child_get_tgt_sync(TALLOC_CTX *memctx,
     }
 
     /* Verify the keytab */
-    ret = sss_krb5_verify_keytab_ex(full_princ, keytab_name, context, keytab);
+    ret = lc_verify_keytab_ex(full_princ, keytab_name, context, keytab);
     if (ret) {
         DEBUG(SSSDBG_OP_FAILURE,
                 "Unable to verify principal is present in the keytab\n");
diff --git a/src/util/sss_krb5.c b/src/util/sss_krb5.c
index d9b076ebeccbcd571f14e1b642cade86b21fd5d7..377d61e47254133a6c1b6ed2f20dc0ef1a572aa3 100644
--- a/src/util/sss_krb5.c
+++ b/src/util/sss_krb5.c
@@ -249,82 +249,6 @@ done:
     return ret;
 }
 
-int sss_krb5_verify_keytab_ex(const char *principal, const char *keytab_name,
-                              krb5_context context, krb5_keytab keytab)
-{
-    bool found;
-    char *kt_principal;
-    krb5_error_code krberr;
-    krb5_kt_cursor cursor;
-    krb5_keytab_entry entry;
-
-    krberr = krb5_kt_start_seq_get(context, keytab, &cursor);
-    if (krberr) {
-        DEBUG(SSSDBG_FATAL_FAILURE,
-              "Cannot read keytab [%s].\n", KEYTAB_CLEAN_NAME);
-
-        sss_log(SSS_LOG_ERR, "Error reading keytab file [%s]: [%d][%s]. "
-                             "Unable to create GSSAPI-encrypted LDAP "
-                             "connection.",
-                             KEYTAB_CLEAN_NAME, krberr,
-                             sss_krb5_get_error_message(context, krberr));
-
-        return EIO;
-    }
-
-    found = false;
-    while((krb5_kt_next_entry(context, keytab, &entry, &cursor)) == 0){
-        krberr = krb5_unparse_name(context, entry.principal, &kt_principal);
-        if (krberr) {
-            DEBUG(SSSDBG_FATAL_FAILURE,
-                  "Could not parse keytab entry\n");
-            sss_log(SSS_LOG_ERR, "Could not parse keytab entry\n");
-            return EIO;
-        }
-
-        if (strcmp(principal, kt_principal) == 0) {
-            found = true;
-        }
-        free(kt_principal);
-        krberr = sss_krb5_free_keytab_entry_contents(context, &entry);
-        if (krberr) {
-            /* This should never happen. The API docs for this function
-             * specify only success for this function
-             */
-            DEBUG(SSSDBG_CRIT_FAILURE,"Could not free keytab entry contents\n");
-            /* This is non-fatal, so we'll continue here */
-        }
-
-        if (found) {
-            break;
-        }
-    }
-
-    krberr = krb5_kt_end_seq_get(context, keytab, &cursor);
-    if (krberr) {
-        DEBUG(SSSDBG_FATAL_FAILURE, "Could not close keytab.\n");
-        sss_log(SSS_LOG_ERR, "Could not close keytab file [%s].",
-                             KEYTAB_CLEAN_NAME);
-        return EIO;
-    }
-
-    if (!found) {
-        DEBUG(SSSDBG_FATAL_FAILURE,
-              "Principal [%s] not found in keytab [%s]\n",
-               principal,
-               KEYTAB_CLEAN_NAME);
-        sss_log(SSS_LOG_ERR, "Error processing keytab file [%s]: "
-                             "Principal [%s] was not found. "
-                             "Unable to create GSSAPI-encrypted LDAP connection.",
-                             KEYTAB_CLEAN_NAME, principal);
-
-        return EFAULT;
-    }
-
-    return EOK;
-}
-
-
 enum matching_mode {MODE_NORMAL, MODE_PREFIX, MODE_POSTFIX};
 /**
  * We only have primary and instances stored separately, we need to
diff --git a/src/util/sss_krb5.h b/src/util/sss_krb5.h
index 83c72097594dc24de1f8ac93d5394b6766a449f4..afa0d1943d8a23ae1543ae3874b5abbfbb4b3372 100644
--- a/src/util/sss_krb5.h
+++ b/src/util/sss_krb5.h
@@ -70,9 +70,6 @@ void KRB5_CALLCONV sss_krb5_get_init_creds_opt_free (krb5_context context,
 
 void KRB5_CALLCONV sss_krb5_free_unparsed_name(krb5_context context, char *name);
 
-int sss_krb5_verify_keytab_ex(const char *principal, const char *keytab_name,
-                              krb5_context context, krb5_keytab keytab);
-
 krb5_error_code find_principal_in_keytab(krb5_context ctx,
                                          krb5_keytab keytab,
                                          const char *pattern_primary,
-- 
1.9.3

-------------- next part --------------
>From 981ce3ece13636d8b410de297d5781fa8e20e919 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Sun, 19 Oct 2014 19:20:28 +0200
Subject: [PATCH 3/6] LDAP: read the correct data type from ldap_child's input
 buffer

The back end wrote uint32_t, the ldap_child process would read int32_t.
---
 src/providers/ldap/ldap_child.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/providers/ldap/ldap_child.c b/src/providers/ldap/ldap_child.c
index faa6f027ba52f55985c63e8055292e616b5bb6bf..6195d503ca6503dc754ed9d2c840210ed5c8a492 100644
--- a/src/providers/ldap/ldap_child.c
+++ b/src/providers/ldap/ldap_child.c
@@ -96,7 +96,7 @@ static errno_t unpack_buffer(uint8_t *buf, size_t size,
     }
 
     /* ticket lifetime */
-    SAFEALIGN_COPY_INT32_CHECK(&ibuf->lifetime, buf + p, size, &p);
+    SAFEALIGN_COPY_UINT32_CHECK(&ibuf->lifetime, buf + p, size, &p);
     DEBUG(SSSDBG_TRACE_LIBS, "lifetime: %d\n", ibuf->lifetime);
 
     return EOK;
-- 
1.9.3

-------------- next part --------------
>From 084e515f0a0ec0c7275ff968fd2612d7ccf37516 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Sun, 19 Oct 2014 19:15:52 +0200
Subject: [PATCH 4/6] LDAP: Drop privileges after kinit in ldap_child

After ldap_child initializes privileges using root-owned keytab, it
drops privileges to the SSSD user, minimizing the amount of code that
runs as root.
---
 Makefile.am                             |  4 +-
 src/providers/ldap/ldap_child.c         | 79 ++++++++++++++++++++-------------
 src/providers/ldap/sdap_child_helpers.c |  8 +++-
 3 files changed, 59 insertions(+), 32 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 083837e4894c0bf3875ab09893cdc69903c10e06..9eabc4e5f91b8473f4fb90358f7666b752a68250 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2479,7 +2479,9 @@ ldap_child_SOURCES = \
     src/util/atomic_io.c \
     src/util/authtok.c \
     src/util/util.c \
-    src/util/signal.c
+    src/util/signal.c \
+    src/util/become_user.c \
+    $(NULL)
 ldap_child_CFLAGS = \
     $(AM_CFLAGS) \
     $(POPT_CFLAGS) \
diff --git a/src/providers/ldap/ldap_child.c b/src/providers/ldap/ldap_child.c
index 6195d503ca6503dc754ed9d2c840210ed5c8a492..5eced9dae1694b2adafafb13441e8a7368b136e0 100644
--- a/src/providers/ldap/ldap_child.c
+++ b/src/providers/ldap/ldap_child.c
@@ -49,6 +49,8 @@ struct input_buffer {
     const char *princ_str;
     const char *keytab_name;
     krb5_deltat lifetime;
+    uid_t uid;
+    gid_t gid;
 };
 
 static errno_t unpack_buffer(uint8_t *buf, size_t size,
@@ -99,6 +101,12 @@ static errno_t unpack_buffer(uint8_t *buf, size_t size,
     SAFEALIGN_COPY_UINT32_CHECK(&ibuf->lifetime, buf + p, size, &p);
     DEBUG(SSSDBG_TRACE_LIBS, "lifetime: %d\n", ibuf->lifetime);
 
+    /* UID and GID to run as */
+    SAFEALIGN_COPY_UINT32_CHECK(&ibuf->uid, buf + p, size, &p);
+    SAFEALIGN_COPY_UINT32_CHECK(&ibuf->gid, buf + p, size, &p);
+    DEBUG(SSSDBG_FUNC_DATA,
+          "Will run as [%"SPRIuid"][%"SPRIgid"].\n", ibuf->uid, ibuf->gid);
+
     return EOK;
 }
 
@@ -243,6 +251,8 @@ static krb5_error_code ldap_child_get_tgt_sync(TALLOC_CTX *memctx,
                                                const char *princ_str,
                                                const char *keytab_name,
                                                const krb5_deltat lifetime,
+                                               uid_t uid,
+                                               gid_t gid,
                                                const char **ccname_out,
                                                time_t *expire_time_out)
 {
@@ -373,6 +383,44 @@ static krb5_error_code ldap_child_get_tgt_sync(TALLOC_CTX *memctx,
         goto done;
     }
 
+    memset(&my_creds, 0, sizeof(my_creds));
+    memset(&options, 0, sizeof(options));
+
+    krb5_get_init_creds_opt_set_address_list(&options, NULL);
+    krb5_get_init_creds_opt_set_forwardable(&options, 0);
+    krb5_get_init_creds_opt_set_proxiable(&options, 0);
+    krb5_get_init_creds_opt_set_tkt_life(&options, lifetime);
+
+    tmp_str = getenv("KRB5_CANONICALIZE");
+    if (tmp_str != NULL && strcasecmp(tmp_str, "true") == 0) {
+        DEBUG(SSSDBG_CONF_SETTINGS, "Will canonicalize principals\n");
+        canonicalize = 1;
+    }
+    sss_krb5_get_init_creds_opt_set_canonicalize(&options, canonicalize);
+
+    krberr = krb5_get_init_creds_keytab(context, &my_creds, kprinc,
+                                        keytab, 0, NULL, &options);
+    krb5_kt_close(context, keytab);
+    keytab = NULL;
+    if (krberr) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              "Failed to init credentials: %s\n",
+               sss_krb5_get_error_message(context, krberr));
+        sss_log(SSS_LOG_ERR,
+                "Failed to initialize credentials using keytab [%s]: %s. "
+                "Unable to create GSSAPI-encrypted LDAP connection.",
+                KEYTAB_CLEAN_NAME,
+                sss_krb5_get_error_message(context, krberr));
+        goto done;
+    }
+    DEBUG(SSSDBG_TRACE_INTERNAL, "credentials initialized\n");
+
+    krberr = become_user(uid, gid);
+    if (krberr != 0) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "become_user failed.\n");
+        goto done;
+    }
+
     ccname_file_dummy = talloc_asprintf(tmp_ctx, "%s/ccache_%s_XXXXXX",
                                         DB_PATH, realm_name);
     ccname_file = talloc_asprintf(tmp_ctx, "%s/ccache_%s",
@@ -409,36 +457,6 @@ static krb5_error_code ldap_child_get_tgt_sync(TALLOC_CTX *memctx,
         goto done;
     }
 
-    memset(&my_creds, 0, sizeof(my_creds));
-    memset(&options, 0, sizeof(options));
-
-    krb5_get_init_creds_opt_set_address_list(&options, NULL);
-    krb5_get_init_creds_opt_set_forwardable(&options, 0);
-    krb5_get_init_creds_opt_set_proxiable(&options, 0);
-    krb5_get_init_creds_opt_set_tkt_life(&options, lifetime);
-
-    tmp_str = getenv("KRB5_CANONICALIZE");
-    if (tmp_str != NULL && strcasecmp(tmp_str, "true") == 0) {
-        DEBUG(SSSDBG_CONF_SETTINGS, "Will canonicalize principals\n");
-        canonicalize = 1;
-    }
-    sss_krb5_get_init_creds_opt_set_canonicalize(&options, canonicalize);
-
-    krberr = krb5_get_init_creds_keytab(context, &my_creds, kprinc,
-                                        keytab, 0, NULL, &options);
-    if (krberr) {
-        DEBUG(SSSDBG_FATAL_FAILURE,
-              "Failed to init credentials: %s\n",
-               sss_krb5_get_error_message(context, krberr));
-        sss_log(SSS_LOG_ERR,
-                "Failed to initialize credentials using keytab [%s]: %s. "
-                "Unable to create GSSAPI-encrypted LDAP connection.",
-                KEYTAB_CLEAN_NAME,
-                sss_krb5_get_error_message(context, krberr));
-        goto done;
-    }
-    DEBUG(SSSDBG_TRACE_INTERNAL, "credentials initialized\n");
-
     /* Use updated principal if changed due to canonicalization. */
     krberr = krb5_cc_initialize(context, ccache, my_creds.client);
     if (krberr) {
@@ -644,6 +662,7 @@ int main(int argc, const char *argv[])
     kerr = ldap_child_get_tgt_sync(main_ctx,
                                    ibuf->realm_str, ibuf->princ_str,
                                    ibuf->keytab_name, ibuf->lifetime,
+                                   ibuf->uid, ibuf->gid,
                                    &ccname, &expire_time);
     if (kerr != EOK) {
         DEBUG(SSSDBG_CRIT_FAILURE, "ldap_child_get_tgt_sync failed.\n");
diff --git a/src/providers/ldap/sdap_child_helpers.c b/src/providers/ldap/sdap_child_helpers.c
index 448c5af10e9da8949bcaa39d8a3fa05ec309e16f..e5d46b9b756cd50fadb212da72ad1cc9bdd93330 100644
--- a/src/providers/ldap/sdap_child_helpers.c
+++ b/src/providers/ldap/sdap_child_helpers.c
@@ -152,7 +152,7 @@ static errno_t create_tgt_req_send_buffer(TALLOC_CTX *mem_ctx,
         return ENOMEM;
     }
 
-    buf->size = 4 * sizeof(uint32_t);
+    buf->size = 6 * sizeof(uint32_t);
     if (realm_str) {
         buf->size += strlen(realm_str);
     }
@@ -201,6 +201,12 @@ static errno_t create_tgt_req_send_buffer(TALLOC_CTX *mem_ctx,
     /* lifetime */
     SAFEALIGN_SET_UINT32(&buf->data[rp], lifetime, &rp);
 
+    /* UID and GID to drop privileges to, if needed. The ldap_child process runs as
+     * setuid if the back end runs unprivileged as it needs to access the keytab
+     */
+    SAFEALIGN_SET_UINT32(&buf->data[rp], geteuid(), &rp);
+    SAFEALIGN_SET_UINT32(&buf->data[rp], getegid(), &rp);
+
     *io_buf = buf;
     return EOK;
 }
-- 
1.9.3

-------------- next part --------------
>From e4405fc672ec9e2a247c55ca769de3652610e8a9 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Wed, 8 Oct 2014 20:27:34 +0200
Subject: [PATCH 5/6] TESTS: Fix krb5_child test

---
 src/tests/krb5_child-test.c | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/src/tests/krb5_child-test.c b/src/tests/krb5_child-test.c
index 63caa5f6c97f3e5c0df459b7549e681e51fcc108..b89631d93d86880d5a7327f2c0c3c412c5c80b28 100644
--- a/src/tests/krb5_child-test.c
+++ b/src/tests/krb5_child-test.c
@@ -50,6 +50,8 @@ static krb5_context krb5_error_ctx;
     }                                           \
 } while(0)                                      \
 
+#define KRB5_CHILD_TEST_DOM_NAME    "domname"
+
 struct krb5_child_test_ctx {
     struct tevent_context *ev;
     struct krb5child_req *kr;
@@ -192,6 +194,7 @@ create_dummy_req(TALLOC_CTX *mem_ctx, const char *user,
     struct krb5child_req *kr;
     struct passwd *pwd;
     errno_t ret;
+    struct sss_domain_info *dom;
 
     /* The top level child request */
     kr = talloc_zero(mem_ctx, struct krb5child_req);
@@ -212,7 +215,21 @@ create_dummy_req(TALLOC_CTX *mem_ctx, const char *user,
     /* PAM Data structure */
     kr->pd = create_dummy_pam_data(kr, user, password);
 
-    ret = krb5_get_simple_upn(kr, kr->krb5_ctx, NULL, kr->pd->user, NULL,
+    /* Create a mock domain */
+    dom = talloc_zero(kr, struct sss_domain_info);
+    if (!dom) goto fail;
+    dom->name = discard_const(KRB5_CHILD_TEST_DOM_NAME);
+
+    ret = sss_names_init_from_args(dom,
+                                   "(?P<name>[^@]+)@?(?P<domain>[^@]*$)",
+                                   CONFDB_DEFAULT_FULL_NAME_FORMAT,
+                                   &dom->names);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "krb5_get_simple_upn failed.\n");
+        goto fail;
+    }
+
+    ret = krb5_get_simple_upn(kr, kr->krb5_ctx, dom, kr->pd->user, NULL,
                               &kr->upn);
     if (ret != EOK) {
         DEBUG(SSSDBG_OP_FAILURE, "krb5_get_simple_upn failed.\n");
-- 
1.9.3

-------------- next part --------------
>From 23157f7df55e5c27aa786f59567a3ff5494b24ed Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Wed, 8 Oct 2014 19:08:43 +0200
Subject: [PATCH 6/6] TESTS: Add a cwrap-enabled test for krb5_child

---
 Makefile.am                             |   2 +-
 src/providers/krb5/krb5_child_handler.c |   5 +-
 src/tests/cwrap/Makefile.am             |  12 ++-
 src/tests/cwrap/fake_kdc.py             | 137 ++++++++++++++++++++++++++++++++
 src/tests/cwrap/test_krb5_child.py      | 102 ++++++++++++++++++++++++
 5 files changed, 254 insertions(+), 4 deletions(-)
 create mode 100644 src/tests/cwrap/fake_kdc.py
 create mode 100755 src/tests/cwrap/test_krb5_child.py

diff --git a/Makefile.am b/Makefile.am
index 9eabc4e5f91b8473f4fb90358f7666b752a68250..7caa2eb7ede3bf5f578b366ec1e13ae0a7f59b0d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1592,7 +1592,7 @@ krb5_child_test_SOURCES = \
     $(NULL)
 krb5_child_test_CFLAGS = \
     $(AM_CFLAGS) \
-    -DKRB5_CHILD_DIR=\"$(builddir)\" \
+    -DKRB5_CHILD_DIR=\"$(abs_top_builddir)\" \
     $(KRB5_CFLAGS) \
     $(CHECK_CFLAGS)
 krb5_child_test_LDADD = \
diff --git a/src/providers/krb5/krb5_child_handler.c b/src/providers/krb5/krb5_child_handler.c
index 114e72a33e48da299647c2fe3096b9bf61cf294a..ef2b114f8829c03fa276126089dce76fd72a6202 100644
--- a/src/providers/krb5/krb5_child_handler.c
+++ b/src/providers/krb5/krb5_child_handler.c
@@ -330,8 +330,9 @@ static errno_t fork_child(struct tevent_req *req)
                          pipefd_to_child, pipefd_from_child,
                          KRB5_CHILD, state->kr->krb5_ctx->child_debug_fd);
         if (err != EOK) {
-            DEBUG(SSSDBG_CRIT_FAILURE, "Could not exec KRB5 child: [%d][%s].\n",
-                      err, strerror(err));
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "Could not exec KRB5 child [%s]: [%d][%s].\n",
+                  KRB5_CHILD, err, strerror(err));
             return err;
         }
     } else if (pid > 0) { /* parent */
diff --git a/src/tests/cwrap/Makefile.am b/src/tests/cwrap/Makefile.am
index 6f41a4e3c30fa735c7bc285ad0cc3547635d9056..1cc924b56f8565d3d2f39e7a522841038140b7b9 100644
--- a/src/tests/cwrap/Makefile.am
+++ b/src/tests/cwrap/Makefile.am
@@ -17,6 +17,7 @@ TESTS_ENVIRONMENT = \
 
 dist_noinst_SCRIPTS = \
     cwrap_test_setup.sh \
+    test_krb5_child.py \
     $(NULL)
 
 SSSD_LIBS = \
@@ -39,6 +40,7 @@ dist_noinst_DATA = \
     $(NULL)
 
 check_PROGRAMS =
+PYTHON_TESTS =
 if HAVE_CMOCKA
 if HAVE_NSS_WRAPPER
 if HAVE_UID_WRAPPER
@@ -48,11 +50,19 @@ check_PROGRAMS = \
 	usertools-tests \
 	responder_common-tests \
 	$(NULL)
+
+PYTHON_TESTS = \
+	test_krb5_child.py \
+	$(NULL)
+
 endif # HAVE_UID_WRAPPER
 endif # HAVE_NSS_WRAPPER
 endif # HAVE_CMOCKA
 
-TESTS = $(check_PROGRAMS)
+TESTS = \
+	$(check_PROGRAMS) \
+	$(PYTHON_TESTS) \
+	$(NULL)
 
 become_user_tests_SOURCES = \
     test_become_user.c \
diff --git a/src/tests/cwrap/fake_kdc.py b/src/tests/cwrap/fake_kdc.py
new file mode 100644
index 0000000000000000000000000000000000000000..896d1c8cb3a0240136e70db7f786670267845eac
--- /dev/null
+++ b/src/tests/cwrap/fake_kdc.py
@@ -0,0 +1,137 @@
+#!/usr/bin/python2
+
+import tempfile
+import subprocess
+import shutil
+import time
+import os
+import os.path
+import signal
+from string import Template
+
+mock_kdc_conf = \
+"""
+[kdcdefaults]
+ kdc_ports = $KDC_PORT, $KADMIN_PORT
+ kdc_tcp_ports = $KDC_PORT, $KADMIN_PORT
+
+[logging]
+ default = FILE:$DIR/krb5.log
+ kdc = FILE:$DIR/kdc.log
+ admin_server = FILE:$DIR/kadmind.log
+
+[realms]
+ $REALM = {
+  key_stash_file = $DIR/key_stash
+  acl_file = $KADM_ACL_FILE
+  admin_keytab = $DIR/kadm5.keytab
+  supported_enctypes = aes256-cts:normal aes128-cts:normal des3-hmac-sha1:normal arcfour-hmac:normal camellia256-cts:normal camellia128-cts:normal des-hmac-sha1:normal des-cbc-md5:normal des-cbc-crc:normal
+  database_name = $DIR/database
+ }
+"""
+
+mock_krb5_conf = \
+"""
+[logging]
+ default = FILE:$DIR/krb5.log
+ kdc = FILE:$DIR/kdc.log
+ admin_server = FILE:$DIR/kadmind.log
+
+[libdefaults]
+ dns_lookup_realm = false
+ default_realm = $REALM
+ rdns = false
+
+[realms]
+ $REALM = {
+   kdc = $KDC_HOSTNAME:$KDC_PORT
+   master_kdc = $KDC_HOSTNAME:$KDC_PORT
+   admin_server = $KADMIN_HOSTNAME:$KADMIN_PORT
+   kpasswd_server = $KADMIN_HOSTNAME:$KADMIN_PORT
+ }
+
+[domain_realm]
+ $KDC_HOSTNAME = $REALM
+"""
+
+mock_kadm_acl = "*/admin@$REALM      *"
+
+def tmpfile_from_template(template, basename, dir, **subst_dict):
+    tmpl = Template(template)
+    content = tmpl.substitute(**subst_dict)
+    full_path = os.path.join(dir, basename)
+
+    with open(full_path, 'w') as f:
+        f.write(content)
+        f.flush()
+
+    return full_path
+
+def mock_kdc(wdir, users):
+    subst_dict = {
+            'REALM' : 'SSSD.MOCK',
+            'KDC_HOSTNAME' : 'localhost',
+            'KADMIN_HOSTNAME' : 'localhost',
+            'KDC_PORT' : '20088',
+            'KADMIN_PORT' : '20750',
+            'DIR' : wdir,
+    }
+
+    env = dict(os.environ)
+
+    kadm_acl = tmpfile_from_template(mock_kadm_acl, 'kadm5.acl', wdir, **subst_dict)
+
+    subst_dict['KADM_ACL_FILE'] = kadm_acl
+    kdc_conf = tmpfile_from_template(mock_kdc_conf, 'kdc.conf', wdir, **subst_dict)
+    env['KRB5_KDC_PROFILE'] = kdc_conf
+
+    krb5_conf = tmpfile_from_template(mock_krb5_conf, 'krb5.conf', wdir, **subst_dict)
+    env['KRB5_CONFIG'] = krb5_conf
+
+    # Generate the KDC database, undocumented -W argument: no strong random
+    kdb_util = subprocess.Popen(['kdb5_util', 'create', '-r',
+                                 subst_dict['REALM'], '-s', '-W',
+                                 '-P', 'foobar'],
+                                 env = env,
+                                 cwd = wdir)
+    kdb_util.communicate()
+
+    addprinc_cmd = [ 'kadmin.local', '-r', subst_dict['REALM'], '-q' ]
+
+    for username, password in users.iteritems():
+        addprinc_arg = 'addprinc -pw %s -clearpolicy %s' % (password, username)
+        kadmin = subprocess.Popen(addprinc_cmd + [addprinc_arg],
+                                  env = env, cwd = wdir)
+        kadmin.communicate()
+
+    pidfile = os.path.join(wdir, "kdc.pid")
+    kdc = subprocess.Popen(['krb5kdc', '-P', pidfile, '-r', subst_dict['REALM']],
+                           env = env, cwd = wdir)
+    kdc.communicate()
+
+    # Wait for the KDC to come up
+    for i in range(1, 10):
+        try:
+            with open(pidfile) as pf:
+                pid = int(pf.read())
+        except IOError:
+            time.sleep(1)
+
+    return (krb5_conf, pid)
+
+if __name__ == "__main__":
+    users = { 'root/admin' : 'TurboGoesToRocket',
+              'foobar' : 'Secret123' }
+    realm = 'SSSD.MOCK'
+
+    wdir = tempfile.mkdtemp(prefix='sssd_mock_kdc')
+    kdc_pid = None
+    try:
+        krb5_conf, kdc_pid = mock_kdc(wdir, users)
+        print "KRB5_CONFIG=%s" % krb5_conf
+        print "PID=%d" % kdc_pid
+        raw_input()
+    finally:
+        shutil.rmtree(wdir)
+        if kdc_pid:
+            os.kill(kdc_pid, signal.SIGTERM)
diff --git a/src/tests/cwrap/test_krb5_child.py b/src/tests/cwrap/test_krb5_child.py
new file mode 100755
index 0000000000000000000000000000000000000000..50f2734831a01b52df0261b5d6bd8ed2c372ff84
--- /dev/null
+++ b/src/tests/cwrap/test_krb5_child.py
@@ -0,0 +1,102 @@
+#!/usr/bin/python2
+
+import unittest
+import os
+import os.path
+import sys
+import tempfile
+import subprocess
+import shutil
+import signal
+
+from fake_kdc import mock_kdc
+
+test_bin = '../../../krb5-child-test'
+
+class Krb5ChildTest(unittest.TestCase):
+    def setUp(self):
+        # No point in running the tests without wrappers in place
+        self.assertWrappers()
+
+        # It would be nice to not pollute /tmp with testing data, but
+        # it's not really possible to chown a directory to the test user
+        # either
+        self.ccache_dir = '/tmp'
+        self.realm = 'SSSD.MOCK'
+        self.users = { 'root/admin' : 'TurboGoesToRocket',
+                       'foobar' : 'Secret123' }
+
+        self.wdir = tempfile.mkdtemp(prefix='sssd_mock_kdc')
+
+        self.krb5_conf, self.kdc_pid = mock_kdc(self.wdir, self.users)
+        self.env = dict(os.environ)
+        self.env['KRB5_CONFIG'] = self.krb5_conf
+
+    def tearDown(self):
+        os.kill(self.kdc_pid, signal.SIGTERM)
+        shutil.rmtree(self.wdir)
+
+    def testKinit(self):
+        username = 'foobar'
+
+        child_test = subprocess.Popen([test_bin, '-u', username,
+                                     '-w', self.users[username],
+                                     '-r', self.realm,
+                                     '--debug', '10',
+                                     '-c', 'FILE:%s' % self.ccache_path(username),
+                                     '-k'],
+                                     env = self.env)
+        child_test.communicate()
+        self.assertEqual(child_test.returncode, 0)
+        self.assertPrincipalInCcache(self.principal(username, self.realm),
+                                     self.ccache_path(username))
+
+    def testKinitBadPassword(self):
+        username = 'foobar'
+
+        child_test = subprocess.Popen([test_bin, '-u', username,
+                                     '-w', 'NotTheRightOne',
+                                     '-r', self.realm,
+                                     '--debug', '10',
+                                     '-c', 'FILE:%s' % self.ccache_path(username)],
+                                     env = self.env)
+        child_test.communicate()
+        self.assertEqual(child_test.returncode, 6)
+
+    def assertPrincipalInCcache(self, principal, ccache):
+        klist = subprocess.Popen(['klist', ccache], stdout=subprocess.PIPE)
+        klist.communicate()
+        # FIXME - open the ccache with python-kerberos and check the contents
+        self.assertEqual(klist.returncode, 0)
+
+    def assertWrappers(self):
+        required_vars = [ 'UID_WRAPPER', 'UID_WRAPPER_ROOT',
+                          'NSS_WRAPPER_PASSWD', 'NSS_WRAPPER_GROUP' ]
+        for v in required_vars:
+            assert v in os.environ
+
+    def principal(self, username, realm):
+        return '%s@%s' % (username, realm)
+
+    def ccache_path(self, username):
+        return os.path.join(self.ccache_dir, "%s_ccache" % username)
+
+if __name__ == "__main__":
+    error = 0
+
+    try:
+        subprocess.call(["krb5kdc"])
+    except OSError as e:
+        if e.errno == os.errno.ENOENT:
+            print "KRB5KDC not found, cannot run tests!\n"
+            sys.exit(error)
+        else:
+            # Something else went wrong while trying to run `wget`
+            raise
+
+    suite = unittest.TestLoader().loadTestsFromTestCase(Krb5ChildTest)
+    res = unittest.TextTestRunner().run(suite)
+    if not res.wasSuccessful():
+        error |= 0x1
+
+    sys.exit(error)
-- 
1.9.3



More information about the sssd-devel mailing list