[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