From d4cc148a4da5751146be3a495b0103ce210f2d31 Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Jan 17 2017 17:27:20 +0000 Subject: [PATCH 1/7] Fix print_return logic Signed-off-by: Simo Sorce --- diff --git a/proxy/tests/testlib.py b/proxy/tests/testlib.py index 66c5672..84accb1 100755 --- a/proxy/tests/testlib.py +++ b/proxy/tests/testlib.py @@ -49,11 +49,12 @@ def print_warning(key, text, io=sys.stderr): print_keyed("other", key, text, io) def print_return(ret, name, expected_failure): - if ret != 0 and not expected_failure: - print_failure("SUCCESS" if ret == 0 else "FAILED", + if ((ret == 0 and expected_failure == False) or + (ret != 0 and expected_failure == True)): + print_success("SUCCESS" if ret == 0 else "FAILED", "%s test returned %s" % (name, str(ret))) else: - print_success("SUCCESS" if ret == 0 else "FAILED", + print_failure("SUCCESS" if ret == 0 else "FAILED", "%s test returned %s" % (name, str(ret))) WRAP_HOSTNAME = "kdc.gssproxy.dev" From dea14c4bc293336cd2c571a27fb6575c018f68b8 Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Jan 17 2017 17:27:20 +0000 Subject: [PATCH 2/7] Use a local keytab for creds encryption If available use a keytab for creds encryption. Since now we can store encrypted credentials, on the cient side, for later reuse, it is better to be able to decrypt them even after a gssproxy daemon restart (maintenance, crashes, etc..) If a keytab is rotated this can cause a restarted gssproxy to fail to decrypt stored credentials, but in that case those credentials are also probably useless and need to be refreshed, so this is not a huge deal, and definitely better than the status quo. Signed-off-by: Simo Sorce --- diff --git a/proxy/src/gp_config.c b/proxy/src/gp_config.c index 2e14072..72b587d 100644 --- a/proxy/src/gp_config.c +++ b/proxy/src/gp_config.c @@ -62,12 +62,33 @@ static void gp_service_free(struct gp_service *svc) free(svc->krb5.principal); free_str_array(&(svc->krb5.cred_store), &svc->krb5.cred_count); + gp_free_creds_handle(&svc->krb5.creds_handle); } - gp_free_creds_handle(&svc->creds_handle); SELINUX_context_free(svc->selinux_ctx); memset(svc, 0, sizeof(struct gp_service)); } +static int setup_krb5_creds_handle(struct gp_service *svc) +{ + uint32_t ret_maj, ret_min; + const char *keytab = NULL; + + for (unsigned i = 0; i < svc->krb5.cred_count; i++) { + if (strncmp(svc->krb5.cred_store[i], "keytab:", 7) == 0) { + keytab = svc->krb5.cred_store[i] + 7; + break; + } + } + + ret_maj = gp_init_creds_handle(&ret_min, svc->name, keytab, + &svc->krb5.creds_handle); + if (ret_maj) { + return ret_min; + } + + return 0; +} + static int get_krb5_mech_cfg(struct gp_service *svc, struct gp_ini_context *ctx, const char *secname) @@ -115,6 +136,10 @@ static int get_krb5_mech_cfg(struct gp_service *svc, ret = 0; } + if (ret == 0) { + ret = setup_krb5_creds_handle(svc); + } + return ret; } @@ -171,18 +196,6 @@ static int parse_flags(const char *value, uint32_t *storage) return 0; } -static int setup_service_creds_handle(struct gp_service *svc) -{ - uint32_t ret_maj, ret_min; - - ret_maj = gp_init_creds_handle(&ret_min, &svc->creds_handle); - if (ret_maj) { - return ret_min; - } - - return 0; -} - static int check_services(const struct gp_config *cfg) { int i, j; @@ -339,11 +352,6 @@ static int load_services(struct gp_config *cfg, struct gp_ini_context *ctx) } } - ret = setup_service_creds_handle(cfg->svcs[n]); - if (ret) { - goto done; - } - ret = gp_config_get_string(ctx, secname, "mechs", &value); if (ret != 0) { /* if mechs is missing or there is an error retrieving it @@ -377,6 +385,7 @@ static int load_services(struct gp_config *cfg, struct gp_ini_context *ctx) safefree(vcopy); return ret; } + } else { GPERROR("Unknown mech: %s in [%s], ignoring.\n", token, secname); @@ -604,7 +613,7 @@ done: struct gp_creds_handle *gp_service_get_creds_handle(struct gp_service *svc) { - return svc->creds_handle; + return svc->krb5.creds_handle; } void free_config(struct gp_config **cfg) diff --git a/proxy/src/gp_export.c b/proxy/src/gp_export.c index 41af67b..c83303a 100644 --- a/proxy/src/gp_export.c +++ b/proxy/src/gp_export.c @@ -17,8 +17,8 @@ #define GP_CREDS_HANDLE_KEY_ENCTYPE ENCTYPE_AES256_CTS_HMAC_SHA1_96 struct gp_creds_handle { - krb5_keyblock key; krb5_context context; + krb5_keyblock *key; }; void gp_free_creds_handle(struct gp_creds_handle **in) @@ -30,7 +30,7 @@ void gp_free_creds_handle(struct gp_creds_handle **in) } if (handle->context) { - krb5_free_keyblock_contents(handle->context, &handle->key); + krb5_free_keyblock(handle->context, handle->key); krb5_free_context(handle->context); } @@ -39,11 +39,15 @@ void gp_free_creds_handle(struct gp_creds_handle **in) return; } -uint32_t gp_init_creds_handle(uint32_t *min, struct gp_creds_handle **out) +uint32_t gp_init_creds_handle(uint32_t *min, const char *svc_name, + const char *keytab, + struct gp_creds_handle **out) { struct gp_creds_handle *handle; uint32_t ret_maj = 0; uint32_t ret_min = 0; + krb5_keytab ktid = NULL; + char ktname[MAX_KEYTAB_NAME_LEN + 1] = {0}; int ret; handle = calloc(1, sizeof(struct gp_creds_handle)); @@ -61,19 +65,111 @@ uint32_t gp_init_creds_handle(uint32_t *min, struct gp_creds_handle **out) goto done; } - ret = krb5_c_make_random_key(handle->context, - GP_CREDS_HANDLE_KEY_ENCTYPE, + /* Try to use a keytab, and fall back to a random runtime secret if all + * else fails */ + if (keytab) { + ret = krb5_kt_resolve(handle->context, keytab, &ktid); + if (ret == 0) { + ret = krb5_kt_have_content(handle->context, ktid); + } + /* if a keytab is specified then it must be usable */ + if (ret) { + ret_min = ret; + ret_maj = GSS_S_CRED_UNAVAIL; + goto done; + } + strncpy(ktname, keytab, MAX_KEYTAB_NAME_LEN); + } else { + ret = krb5_kt_default(handle->context, &ktid); + /* if the default keyab does not exist or is empty it is not fatal */ + if (ret) { + ktid = NULL; + } else { + ret = krb5_kt_have_content(handle->context, ktid); + if (ret) { + (void)krb5_kt_close(handle->context, ktid); + ktid = NULL; + } else { + ret = krb5_kt_default_name(handle->context, ktname, + MAX_KEYTAB_NAME_LEN); + if (ret) strncpy(ktname, "[default]", MAX_KEYTAB_NAME_LEN); + } + } + } + + if (ktid) { + krb5_kt_cursor cursor; + krb5_keytab_entry entry; + krb5_enctype *permitted; + + ret = krb5_get_permitted_enctypes(handle->context, &permitted); + if (ret) { + ret_min = ret; + ret_maj = GSS_S_FAILURE; + goto done; + } + + ret = krb5_kt_start_seq_get(handle->context, ktid, &cursor); + if (ret) { + ret_min = ret; + ret_maj = GSS_S_FAILURE; + goto done; + } + do { + ret = krb5_kt_next_entry(handle->context, ktid, &entry, &cursor); + if (ret == 0) { + for (unsigned i = 0; permitted[i] != 0; i++) { + if (permitted[i] == entry.key.enctype) { + /* should we derive a key instead ? */ + ret = krb5_copy_keyblock(handle->context, &entry.key, + &handle->key); + if (ret == 0) { + GPDEBUG("Service: %s, Enckey: %s, Enctype: %d\n", + svc_name, ktname, entry.key.enctype); + ret = KRB5_KT_END; + } + break; + } + } + (void)krb5_free_keytab_entry_contents(handle->context, &entry); + } + } while (ret == 0); + (void)krb5_kt_end_seq_get(handle->context, ktid, &cursor); + if ((ret == KRB5_KT_END) && (handle->key == NULL)) { + ret = KRB5_WRONG_ETYPE; + ret_maj = GSS_S_CRED_UNAVAIL; + goto done; + } + if (ret != KRB5_KT_END) { + ret_min = ret; + ret_maj = GSS_S_CRED_UNAVAIL; + goto done; + } + } else { + ret = krb5_init_keyblock(handle->context, + GP_CREDS_HANDLE_KEY_ENCTYPE, 0, &handle->key); - if (ret) { - ret_min = ret; - ret_maj = GSS_S_FAILURE; - goto done; + if (ret == 0) { + ret = krb5_c_make_random_key(handle->context, + GP_CREDS_HANDLE_KEY_ENCTYPE, + handle->key); + GPDEBUG("Service: %s, Enckey: [ephemeral], Enctype: %d\n", + svc_name, GP_CREDS_HANDLE_KEY_ENCTYPE); + } + if (ret) { + ret_min = ret; + ret_maj = GSS_S_FAILURE; + goto done; + } } ret_maj = GSS_S_COMPLETE; ret_min = 0; done: + if (handle->context && ktid) { + (void)krb5_kt_close(handle->context, ktid); + } *min = ret_min; if (ret_maj) { gp_free_creds_handle(&handle); @@ -97,7 +193,7 @@ static int gp_encrypt_buffer(krb5_context context, krb5_keyblock *key, memset(&enc_handle, '\0', sizeof(krb5_enc_data)); ret = krb5_c_encrypt_length(context, - GP_CREDS_HANDLE_KEY_ENCTYPE, + key->enctype, data_in.length, &cipherlen); if (ret) { @@ -254,7 +350,7 @@ uint32_t gp_export_gssx_cred(uint32_t *min, struct gp_call_ctx *gpcall, goto done; } - ret = gp_encrypt_buffer(handle->context, &handle->key, + ret = gp_encrypt_buffer(handle->context, handle->key, token.length, token.value, &out->cred_handle_reference); if (ret) { @@ -338,7 +434,7 @@ uint32_t gp_import_gssx_cred(uint32_t *min, struct gp_call_ctx *gpcall, goto done; } - ret = gp_decrypt_buffer(handle->context, &handle->key, + ret = gp_decrypt_buffer(handle->context, handle->key, &cred->cred_handle_reference, &token.length, token.value); if (ret) { diff --git a/proxy/src/gp_proxy.h b/proxy/src/gp_proxy.h index be71d38..c7f4bb2 100644 --- a/proxy/src/gp_proxy.h +++ b/proxy/src/gp_proxy.h @@ -17,14 +17,15 @@ #define GP_CRED_KRB5 0x01 +struct gp_creds_handle; + struct gp_cred_krb5 { char *principal; const char **cred_store; int cred_count; + struct gp_creds_handle *creds_handle; }; -struct gp_creds_handle; - struct gp_service { char *name; uid_t euid; @@ -41,8 +42,6 @@ struct gp_service { uint32_t mechs; struct gp_cred_krb5 krb5; - struct gp_creds_handle *creds_handle; - verto_ev *ev; }; @@ -127,7 +126,9 @@ struct gp_service *gp_creds_match_conn(struct gssproxy_ctx *gpctx, struct gp_conn *conn); /* from gp_export.c */ -uint32_t gp_init_creds_handle(uint32_t *min, struct gp_creds_handle **out); +uint32_t gp_init_creds_handle(uint32_t *min, const char *svc_name, + const char *keytab, + struct gp_creds_handle **out); void gp_free_creds_handle(struct gp_creds_handle **in); #endif /* _GP_PROXY_H_ */ From ad86836b73e60c6e4caac7118f1c479234c747c0 Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Jan 17 2017 17:27:20 +0000 Subject: [PATCH 3/7] Parse cred_store struct earlier This will provide immediate feedback if an option is incorrectly formatted as well as avoid multiple parsing when the cred store spec needs to be used in multiple places. Signed-off-by: Simo Sorce --- diff --git a/proxy/src/gp_config.c b/proxy/src/gp_config.c index 72b587d..7135cb4 100644 --- a/proxy/src/gp_config.c +++ b/proxy/src/gp_config.c @@ -55,13 +55,26 @@ static void free_str_array(const char ***a, int *count) safefree(*a); } +void free_cred_store_elements(gss_key_value_set_desc *cs) +{ + int i; + + if (!cs->elements) return; + + for (i = 0; i < cs->count; i++) { + safefree(cs->elements[i].key); + safefree(cs->elements[i].value); + } + safefree(cs->elements); + cs->count = 0; +} + static void gp_service_free(struct gp_service *svc) { free(svc->name); if (svc->mechs & GP_CRED_KRB5) { free(svc->krb5.principal); - free_str_array(&(svc->krb5.cred_store), - &svc->krb5.cred_count); + free_cred_store_elements(&svc->krb5.store); gp_free_creds_handle(&svc->krb5.creds_handle); } SELINUX_context_free(svc->selinux_ctx); @@ -73,9 +86,9 @@ static int setup_krb5_creds_handle(struct gp_service *svc) uint32_t ret_maj, ret_min; const char *keytab = NULL; - for (unsigned i = 0; i < svc->krb5.cred_count; i++) { - if (strncmp(svc->krb5.cred_store[i], "keytab:", 7) == 0) { - keytab = svc->krb5.cred_store[i] + 7; + for (unsigned i = 0; i < svc->krb5.store.count; i++) { + if (strcmp(svc->krb5.store.elements[i].key, "keytab") == 0) { + keytab = svc->krb5.store.elements[i].value; break; } } @@ -99,6 +112,8 @@ static int get_krb5_mech_cfg(struct gp_service *svc, {"krb5_client_keytab", "client_keytab" } }; const char *value; + const char **strings = NULL; + int count = 0; int i; int ret; @@ -127,11 +142,43 @@ static int get_krb5_mech_cfg(struct gp_service *svc, } /* instead look for the cred_store parameter */ - ret = gp_config_get_string_array(ctx, secname, - "cred_store", - &svc->krb5.cred_count, - &svc->krb5.cred_store); - if (ret == ENOENT) { + ret = gp_config_get_string_array(ctx, secname, "cred_store", + &count, &strings); + if (ret == 0) { + const char *p; + size_t len; + char *key; + + svc->krb5.store.elements = + calloc(count, sizeof(gss_key_value_element_desc)); + if (!svc->krb5.store.elements) { + ret = ENOMEM; + goto done; + } + svc->krb5.store.count = count; + + for (int c = 0; c < count; c++) { + p = strchr(strings[c], ':'); + if (!p) { + GPERROR("Invalid cred_store value, no ':' separator found in" + " [%s].\n", strings[c]); + ret = EINVAL; + goto done; + } + len = asprintf(&key, "%.*s", (int)(p - strings[c]), strings[c]); + if (len == -1) { + ret = ENOMEM; + goto done; + } + svc->krb5.store.elements[c].key = key; + svc->krb5.store.elements[c].value = strdup(p + 1); + if (!svc->krb5.store.elements[c].value) { + ret = ENOMEM; + goto done; + } + } + + } else if (ret == ENOENT) { /* when not there we ignore */ ret = 0; } @@ -140,6 +187,8 @@ static int get_krb5_mech_cfg(struct gp_service *svc, ret = setup_krb5_creds_handle(svc); } +done: + free_str_array(&strings, &count); return ret; } diff --git a/proxy/src/gp_creds.c b/proxy/src/gp_creds.c index 8280ef2..05ec659 100644 --- a/proxy/src/gp_creds.c +++ b/proxy/src/gp_creds.c @@ -181,17 +181,6 @@ done: return str; } -static void free_cred_store_elements(gss_key_value_set_desc *cs) -{ - int i; - - for (i = 0; i < cs->count; i++) { - safefree(cs->elements[i].key); - safefree(cs->elements[i].value); - } - safefree(cs->elements); -} - int gp_get_acquire_type(struct gssx_arg_acquire_cred *arg) { struct gssx_option *val = NULL; @@ -240,9 +229,6 @@ static int gp_get_cred_environment(struct gp_call_ctx *gpcall, uint32_t ret_maj = 0; uint32_t ret_min = 0; uid_t target_uid; - const char *fmtstr; - const char *p; - char *str; bool user_requested = false; bool use_service_keytab = false; int ret = -1; @@ -301,6 +287,7 @@ static int gp_get_cred_environment(struct gp_call_ctx *gpcall, /* impersonation case (only for initiation) */ if (user_requested) { if (try_impersonate(svc, *cred_usage, ACQ_NORMAL)) { + char *str; /* When impersonating we want to use the service keytab to * acquire initial credential ... */ use_service_keytab = true; @@ -342,47 +329,36 @@ static int gp_get_cred_environment(struct gp_call_ctx *gpcall, } } - if (svc->krb5.cred_store == NULL) { + if (svc->krb5.store.count == 0) { return 0; } /* allocate 1 more than in source, just in case we need to add * an internal client_keytab element */ - cs->elements = calloc(svc->krb5.cred_count + 1, + cs->elements = calloc(svc->krb5.store.count + 1, sizeof(gss_key_value_element_desc)); if (!cs->elements) { ret = ENOMEM; goto done; } - for (d = 0; d < svc->krb5.cred_count; d++) { - p = strchr(svc->krb5.cred_store[d], ':'); - if (!p) { - GPERROR("Invalid cred_store value" - "no ':' separator found in [%s].\n", - svc->krb5.cred_store[d]); - ret = EINVAL; - goto done; - } - - if (strncmp(svc->krb5.cred_store[d], "client_keytab:", 14) == 0) { + for (d = 0; d < svc->krb5.store.count; d++) { + if (strcmp(svc->krb5.store.elements[d].key, "client_keytab") == 0) { ck_num = cs->count; - } else if (strncmp(svc->krb5.cred_store[d], "keytab:", 7) == 0) { + } else if (strcmp(svc->krb5.store.elements[d].key, "keytab") == 0) { k_num = cs->count; } - ret = asprintf(&str, "%.*s", (int)(p - svc->krb5.cred_store[d]), - svc->krb5.cred_store[d]); - if (ret == -1) { + cs->elements[cs->count].key = strdup(svc->krb5.store.elements[d].key); + if (!cs->elements[cs->count].key) { ret = ENOMEM; goto done; } - cs->elements[cs->count].key = str; - fmtstr = p + 1; cs->elements[cs->count].value = - get_formatted_string(fmtstr, target_uid); + get_formatted_string(svc->krb5.store.elements[d].value, + target_uid); if (!cs->elements[cs->count].value) { - safefree(str); + safefree(cs->elements[cs->count].key); GPDEBUG("Failed to build credential store formatted string.\n"); ret = ENOMEM; goto done; diff --git a/proxy/src/gp_proxy.h b/proxy/src/gp_proxy.h index c7f4bb2..abcd201 100644 --- a/proxy/src/gp_proxy.h +++ b/proxy/src/gp_proxy.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "verto.h" #include "gp_common.h" #include "gp_selinux.h" @@ -21,8 +22,7 @@ struct gp_creds_handle; struct gp_cred_krb5 { char *principal; - const char **cred_store; - int cred_count; + gss_key_value_set_desc store; struct gp_creds_handle *creds_handle; }; @@ -86,6 +86,7 @@ struct gp_config *read_config(char *config_file, char *config_dir, char *socket_name, int opt_daemonize); struct gp_creds_handle *gp_service_get_creds_handle(struct gp_service *svc); void free_config(struct gp_config **config); +void free_cred_store_elements(gss_key_value_set_desc *cs); /* from gp_init.c */ void init_server(bool daemonize, int *wait_fd); From c7aab874061ccdfb13ed1d58f67a89b24018f43f Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Jan 17 2017 17:27:20 +0000 Subject: [PATCH 4/7] Make local call static Signed-off-by: Simo Sorce --- diff --git a/proxy/src/gp_creds.c b/proxy/src/gp_creds.c index 05ec659..8af5a9d 100644 --- a/proxy/src/gp_creds.c +++ b/proxy/src/gp_creds.c @@ -410,10 +410,10 @@ done: return ret; } -uint32_t gp_check_cred(uint32_t *min, - gss_cred_id_t in_cred, - gssx_name *desired_name, - gss_cred_usage_t cred_usage) +static uint32_t gp_check_cred(uint32_t *min, + gss_cred_id_t in_cred, + gssx_name *desired_name, + gss_cred_usage_t cred_usage) { uint32_t ret_maj = 0; uint32_t ret_min = 0; From 26589fb2ca8f1665e9815ae685c20a9b6c4b1ef0 Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Jan 17 2017 17:27:20 +0000 Subject: [PATCH 5/7] Add control to permit/deny protocol transition Denies by default. Signed-off-by: Simo Sorce --- diff --git a/proxy/man/gssproxy.conf.5.xml b/proxy/man/gssproxy.conf.5.xml index 9a79894..6d44e8a 100644 --- a/proxy/man/gssproxy.conf.5.xml +++ b/proxy/man/gssproxy.conf.5.xml @@ -102,6 +102,18 @@ + allow_protocol_transition (boolean) + + Allow clients to request a ticket to self for an arbitrary user. + This option controls whether s4u2self requests are allowed for the + requesting client. The configured keytab is used as the service + identity for which a ticket is requested. The KDC still needs to allow + the operation for it to succeed. + Default: false + + + + cred_usage (string) Allow to restrict the kind of operations permitted for this service. diff --git a/proxy/src/gp_config.c b/proxy/src/gp_config.c index 7135cb4..8a449c7 100644 --- a/proxy/src/gp_config.c +++ b/proxy/src/gp_config.c @@ -371,6 +371,14 @@ static int load_services(struct gp_config *cfg, struct gp_ini_context *ctx) } } + ret = gp_config_get_string(ctx, secname, + "allow_protocol_transition", &value); + if (ret == 0) { + if (gp_boolean_is_true(value)) { + cfg->svcs[n]->allow_proto_trans = true; + } + } + ret = gp_config_get_string(ctx, secname, "trusted", &value); if (ret == 0) { if (gp_boolean_is_true(value)) { diff --git a/proxy/src/gp_creds.c b/proxy/src/gp_creds.c index 8af5a9d..8fafa66 100644 --- a/proxy/src/gp_creds.c +++ b/proxy/src/gp_creds.c @@ -203,17 +203,16 @@ static bool try_impersonate(struct gp_service *svc, gss_cred_usage_t cred_usage, enum gp_aqcuire_cred_type acquire_type) { - if (acquire_type == ACQ_IMPNAME) { + if (acquire_type == ACQ_IMPNAME && + (svc->allow_proto_trans || svc->trusted)) { return true; } - if (!svc->impersonate) { - return false; - } - if (cred_usage == GSS_C_ACCEPT) { - return false; + if (svc->impersonate && + (cred_usage == GSS_C_INITIATE || cred_usage == GSS_C_BOTH)) { + return true; } - return true; + return false; } static int gp_get_cred_environment(struct gp_call_ctx *gpcall, diff --git a/proxy/src/gp_proxy.h b/proxy/src/gp_proxy.h index abcd201..4216b72 100644 --- a/proxy/src/gp_proxy.h +++ b/proxy/src/gp_proxy.h @@ -30,6 +30,7 @@ struct gp_service { char *name; uid_t euid; bool any_uid; + bool allow_proto_trans; bool trusted; bool kernel_nfsd; bool impersonate; From 76164da5d513e8aea1ea7f179a10eccc9a1c3133 Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Jan 17 2017 17:27:20 +0000 Subject: [PATCH 6/7] Control access to constrained delegation A client must be tusted or must be explicitly allowed to perform impersonation or constrained delegation to be able to use evidence tickets for s4u2proxy operations. Signed-off-by: Simo Sorce --- diff --git a/proxy/man/gssproxy.conf.5.xml b/proxy/man/gssproxy.conf.5.xml index 6d44e8a..7c724d6 100644 --- a/proxy/man/gssproxy.conf.5.xml +++ b/proxy/man/gssproxy.conf.5.xml @@ -114,6 +114,18 @@ + allow_constrained_delegation (boolean) + + Allow clients to request a ticket to another service using an + evidence ticket. + This option controls whether s4u2proxy requests are allowed for + the requesting client. The KDC still needs to allow the operation + for it to succeed. + Default: false + + + + cred_usage (string) Allow to restrict the kind of operations permitted for this service. diff --git a/proxy/src/gp_config.c b/proxy/src/gp_config.c index 8a449c7..cf1c08b 100644 --- a/proxy/src/gp_config.c +++ b/proxy/src/gp_config.c @@ -379,6 +379,14 @@ static int load_services(struct gp_config *cfg, struct gp_ini_context *ctx) } } + ret = gp_config_get_string(ctx, secname, + "allow_constrained_delegation", &value); + if (ret == 0) { + if (gp_boolean_is_true(value)) { + cfg->svcs[n]->allow_const_deleg = true; + } + } + ret = gp_config_get_string(ctx, secname, "trusted", &value); if (ret == 0) { if (gp_boolean_is_true(value)) { diff --git a/proxy/src/gp_creds.c b/proxy/src/gp_creds.c index 8fafa66..7236ee1 100644 --- a/proxy/src/gp_creds.c +++ b/proxy/src/gp_creds.c @@ -3,6 +3,7 @@ #include "config.h" #include #include +#include #include #include #include @@ -681,3 +682,107 @@ void gp_filter_flags(struct gp_call_ctx *gpcall, uint32_t *flags) *flags |= gpcall->service->enforce_flags; *flags &= ~gpcall->service->filter_flags; } + +uint32_t gp_cred_allowed(uint32_t *min, + struct gp_call_ctx *gpcall, + gss_cred_id_t cred) +{ + uint32_t ret_maj = 0; + uint32_t ret_min = 0; + char *memcache = NULL; + krb5_context context = NULL; + krb5_ccache ccache = NULL; + krb5_data config; + int err; + + if (cred == GSS_C_NO_CREDENTIAL) { + return GSS_S_CRED_UNAVAIL; + } + + if (gpcall->service->trusted || + gpcall->service->impersonate || + gpcall->service->allow_const_deleg) { + + GPDEBUGN(2, "Credentials allowed by configuration\n"); + *min = 0; + return GSS_S_COMPLETE; + } + + /* FIXME: krb5 specific code, should get an oid registerd to query the + * cred with gss_inquire_cred_by_oid() or similar instead */ + + err = krb5_init_context(&context); + if (err) { + ret_min = err; + ret_maj = GSS_S_FAILURE; + goto done; + } + + /* Create a memory ccache we can iterate with libkrb5 functions */ + gss_key_value_element_desc ccelement = { "ccache", NULL }; + gss_key_value_set_desc cred_store = { 1, &ccelement }; + + err = asprintf(&memcache, "MEMORY:cred_allowed_%p", &memcache); + if (err == -1) { + memcache = NULL; + ret_min = ENOMEM; + ret_maj = GSS_S_FAILURE; + goto done; + } + cred_store.elements[0].value = memcache; + + ret_maj = gss_store_cred_into(&ret_min, cred, GSS_C_INITIATE, + discard_const(gss_mech_krb5), 1, 0, + &cred_store, NULL, NULL); + if (ret_maj != GSS_S_COMPLETE) { + goto done; + } + + err = krb5_cc_resolve(context, memcache, &ccache); + if (err) { + ret_min = err; + ret_maj = GSS_S_FAILURE; + goto done; + } + + /* if we find an impersonator entry we bail as that is not authorized, + * if it were then gpcall->service->allow_const_deleg would have caused + * the ealier check to return GSS_S_COMPLETE already */ + err = krb5_cc_get_config(context, ccache, NULL, "proxy_impersonator", + &config); + if (!err) { + krb5_free_data_contents(context, &config); + ret_min = 0; + ret_maj = GSS_S_UNAUTHORIZED; + } else if (err != KRB5_CC_NOTFOUND) { + ret_min = err; + ret_maj = GSS_S_FAILURE; + } else { + ret_min = 0; + ret_maj = GSS_S_COMPLETE; + } + +done: + switch (ret_maj) { + case GSS_S_UNAUTHORIZED: + GPDEBUGN(2, "Unauthorized impersonator credentials detected\n"); + break; + case GSS_S_COMPLETE: + GPDEBUGN(2, "No impersonator credentials detected\n"); + break; + default: + GPDEBUG("Failure while checking credentials\n"); + break; + } + if (context) { + /* NOTE: destroy only if we created a MEMORY ccache */ + if (ccache) { + if (memcache) krb5_cc_destroy(context, ccache); + else krb5_cc_close(context, ccache); + } + krb5_free_context(context); + } + free(memcache); + *min = ret_min; + return ret_maj; +} diff --git a/proxy/src/gp_proxy.h b/proxy/src/gp_proxy.h index 4216b72..ad6806a 100644 --- a/proxy/src/gp_proxy.h +++ b/proxy/src/gp_proxy.h @@ -31,6 +31,7 @@ struct gp_service { uid_t euid; bool any_uid; bool allow_proto_trans; + bool allow_const_deleg; bool trusted; bool kernel_nfsd; bool impersonate; diff --git a/proxy/src/gp_rpc_creds.h b/proxy/src/gp_rpc_creds.h index ead6afc..93df7e1 100644 --- a/proxy/src/gp_rpc_creds.h +++ b/proxy/src/gp_rpc_creds.h @@ -32,6 +32,10 @@ uint32_t gp_add_krb5_creds(uint32_t *min, uint32_t *initiator_time_rec, uint32_t *acceptor_time_rec); +uint32_t gp_cred_allowed(uint32_t *min, + struct gp_call_ctx *gpcall, + gss_cred_id_t cred); + void gp_filter_flags(struct gp_call_ctx *gpcall, uint32_t *flags); #endif /* _GP_RPC_CREDS_H_ */ diff --git a/proxy/src/gp_rpc_init_sec_context.c b/proxy/src/gp_rpc_init_sec_context.c index d607b07..f3dc11d 100644 --- a/proxy/src/gp_rpc_init_sec_context.c +++ b/proxy/src/gp_rpc_init_sec_context.c @@ -55,6 +55,7 @@ int gp_init_sec_context(struct gp_call_ctx *gpcall, goto done; } } + ret_maj = gp_conv_gssx_to_name(&ret_min, isca->target_name, &target_name); if (ret_maj) { goto done; @@ -98,6 +99,11 @@ int gp_init_sec_context(struct gp_call_ctx *gpcall, } } + ret_maj = gp_cred_allowed(&ret_min, gpcall, ich); + if (ret_maj) { + goto done; + } + gp_filter_flags(gpcall, &req_flags); ret_maj = gss_init_sec_context(&ret_min, From 140c68e50610592a89e2b856df9cdb5be5803ba2 Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Jan 17 2017 17:27:20 +0000 Subject: [PATCH 7/7] Add more impersonation tests Sets up separate service to test multiple configurations. Signed-off-by: Simo Sorce --- diff --git a/proxy/tests/t_impersonate.c b/proxy/tests/t_impersonate.c index 1e54a2b..3ff463d 100644 --- a/proxy/tests/t_impersonate.c +++ b/proxy/tests/t_impersonate.c @@ -2,13 +2,13 @@ #include "t_utils.h" #include +#include int main(int argc, const char *argv[]) { char buffer[MAX_RPC_SIZE]; uint32_t buflen; gss_cred_id_t impersonator_cred_handle = GSS_C_NO_CREDENTIAL; - gss_cred_id_t user_cred_handle = GSS_C_NO_CREDENTIAL; gss_cred_id_t cred_handle = GSS_C_NO_CREDENTIAL; gss_ctx_id_t init_ctx = GSS_C_NO_CONTEXT; gss_ctx_id_t accept_ctx = GSS_C_NO_CONTEXT; @@ -20,9 +20,13 @@ int main(int argc, const char *argv[]) uint32_t ret_maj; uint32_t ret_min; uint32_t time_rec; + uint32_t flags = GSS_C_MUTUAL_FLAG | GSS_C_DELEG_FLAG; int ret = -1; + bool selfhalf = false; + bool proxyhalf = false; + const char *deleg_ccache = NULL; - if (argc != 3) return -1; + if (argc < 3) return -1; ret = t_string_to_name(argv[1], &user_name, GSS_C_NT_USER_NAME); if (ret) { @@ -39,41 +43,100 @@ int main(int argc, const char *argv[]) goto done; } - ret_maj = gss_acquire_cred(&ret_min, - GSS_C_NO_NAME, - GSS_C_INDEFINITE, - &oid_set, - GSS_C_BOTH, - &impersonator_cred_handle, - NULL, NULL); - if (ret_maj != GSS_S_COMPLETE) { - DEBUG("gss_acquire_cred() failed\n"); - t_log_failure(GSS_C_NO_OID, ret_maj, ret_min); - ret = -1; - goto done; + if (argc > 3) { + if (strcmp(argv[3], "s4u2self") == 0) { + selfhalf = true; + } else if (strcmp(argv[3], "s4u2proxy") == 0) { + proxyhalf = true; + } else { + DEBUG("Invalid argument 3: %s\n", argv[3]); + ret = -1; + goto done; + } + if (argc < 5) { + DEBUG("Option %s requires additional arguments\n", argv[3]); + ret = -1; + goto done; + } + deleg_ccache = argv[4]; + DEBUG("S4U2%s half [ccache %s]\n", selfhalf?"Self":"Proxy", argv[4]); } - ret_maj = gss_acquire_cred_impersonate_name(&ret_min, - impersonator_cred_handle, - user_name, - GSS_C_INDEFINITE, - &oid_set, - GSS_C_INITIATE, - &user_cred_handle, - NULL, NULL); - if (ret_maj != GSS_S_COMPLETE) { - DEBUG("gss_acquire_cred_impersonate_name() failed\n"); - t_log_failure(GSS_C_NO_OID, ret_maj, ret_min); - ret = -1; + if (proxyhalf) { + gss_key_value_element_desc ccelement = { "ccache", deleg_ccache }; + gss_key_value_set_desc cred_store = { 1, &ccelement }; + + ret_maj = gss_acquire_cred_from(&ret_min, + GSS_C_NO_NAME, + GSS_C_INDEFINITE, + &oid_set, + GSS_C_INITIATE, + &cred_store, + &cred_handle, + NULL, NULL); + if (ret_maj != GSS_S_COMPLETE) { + DEBUG("gss_acquire_cred_from() [s4u2proxy] failed\n"); + t_log_failure(GSS_C_NO_OID, ret_maj, ret_min); + ret = -1; + goto done; + } + + flags = GSS_C_MUTUAL_FLAG; + } else { + + ret_maj = gss_acquire_cred(&ret_min, + GSS_C_NO_NAME, + GSS_C_INDEFINITE, + &oid_set, + GSS_C_BOTH, + &impersonator_cred_handle, + NULL, NULL); + if (ret_maj != GSS_S_COMPLETE) { + DEBUG("gss_acquire_cred() failed\n"); + t_log_failure(GSS_C_NO_OID, ret_maj, ret_min); + ret = -1; + goto done; + } + + ret_maj = gss_acquire_cred_impersonate_name(&ret_min, + impersonator_cred_handle, + user_name, + GSS_C_INDEFINITE, + &oid_set, + GSS_C_INITIATE, + &cred_handle, + NULL, NULL); + if (ret_maj != GSS_S_COMPLETE) { + DEBUG("gss_acquire_cred_impersonate_name() failed\n"); + t_log_failure(GSS_C_NO_OID, ret_maj, ret_min); + ret = -1; + goto done; + } + } + + if (selfhalf) { + gss_key_value_element_desc ccelement = { "ccache", deleg_ccache }; + gss_key_value_set_desc cred_store = { 1, &ccelement }; + + ret_maj = gss_store_cred_into(&ret_min, + cred_handle, + GSS_C_INITIATE, + discard_const(gss_mech_krb5), 1, 0, + &cred_store, NULL, NULL); + if (ret_maj != GSS_S_COMPLETE) { + DEBUG("gss_store_cred_into() failed\n"); + t_log_failure(GSS_C_NO_OID, ret_maj, ret_min); + ret = -1; + } goto done; } ret_maj = gss_init_sec_context(&ret_min, - user_cred_handle, + cred_handle, &init_ctx, target_name, GSS_C_NO_OID, - GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG, + flags, 0, GSS_C_NO_CHANNEL_BINDINGS, &in_token, @@ -123,11 +186,11 @@ int main(int argc, const char *argv[]) gss_release_buffer(&ret_min, &out_token); ret_maj = gss_init_sec_context(&ret_min, - user_cred_handle, + cred_handle, &init_ctx, target_name, GSS_C_NO_OID, - GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG, + flags, 0, GSS_C_NO_CHANNEL_BINDINGS, &in_token, @@ -147,6 +210,7 @@ int main(int argc, const char *argv[]) done: gss_release_buffer(&ret_min, &in_token); gss_release_buffer(&ret_min, &out_token); + gss_release_cred(&ret_min, &impersonator_cred_handle); gss_release_cred(&ret_min, &cred_handle); return ret; } diff --git a/proxy/tests/t_impersonate.py b/proxy/tests/t_impersonate.py old mode 100644 new mode 100755 index 43bb084..9bfd2cd --- a/proxy/tests/t_impersonate.py +++ b/proxy/tests/t_impersonate.py @@ -3,21 +3,54 @@ from testlib import * -def run(testdir, env, conf, expected_failure=False): - print("Testing impersonate creds...", file=sys.stderr) - logfile = conf['logfile'] +IMPERSONATE_CONF_TEMPLATE = ''' +[gssproxy] + debug_level = 2 - testenv = {'KRB5CCNAME': os.path.join(testdir, 't' + conf['prefix'] + - '_impersonate.ccache'), - 'KRB5_KTNAME': conf['keytab'], - 'KRB5_TRACE': os.path.join(testdir, 't' + conf['prefix'] + - '_impersonate.trace'), - 'GSS_USE_PROXY': 'yes', - 'GSSPROXY_BEHAVIOR': 'REMOTE_FIRST'} - testenv.update(env) +[service/impersonate] + socket = ${TESTDIR}/impersonate.socket + mechs = krb5 + cred_store = keytab:${GSSPROXY_KEYTAB} + cred_store = ccache:FILE:${GSSPROXY_CLIENT_CCACHE} + cred_store = client_keytab:${GSSPROXY_CLIENT_KEYTAB} + allow_protocol_transition = yes + allow_constrained_delegation = yes + euid = ${UIDNUMBER} - cmd = ["./tests/t_impersonate", USR_NAME, conf['svc_name']] - print("[COMMAND]\n%s\n[ENVIRONMENT]\n%s\n" % (cmd, env), file=logfile) +[service/selfonly] + socket = ${TESTDIR}/impersonate-selfonly.socket + mechs = krb5 + cred_store = keytab:${GSSPROXY_KEYTAB} + cred_store = ccache:FILE:${GSSPROXY_CLIENT_CCACHE} + cred_store = client_keytab:${GSSPROXY_CLIENT_KEYTAB} + allow_protocol_transition = yes + euid = ${UIDNUMBER} + +[service/proxyonly] + socket = ${TESTDIR}/impersonate-proxyonly.socket + mechs = krb5 + cred_store = keytab:${GSSPROXY_KEYTAB} + cred_store = ccache:FILE:${GSSPROXY_CLIENT_CCACHE} + cred_store = client_keytab:${GSSPROXY_CLIENT_KEYTAB} + allow_constrained_delegation = yes + euid = ${UIDNUMBER} + +''' + +def run_cmd(testdir, env, conf, name, socket, cmd, expected_failure): + + logfile = conf['logfile'] + testenv = env.copy() + testenv.update({'KRB5CCNAME': os.path.join(testdir, 't' + conf['prefix'] + + '_impersonate.ccache'), + 'KRB5_KTNAME': os.path.join(testdir, PROXY_KTNAME), + 'KRB5_TRACE': os.path.join(testdir, 't' + conf['prefix'] + + '_impersonate.trace'), + 'GSS_USE_PROXY': 'yes', + 'GSSPROXY_SOCKET': socket, + 'GSSPROXY_BEHAVIOR': 'REMOTE_FIRST'}) + + print("[COMMAND]\n%s\n[ENVIRONMENT]\n%s\n" % (cmd, testenv), file=logfile) logfile.flush() p1 = subprocess.Popen(cmd, stderr=subprocess.STDOUT, stdout=logfile, @@ -27,4 +60,54 @@ def run(testdir, env, conf, expected_failure=False): except subprocess.TimeoutExpired: # p1.returncode is set to None here pass - print_return(p1.returncode, "Impersonate", expected_failure) + print_return(p1.returncode, name, expected_failure) + + +def run(testdir, env, conf, expected_failure=False): + print("Testing impersonate creds...", file=sys.stderr) + path_prefix = os.path.join(testdir, 't' + conf['prefix'] + '_') + + # Change gssproxy conf for our test + keysenv = conf["keysenv"].copy() + keysenv['KRB5_KTNAME'] = os.path.join(testdir, PROXY_KTNAME) + update_gssproxy_conf(testdir, keysenv, IMPERSONATE_CONF_TEMPLATE) + os.kill(conf["gpid"], signal.SIGHUP) + time.sleep(1) #Let gssproxy reload everything + + # Test all permitted + socket = os.path.join(testdir, 'impersonate.socket') + cmd = ["./tests/t_impersonate", USR_NAME, conf['svc_name']] + run_cmd(testdir, env, conf, "Impersonate", socket, cmd, False) + + #Test fail + socket = os.path.join(testdir, 'impersonate-proxyonly.socket') + cmd = ["./tests/t_impersonate", USR_NAME, conf['svc_name']] + run_cmd(testdir, env, conf, "Impersonate fail self", socket, cmd, True) + + #Test fail + socket = os.path.join(testdir, 'impersonate-selfonly.socket') + cmd = ["./tests/t_impersonate", USR_NAME, conf['svc_name']] + run_cmd(testdir, env, conf, "Impersonate fail proxy", socket, cmd, True) + + #Test s4u2self half succeed + socket = os.path.join(testdir, 'impersonate-selfonly.socket') + cmd = ["./tests/t_impersonate", USR_NAME, conf['svc_name'], 's4u2self', + path_prefix + 'impersonate-proxy.ccache'] + run_cmd(testdir, env, conf, "s4u2self delegation", socket, cmd, False) + + #Test s4u2proxy half fail + socket = os.path.join(testdir, 'impersonate-selfonly.socket') + cmd = ["./tests/t_impersonate", USR_NAME, PROXY_GSS, 's4u2proxy', + path_prefix + 'impersonate-proxy.ccache'] + run_cmd(testdir, env, conf, "s4u2proxy fail", socket, cmd, True) + + #Test s4u2proxy half succeed + socket = os.path.join(testdir, 'impersonate-proxyonly.socket') + cmd = ["./tests/t_impersonate", USR_NAME, PROXY_GSS, 's4u2proxy', + path_prefix + 'impersonate-proxy.ccache'] + run_cmd(testdir, env, conf, "s4u2proxy", socket, cmd, False) + + # Reset back gssproxy conf + update_gssproxy_conf(testdir, keysenv, GSSPROXY_CONF_TEMPLATE) + os.kill(conf["gpid"], signal.SIGHUP) + time.sleep(1) #Let gssproxy reload everything diff --git a/proxy/tests/testlib.py b/proxy/tests/testlib.py index 84accb1..43029e8 100755 --- a/proxy/tests/testlib.py +++ b/proxy/tests/testlib.py @@ -344,6 +344,43 @@ USR2_PWD = "usrpwd" MULTI_KTNAME = "multi.gssproxy.keytab" MULTI_UPN = "multi$" MULTI_SVC = "multi/%s" % WRAP_HOSTNAME +HOST_SVC = "host/%s" % WRAP_HOSTNAME +PROXY_SVC = "proxy/%s" % WRAP_HOSTNAME +PROXY_GSS = "proxy@%s" % WRAP_HOSTNAME +PROXY_KTNAME = "proxy.keytab" + +PROXY_LDIF_TEMPLATE = """ +dn: krbPrincipalName=${HOST_SVC}@${TESTREALM},cn=${TESTREALM},cn=${KRB5_CN},${LDAP_REALM} +changetype: modify +add: krbAllowedToDelegateTo +krbAllowedToDelegateTo: ${PROXY_SVC}@${TESTREALM} +- +""" + +def authorize_to_proxy(testdir, env): + testlog = os.path.join(testdir, 'kerbsetup.log') + + t = Template(PROXY_LDIF_TEMPLATE) + text = t.substitute({"HOST_SVC": HOST_SVC, + "PROXY_SVC": PROXY_SVC, + "TESTREALM": TESTREALM, + "LDAP_REALM": LDAP_REALM, + "KRB5_CN": KRB5_CN}) + ldif = os.path.join(testdir, "ldap", "k5proxy.ldif") + with open(ldif, "w+") as f: + f.write(text) + + with open(testlog, "a") as logfile: + lmod = subprocess.Popen(["ldapmodify", "-w", LDAP_PW, "-H", + "ldap://%s" % WRAP_HOSTNAME, "-D", + "%s,%s" % (KRB5_USER, LDAP_REALM), + "-f", ldif], + stdout=logfile, stderr=logfile, env=env, + preexec_fn=os.setsid) + + lmod.wait() + if lmod.returncode != 0: + raise ValueError("Proxy princ setup failed") def setup_keys(testdir, env): @@ -351,7 +388,8 @@ def setup_keys(testdir, env): svc_name = "host/%s" % WRAP_HOSTNAME svc_keytab = os.path.join(testdir, SVC_KTNAME) - cmd = "addprinc -randkey -e %s %s" % (KEY_TYPE, svc_name) + cmd = "addprinc -randkey -e %s +ok_to_auth_as_delegate %s" % (KEY_TYPE, + svc_name) with (open(testlog, 'a')) as logfile: kadmin_local(cmd, env, logfile) cmd = "ktadd -k %s -e %s %s" % (svc_keytab, KEY_TYPE, svc_name) @@ -370,6 +408,18 @@ def setup_keys(testdir, env): with (open(testlog, 'a')) as logfile: kadmin_local(cmd, env, logfile) + proxy_keytab = os.path.join(testdir, PROXY_KTNAME) + cmd = "addprinc -randkey -e %s -requires_preauth %s" % (KEY_TYPE, + PROXY_SVC) + with (open(testlog, 'a')) as logfile: + kadmin_local(cmd, env, logfile) + shutil.copy(svc_keytab, proxy_keytab) + cmd = "ktadd -k %s -e %s %s" % (proxy_keytab, KEY_TYPE, PROXY_SVC) + with (open(testlog, 'a')) as logfile: + kadmin_local(cmd, env, logfile) + + authorize_to_proxy(testdir, env) + keys_env = {"client_keytab": usr_keytab, "KRB5_KTNAME": svc_keytab} keys_env.update(env) @@ -532,7 +582,8 @@ def update_gssproxy_conf(testdir, env, template): 'GSSPROXY_CLIENT_CCACHE': ccache, 'GSSPROXY_CLIENT_KEYTAB': ckeytab, 'UIDNUMBER': os.getuid(), - 'SECOND_SOCKET': socket2} + 'SECOND_SOCKET': socket2, + 'TESTDIR': testdir} if 'client_name' in env: subs['GSSPROXY_CLIENT_PRINCIPAL'] = env['client_name'] text = t.substitute(subs)