From cac0db2f8004ae88b9263dc3888a11a2d3d3d114 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Mar 27 2017 07:58:48 +0000 Subject: KCM: Store ccaches in secrets Adds a new KCM responder ccache back end that forwards all requests to sssd-secrets. Reviewed-by: Michal Židek Reviewed-by: Simo Sorce --- diff --git a/Makefile.am b/Makefile.am index 49b4cab..e9eaa31 100644 --- a/Makefile.am +++ b/Makefile.am @@ -303,6 +303,10 @@ if HAVE_INOTIFY non_interactive_cmocka_based_tests += test_inotify endif # HAVE_INOTIFY +if BUILD_KCM +non_interactive_cmocka_based_tests += test_kcm_json +endif # BUILD_KCM + if BUILD_SAMBA non_interactive_cmocka_based_tests += \ ad_access_filter_tests \ @@ -1494,19 +1498,26 @@ sssd_kcm_SOURCES = \ src/responder/kcm/kcmsrv_cmd.c \ src/responder/kcm/kcmsrv_ccache.c \ src/responder/kcm/kcmsrv_ccache_mem.c \ + src/responder/kcm/kcmsrv_ccache_json.c \ + src/responder/kcm/kcmsrv_ccache_secrets.c \ src/responder/kcm/kcmsrv_ops.c \ src/util/sss_sockets.c \ src/util/sss_krb5.c \ src/util/sss_iobuf.c \ + src/util/tev_curl.c \ $(SSSD_RESPONDER_OBJ) \ $(NULL) sssd_kcm_CFLAGS = \ $(AM_CFLAGS) \ $(KRB5_CFLAGS) \ $(UUID_CFLAGS) \ + $(CURL_CFLAGS) \ + $(JANSSON_CFLAGS) \ $(NULL) sssd_kcm_LDADD = \ $(KRB5_LIBS) \ + $(CURL_LIBS) \ + $(JANSSON_LIBS) \ $(SSSD_LIBS) \ $(UUID_LIBS) \ $(SYSTEMD_DAEMON_LIBS) \ @@ -3369,6 +3380,30 @@ sss_certmap_test_LDADD = \ libsss_certmap.la \ $(NULL) endif + +if BUILD_KCM +test_kcm_json_SOURCES = \ + src/tests/cmocka/test_kcm_json_marshalling.c \ + src/responder/kcm/kcmsrv_ccache_json.c \ + src/responder/kcm/kcmsrv_ccache.c \ + src/util/sss_krb5.c \ + src/util/sss_iobuf.c \ + $(NULL) +test_kcm_json_CFLAGS = \ + $(AM_CFLAGS) \ + $(UUID_CFLAGS) \ + $(NULL) +test_kcm_json_LDADD = \ + $(JANSSON_LIBS) \ + $(UUID_LIBS) \ + $(KRB5_LIBS) \ + $(CMOCKA_LIBS) \ + $(SSSD_LIBS) \ + $(SSSD_INTERNAL_LTLIBS) \ + libsss_test_common.la \ + $(NULL) +endif # BUILD_KCM + endif # HAVE_CMOCKA noinst_PROGRAMS = pam_test_client @@ -3431,8 +3466,9 @@ intgcheck-prepare: --enable-intgcheck-reqs \ --without-semanage \ --enable-files-domain \ - $(INTGCHECK_CONFIGURE_FLAGS); \ - $(MAKE) $(AM_MAKEFLAGS); \ + $(INTGCHECK_CONFIGURE_FLAGS) \ + CFLAGS="$$CFLAGS $(AM_CFLAGS) -DKCM_PEER_UID=$$(id -u)"; \ + $(MAKE) $(AM_MAKEFLAGS) ; \ : Force single-thread install to workaround concurrency issues; \ $(MAKE) $(AM_MAKEFLAGS) -j1 install; \ : Remove .la files from LDB module directory to avoid loader warnings; \ diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in index 1d4d020..af14d4e 100644 --- a/contrib/sssd.spec.in +++ b/contrib/sssd.spec.in @@ -223,6 +223,7 @@ BuildRequires: systemtap-sdt-devel BuildRequires: http-parser-devel BuildRequires: jansson-devel BuildRequires: libuuid-devel +BuildRequires: libcurl-devel %description Provides a set of daemons to manage access to remote directories and diff --git a/src/responder/kcm/kcmsrv_ccache.h b/src/responder/kcm/kcmsrv_ccache.h index 130ae48..18c8c47 100644 --- a/src/responder/kcm/kcmsrv_ccache.h +++ b/src/responder/kcm/kcmsrv_ccache.h @@ -303,4 +303,53 @@ void kcm_debug_uuid(uuid_t uuid); */ errno_t kcm_check_name(const char *name, struct cli_creds *client); +/* + * ccahe marshalling to and from JSON. This is used when the ccaches + * are stored in the secrets store + */ + +/* + * The secrets store is a key-value store at heart. We store the UUID + * and the name in the key to allow easy lookups be either key + */ +bool sec_key_match_name(const char *sec_key, + const char *name); + +bool sec_key_match_uuid(const char *sec_key, + uuid_t uuid); + +const char *sec_key_get_name(const char *sec_key); + +errno_t sec_key_get_uuid(const char *sec_key, + uuid_t uuid); + +/* Create a URL for the default client's ccache */ +const char *sec_dfl_url_create(TALLOC_CTX *mem_ctx, + struct cli_creds *client); + +/* Create a URL for the client's ccache container */ +const char *sec_container_url_create(TALLOC_CTX *mem_ctx, + struct cli_creds *client); + +const char *sec_cc_url_create(TALLOC_CTX *mem_ctx, + struct cli_creds *client, + const char *sec_key); + +/* + * sec_key is a concatenation of the ccache's UUID and name + * sec_value is the JSON dump of the ccache contents + */ +errno_t sec_kv_to_ccache(TALLOC_CTX *mem_ctx, + const char *sec_key, + const char *sec_value, + struct cli_creds *client, + struct kcm_ccache **_cc); + +/* Convert a kcm_ccache to a key-value pair to be stored in secrets */ +errno_t kcm_ccache_to_sec_input(TALLOC_CTX *mem_ctx, + struct kcm_ccache *cc, + struct cli_creds *client, + const char **_url, + struct sss_iobuf **_payload); + #endif /* _KCMSRV_CCACHE_H_ */ diff --git a/src/responder/kcm/kcmsrv_ccache_json.c b/src/responder/kcm/kcmsrv_ccache_json.c new file mode 100644 index 0000000..40b6486 --- /dev/null +++ b/src/responder/kcm/kcmsrv_ccache_json.c @@ -0,0 +1,921 @@ +/* + SSSD + + KCM Server - ccache JSON (un)marshalling for storing ccaches in + sssd-secrets + + Copyright (C) Red Hat, 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "config.h" + +#include +#include +#include + +#include "util/util.h" +#include "util/util_creds.h" +#include "util/crypto/sss_crypto.h" +#include "responder/kcm/kcmsrv_ccache_pvt.h" + +/* The base for storing secrets is: + * http://localhost/kcm/persistent/$uid + * + * Under $base, there are two containers: + * /ccache - stores the ccaches + * /ntlm - stores NTLM creds [Not implement yet] + * + * There is also a special entry that contains the UUID of the default + * cache for this UID: + * /default - stores the UUID of the default ccache for this UID + * + * Each ccache has a name and an UUID. On the secrets level, the 'secret' + * is a concatenation of the stringified UUID and the name separated + * by a plus-sign. + */ +#define KCM_SEC_URL "http://localhost/kcm/persistent" +#define KCM_SEC_BASE_FMT KCM_SEC_URL"/%"SPRIuid"/" +#define KCM_SEC_CCACHE_FMT KCM_SEC_BASE_FMT"ccache/" +#define KCM_SEC_DFL_FMT KCM_SEC_BASE_FMT"default" + + +/* + * We keep the JSON representation of the ccache versioned to allow + * us to modify the format in a future version + */ +#define KS_JSON_VERSION 1 + +/* + * The secrets store is a key-value store at heart. We store the UUID + * and the name in the key to allow easy lookups be either key + */ +#define SEC_KEY_SEPARATOR '-' + +/* Compat definition of json_array_foreach for older systems */ +#ifndef json_array_foreach +#define json_array_foreach(array, idx, value) \ + for(idx = 0; \ + idx < json_array_size(array) && (value = json_array_get(array, idx)); \ + idx++) +#endif + +const char *sec_container_url_create(TALLOC_CTX *mem_ctx, + struct cli_creds *client) +{ + return talloc_asprintf(mem_ctx, + KCM_SEC_CCACHE_FMT, + cli_creds_get_uid(client)); +} + +const char *sec_cc_url_create(TALLOC_CTX *mem_ctx, + struct cli_creds *client, + const char *sec_key) +{ + return talloc_asprintf(mem_ctx, + KCM_SEC_CCACHE_FMT"%s", + cli_creds_get_uid(client), + sec_key); +} + +const char *sec_dfl_url_create(TALLOC_CTX *mem_ctx, + struct cli_creds *client) +{ + return talloc_asprintf(mem_ctx, + KCM_SEC_DFL_FMT, + cli_creds_get_uid(client)); +} + +static const char *sec_key_create(TALLOC_CTX *mem_ctx, + const char *name, + uuid_t uuid) +{ + char uuid_str[UUID_STR_SIZE]; + + uuid_unparse(uuid, uuid_str); + return talloc_asprintf(mem_ctx, + "%s%c%s", uuid_str, SEC_KEY_SEPARATOR, name); +} + +static errno_t sec_key_parse(TALLOC_CTX *mem_ctx, + const char *sec_key, + const char **_name, + uuid_t uuid) +{ + char uuid_str[UUID_STR_SIZE]; + + if (strlen(sec_key) < UUID_STR_SIZE + 2) { + /* One char for separator and at least one for the name */ + DEBUG(SSSDBG_CRIT_FAILURE, "Key %s is too short\n", sec_key); + return EINVAL; + } + + strncpy(uuid_str, sec_key, sizeof(uuid_str)-1); + if (sec_key[UUID_STR_SIZE - 1] != SEC_KEY_SEPARATOR) { + DEBUG(SSSDBG_CRIT_FAILURE, "Key doesn't contain the separator\n"); + return EINVAL; + } + uuid_str[UUID_STR_SIZE-1] = '\0'; + + *_name = talloc_strdup(mem_ctx, sec_key + UUID_STR_SIZE); + if (*_name == NULL) { + return ENOMEM; + } + uuid_parse(uuid_str, uuid); + + return EOK; +} + +errno_t sec_key_get_uuid(const char *sec_key, + uuid_t uuid) +{ + char uuid_str[UUID_STR_SIZE]; + + if (strlen(sec_key) < UUID_STR_SIZE + 2) { + /* One char for separator and at least one for the name */ + DEBUG(SSSDBG_CRIT_FAILURE, "Key %s is too short\n", sec_key); + return EINVAL; + } + + if (sec_key[UUID_STR_SIZE-1] != SEC_KEY_SEPARATOR) { + DEBUG(SSSDBG_CRIT_FAILURE, "Key doesn't contain the separator\n"); + return EINVAL; + } + + strncpy(uuid_str, sec_key, UUID_STR_SIZE-1); + uuid_str[UUID_STR_SIZE-1] = '\0'; + uuid_parse(uuid_str, uuid); + return EOK; +} + +const char *sec_key_get_name(const char *sec_key) +{ + if (strlen(sec_key) < UUID_STR_SIZE + 2) { + /* One char for separator and at least one for the name */ + DEBUG(SSSDBG_CRIT_FAILURE, "Key %s is too short\n", sec_key); + return NULL; + } + + return sec_key + UUID_STR_SIZE; +} + +bool sec_key_match_name(const char *sec_key, + const char *name) +{ + if (strlen(sec_key) < UUID_STR_SIZE + 2) { + /* One char for separator and at least one for the name */ + DEBUG(SSSDBG_MINOR_FAILURE, "Key %s is too short\n", sec_key); + return false; + } + + return strcmp(sec_key + UUID_STR_SIZE, name) == 0; +} + +bool sec_key_match_uuid(const char *sec_key, + uuid_t uuid) +{ + errno_t ret; + uuid_t key_uuid; + + ret = sec_key_get_uuid(sec_key, key_uuid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot convert key to UUID\n"); + return false; + } + + return uuid_compare(key_uuid, uuid) == 0; +} + +/* + * Creates an array of principal elements that will be used later + * in the form of: + * "componenets": [ "elem1", "elem2", ...] + */ +static json_t *princ_data_to_json(TALLOC_CTX *mem_ctx, + krb5_principal princ) +{ + json_t *jdata = NULL; + json_t *data_array = NULL; + int ret; + char *str_princ_data; + + data_array = json_array(); + if (data_array == NULL) { + return NULL; + } + + for (ssize_t i = 0; i < princ->length; i++) { + /* FIXME - it might be cleaner to use stringn here, but the libjansson + * version on RHEL-7 doesn't support that + */ + str_princ_data = talloc_zero_array(mem_ctx, + char, + princ->data[i].length + 1); + if (str_princ_data == NULL) { + return NULL; + } + memcpy(str_princ_data, princ->data[i].data, princ->data[i].length); + str_princ_data[princ->data[i].length] = '\0'; + + jdata = json_string(str_princ_data); + talloc_free(str_princ_data); + if (jdata == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot convert principal data to string\n"); + json_decref(data_array); + return NULL; + } + + ret = json_array_append_new(data_array, jdata); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot append principal data to array\n"); + json_decref(jdata); + json_decref(data_array); + return NULL; + } + /* data_array now owns the reference to jdata */ + } + + return data_array; +} + +/* Creates: + * { + * "type": "number", + * "realm": "string", + * "componenents": [ "elem1", "elem2", ...] + * } + */ +static json_t *princ_to_json(TALLOC_CTX *mem_ctx, + krb5_principal princ) +{ + json_t *jprinc = NULL; + json_t *components = NULL; + json_error_t error; + char *str_realm_data; + + components = princ_data_to_json(mem_ctx, princ); + if (components == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot convert principal data to JSON\n"); + return NULL; + } + + /* FIXME - it might be cleaner to use the s% specifier here, but the libjansson + * version on RHEL-7 doesn't support that + */ + str_realm_data = talloc_zero_array(mem_ctx, + char, + princ->realm.length + 1); + if (str_realm_data == NULL) { + return NULL; + } + memcpy(str_realm_data, princ->realm.data, princ->realm.length); + str_realm_data[princ->realm.length] = '\0'; + + jprinc = json_pack_ex(&error, + JSON_STRICT, + "{s:i, s:s, s:o}", + "type", princ->type, + "realm", str_realm_data, + "components", components); + talloc_free(str_realm_data); + if (jprinc == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to pack JSON princ structure on line %d: %s\n", + error.line, error.text); + json_decref(components); + return NULL; + } + + return jprinc; +} + +/* Creates: + * { + * "uuid": , + * "payload": , + * }, + */ +static json_t *cred_to_json(struct kcm_cred *crd) +{ + char uuid_str[UUID_STR_SIZE]; + uint8_t *cred_blob_data; + size_t cred_blob_size; + json_t *jcred; + json_error_t error; + char *base64_cred_blob; + + uuid_unparse(crd->uuid, uuid_str); + cred_blob_data = sss_iobuf_get_data(crd->cred_blob); + cred_blob_size = sss_iobuf_get_size(crd->cred_blob); + + base64_cred_blob = sss_base64_encode(crd, cred_blob_data, cred_blob_size); + if (base64_cred_blob == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot base64 encode the certificate blob\n"); + return NULL; + } + + jcred = json_pack_ex(&error, + JSON_STRICT, + "{s:s, s:s}", + "uuid", uuid_str, + "payload", base64_cred_blob); + talloc_free(base64_cred_blob); + if (jcred == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to pack JSON cred structure on line %d: %s\n", + error.line, error.text); + return NULL; + } + return jcred; +} + +/* + * Creates: + * [ + * { + * "uuid": , + * "payload": , + * }, + * ... + * ] + */ +static json_t *creds_to_json_array(struct kcm_cred *creds) +{ + struct kcm_cred *crd; + json_t *array; + json_t *jcred; + + array = json_array(); + if (array == NULL) { + return NULL; + } + + DLIST_FOR_EACH(crd, creds) { + jcred = cred_to_json(crd); + if (jcred == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot convert credentials to JSON\n"); + json_decref(array); + return NULL; + } + + json_array_append_new(array, jcred); + /* array now owns jcred */ + jcred = NULL; + } + + return array; +} + +/* + * The ccache is formatted in JSON as: + * { + * version: number + * kdc_offset: number + * principal : { + * "type": "number", + * "realm": "string", + * "componenents": [ "elem1", "elem2", ...] + * } + * creds : [ + * { + * "uuid": , + * "payload": , + * }, + * { + * ... + * } + * ] + * } + * } + */ +static json_t *ccache_to_json(struct kcm_ccache *cc) +{ + json_t *princ = NULL; + json_t *creds = NULL; + json_t *jcc = NULL; + json_error_t error; + + princ = princ_to_json(cc, cc->client); + if (princ == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot convert princ to JSON\n"); + return NULL; + } + + creds = creds_to_json_array(cc->creds); + if (creds == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot convert creds to JSON array\n"); + json_decref(princ); + return NULL; + } + + jcc = json_pack_ex(&error, + JSON_STRICT, + "{s:i, s:i, s:o, s:o}", + "version", KS_JSON_VERSION, + "kdc_offset", cc->kdc_offset, + "principal", princ, + "creds", creds); + if (jcc == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to pack JSON ccache structure on line %d: %s\n", + error.line, error.text); + json_decref(creds); + json_decref(princ); + return NULL; + } + + return jcc; +} + +static errno_t ccache_to_sec_kv(TALLOC_CTX *mem_ctx, + struct kcm_ccache *cc, + const char **_sec_key, + const char **_sec_value) +{ + json_t *jcc = NULL; + char *jdump; + + jcc = ccache_to_json(cc); + if (jcc == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot convert ccache to JSON\n"); + return ERR_JSON_ENCODING; + } + + /* it would be more efficient to learn the size with json_dumpb and + * a NULL buffer, but that's only available since 2.10 + */ + jdump = json_dumps(jcc, JSON_INDENT(4) | JSON_ENSURE_ASCII); + if (jdump == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot dump JSON\n"); + return ERR_JSON_ENCODING; + } + + *_sec_key = sec_key_create(mem_ctx, cc->name, cc->uuid); + *_sec_value = talloc_strdup(mem_ctx, jdump); + free(jdump); + json_decref(jcc); + if (*_sec_key == NULL || *_sec_value == NULL) { + return ENOMEM; + } + + return EOK; +} + +errno_t kcm_ccache_to_sec_input(TALLOC_CTX *mem_ctx, + struct kcm_ccache *cc, + struct cli_creds *client, + const char **_url, + struct sss_iobuf **_payload) +{ + errno_t ret; + const char *key; + const char *value; + const char *url; + struct sss_iobuf *payload; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = ccache_to_sec_kv(mem_ctx, cc, &key, &value); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot convert cache %s to JSON [%d]: %s\n", + cc->name, ret, sss_strerror(ret)); + goto done; + } + + url = sec_cc_url_create(tmp_ctx, client, key); + if (url == NULL) { + ret = ENOMEM; + goto done; + } + + payload = sss_iobuf_init_readonly(tmp_ctx, + (const uint8_t *) value, + strlen(value)+1); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot create payload buffer\n"); + goto done; + } + + ret = EOK; + *_url = talloc_steal(mem_ctx, url); + *_payload = talloc_steal(mem_ctx, payload); +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t sec_value_to_json(const char *input, + json_t **_root) +{ + json_t *root = NULL; + json_error_t error; + int ok; + + root = json_loads(input, 0, &error); + if (root == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to parse JSON payload on line %d: %s\n", + error.line, error.text); + return ERR_JSON_DECODING; + } + + ok = json_is_object(root); + if (!ok) { + DEBUG(SSSDBG_CRIT_FAILURE, "Json data is not an object.\n"); + json_decref(root); + return ERR_JSON_DECODING; + } + + *_root = root; + return EOK; +} + +/* + * ccache unmarshalling from JSON + */ +static errno_t json_element_to_krb5_data(TALLOC_CTX *mem_ctx, + json_t *element, + krb5_data *data) +{ + const char *str_value; + size_t str_len; + + /* FIXME - it might be cleaner to use stringn here, but the libjansson + * version on RHEL-7 doesn't support that + */ + str_value = json_string_value(element); + if (str_value == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "JSON element not a string\n"); + return EINVAL; + } + str_len = strlen(str_value); + + data->data = talloc_strndup(mem_ctx, str_value, str_len); + if (data->data == NULL) { + return ENOMEM; + } + data->length = str_len; + + return EOK; +} + +static errno_t json_array_to_krb5_data(TALLOC_CTX *mem_ctx, + json_t *array, + krb5_data **_data, + size_t *_len) +{ + errno_t ret; + int ok; + size_t len; + size_t idx; + json_t *element; + krb5_data *data; + + ok = json_is_array(array); + if (!ok) { + DEBUG(SSSDBG_CRIT_FAILURE, "Json object is not an array.\n"); + return ERR_JSON_DECODING; + } + + len = json_array_size(array); + if (len == 0) { + *_data = NULL; + *_len = 0; + return EOK; + } + + data = talloc_zero_array(mem_ctx, krb5_data, len); + if (data == NULL) { + return ENOMEM; + } + + json_array_foreach(array, idx, element) { + ret = json_element_to_krb5_data(data, element, &data[idx]); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot convert krb5 data element from JSON"); + talloc_free(data); + return ret; + } + } + + *_data = data; + *_len = len; + return EOK; +} + +static errno_t json_to_princ(TALLOC_CTX *mem_ctx, + json_t *js_princ, + krb5_principal *_princ) +{ + errno_t ret; + json_t *components = NULL; + int ok; + krb5_principal princ = NULL; + TALLOC_CTX *tmp_ctx = NULL; + char *realm_str; + size_t realm_size; + json_error_t error; + + ok = json_is_object(js_princ); + if (!ok) { + DEBUG(SSSDBG_CRIT_FAILURE, "Json principal is not an object.\n"); + ret = ERR_JSON_DECODING; + goto done; + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + princ = talloc_zero(tmp_ctx, struct krb5_principal_data); + if (princ == NULL) { + return ENOMEM; + } + princ->magic = KV5M_PRINCIPAL; + + /* FIXME - it might be cleaner to use the s% specifier here, but the libjansson + * version on RHEL-7 doesn't support that + */ + ret = json_unpack_ex(js_princ, + &error, + JSON_STRICT, + "{s:i, s:s, s:o}", + "type", &princ->type, + "realm", &realm_str, + "components", &components); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to unpack JSON princ structure on line %d: %s\n", + error.line, error.text); + ret = EINVAL; + goto done; + } + + realm_size = strlen(realm_str); + + princ->realm.data = talloc_strndup(mem_ctx, realm_str, realm_size); + if (princ->realm.data == NULL) { + return ENOMEM; + } + princ->realm.length = realm_size; + princ->realm.magic = 0; + + ret = json_array_to_krb5_data(princ, components, + &princ->data, + (size_t *) &princ->length); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot convert principal from JSON"); + ret = EINVAL; + goto done; + } + + *_princ = talloc_steal(mem_ctx, princ); + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t json_elem_to_cred(TALLOC_CTX *mem_ctx, + json_t *element, + struct kcm_cred **_crd) +{ + errno_t ret; + char *uuid_str; + json_error_t error; + uuid_t uuid; + struct sss_iobuf *cred_blob; + const char *base64_cred_blob; + struct kcm_cred *crd; + uint8_t *outbuf; + size_t outbuf_size; + TALLOC_CTX *tmp_ctx = NULL; + + ret = json_unpack_ex(element, + &error, + JSON_STRICT, + "{s:s, s:s}", + "uuid", &uuid_str, + "payload", &base64_cred_blob); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to unpack JSON cred structure on line %d: %s\n", + error.line, error.text); + return EINVAL; + } + + uuid_parse(uuid_str, uuid); + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + outbuf = sss_base64_decode(tmp_ctx, base64_cred_blob, &outbuf_size); + if (outbuf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot decode cred blob\n"); + ret = EIO; + goto done; + } + + cred_blob = sss_iobuf_init_readonly(tmp_ctx, outbuf, outbuf_size); + if (cred_blob == NULL) { + ret = ENOMEM; + goto done; + } + + crd = kcm_cred_new(tmp_ctx, uuid, cred_blob); + if (crd == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + *_crd = talloc_steal(mem_ctx, crd); +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t json_to_creds(struct kcm_ccache *cc, + json_t *jcreds) +{ + errno_t ret; + int ok; + size_t idx; + json_t *value; + struct kcm_cred *crd; + + ok = json_is_array(jcreds); + if (!ok) { + DEBUG(SSSDBG_CRIT_FAILURE, "Json creds object is not an array.\n"); + return ERR_JSON_DECODING; + } + + json_array_foreach(jcreds, idx, value) { + ret = json_elem_to_cred(cc, value, &crd); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot convert JSON cred element [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + ret = kcm_cc_store_creds(cc, crd); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot store creds in ccache [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + } + + return EOK; +} + +static errno_t sec_json_value_to_ccache(struct kcm_ccache *cc, + json_t *root) +{ + errno_t ret; + json_t *princ = NULL; + json_t *creds = NULL; + json_error_t error; + int version; + + ret = json_unpack_ex(root, + &error, + JSON_STRICT, + "{s:i, s:i, s:o, s:o}", + "version", &version, + "kdc_offset", &cc->kdc_offset, + "principal", &princ, + "creds", &creds); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to unpack JSON creds structure on line %d: %s\n", + error.line, error.text); + return EINVAL; + } + + if (version != KS_JSON_VERSION) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Expected version %d, received version %d\n", + KS_JSON_VERSION, version); + return EINVAL; + } + + ret = json_to_princ(cc, princ, &cc->client); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot store JSON to principal [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + ret = json_to_creds(cc, creds); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot store JSON to creds [%d]: %s\n", + ret, sss_strerror(ret)); + return EOK; + } + + return EOK; +} + +/* + * sec_key is a concatenation of the ccache's UUID and name + * sec_value is the JSON dump of the ccache contents + */ +errno_t sec_kv_to_ccache(TALLOC_CTX *mem_ctx, + const char *sec_key, + const char *sec_value, + struct cli_creds *client, + struct kcm_ccache **_cc) +{ + errno_t ret; + json_t *root = NULL; + struct kcm_ccache *cc = NULL; + TALLOC_CTX *tmp_ctx = NULL; + + ret = sec_value_to_json(sec_value, &root); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot store secret to JSN [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + cc = talloc_zero(tmp_ctx, struct kcm_ccache); + if (cc == NULL) { + ret = ENOMEM; + goto done; + } + + /* We rely on sssd-secrets only searching the user's subtree so we + * set the ownership to the client + */ + cc->owner.uid = cli_creds_get_uid(client); + cc->owner.gid = cli_creds_get_gid(client); + + ret = sec_key_parse(cc, sec_key, &cc->name, cc->uuid); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannt parse secret key [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sec_json_value_to_ccache(cc, root); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannt parse secret value [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + *_cc = talloc_steal(mem_ctx, cc); +done: + talloc_free(tmp_ctx); + json_decref(root); + return ret; +} diff --git a/src/responder/kcm/kcmsrv_ccache_secrets.c b/src/responder/kcm/kcmsrv_ccache_secrets.c new file mode 100644 index 0000000..8be7dae --- /dev/null +++ b/src/responder/kcm/kcmsrv_ccache_secrets.c @@ -0,0 +1,2169 @@ +/* + SSSD + + KCM Server - ccache storage in sssd-secrets + + Copyright (C) Red Hat, 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "config.h" + +#include +#include +#include + +#include "util/util.h" +#include "util/crypto/sss_crypto.h" +#include "util/tev_curl.h" +#include "responder/kcm/kcmsrv_ccache_pvt.h" +#include "responder/kcm/kcmsrv_ccache_be.h" + +#ifndef SSSD_SECRETS_SOCKET +#define SSSD_SECRETS_SOCKET VARDIR"/run/secrets.socket" +#endif /* SSSD_SECRETS_SOCKET */ + +#ifndef SEC_TIMEOUT +#define SEC_TIMEOUT 5 +#endif /* SEC_TIMEOUT */ + +/* Just to keep the name of the ccache readable */ +#define MAX_CC_NUM 99999 + +/* Compat definition of json_array_foreach for older systems */ +#ifndef json_array_foreach +#define json_array_foreach(array, idx, value) \ + for(idx = 0; \ + idx < json_array_size(array) && (value = json_array_get(array, idx)); \ + idx++) +#endif + +static const char *find_by_name(const char **sec_key_list, + const char *name) +{ + const char *sec_name = NULL; + + if (sec_key_list == NULL) { + return NULL; + } + + for (int i = 0; sec_key_list[i]; i++) { + if (sec_key_match_name(sec_key_list[i], name)) { + sec_name = sec_key_list[i]; + break; + } + } + + return sec_name; +} + +static const char *find_by_uuid(const char **sec_key_list, + uuid_t uuid) +{ + const char *sec_name = NULL; + + if (sec_key_list == NULL) { + return NULL; + } + + for (int i = 0; sec_key_list[i]; i++) { + if (sec_key_match_uuid(sec_key_list[i], uuid)) { + sec_name = sec_key_list[i]; + break; + } + } + + return sec_name; +} + +static const char *sec_headers[] = { + "Content-type: application/octet-stream", + NULL, +}; + +struct ccdb_sec { + struct tcurl_ctx *tctx; +}; + +static errno_t http2errno(int http_code) +{ + if (http_code != 200) { + DEBUG(SSSDBG_OP_FAILURE, "HTTP request returned %d\n", http_code); + } + + switch (http_code) { + case 200: + return EOK; + case 404: + return ERR_NO_CREDS; + case 400: + return ERR_INPUT_PARSE; + case 403: + return EACCES; + case 409: + return EEXIST; + case 413: + return E2BIG; + case 507: + return ENOSPC; + } + + return EIO; +} + +/* + * Helper request to list all UUID+name pairs + */ +struct sec_list_state { + const char **sec_key_list; + size_t sec_key_list_len; +}; + +static void sec_list_done(struct tevent_req *subreq); +static errno_t sec_list_parse(struct sss_iobuf *outbuf, + TALLOC_CTX *mem_ctx, + const char ***_list, + size_t *_list_len); + +static struct tevent_req *sec_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ccdb_sec *secdb, + struct cli_creds *client) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sec_list_state *state = NULL; + errno_t ret; + const char *container_url; + + req = tevent_req_create(mem_ctx, &state, struct sec_list_state); + if (req == NULL) { + return NULL; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Listing all ccaches in the secrets store\n"); + container_url = sec_container_url_create(state, client); + if (container_url == NULL) { + ret = ENOMEM; + goto immediate; + } + + subreq = tcurl_http_send(state, ev, secdb->tctx, + TCURL_HTTP_GET, + SSSD_SECRETS_SOCKET, + container_url, + sec_headers, + NULL, + SEC_TIMEOUT); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, sec_list_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void sec_list_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + struct sec_list_state *state = tevent_req_data(req, + struct sec_list_state); + struct sss_iobuf *outbuf; + int http_code; + + ret = tcurl_http_recv(state, subreq, &http_code, &outbuf); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "list HTTP request failed [%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (http_code == 404) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Nothing to list\n"); + /* If no ccaches are found, return an empty list */ + state->sec_key_list = talloc_zero_array(state, const char *, 1); + if (state->sec_key_list == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + } else if (http_code == 200) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Found %zu items\n", state->sec_key_list_len); + ret = sec_list_parse(outbuf, state, + &state->sec_key_list, + &state->sec_key_list_len); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + } else { + tevent_req_error(req, http2errno(http_code)); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "list done\n"); + tevent_req_done(req); +} + +static errno_t sec_list_parse(struct sss_iobuf *outbuf, + TALLOC_CTX *mem_ctx, + const char ***_list, + size_t *_list_len) +{ + json_t *root; + uint8_t *sec_http_list; + json_error_t error; + json_t *element; + errno_t ret; + int ok; + size_t idx; + const char **list = NULL; + size_t list_len; + + sec_http_list = sss_iobuf_get_data(outbuf); + if (sec_http_list == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No data in output buffer?\n"); + return EINVAL; + } + + root = json_loads((const char *) sec_http_list, 0, &error); + if (root == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to parse JSON payload on line %d: %s\n", + error.line, error.text); + return ERR_JSON_DECODING; + } + + ok = json_is_array(root); + if (!ok) { + DEBUG(SSSDBG_CRIT_FAILURE, "list reply is not an object.\n"); + ret = ERR_JSON_DECODING; + goto done; + } + + list_len = json_array_size(root); + list = talloc_zero_array(mem_ctx, const char *, list_len + 1); + if (list == NULL) { + ret = ENOMEM; + goto done; + } + + json_array_foreach(root, idx, element) { + list[idx] = talloc_strdup(list, json_string_value(element)); + if (list[idx] == NULL) { + ret = ENOMEM; + goto done; + } + } + + ret = EOK; + *_list = list; + *_list_len = list_len; +done: + if (ret != EOK) { + talloc_free(list); + } + json_decref(root); + return ret; +} + +static errno_t sec_list_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + const char ***_sec_key_list, + size_t *_sec_key_list_len) + +{ + struct sec_list_state *state = tevent_req_data(req, + struct sec_list_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_sec_key_list != NULL) { + *_sec_key_list = talloc_steal(mem_ctx, state->sec_key_list); + } + if (_sec_key_list_len != NULL) { + *_sec_key_list_len = state->sec_key_list_len; + } + return EOK; +} + +/* + * Helper request to get a ccache by key + */ +struct sec_get_state { + const char *sec_key; + struct cli_creds *client; + + struct kcm_ccache *cc; +}; + +static void sec_get_done(struct tevent_req *subreq); + +static struct tevent_req *sec_get_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ccdb_sec *secdb, + struct cli_creds *client, + const char *sec_key) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sec_get_state *state = NULL; + errno_t ret; + const char *cc_url; + + req = tevent_req_create(mem_ctx, &state, struct sec_get_state); + if (req == NULL) { + return NULL; + } + state->sec_key = sec_key; + state->client = client; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Retrieving ccache %s\n", sec_key); + + cc_url = sec_cc_url_create(state, state->client, state->sec_key); + if (cc_url == NULL) { + ret = ENOMEM; + goto immediate; + } + + subreq = tcurl_http_send(state, + ev, + secdb->tctx, + TCURL_HTTP_GET, + SSSD_SECRETS_SOCKET, + cc_url, + sec_headers, + NULL, + SEC_TIMEOUT); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, sec_get_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void sec_get_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + struct sec_get_state *state = tevent_req_data(req, + struct sec_get_state); + struct sss_iobuf *outbuf; + const char *sec_value; + int http_code; + + ret = tcurl_http_recv(state, subreq, &http_code, &outbuf); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "GET HTTP request failed [%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (http_code != 200) { + DEBUG(SSSDBG_OP_FAILURE, + "GET operation returned HTTP error %d\n", http_code); + ret = http2errno(http_code); + tevent_req_error(req, ret); + return; + } + + sec_value = (const char *) sss_iobuf_get_data(outbuf); + if (sec_value == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No data in output buffer\n"); + tevent_req_error(req, EINVAL); + return; + } + + ret = sec_kv_to_ccache(state, + state->sec_key, + sec_value, + state->client, + &state->cc); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot convert JSON keyval to ccache blob [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "GET done\n"); + tevent_req_done(req); +} + +static errno_t sec_get_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ccache **_cc) +{ + struct sec_get_state *state = tevent_req_data(req, struct sec_get_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + *_cc = talloc_steal(mem_ctx, state->cc); + return EOK; +} + +/* + * Helper request to get a ccache name or ID + */ +struct sec_get_ccache_state { + struct tevent_context *ev; + struct ccdb_sec *secdb; + struct cli_creds *client; + const char *name; + uuid_t uuid; + + const char *sec_key; + + struct kcm_ccache *cc; +}; + +static void sec_get_ccache_list_done(struct tevent_req *subreq); +static void sec_get_ccache_done(struct tevent_req *subreq); + +static struct tevent_req *sec_get_ccache_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ccdb_sec *secdb, + struct cli_creds *client, + const char *name, + uuid_t uuid) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sec_get_ccache_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sec_get_ccache_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->secdb = secdb; + state->client = client; + state->name = name; + uuid_copy(state->uuid, uuid); + + if ((name == NULL && uuid_is_null(uuid)) + || (name != NULL && !uuid_is_null(uuid))) { + DEBUG(SSSDBG_OP_FAILURE, "Expected one of name, uuid to be set\n"); + ret = EINVAL; + goto immediate; + } + + subreq = sec_list_send(state, ev, secdb, client); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, sec_get_ccache_list_done, req); + return req; + + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void sec_get_ccache_list_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + struct sec_get_ccache_state *state = tevent_req_data(req, + struct sec_get_ccache_state); + const char **sec_key_list; + + ret = sec_list_recv(subreq, state, &sec_key_list, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot list keys [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (state->name != NULL) { + state->sec_key = find_by_name(sec_key_list, state->name); + } else { + state->sec_key = find_by_uuid(sec_key_list, state->uuid); + } + + if (state->sec_key == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot find item in the ccache list\n"); + /* Don't error out, just return an empty list */ + tevent_req_done(req); + return; + } + + subreq = sec_get_send(state, + state->ev, + state->secdb, + state->client, + state->sec_key); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sec_get_ccache_done, req); +} + +static void sec_get_ccache_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sec_get_ccache_state *state = tevent_req_data(req, + struct sec_get_ccache_state); + + ret = sec_get_recv(subreq, state, &state->cc); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot resolve key to ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t sec_get_ccache_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ccache **_cc) +{ + struct sec_get_ccache_state *state = tevent_req_data(req, + struct sec_get_ccache_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + *_cc = talloc_steal(mem_ctx, state->cc); + return EOK; +} + +/* + * The actual sssd-secrets back end + */ +static errno_t ccdb_sec_init(struct kcm_ccdb *db) +{ + struct ccdb_sec *secdb = NULL; + + secdb = talloc_zero(db, struct ccdb_sec); + if (secdb == NULL) { + return ENOMEM; + } + + secdb->tctx = tcurl_init(secdb, db->ev); + if (secdb->tctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Cannot initialize tcurl\n"); + talloc_zfree(secdb); + return ENOMEM; + } + + /* We just need the random numbers to generate pseudo-random ccache names + * and avoid conflicts */ + srand(time(NULL)); + + db->db_handle = secdb; + return EOK; +} + +/* + * Helper request to get a ccache by key + */ +struct sec_patch_state { + struct tevent_context *ev; + struct ccdb_sec *secdb; + struct cli_creds *client; + + const char *sec_key_url; + struct sss_iobuf *sec_value; +}; + +static void sec_patch_del_done(struct tevent_req *subreq); +static void sec_patch_put_done(struct tevent_req *subreq); + +static struct tevent_req *sec_patch_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ccdb_sec *secdb, + struct cli_creds *client, + const char *sec_key_url, + struct sss_iobuf *sec_value) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sec_patch_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sec_patch_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->secdb = secdb; + state->client = client; + state->sec_key_url = sec_key_url; + state->sec_value = sec_value; + + subreq = tcurl_http_send(state, state->ev, + state->secdb->tctx, + TCURL_HTTP_DELETE, + SSSD_SECRETS_SOCKET, + state->sec_key_url, + sec_headers, + NULL, + SEC_TIMEOUT); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, sec_patch_del_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void sec_patch_del_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sec_patch_state *state = tevent_req_data(req, + struct sec_patch_state); + int http_code; + + ret = tcurl_http_recv(state, subreq, &http_code, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot delete key [%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (http_code == 404) { + DEBUG(SSSDBG_TRACE_LIBS, + "Key %s does not exist, moving on\n", state->sec_key_url); + } else if (http_code != 200) { + ret = http2errno(http_code); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Adding new payload\n"); + + subreq = tcurl_http_send(state, + state->ev, + state->secdb->tctx, + TCURL_HTTP_PUT, + SSSD_SECRETS_SOCKET, + state->sec_key_url, + sec_headers, + state->sec_value, + SEC_TIMEOUT); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sec_patch_put_done, req); +} + +static void sec_patch_put_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sec_patch_state *state = tevent_req_data(req, + struct sec_patch_state); + int http_code; + + ret = tcurl_http_recv(state, subreq, &http_code, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot put new value [%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (http_code != 200) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add the payload\n"); + ret = http2errno(http_code); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "payload created\n"); + tevent_req_done(req); +} + + +static errno_t sec_patch_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +/* The operations between the KCM and sssd-secrets */ + +struct ccdb_sec_nextid_state { + struct tevent_context *ev; + struct ccdb_sec *secdb; + struct cli_creds *client; + + unsigned int nextid; + char *nextid_name; + + int maxtries; + int numtry; +}; + +static errno_t ccdb_sec_nextid_generate(struct tevent_req *req); +static void ccdb_sec_nextid_list_done(struct tevent_req *subreq); + +/* Generate a unique ID */ +/* GET the name from secrets, if doesn't exist, OK, if exists, try again */ +static struct tevent_req *ccdb_sec_nextid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client) +{ + struct tevent_req *req = NULL; + struct ccdb_sec_nextid_state *state = NULL; + struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec); + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_sec_nextid_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->secdb = secdb; + state->client = client; + + state->maxtries = 3; + state->numtry = 0; + + ret = ccdb_sec_nextid_generate(req); + if (ret != EOK) { + goto immediate; + } + + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_sec_nextid_generate(struct tevent_req *req) +{ + struct tevent_req *subreq = NULL; + struct ccdb_sec_nextid_state *state = tevent_req_data(req, + struct ccdb_sec_nextid_state); + + if (state->numtry >= state->maxtries) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to find a random ccache in %d tries\n", state->numtry); + return EBUSY; + } + + state->nextid = rand() % MAX_CC_NUM; + state->nextid_name = talloc_asprintf(state, "%"SPRIuid":%u", + cli_creds_get_uid(state->client), + state->nextid); + if (state->nextid_name == NULL) { + return ENOMEM; + } + + subreq = sec_list_send(state, state->ev, state->secdb, state->client); + if (subreq == NULL) { + return ENOMEM; + } + tevent_req_set_callback(subreq, ccdb_sec_nextid_list_done, req); + + state->numtry++; + return EOK; +} + +static void ccdb_sec_nextid_list_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ccdb_sec_nextid_state *state = tevent_req_data(req, + struct ccdb_sec_nextid_state); + const char **sec_key_list; + size_t sec_key_list_len; + size_t i; + + ret = sec_list_recv(subreq, state, &sec_key_list, &sec_key_list_len); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot list keys [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + for (i = 0; i < sec_key_list_len; i++) { + if (sec_key_match_name(sec_key_list[i], state->nextid_name) == true) { + break; + } + } + + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to find a random key, trying again..\n"); + if (i < sec_key_list_len) { + /* Try again */ + ret = ccdb_sec_nextid_generate(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Generated new ccache name %u\n", state->nextid); + tevent_req_done(req); +} + +static errno_t ccdb_sec_nextid_recv(struct tevent_req *req, + unsigned int *_nextid) +{ + struct ccdb_sec_nextid_state *state = tevent_req_data(req, + struct ccdb_sec_nextid_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + *_nextid = state->nextid; + return EOK; +} + +/* IN: HTTP PUT $base/default -d 'uuid' */ +/* We chose only UUID here to avoid issues later with renaming */ +struct ccdb_sec_set_default_state { +}; + +static void ccdb_sec_set_default_done(struct tevent_req *subreq); + +static struct tevent_req *ccdb_sec_set_default_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct ccdb_sec_set_default_state *state = NULL; + struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec); + struct sss_iobuf *uuid_iobuf; + errno_t ret; + const char *url; + char uuid_str[UUID_STR_SIZE]; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_sec_set_default_state); + if (req == NULL) { + return NULL; + } + + uuid_unparse(uuid, uuid_str); + DEBUG(SSSDBG_TRACE_INTERNAL, + "Setting the default ccache to %s\n", uuid_str); + + url = sec_dfl_url_create(state, client); + if (url == NULL) { + ret = ENOMEM; + goto immediate; + } + + uuid_iobuf = sss_iobuf_init_readonly(state, + (uint8_t *) uuid_str, + UUID_STR_SIZE); + if (uuid_iobuf == NULL) { + ret = ENOMEM; + goto immediate; + } + + subreq = sec_patch_send(state, ev, secdb, client, url, uuid_iobuf); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, ccdb_sec_set_default_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void ccdb_sec_set_default_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = sec_patch_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sec_patch request failed [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Set the default ccache\n"); + tevent_req_done(req); +} + +static errno_t ccdb_sec_set_default_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +/* IN: HTTP GET $base/default */ +/* OUT: uuid */ +struct ccdb_sec_get_default_state { + uuid_t uuid; +}; + +static void ccdb_sec_get_default_done(struct tevent_req *subreq); + +static struct tevent_req *ccdb_sec_get_default_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct ccdb_sec_get_default_state *state = NULL; + struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec); + const char *url; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_sec_get_default_state); + if (req == NULL) { + return NULL; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Getting the default ccache\n"); + url = sec_dfl_url_create(state, client); + if (url == NULL) { + ret = ENOMEM; + goto immediate; + } + + subreq = tcurl_http_send(state, ev, secdb->tctx, + TCURL_HTTP_GET, + SSSD_SECRETS_SOCKET, + url, + sec_headers, + NULL, + SEC_TIMEOUT); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, ccdb_sec_get_default_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void ccdb_sec_get_default_done(struct tevent_req *subreq) +{ + errno_t ret; + int http_code; + struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + struct ccdb_sec_get_default_state *state = tevent_req_data(req, + struct ccdb_sec_get_default_state); + struct sss_iobuf *outbuf; + size_t uuid_size; + + ret = tcurl_http_recv(state, subreq, &http_code, &outbuf); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Communication with the secrets responder failed [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (http_code == 404) { + /* Return a NULL uuid */ + uuid_clear(state->uuid); + tevent_req_done(req); + return; + } else if (http_code != 200) { + ret = http2errno(http_code); + tevent_req_error(req, ret); + return; + } + + uuid_size = sss_iobuf_get_len(outbuf); + if (uuid_size != UUID_STR_SIZE) { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected UUID size %zu\n", uuid_size); + tevent_req_error(req, EIO); + return; + } + + uuid_parse((const char *) sss_iobuf_get_data(outbuf), state->uuid); + DEBUG(SSSDBG_TRACE_INTERNAL, "Got the default ccache\n"); + tevent_req_done(req); +} + +static errno_t ccdb_sec_get_default_recv(struct tevent_req *req, + uuid_t uuid) +{ + struct ccdb_sec_get_default_state *state = tevent_req_data(req, + struct ccdb_sec_get_default_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + uuid_copy(uuid, state->uuid); + return EOK; +} + +/* HTTP GET $base/ccache/ */ +/* OUT: a list of */ +struct ccdb_sec_list_state { + uuid_t *uuid_list; +}; + +static void ccdb_sec_list_done(struct tevent_req *subreq); + +static struct tevent_req *ccdb_sec_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct ccdb_sec_list_state *state = NULL; + struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec); + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_sec_list_state); + if (req == NULL) { + return NULL; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Listing all ccaches\n"); + + subreq = sec_list_send(state, ev, secdb, client); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, ccdb_sec_list_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void ccdb_sec_list_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ccdb_sec_list_state *state = tevent_req_data(req, + struct ccdb_sec_list_state); + const char **sec_key_list; + size_t sec_key_list_len; + + ret = sec_list_recv(subreq, state, &sec_key_list, &sec_key_list_len); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Communication with the secrets responder failed [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Found %zu ccaches\n", sec_key_list_len); + + state->uuid_list = talloc_array(state, uuid_t, sec_key_list_len + 1); + if (state->uuid_list == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + for (size_t i = 0; i < sec_key_list_len; i++) { + ret = sec_key_get_uuid(sec_key_list[i], + state->uuid_list[i]); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + } + /* Sentinel */ + uuid_clear(state->uuid_list[sec_key_list_len]); + + DEBUG(SSSDBG_TRACE_INTERNAL, "Listing all caches done\n"); + tevent_req_done(req); +} + +static errno_t ccdb_sec_list_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uuid_t **_uuid_list) +{ + struct ccdb_sec_list_state *state = tevent_req_data(req, + struct ccdb_sec_list_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + *_uuid_list = talloc_steal(mem_ctx, state->uuid_list); + return EOK; +} + +struct ccdb_sec_getbyuuid_state { + struct kcm_ccache *cc; +}; + + +/* HTTP GET $base/ccache/ */ +/* OUT: a list of */ +/* for each item in list, compare with the uuid: portion */ +/* HTTP GET $base/ccache/uuid:name */ +/* return result */ +static void ccdb_sec_getbyuuid_done(struct tevent_req *subreq); + +static struct tevent_req *ccdb_sec_getbyuuid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + errno_t ret; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct ccdb_sec_getbyuuid_state *state = NULL; + struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec); + + req = tevent_req_create(mem_ctx, &state, struct ccdb_sec_getbyuuid_state); + if (req == NULL) { + return NULL; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Getting ccache by UUID\n"); + + subreq = sec_get_ccache_send(state, ev, secdb, client, NULL, uuid); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, ccdb_sec_getbyuuid_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void ccdb_sec_getbyuuid_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ccdb_sec_getbyuuid_state *state = tevent_req_data(req, + struct ccdb_sec_getbyuuid_state); + + ret = sec_get_ccache_recv(subreq, state, &state->cc); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot retrieve the ccache [%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Got ccache by UUID\n"); + tevent_req_done(req); +} + +static errno_t ccdb_sec_getbyuuid_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ccache **_cc) +{ + struct ccdb_sec_getbyuuid_state *state = tevent_req_data(req, + struct ccdb_sec_getbyuuid_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + *_cc = talloc_steal(mem_ctx, state->cc); + return EOK; +} + +/* HTTP GET $base/ccache/ */ +/* OUT: a list of */ +/* for each item in list, compare with the :name portion */ +/* HTTP GET $base/ccache/uuid:name */ +/* return result */ +struct ccdb_sec_getbyname_state { + struct kcm_ccache *cc; +}; + +static void ccdb_sec_getbyname_done(struct tevent_req *subreq); + +static struct tevent_req *ccdb_sec_getbyname_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + const char *name) +{ + errno_t ret; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct ccdb_sec_getbyname_state *state = NULL; + struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec); + uuid_t null_uuid; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_sec_getbyname_state); + if (req == NULL) { + return NULL; + } + uuid_clear(null_uuid); + + DEBUG(SSSDBG_TRACE_INTERNAL, "Getting ccache by name\n"); + + subreq = sec_get_ccache_send(state, ev, secdb, client, name, null_uuid); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, ccdb_sec_getbyname_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void ccdb_sec_getbyname_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ccdb_sec_getbyname_state *state = tevent_req_data(req, + struct ccdb_sec_getbyname_state); + + ret = sec_get_ccache_recv(subreq, state, &state->cc); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot retrieve the ccache [%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Got ccache by UUID\n"); + tevent_req_done(req); +} + +static errno_t ccdb_sec_getbyname_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ccache **_cc) +{ + struct ccdb_sec_getbyname_state *state = tevent_req_data(req, + struct ccdb_sec_getbyname_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + *_cc = talloc_steal(mem_ctx, state->cc); + return EOK; +} + +struct ccdb_sec_name_by_uuid_state { + struct tevent_context *ev; + struct ccdb_sec *secdb; + struct cli_creds *client; + + uuid_t uuid; + + const char *name; +}; + +static void ccdb_sec_name_by_uuid_done(struct tevent_req *subreq); + +struct tevent_req *ccdb_sec_name_by_uuid_send(TALLOC_CTX *sec_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct ccdb_sec_name_by_uuid_state *state = NULL; + struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec); + errno_t ret; + + req = tevent_req_create(sec_ctx, &state, struct ccdb_sec_name_by_uuid_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->secdb = secdb; + state->client = client; + uuid_copy(state->uuid, uuid); + + DEBUG(SSSDBG_TRACE_INTERNAL, "Translating UUID to name\n"); + + subreq = sec_list_send(state, state->ev, state->secdb, state->client); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, ccdb_sec_name_by_uuid_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void ccdb_sec_name_by_uuid_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ccdb_sec_name_by_uuid_state *state = tevent_req_data(req, + struct ccdb_sec_name_by_uuid_state); + const char **sec_key_list; + const char *name; + size_t sec_key_list_len; + size_t i; + + ret = sec_list_recv(subreq, state, &sec_key_list, &sec_key_list_len); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + for (i = 0; i < sec_key_list_len; i++) { + if (sec_key_match_uuid(sec_key_list[i], state->uuid) == true) { + /* Match, copy name */ + name = sec_key_get_name(sec_key_list[i]); + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Malformed key, cannot get name\n"); + tevent_req_error(req, EINVAL); + return; + } + + state->name = talloc_strdup(state, name); + if (state->name == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Got ccache by UUID\n"); + tevent_req_done(req); + return; + } + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "No such UUID\n"); + tevent_req_error(req, ERR_NO_CREDS); + return; +} + +errno_t ccdb_sec_name_by_uuid_recv(struct tevent_req *req, + TALLOC_CTX *sec_ctx, + const char **_name) +{ + struct ccdb_sec_name_by_uuid_state *state = tevent_req_data(req, + struct ccdb_sec_name_by_uuid_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + *_name = talloc_steal(sec_ctx, state->name); + return EOK; +} + +struct ccdb_sec_uuid_by_name_state { + struct tevent_context *ev; + struct ccdb_sec *secdb; + struct cli_creds *client; + + const char *name; + + uuid_t uuid; +}; + +static void ccdb_sec_uuid_by_name_done(struct tevent_req *subreq); + +struct tevent_req *ccdb_sec_uuid_by_name_send(TALLOC_CTX *sec_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + const char *name) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct ccdb_sec_uuid_by_name_state *state = NULL; + struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec); + errno_t ret; + + req = tevent_req_create(sec_ctx, &state, struct ccdb_sec_uuid_by_name_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->secdb = secdb; + state->client = client; + state->name = name; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Translating name to UUID\n"); + + subreq = sec_list_send(state, state->ev, state->secdb, state->client); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, ccdb_sec_uuid_by_name_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void ccdb_sec_uuid_by_name_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ccdb_sec_uuid_by_name_state *state = tevent_req_data(req, + struct ccdb_sec_uuid_by_name_state); + const char **sec_key_list; + size_t sec_key_list_len; + size_t i; + + ret = sec_list_recv(subreq, state, &sec_key_list, &sec_key_list_len); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + for (i = 0; i < sec_key_list_len; i++) { + if (sec_key_match_name(sec_key_list[i], state->name) == true) { + /* Match, copy UUID */ + ret = sec_key_get_uuid(sec_key_list[i], state->uuid); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Malformed key, cannot get UUID\n"); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Got ccache by name\n"); + tevent_req_done(req); + return; + } + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "No such name\n"); + tevent_req_error(req, ERR_NO_CREDS); + return; +} + +errno_t ccdb_sec_uuid_by_name_recv(struct tevent_req *req, + TALLOC_CTX *sec_ctx, + uuid_t _uuid) +{ + struct ccdb_sec_uuid_by_name_state *state = tevent_req_data(req, + struct ccdb_sec_uuid_by_name_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + uuid_copy(_uuid, state->uuid); + return EOK; +} + +/* HTTP POST $base to create the container */ +/* HTTP PUT $base to create the container. Since PUT errors out on duplicates, at least + * we fail consistently here and don't overwrite the ccache on concurrent requests + */ +struct ccdb_sec_create_state { + struct tevent_context *ev; + struct ccdb_sec *secdb; + + const char *key_url; + struct sss_iobuf *ccache_payload; +}; + +static void ccdb_sec_container_done(struct tevent_req *subreq); +static void ccdb_sec_ccache_done(struct tevent_req *subreq); + +static struct tevent_req *ccdb_sec_create_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + struct kcm_ccache *cc) +{ + struct tevent_req *subreq = NULL; + struct tevent_req *req = NULL; + struct ccdb_sec_create_state *state = NULL; + struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec); + errno_t ret; + const char *container_url; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_sec_create_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->secdb = secdb; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Creating ccache storage for %s\n", cc->name); + + /* Do the encoding asap so that if we fail, we don't even attempt any + * writes */ + ret = kcm_ccache_to_sec_input(state, cc, client, &state->key_url, &state->ccache_payload); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot convert cache %s to JSON [%d]: %s\n", + cc->name, ret, sss_strerror(ret)); + goto immediate; + } + + container_url = sec_container_url_create(state, client); + if (container_url == NULL) { + ret = ENOMEM; + goto immediate; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Creating the ccache container\n"); + subreq = tcurl_http_send(state, ev, secdb->tctx, + TCURL_HTTP_POST, + SSSD_SECRETS_SOCKET, + container_url, + sec_headers, + NULL, + SEC_TIMEOUT); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, ccdb_sec_container_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void ccdb_sec_container_done(struct tevent_req *subreq) +{ + errno_t ret; + int http_code; + struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + struct ccdb_sec_create_state *state = tevent_req_data(req, + struct ccdb_sec_create_state); + + ret = tcurl_http_recv(state, subreq, &http_code, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Communication with the secrets responder failed [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + /* Conflict is not an error as multiple ccaches are under the same + * container */ + if (http_code == 409) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Container already exists, ignoring\n"); + } else if (http_code != 200) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create the ccache container\n"); + ret = http2errno(http_code); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "ccache container created\n"); + DEBUG(SSSDBG_TRACE_INTERNAL, "creating empty ccache payload\n"); + + subreq = tcurl_http_send(state, + state->ev, + state->secdb->tctx, + TCURL_HTTP_PUT, + SSSD_SECRETS_SOCKET, + state->key_url, + sec_headers, + state->ccache_payload, + SEC_TIMEOUT); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, ccdb_sec_ccache_done, req); +} + +static void ccdb_sec_ccache_done(struct tevent_req *subreq) +{ + errno_t ret; + int http_code; + struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + struct ccdb_sec_create_state *state = tevent_req_data(req, + struct ccdb_sec_create_state); + + ret = tcurl_http_recv(state, subreq, &http_code, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Communication with the secrets responder failed [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (http_code != 200) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add the payload\n"); + ret = http2errno(http_code); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "payload created\n"); + tevent_req_done(req); +} + +static errno_t ccdb_sec_create_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct ccdb_sec_mod_cred_state { + struct tevent_context *ev; + struct kcm_ccdb *db; + struct cli_creds *client; + struct kcm_mod_ctx *mod_cc; + + struct ccdb_sec *secdb; +}; + +static void ccdb_sec_mod_cred_get_done(struct tevent_req *subreq); +static void ccdb_sec_mod_cred_patch_done(struct tevent_req *subreq); + +static struct tevent_req *ccdb_sec_mod_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid, + struct kcm_mod_ctx *mod_cc) +{ + errno_t ret; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct ccdb_sec_mod_cred_state *state = NULL; + struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec); + + req = tevent_req_create(mem_ctx, &state, struct ccdb_sec_mod_cred_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->db =db; + state->client = client; + state->secdb = secdb; + state->mod_cc = mod_cc; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Modifying ccache\n"); + + subreq = sec_get_ccache_send(state, ev, secdb, client, NULL, uuid); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, *ccdb_sec_mod_cred_get_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void ccdb_sec_mod_cred_get_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ccdb_sec_mod_cred_state *state = tevent_req_data(req, + struct ccdb_sec_mod_cred_state); + struct kcm_ccache *cc; + const char *url; + struct sss_iobuf *payload; + + ret = sec_get_ccache_recv(subreq, state, &cc); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot retrieve the ccache [%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (cc == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "No such ccache\n"); + tevent_req_error(req, ERR_NO_CREDS); + return; + } + + kcm_mod_cc(cc, state->mod_cc); + + ret = kcm_ccache_to_sec_input(state, cc, state->client, &url, &payload); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to marshall modified ccache to payload [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + subreq = sec_patch_send(state, + state->ev, + state->secdb, + state->client, + url, + payload); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, ccdb_sec_mod_cred_patch_done, req); +} + +static void ccdb_sec_mod_cred_patch_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + + ret = sec_patch_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sec_patch request failed [%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "ccache modified\n"); + tevent_req_done(req); +} + +static errno_t ccdb_sec_mod_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct ccdb_sec_store_cred_state { + struct tevent_context *ev; + struct kcm_ccdb *db; + struct cli_creds *client; + struct sss_iobuf *cred_blob; + + struct ccdb_sec *secdb; +}; + +static void ccdb_sec_store_cred_get_done(struct tevent_req *subreq); +static void ccdb_sec_store_cred_patch_done(struct tevent_req *subreq); + +/* HTTP DEL/PUT $base/ccache/uuid:name */ +static struct tevent_req *ccdb_sec_store_cred_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid, + struct sss_iobuf *cred_blob) +{ + errno_t ret; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct ccdb_sec_store_cred_state *state = NULL; + struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec); + + req = tevent_req_create(mem_ctx, &state, struct ccdb_sec_store_cred_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->db =db; + state->client = client; + state->cred_blob = cred_blob; + state->secdb = secdb; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Storing creds in ccache\n"); + + subreq = sec_get_ccache_send(state, ev, secdb, client, NULL, uuid); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, *ccdb_sec_store_cred_get_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void ccdb_sec_store_cred_get_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ccdb_sec_store_cred_state *state = tevent_req_data(req, + struct ccdb_sec_store_cred_state); + struct kcm_ccache *cc; + const char *url; + struct sss_iobuf *payload; + + ret = sec_get_ccache_recv(subreq, state, &cc); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = kcm_cc_store_cred_blob(cc, state->cred_blob); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot store credentials to ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + ret = kcm_ccache_to_sec_input(state, cc, state->client, &url, &payload); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to marshall modified ccache to payload [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + subreq = sec_patch_send(state, + state->ev, + state->secdb, + state->client, + url, + payload); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, ccdb_sec_store_cred_patch_done, req); +} + +static void ccdb_sec_store_cred_patch_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + + ret = sec_patch_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sec_patch request failed [%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "ccache creds stored\n"); + tevent_req_done(req); +} + +static errno_t ccdb_sec_store_cred_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +/* HTTP DELETE $base/ccache/uuid:name */ +struct ccdb_sec_delete_state { + struct tevent_context *ev; + struct ccdb_sec *secdb; + struct cli_creds *client; + uuid_t uuid; + + size_t sec_key_list_len; +}; + +static void ccdb_sec_delete_list_done(struct tevent_req *subreq); +static void ccdb_sec_delete_cc_done(struct tevent_req *subreq); +static void ccdb_sec_delete_container_done(struct tevent_req *subreq); + +static struct tevent_req *ccdb_sec_delete_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + errno_t ret; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct ccdb_sec_delete_state *state = NULL; + struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec); + + req = tevent_req_create(mem_ctx, &state, struct ccdb_sec_delete_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->secdb = secdb; + state->client = client; + uuid_copy(state->uuid, uuid); + + DEBUG(SSSDBG_TRACE_INTERNAL, "Deleting ccache\n"); + + subreq = sec_list_send(state, ev, secdb, client); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, ccdb_sec_delete_list_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void ccdb_sec_delete_list_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ccdb_sec_delete_state *state = tevent_req_data(req, + struct ccdb_sec_delete_state); + const char **sec_key_list; + const char *sec_key; + const char *cc_url; + + ret = sec_list_recv(subreq, + state, + &sec_key_list, + &state->sec_key_list_len); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (sec_key_list == 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "No ccaches to delete\n"); + tevent_req_done(req); + return; + } + + sec_key = find_by_uuid(sec_key_list, state->uuid); + if (sec_key == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot find ccache by UUID\n"); + tevent_req_done(req); + return; + } + + cc_url = sec_cc_url_create(state, state->client, sec_key); + if (cc_url == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + subreq = tcurl_http_send(state, state->ev, + state->secdb->tctx, + TCURL_HTTP_DELETE, + SSSD_SECRETS_SOCKET, + cc_url, + sec_headers, + NULL, + SEC_TIMEOUT); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, ccdb_sec_delete_cc_done, req); +} + +static void ccdb_sec_delete_cc_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ccdb_sec_delete_state *state = tevent_req_data(req, + struct ccdb_sec_delete_state); + int http_code; + const char *container_url; + + ret = tcurl_http_recv(state, subreq, &http_code, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot delete ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (http_code != 200) { + ret = http2errno(http_code); + tevent_req_error(req, ret); + return; + } + + if (state->sec_key_list_len != 1) { + DEBUG(SSSDBG_TRACE_INTERNAL, "There are other ccaches, done\n"); + tevent_req_done(req); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Removing ccache container\n"); + + container_url = sec_container_url_create(state, state->client); + if (container_url == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + subreq = tcurl_http_send(state, state->ev, + state->secdb->tctx, + TCURL_HTTP_DELETE, + SSSD_SECRETS_SOCKET, + container_url, + sec_headers, + NULL, + SEC_TIMEOUT); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, ccdb_sec_delete_container_done, req); +} + +static void ccdb_sec_delete_container_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ccdb_sec_delete_state *state = tevent_req_data(req, + struct ccdb_sec_delete_state); + int http_code; + + ret = tcurl_http_recv(state, subreq, &http_code, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot delete ccache container [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (http_code != 200) { + ret = http2errno(http_code); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Removed ccache container\n"); + tevent_req_done(req); +} + +static errno_t ccdb_sec_delete_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +const struct kcm_ccdb_ops ccdb_sec_ops = { + .init = ccdb_sec_init, + + .nextid_send = ccdb_sec_nextid_send, + .nextid_recv = ccdb_sec_nextid_recv, + + .set_default_send = ccdb_sec_set_default_send, + .set_default_recv = ccdb_sec_set_default_recv, + + .get_default_send = ccdb_sec_get_default_send, + .get_default_recv = ccdb_sec_get_default_recv, + + .list_send = ccdb_sec_list_send, + .list_recv = ccdb_sec_list_recv, + + .getbyname_send = ccdb_sec_getbyname_send, + .getbyname_recv = ccdb_sec_getbyname_recv, + + .getbyuuid_send = ccdb_sec_getbyuuid_send, + .getbyuuid_recv = ccdb_sec_getbyuuid_recv, + + .name_by_uuid_send = ccdb_sec_name_by_uuid_send, + .name_by_uuid_recv = ccdb_sec_name_by_uuid_recv, + + .uuid_by_name_send = ccdb_sec_uuid_by_name_send, + .uuid_by_name_recv = ccdb_sec_uuid_by_name_recv, + + .create_send = ccdb_sec_create_send, + .create_recv = ccdb_sec_create_recv, + + .mod_send = ccdb_sec_mod_send, + .mod_recv = ccdb_sec_mod_recv, + + .store_cred_send = ccdb_sec_store_cred_send, + .store_cred_recv = ccdb_sec_store_cred_recv, + + .delete_send = ccdb_sec_delete_send, + .delete_recv = ccdb_sec_delete_recv, +}; diff --git a/src/tests/cmocka/test_kcm_json_marshalling.c b/src/tests/cmocka/test_kcm_json_marshalling.c new file mode 100644 index 0000000..8eff2f5 --- /dev/null +++ b/src/tests/cmocka/test_kcm_json_marshalling.c @@ -0,0 +1,234 @@ +/* + Copyright (C) 2017 Red Hat + + SSSD tests: Test KCM JSON marshalling + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "config.h" + +#include +#include + +#include "util/util_creds.h" +#include "responder/kcm/kcmsrv_ccache.h" +#include "responder/kcm/kcmsrv_ccache_be.h" +#include "tests/cmocka/common_mock.h" + +#define TEST_REALM "TESTREALM" +#define TEST_PRINC_COMPONENT "PRINC_NAME" + +#define TEST_CREDS "TESTCREDS" + +const struct kcm_ccdb_ops ccdb_mem_ops; +const struct kcm_ccdb_ops ccdb_sec_ops; + +struct kcm_marshalling_test_ctx { + krb5_context kctx; + krb5_principal princ; +}; + +static int setup_kcm_marshalling(void **state) +{ + struct kcm_marshalling_test_ctx *test_ctx; + krb5_error_code kerr; + + test_ctx = talloc_zero(NULL, struct kcm_marshalling_test_ctx); + assert_non_null(test_ctx); + + kerr = krb5_init_context(&test_ctx->kctx); + assert_int_equal(kerr, 0); + + kerr = krb5_build_principal(test_ctx->kctx, + &test_ctx->princ, + sizeof(TEST_REALM)-1, TEST_REALM, + TEST_PRINC_COMPONENT, NULL); + assert_int_equal(kerr, 0); + + *state = test_ctx; + return 0; +} + +static int teardown_kcm_marshalling(void **state) +{ + struct kcm_marshalling_test_ctx *test_ctx = talloc_get_type(*state, + struct kcm_marshalling_test_ctx); + assert_non_null(test_ctx); + + krb5_free_principal(test_ctx->kctx, test_ctx->princ); + krb5_free_context(test_ctx->kctx); + talloc_free(test_ctx); + return 0; +} + +static void assert_cc_name_equal(struct kcm_ccache *cc1, + struct kcm_ccache *cc2) +{ + const char *name1, *name2; + + name1 = kcm_cc_get_name(cc1); + name2 = kcm_cc_get_name(cc2); + assert_string_equal(name1, name2); +} + +static void assert_cc_uuid_equal(struct kcm_ccache *cc1, + struct kcm_ccache *cc2) +{ + uuid_t u1, u2; + errno_t ret; + + ret = kcm_cc_get_uuid(cc1, u1); + assert_int_equal(ret, EOK); + ret = kcm_cc_get_uuid(cc2, u2); + assert_int_equal(ret, EOK); + ret = uuid_compare(u1, u2); + assert_int_equal(ret, 0); +} + +static void assert_cc_princ_equal(struct kcm_ccache *cc1, + struct kcm_ccache *cc2) +{ + krb5_principal p1; + krb5_principal p2; + char *name1; + char *name2; + krb5_error_code kerr; + + p1 = kcm_cc_get_client_principal(cc1); + p2 = kcm_cc_get_client_principal(cc1); + + kerr = krb5_unparse_name(NULL, p1, &name1); + assert_int_equal(kerr, 0); + kerr = krb5_unparse_name(NULL, p2, &name2); + assert_int_equal(kerr, 0); + + assert_string_equal(name1, name2); + krb5_free_unparsed_name(NULL, name1); + krb5_free_unparsed_name(NULL, name2); +} + +static void assert_cc_offset_equal(struct kcm_ccache *cc1, + struct kcm_ccache *cc2) +{ + int32_t off1; + int32_t off2; + + off1 = kcm_cc_get_offset(cc1); + off2 = kcm_cc_get_offset(cc2); + assert_int_equal(off1, off2); +} + +static void assert_cc_equal(struct kcm_ccache *cc1, + struct kcm_ccache *cc2) +{ + assert_cc_name_equal(cc1, cc2); + assert_cc_uuid_equal(cc1, cc2); + assert_cc_princ_equal(cc1, cc2); + assert_cc_offset_equal(cc1, cc2); +} + +static void test_kcm_ccache_marshall_unmarshall(void **state) +{ + struct kcm_marshalling_test_ctx *test_ctx = talloc_get_type(*state, + struct kcm_marshalling_test_ctx); + errno_t ret; + struct cli_creds owner; + struct kcm_ccache *cc; + struct kcm_ccache *cc2; + const char *url; + struct sss_iobuf *payload; + const char *name; + const char *key; + uint8_t *data; + + owner.ucred.uid = getuid(); + owner.ucred.gid = getuid(); + + name = talloc_asprintf(test_ctx, "%"SPRIuid, getuid()); + assert_non_null(name); + + ret = kcm_cc_new(test_ctx, + test_ctx->kctx, + &owner, + name, + test_ctx->princ, + &cc); + assert_int_equal(ret, EOK); + + ret = kcm_ccache_to_sec_input(test_ctx, + cc, + &owner, + &url, + &payload); + assert_int_equal(ret, EOK); + + key = strrchr(url, '/') + 1; + assert_non_null(key); + + data = sss_iobuf_get_data(payload); + assert_non_null(data); + + ret = sec_kv_to_ccache(test_ctx, + key, + (const char *) data, + &owner, + &cc2); + assert_int_equal(ret, EOK); + + assert_cc_equal(cc, cc2); +} + +int main(int argc, const char *argv[]) +{ + poptContext pc; + int opt; + int rv; + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_DEBUG_OPTS + POPT_TABLEEND + }; + + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(test_kcm_ccache_marshall_unmarshall, + setup_kcm_marshalling, + teardown_kcm_marshalling), + }; + + /* Set debug level to invalid value so we can deside if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + poptFreeContext(pc); + + DEBUG_CLI_INIT(debug_level); + + /* Even though normally the tests should clean up after themselves + * they might not after a failed run. Remove the old db to be sure */ + tests_set_cwd(); + + rv = cmocka_run_group_tests(tests, NULL, NULL); + + return rv; +} diff --git a/src/tests/intg/test_kcm.py b/src/tests/intg/test_kcm.py index ad1e492..11f80a1 100644 --- a/src/tests/intg/test_kcm.py +++ b/src/tests/intg/test_kcm.py @@ -27,7 +27,8 @@ import signal import kdc import krb5utils import config -from util import unindent, run_shell +from util import unindent +from test_secrets import create_sssd_secrets_fixture class KcmTestEnv(object): def __init__(self, k5kdc, k5util): @@ -107,15 +108,8 @@ def create_sssd_kcm_fixture(sock_path, request): return kcm_pid -@pytest.fixture -def setup_for_kcm(request, kdc_instance): - """ - Just set up the local provider for tests and enable the KCM - responder - """ - kcm_path = os.path.join(config.RUNSTATEDIR, "kcm.socket") - - sssd_conf = unindent("""\ +def create_sssd_conf(kcm_path, ccache_storage): + return unindent("""\ [sssd] domains = local services = nss @@ -125,8 +119,11 @@ def setup_for_kcm(request, kdc_instance): [kcm] socket_path = {kcm_path} + ccache_storage = {ccache_storage} """).format(**locals()) + +def common_setup_for_kcm_mem(request, kdc_instance, kcm_path, sssd_conf): kcm_socket_include = unindent(""" [libdefaults] default_ccache_name = KCM: @@ -142,11 +139,35 @@ def setup_for_kcm(request, kdc_instance): return KcmTestEnv(kdc_instance, k5util) -def test_kcm_init_list_destroy(setup_for_kcm): +@pytest.fixture +def setup_for_kcm_mem(request, kdc_instance): + """ + Just set up the local provider for tests and enable the KCM + responder + """ + kcm_path = os.path.join(config.RUNSTATEDIR, "kcm.socket") + sssd_conf = create_sssd_conf(kcm_path, "memory") + return common_setup_for_kcm_mem(request, kdc_instance, kcm_path, sssd_conf) + +@pytest.fixture +def setup_secrets(request): + create_sssd_secrets_fixture(request) + +@pytest.fixture +def setup_for_kcm_sec(request, kdc_instance): + """ + Just set up the local provider for tests and enable the KCM + responder + """ + kcm_path = os.path.join(config.RUNSTATEDIR, "kcm.socket") + sssd_conf = create_sssd_conf(kcm_path, "secrets") + return common_setup_for_kcm_mem(request, kdc_instance, kcm_path, sssd_conf) + + +def kcm_init_list_destroy(testenv): """ Test that kinit, kdestroy and klist work with KCM """ - testenv = setup_for_kcm testenv.k5kdc.add_principal("kcmtest", "Secret123") ok = testenv.k5util.has_principal("kcmtest@KCMTEST") @@ -172,12 +193,22 @@ def test_kcm_init_list_destroy(setup_for_kcm): assert nprincs == 0 -def test_kcm_overwrite(setup_for_kcm): +def test_kcm_mem_init_list_destroy(setup_for_kcm_mem): + testenv = setup_for_kcm_mem + kcm_init_list_destroy(testenv) + + +def test_kcm_sec_init_list_destroy(setup_for_kcm_sec, + setup_secrets): + testenv = setup_for_kcm_sec + kcm_init_list_destroy(testenv) + + +def kcm_overwrite(testenv): """ That that reusing a ccache reinitializes the cache and doesn't add the same principal twice """ - testenv = setup_for_kcm testenv.k5kdc.add_principal("kcmtest", "Secret123") exp_ccache = {'kcmtest@KCMTEST': ['krbtgt/KCMTEST@KCMTEST']} @@ -192,12 +223,22 @@ def test_kcm_overwrite(setup_for_kcm): assert exp_ccache == testenv.k5util.list_all_princs() -def test_collection_init_list_destroy(setup_for_kcm): +def test_kcm_mem_overwrite(setup_for_kcm_mem): + testenv = setup_for_kcm_mem + kcm_overwrite(testenv) + + +def test_kcm_sec_overwrite(setup_for_kcm_sec, + setup_secrets): + testenv = setup_for_kcm_sec + kcm_overwrite(testenv) + + +def collection_init_list_destroy(testenv): """ Test that multiple principals and service tickets can be stored in a collection. """ - testenv = setup_for_kcm testenv.k5kdc.add_principal("alice", "alicepw") testenv.k5kdc.add_principal("bob", "bobpw") testenv.k5kdc.add_principal("carol", "carolpw") @@ -241,7 +282,11 @@ def test_collection_init_list_destroy(setup_for_kcm): out = testenv.k5util.kdestroy() assert out == 0 - assert testenv.k5util.default_principal() == 'bob@KCMTEST' + # If the default is removed, KCM just uses whetever is the first entry + # in the collection as the default. And sine the KCM back ends don't + # guarantee if they are FIFO or LIFO, just check for either alice or bob + assert testenv.k5util.default_principal() in \ + ['alice@KCMTEST', 'bob@KCMTEST'] cc_coll = testenv.k5util.list_all_princs() assert len(cc_coll) == 2 assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST'] @@ -255,11 +300,21 @@ def test_collection_init_list_destroy(setup_for_kcm): #assert len(cc_coll) == 0 -def test_kswitch(setup_for_kcm): +def test_kcm_mem_collection_init_list_destroy(setup_for_kcm_mem): + testenv = setup_for_kcm_mem + collection_init_list_destroy(testenv) + + +def test_kcm_sec_collection_init_list_destroy(setup_for_kcm_sec, + setup_secrets): + testenv = setup_for_kcm_sec + collection_init_list_destroy(testenv) + + +def exercise_kswitch(testenv): """ Test switching between principals """ - testenv = setup_for_kcm testenv.k5kdc.add_principal("alice", "alicepw") testenv.k5kdc.add_principal("bob", "bobpw") testenv.k5kdc.add_principal("host/somehostname") @@ -301,12 +356,22 @@ def test_kswitch(setup_for_kcm): 'host/differenthostname@KCMTEST']) -def test_subsidiaries(setup_for_kcm): +def test_kcm_mem_kswitch(setup_for_kcm_mem): + testenv = setup_for_kcm_mem + exercise_kswitch(testenv) + + +def test_kcm_sec_kswitch(setup_for_kcm_sec, + setup_secrets): + testenv = setup_for_kcm_sec + exercise_kswitch(testenv) + + +def exercise_subsidiaries(testenv): """ Test that subsidiary caches are usable and KCM: without specifying UID can be used to identify the collection """ - testenv = setup_for_kcm testenv.k5kdc.add_principal("alice", "alicepw") testenv.k5kdc.add_principal("bob", "bobpw") testenv.k5kdc.add_principal("host/somehostname") @@ -346,11 +411,21 @@ def test_subsidiaries(setup_for_kcm): 'host/differenthostname@KCMTEST']) -def test_kdestroy_nocache(setup_for_kcm): +def test_kcm_mem_subsidiaries(setup_for_kcm_mem): + testenv = setup_for_kcm_mem + exercise_subsidiaries(testenv) + + +def test_kcm_sec_subsidiaries(setup_for_kcm_sec, + setup_secrets): + testenv = setup_for_kcm_sec + exercise_subsidiaries(testenv) + + +def kdestroy_nocache(testenv): """ Destroying a non-existing ccache should not throw an error """ - testenv = setup_for_kcm testenv.k5kdc.add_principal("alice", "alicepw") out, _, _ = testenv.k5util.kinit("alice", "alicepw") assert out == 0 @@ -359,3 +434,14 @@ def test_kdestroy_nocache(setup_for_kcm): assert out == 0 out = testenv.k5util.kdestroy() assert out == 0 + + +def test_kcm_mem_kdestroy_nocache(setup_for_kcm_mem): + testenv = setup_for_kcm_mem + exercise_subsidiaries(testenv) + + +def test_kcm_sec_kdestroy_nocache(setup_for_kcm_sec, + setup_secrets): + testenv = setup_for_kcm_sec + exercise_subsidiaries(testenv) diff --git a/src/util/util_errors.c b/src/util/util_errors.c index 23cfdf9..60c2f43 100644 --- a/src/util/util_errors.c +++ b/src/util/util_errors.c @@ -109,6 +109,8 @@ struct err_string error_to_str[] = { { "KCM operation not implemented" }, /* ERR_KCM_OP_NOT_IMPLEMENTED */ { "End of credential cache reached" }, /* ERR_KCM_CC_END */ { "Credential cache name not allowed" }, /* ERR_KCM_WRONG_CCNAME_FORMAT */ + { "Cannot encode a JSON object to string" }, /* ERR_JSON_ENCODING */ + { "Cannot decode a JSON object from string" }, /* ERR_JSON_DECODING */ { "ERR_LAST" } /* ERR_LAST */ }; diff --git a/src/util/util_errors.h b/src/util/util_errors.h index 387d481..4e9da81 100644 --- a/src/util/util_errors.h +++ b/src/util/util_errors.h @@ -131,6 +131,8 @@ enum sssd_errors { ERR_KCM_OP_NOT_IMPLEMENTED, ERR_KCM_CC_END, ERR_KCM_WRONG_CCNAME_FORMAT, + ERR_JSON_ENCODING, + ERR_JSON_DECODING, ERR_LAST /* ALWAYS LAST */ };