diff options
author | Daan De Meyer <daan.j.demeyer@gmail.com> | 2023-01-13 16:22:46 +0100 |
---|---|---|
committer | Daan De Meyer <daan.j.demeyer@gmail.com> | 2023-06-08 14:09:18 +0200 |
commit | bbfb25f4b923bde7bcff7ef69e6e83e90440e704 (patch) | |
tree | 225f6e17270b20239288da4566d294531ae59024 /src/core | |
parent | execute: Make credential_search_path() more flexible (diff) | |
download | systemd-bbfb25f4b923bde7bcff7ef69e6e83e90440e704.tar.xz systemd-bbfb25f4b923bde7bcff7ef69e6e83e90440e704.zip |
creds: Add ImportCredential=
ImportCredential= takes a credential name and searches for a matching
credential in all the credential stores we know about it. It supports
globs which are expanded so that all matching credentials are loaded.
Diffstat (limited to 'src/core')
-rw-r--r-- | src/core/dbus-execute.c | 101 | ||||
-rw-r--r-- | src/core/execute.c | 178 | ||||
-rw-r--r-- | src/core/execute.h | 1 | ||||
-rw-r--r-- | src/core/load-fragment-gperf.gperf.in | 1 | ||||
-rw-r--r-- | src/core/load-fragment.c | 106 | ||||
-rw-r--r-- | src/core/load-fragment.h | 3 |
6 files changed, 312 insertions, 78 deletions
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index fb22a9769d..57e9eb8254 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -26,6 +26,7 @@ #include "io-util.h" #include "ioprio-util.h" #include "journal-file.h" +#include "load-fragment.h" #include "memstream-util.h" #include "missing_ioprio.h" #include "mountpoint-util.h" @@ -928,6 +929,36 @@ static int property_get_load_credential( return sd_bus_message_close_container(reply); } +static int property_get_import_credential( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = ASSERT_PTR(userdata); + const char *s; + int r; + + assert(bus); + assert(property); + assert(reply); + + r = sd_bus_message_open_container(reply, 'a', "s"); + if (r < 0) + return r; + + SET_FOREACH(s, c->import_credentials) { + r = sd_bus_message_append(reply, "s", s); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + static int property_get_root_hash( sd_bus *bus, const char *path, @@ -1281,6 +1312,7 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("SetCredentialEncrypted", "a(say)", property_get_set_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("LoadCredential", "a(ss)", property_get_load_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("LoadCredentialEncrypted", "a(ss)", property_get_load_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ImportCredential", "as", property_get_import_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SupplementaryGroups", "as", NULL, offsetof(ExecContext, supplementary_groups), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PAMName", "s", NULL, offsetof(ExecContext, pam_name), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ReadWritePaths", "as", NULL, offsetof(ExecContext, read_write_paths), SD_BUS_VTABLE_PROPERTY_CONST), @@ -2311,41 +2343,54 @@ int bus_exec_context_set_transient_property( isempty = false; if (!UNIT_WRITE_FLAGS_NOOP(flags)) { - _cleanup_free_ char *copy = NULL; - ExecLoadCredential *old; + bool encrypted = streq(name, "LoadCredentialEncrypted"); - copy = strdup(source); - if (!copy) - return -ENOMEM; + r = hashmap_put_credential(&c->load_credentials, id, source, encrypted); + if (r < 0) + return r; - old = hashmap_get(c->load_credentials, id); - if (old) { - free_and_replace(old->path, copy); - old->encrypted = streq(name, "LoadCredentialEncrypted"); - } else { - _cleanup_(exec_load_credential_freep) ExecLoadCredential *lc = NULL; + (void) unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "%s=%s:%s", name, id, source); + } + } - lc = new(ExecLoadCredential, 1); - if (!lc) - return -ENOMEM; + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; - *lc = (ExecLoadCredential) { - .id = strdup(id), - .path = TAKE_PTR(copy), - .encrypted = streq(name, "LoadCredentialEncrypted"), - }; + if (!UNIT_WRITE_FLAGS_NOOP(flags) && isempty) { + c->load_credentials = hashmap_free(c->load_credentials); + (void) unit_write_settingf(u, flags, name, "%s=", name); + } - if (!lc->id) - return -ENOMEM; + return 1; - r = hashmap_ensure_put(&c->load_credentials, &exec_load_credential_hash_ops, lc->id, lc); - if (r < 0) - return r; + } else if (streq(name, "ImportCredential")) { + bool isempty = true; - TAKE_PTR(lc); - } + r = sd_bus_message_enter_container(message, 'a', "s"); + if (r < 0) + return r; - (void) unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "%s=%s:%s", name, id, source); + for (;;) { + const char *s; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + if (r == 0) + break; + + if (!filename_is_valid(s)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Credential name is invalid: %s", s); + + isempty = false; + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + r = set_put_strdup(&c->import_credentials, s); + if (r < 0) + return r; + + (void) unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "%s=%s", name, s); } } @@ -2354,7 +2399,7 @@ int bus_exec_context_set_transient_property( return r; if (!UNIT_WRITE_FLAGS_NOOP(flags) && isempty) { - c->load_credentials = hashmap_free(c->load_credentials); + c->import_credentials = set_free(c->import_credentials); (void) unit_write_settingf(u, flags, name, "%s=", name); } diff --git a/src/core/execute.c b/src/core/execute.c index 2d1538be85..29e06e837e 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -1544,7 +1544,8 @@ bool exec_context_has_credentials(const ExecContext *context) { assert(context); return !hashmap_isempty(context->set_credentials) || - !hashmap_isempty(context->load_credentials); + !hashmap_isempty(context->load_credentials) || + !set_isempty(context->import_credentials); } #if HAVE_SECCOMP @@ -2802,6 +2803,111 @@ static char **credential_search_path(const ExecParameters *params, CredentialSea return TAKE_PTR(l); } +static int maybe_decrypt_and_write_credential( + int dir_fd, + const char *id, + bool encrypted, + uid_t uid, + bool ownership_ok, + const char *data, + size_t size, + uint64_t *left) { + + _cleanup_free_ void *plaintext = NULL; + size_t add; + int r; + + if (encrypted) { + size_t plaintext_size = 0; + + r = decrypt_credential_and_warn(id, now(CLOCK_REALTIME), NULL, NULL, data, size, + &plaintext, &plaintext_size); + if (r < 0) + return r; + + data = plaintext; + size = plaintext_size; + } + + add = strlen(id) + size; + if (add > *left) + return -E2BIG; + + r = write_credential(dir_fd, id, data, size, uid, ownership_ok); + if (r < 0) + return log_debug_errno(r, "Failed to write credential '%s': %m", id); + + *left -= add; + return 0; +} + +static int load_credential_glob( + const char *path, + bool encrypted, + char **search_path, + ReadFullFileFlags flags, + int write_dfd, + uid_t uid, + bool ownership_ok, + uint64_t *left) { + + int r; + + STRV_FOREACH(d, search_path) { + _cleanup_globfree_ glob_t pglob = {}; + _cleanup_free_ char *j = NULL; + + j = path_join(*d, path); + if (!j) + return -ENOMEM; + + r = safe_glob(j, 0, &pglob); + if (r == -ENOENT) + continue; + if (r < 0) + return r; + + for (unsigned n = 0; n < pglob.gl_pathc; n++) { + _cleanup_free_ char *fn = NULL; + _cleanup_(erase_and_freep) char *data = NULL; + size_t size; + + /* path is absolute, hence pass AT_FDCWD as nop dir fd here */ + r = read_full_file_full( + AT_FDCWD, + pglob.gl_pathv[n], + UINT64_MAX, + encrypted ? CREDENTIAL_ENCRYPTED_SIZE_MAX : CREDENTIAL_SIZE_MAX, + flags, + NULL, + &data, &size); + if (r < 0) + return log_debug_errno(r, "Failed to read credential '%s': %m", + pglob.gl_pathv[n]); + + r = path_extract_filename(pglob.gl_pathv[n], &fn); + if (r < 0) + return log_debug_errno(r, "Failed to extract filename from '%s': %m", + pglob.gl_pathv[n]); + + r = maybe_decrypt_and_write_credential( + write_dfd, + fn, + encrypted, + uid, + ownership_ok, + data, size, + left); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + } + + return 0; +} + static int load_credential( const ExecContext *context, const ExecParameters *params, @@ -2821,7 +2927,7 @@ static int load_credential( _cleanup_free_ char *bindname = NULL; const char *source = NULL; bool missing_ok = true; - size_t size, add, maxsz; + size_t size, maxsz; int r; assert(context); @@ -2923,28 +3029,7 @@ static int load_credential( if (r < 0) return log_debug_errno(r, "Failed to read credential '%s': %m", path); - if (encrypted) { - _cleanup_free_ void *plaintext = NULL; - size_t plaintext_size = 0; - - r = decrypt_credential_and_warn(id, now(CLOCK_REALTIME), NULL, NULL, data, size, &plaintext, &plaintext_size); - if (r < 0) - return r; - - free_and_replace(data, plaintext); - size = plaintext_size; - } - - add = strlen(id) + size; - if (add > *left) - return -E2BIG; - - r = write_credential(write_dfd, id, data, size, uid, ownership_ok); - if (r < 0) - return log_debug_errno(r, "Failed to write credential '%s': %m", id); - - *left -= add; - return 0; + return maybe_decrypt_and_write_credential(write_dfd, id, encrypted, uid, ownership_ok, data, size, left); } struct load_cred_args { @@ -3019,6 +3104,7 @@ static int acquire_credentials( uint64_t left = CREDENTIALS_TOTAL_SIZE_MAX; _cleanup_close_ int dfd = -EBADF; + const char *ic; ExecLoadCredential *lc; ExecSetCredential *sc; int r; @@ -3084,8 +3170,47 @@ static int acquire_credentials( return r; } - /* Second, we add in literally specified credentials. If the credentials already exist, we'll not add - * them, so that they can act as a "default" if the same credential is specified multiple times. */ + /* Next, look for system credentials and credentials in the credentials store. Note that these do not + * override any credentials found earlier. */ + SET_FOREACH(ic, context->import_credentials) { + _cleanup_free_ char **search_path = NULL; + + search_path = credential_search_path(params, CREDENTIAL_SEARCH_PATH_TRUSTED); + if (!search_path) + return -ENOMEM; + + r = load_credential_glob( + ic, + /* encrypted = */ false, + search_path, + READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER, + dfd, + uid, + ownership_ok, + &left); + if (r < 0) + return r; + + search_path = strv_free(search_path); + search_path = credential_search_path(params, CREDENTIAL_SEARCH_PATH_ENCRYPTED); + if (!search_path) + return -ENOMEM; + + r = load_credential_glob( + ic, + /* encrypted = */ true, + search_path, + READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER|READ_FULL_FILE_UNBASE64, + dfd, + uid, + ownership_ok, + &left); + if (r < 0) + return r; + } + + /* Finally, we add in literally specified credentials. If the credentials already exist, we'll not + * add them, so that they can act as a "default" if the same credential is specified multiple times. */ HASHMAP_FOREACH(sc, context->set_credentials) { _cleanup_(erase_and_freep) void *plaintext = NULL; const char *data; @@ -5883,6 +6008,7 @@ void exec_context_done(ExecContext *c) { c->load_credentials = hashmap_free(c->load_credentials); c->set_credentials = hashmap_free(c->set_credentials); + c->import_credentials = set_free(c->import_credentials); c->root_image_policy = image_policy_free(c->root_image_policy); c->mount_image_policy = image_policy_free(c->mount_image_policy); diff --git a/src/core/execute.h b/src/core/execute.h index 1c8378c8b0..953dc9e7f7 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -361,6 +361,7 @@ struct ExecContext { Hashmap *set_credentials; /* output id → ExecSetCredential */ Hashmap *load_credentials; /* output id → ExecLoadCredential */ + Set *import_credentials; ImagePolicy *root_image_policy, *mount_image_policy, *extension_image_policy; }; diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index 64a00fef28..ae318dae89 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -151,6 +151,7 @@ {{type}}.SetCredentialEncrypted, config_parse_set_credential, 1, offsetof({{type}}, exec_context) {{type}}.LoadCredential, config_parse_load_credential, 0, offsetof({{type}}, exec_context) {{type}}.LoadCredentialEncrypted, config_parse_load_credential, 1, offsetof({{type}}, exec_context) +{{type}}.ImportCredential, config_parse_import_credential, 0, offsetof({{type}}, exec_context.import_credentials) {{type}}.TimeoutCleanSec, config_parse_sec, 0, offsetof({{type}}, exec_context.timeout_clean_usec) {% if HAVE_PAM %} {{type}}.PAMName, config_parse_unit_string_printf, 0, offsetof({{type}}, exec_context.pam_name) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 554180f1e3..6d129af7b2 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -4840,6 +4840,46 @@ int config_parse_set_credential( return 0; } +int hashmap_put_credential(Hashmap **h, const char *id, const char *path, bool encrypted) { + ExecLoadCredential *old; + int r; + + assert(h); + assert(id); + assert(path); + + old = hashmap_get(*h, id); + if (old) { + r = free_and_strdup(&old->path, path); + if (r < 0) + return r; + + old->encrypted = encrypted; + } else { + _cleanup_(exec_load_credential_freep) ExecLoadCredential *lc = NULL; + + lc = new(ExecLoadCredential, 1); + if (!lc) + return log_oom(); + + *lc = (ExecLoadCredential) { + .id = strdup(id), + .path = strdup(path), + .encrypted = encrypted, + }; + if (!lc->id || !lc->path) + return -ENOMEM; + + r = hashmap_ensure_put(h, &exec_load_credential_hash_ops, lc->id, lc); + if (r < 0) + return r; + + TAKE_PTR(lc); + } + + return 0; +} + int config_parse_load_credential( const char *unit, const char *filename, @@ -4854,7 +4894,6 @@ int config_parse_load_credential( _cleanup_free_ char *word = NULL, *k = NULL, *q = NULL; ExecContext *context = ASSERT_PTR(data); - ExecLoadCredential *old; bool encrypted = ltype; Unit *u = userdata; const char *p; @@ -4907,35 +4946,54 @@ int config_parse_load_credential( } } - old = hashmap_get(context->load_credentials, k); - if (old) { - free_and_replace(old->path, q); - old->encrypted = encrypted; - } else { - _cleanup_(exec_load_credential_freep) ExecLoadCredential *lc = NULL; + r = hashmap_put_credential(&context->load_credentials, k, q, encrypted); + if (r < 0) + return log_error_errno(r, "Failed to store load credential '%s': %m", rvalue); - lc = new(ExecLoadCredential, 1); - if (!lc) - return log_oom(); + return 0; +} - *lc = (ExecLoadCredential) { - .id = TAKE_PTR(k), - .path = TAKE_PTR(q), - .encrypted = encrypted, - }; +int config_parse_import_credential( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { - r = hashmap_ensure_put(&context->load_credentials, &exec_load_credential_hash_ops, lc->id, lc); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Duplicated credential value '%s', ignoring assignment: %s", lc->id, rvalue); - return 0; - } + _cleanup_free_ char *s = NULL; + Set** import_credentials = ASSERT_PTR(data); + Unit *u = userdata; + int r; - TAKE_PTR(lc); + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + *import_credentials = set_free(*import_credentials); + return 0; + } + + r = unit_cred_printf(u, rvalue, &s); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in \"%s\", ignoring: %m", s); + return 0; + } + if (!filename_is_valid(s)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Credential name \"%s\" not valid, ignoring.", s); + return 0; } + r = set_put_strdup(import_credentials, s); + if (r < 0) + return log_error_errno(r, "Failed to store credential name '%s': %m", rvalue); + return 0; } diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index 59f02a3207..3cfb50969a 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -12,6 +12,8 @@ int unit_is_likely_recursive_template_dependency(Unit *u, const char *name, cons int parse_crash_chvt(const char *value, int *data); int parse_confirm_spawn(const char *value, char **console); +int hashmap_put_credential(Hashmap **h, const char *id, const char *path, bool encrypted); + /* Read service data from .desktop file style configuration fragments */ int unit_load_fragment(Unit *u); @@ -105,6 +107,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_preserve_mode); CONFIG_PARSER_PROTOTYPE(config_parse_exec_directories); CONFIG_PARSER_PROTOTYPE(config_parse_set_credential); CONFIG_PARSER_PROTOTYPE(config_parse_load_credential); +CONFIG_PARSER_PROTOTYPE(config_parse_import_credential); CONFIG_PARSER_PROTOTYPE(config_parse_set_status); CONFIG_PARSER_PROTOTYPE(config_parse_namespace_path_strv); CONFIG_PARSER_PROTOTYPE(config_parse_temporary_filesystems); |