From 831f208783aeac443e6f2fc2efc3119535a032ef Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 30 Jul 2024 16:16:26 +0200 Subject: [PATCH] core: Add support for renaming credentials with ImportCredential= This allows for "per-instance" credentials for units. The use case is best explained with an example. Currently all our getty units have the following stanzas in their unit file: """ ImportCredential=agetty.* ImportCredential=login.* """ This means that setting agetty.autologin=root as a system credential will make every instance of our all our getty units autologin as the root user. This prevents us from doing autologin on /dev/hvc0 while still requiring manual login on all other ttys. To solve the issue, we introduce support for renaming credentials with ImportCredential=. This will allow us to add the following to e.g. serial-getty@.service: """ ImportCredential=tty.serial.%I.agetty.*:agetty. ImportCredential=tty.serial.%I.login.*:login. """ which for serial-getty@hvc0.service will make the service manager read all credentials of the form "tty.serial.hvc0.agetty.xxx" and pass them to the service in the form "agetty.xxx" (same goes for login). We can apply the same to each of the getty units to allow setting agetty and login credentials for individual ttys instead of globally. --- man/org.freedesktop.systemd1.xml | 38 +++++++++-- man/systemd.exec.xml | 12 ++++ src/core/dbus-execute.c | 95 +++++++++++++++++++++++---- src/core/exec-credential.c | 93 ++++++++++++++++++++++++-- src/core/exec-credential.h | 9 +++ src/core/execute-serialize.c | 23 ++++--- src/core/execute.c | 2 +- src/core/execute.h | 3 +- src/core/load-fragment-gperf.gperf.in | 2 +- src/core/load-fragment.c | 38 +++++++---- src/shared/bus-unit-util.c | 43 ++++++++++++ test/units/TEST-54-CREDS.sh | 30 +++++++++ 12 files changed, 341 insertions(+), 47 deletions(-) diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 31e6194bec3..b9120cc2228 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -3187,6 +3187,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly as ImportCredential = ['...', ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly a(ss) ImportCredentialEx = [...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly as SupplementaryGroups = ['...', ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s PAMName = '...'; @@ -3800,6 +3802,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -4488,6 +4492,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -5312,6 +5318,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly as ImportCredential = ['...', ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly a(ss) ImportCredentialEx = [...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly as SupplementaryGroups = ['...', ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s PAMName = '...'; @@ -5939,6 +5947,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + @@ -6603,6 +6613,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + @@ -7291,6 +7303,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly as ImportCredential = ['...', ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly a(ss) ImportCredentialEx = [...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly as SupplementaryGroups = ['...', ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s PAMName = '...'; @@ -7844,6 +7858,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + @@ -8420,6 +8436,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + @@ -9231,6 +9249,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly as ImportCredential = ['...', ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly a(ss) ImportCredentialEx = [...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly as SupplementaryGroups = ['...', ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s PAMName = '...'; @@ -9770,6 +9790,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + @@ -10332,6 +10354,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + @@ -12099,8 +12123,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ ExecMainHandoffTimestampMonotonic, and ExecMainHandoffTimestamp were added in version 256. StatusBusError, - StatusVarlinkError, and - PrivateTmpEx were added in version 257. + StatusVarlinkError, + PrivateTmpEx, and + ImportCredentialEx were added in version 257. Socket Unit Objects @@ -12137,7 +12162,8 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ EffectiveTasksMax, MemoryZSwapWriteback, and PassFileDescriptorsToExec were added in version 256. - PrivateTmpEx was added in version 257. + PrivateTmpEx, and + ImportCredentialEx were added in version 257. Mount Unit Objects @@ -12171,7 +12197,8 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ EffectiveMemoryMax, EffectiveTasksMax, and MemoryZSwapWriteback were added in version 256. - PrivateTmpEx was added in version 257. + PrivateTmpEx, and + ImportCredentialEx were added in version 257. Swap Unit Objects @@ -12205,7 +12232,8 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ EffectiveMemoryMax, EffectiveTasksMax, and MemoryZSwapWriteback were added in version 256. - PrivateTmpEx was added in version 257. + PrivateTmpEx, and + ImportCredentialEx were added in version 257. Slice Unit Objects diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index c79cf674458..fe19c8a657c 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -3520,6 +3520,18 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX [] wildcards are not permitted, nor are * wildcards anywhere except at the end of the glob expression. + Optionally, the credential name or glob may be followed by a colon followed by a rename pattern. + If specified, all credentials matching the credential name or glob are renamed according to the given + pattern. For example, if ImportCredential=my.original.cred:my.renamed.cred is + specified, the service manager will read the my.original.cred credential and make + it available as the my.renamed.cred credential to the service. Similarly, if + ImportCredential=my.original.*:my.renamed. is specified, the service manager will + read all credentials starting with my.original. and make them available as + my.renamed.xxx to the service. + + If ImportCredential= is specified multiple times and multiple credentials + end up with the same name after renaming, the first one is kept and later ones are dropped.. + When multiple credentials of the same name are found, credentials found by LoadCredential= and LoadCredentialEncrypted= take priority over credentials found by ImportCredential=. diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index c9b118a1a04..feafa15e430 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -688,6 +688,66 @@ 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); + ExecImportCredential *ic; + int r; + + assert(bus); + assert(property); + assert(reply); + + r = sd_bus_message_open_container(reply, 'a', "s"); + if (r < 0) + return r; + + ORDERED_SET_FOREACH(ic, c->import_credentials) { + r = sd_bus_message_append(reply, "s", ic->glob); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_import_credential_ex( + 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); + ExecImportCredential *ic; + int r; + + assert(bus); + assert(property); + assert(reply); + + r = sd_bus_message_open_container(reply, 'a', "(ss)"); + if (r < 0) + return r; + + ORDERED_SET_FOREACH(ic, c->import_credentials) { + r = sd_bus_message_append(reply, "(ss)", ic->glob, ic->rename); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + static int property_get_root_hash( sd_bus *bus, const char *path, @@ -1068,7 +1128,8 @@ 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", bus_property_get_string_set, offsetof(ExecContext, import_credentials), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ImportCredential", "as", property_get_import_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ImportCredentialEx", "a(ss)", property_get_import_credential_ex, 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), @@ -2142,33 +2203,41 @@ int bus_exec_context_set_transient_property( return 1; - } else if (streq(name, "ImportCredential")) { - bool isempty = true; + } else if (STR_IN_SET(name, "ImportCredential", "ImportCredentialEx")) { + bool empty = true, ex = streq(name, "ImportCredentialEx"); - r = sd_bus_message_enter_container(message, 'a', "s"); + r = sd_bus_message_enter_container(message, 'a', ex ? "(ss)" : "s"); if (r < 0) return r; for (;;) { - const char *s; + const char *glob, *rename = NULL; - r = sd_bus_message_read(message, "s", &s); + if (ex) + r = sd_bus_message_read(message, "(ss)", &glob, &rename); + else + r = sd_bus_message_read(message, "s", &glob); if (r < 0) return r; if (r == 0) break; - if (!credential_glob_valid(s)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Credential name or glob is invalid: %s", s); + if (!credential_glob_valid(glob)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Credential name or glob is invalid: %s", glob); - isempty = false; + if (rename && !credential_name_valid(rename)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Credential name is invalid: %s", rename); + + empty = false; if (!UNIT_WRITE_FLAGS_NOOP(flags)) { - r = set_put_strdup(&c->import_credentials, s); + r = exec_context_put_import_credential(c, glob, rename); if (r < 0) return r; - (void) unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "%s=%s", name, s); + (void) unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, + "ImportCredential=%s%s%s", + glob, rename ? ":" : "", strempty(rename)); } } @@ -2176,8 +2245,8 @@ int bus_exec_context_set_transient_property( if (r < 0) return r; - if (!UNIT_WRITE_FLAGS_NOOP(flags) && isempty) { - c->import_credentials = set_free_free(c->import_credentials); + if (!UNIT_WRITE_FLAGS_NOOP(flags) && empty) { + c->import_credentials = ordered_set_free(c->import_credentials); (void) unit_write_settingf(u, flags, name, "%s=", name); } diff --git a/src/core/exec-credential.c b/src/core/exec-credential.c index 75eca830f80..e1b09f67189 100644 --- a/src/core/exec-credential.c +++ b/src/core/exec-credential.c @@ -38,6 +38,37 @@ ExecLoadCredential* exec_load_credential_free(ExecLoadCredential *lc) { return mfree(lc); } +ExecImportCredential* exec_import_credential_free(ExecImportCredential *ic) { + if (!ic) + return NULL; + + free(ic->glob); + free(ic->rename); + return mfree(ic); +} + +static void exec_import_credential_hash_func(const ExecImportCredential *ic, struct siphash *state) { + assert(ic); + assert(state); + + siphash24_compress_string(ic->glob, state); + if (ic->rename) + siphash24_compress_string(ic->rename, state); +} + +static int exec_import_credential_compare_func(const ExecImportCredential *a, const ExecImportCredential *b) { + int r; + + assert(a); + assert(b); + + r = strcmp(a->glob, b->glob); + if (r != 0) + return r; + + return strcmp_ptr(a->rename, b->rename); +} + DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( exec_set_credential_hash_ops, char, string_hash_func, string_compare_func, @@ -48,6 +79,13 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( char, string_hash_func, string_compare_func, ExecLoadCredential, exec_load_credential_free); +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + exec_import_credential_hash_ops, + ExecImportCredential, + exec_import_credential_hash_func, + exec_import_credential_compare_func, + exec_import_credential_free); + int exec_context_put_load_credential(ExecContext *c, const char *id, const char *path, bool encrypted) { ExecLoadCredential *old; int r; @@ -140,6 +178,39 @@ int exec_context_put_set_credential( return 0; } +int exec_context_put_import_credential(ExecContext *c, const char *glob, const char *rename) { + _cleanup_(exec_import_credential_freep) ExecImportCredential *ic = NULL; + int r; + + assert(c); + + rename = empty_to_null(rename); + + ic = new(ExecImportCredential, 1); + if (!ic) + return -ENOMEM; + + *ic = (ExecImportCredential) { + .glob = strdup(glob), + .rename = rename ? strdup(rename) : NULL, + }; + if (!ic->glob || (rename && !ic->rename)) + return -ENOMEM; + + if (ordered_set_contains(c->import_credentials, ic)) + return 0; + + r = ordered_set_ensure_put(&c->import_credentials, &exec_import_credential_hash_ops, ic); + if (r < 0) { + assert(r != -EEXIST); + return r; + } + + TAKE_PTR(ic); + + return 0; +} + bool exec_params_need_credentials(const ExecParameters *p) { assert(p); @@ -151,7 +222,7 @@ bool exec_context_has_credentials(const ExecContext *c) { return !hashmap_isempty(c->set_credentials) || !hashmap_isempty(c->load_credentials) || - !set_isempty(c->import_credentials); + !ordered_set_isempty(c->import_credentials); } bool exec_context_has_encrypted_credentials(const ExecContext *c) { @@ -381,7 +452,7 @@ static int maybe_decrypt_and_write_credential( } static int load_credential_glob( - const char *path, + const ExecImportCredential *ic, bool encrypted, char * const *search_path, ReadFullFileFlags flags, @@ -393,7 +464,7 @@ static int load_credential_glob( int r; - assert(path); + assert(ic); assert(search_path); assert(write_dfd >= 0); assert(left); @@ -402,7 +473,7 @@ static int load_credential_glob( _cleanup_globfree_ glob_t pglob = {}; _cleanup_free_ char *j = NULL; - j = path_join(*d, path); + j = path_join(*d, ic->glob); if (!j) return -ENOMEM; @@ -421,6 +492,16 @@ static int load_credential_glob( if (r < 0) return log_debug_errno(r, "Failed to extract filename from '%s': %m", *p); + if (ic->rename) { + _cleanup_free_ char *renamed = NULL; + + renamed = strjoin(ic->rename, fn + strlen(ic->glob) - !!endswith(ic->glob, "*")); + if (!renamed) + return log_oom_debug(); + + free_and_replace(fn, renamed); + } + if (faccessat(write_dfd, fn, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) { log_debug("Skipping credential with duplicated ID %s at %s", fn, *p); continue; @@ -661,7 +742,7 @@ static int acquire_credentials( uint64_t left = CREDENTIALS_TOTAL_SIZE_MAX; _cleanup_close_ int dfd = -EBADF; - const char *ic; + ExecImportCredential *ic; ExecLoadCredential *lc; ExecSetCredential *sc; int r; @@ -736,7 +817,7 @@ static int acquire_credentials( /* 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) { + ORDERED_SET_FOREACH(ic, context->import_credentials) { _cleanup_free_ char **search_path = NULL; search_path = credential_search_path(params, CREDENTIAL_SEARCH_PATH_TRUSTED); diff --git a/src/core/exec-credential.h b/src/core/exec-credential.h index 87b85d77099..88d7f715801 100644 --- a/src/core/exec-credential.h +++ b/src/core/exec-credential.h @@ -25,12 +25,20 @@ typedef struct ExecSetCredential { size_t size; } ExecSetCredential; +typedef struct ExecImportCredential { + char *glob; + char *rename; +} ExecImportCredential; + ExecSetCredential* exec_set_credential_free(ExecSetCredential *sc); DEFINE_TRIVIAL_CLEANUP_FUNC(ExecSetCredential*, exec_set_credential_free); ExecLoadCredential* exec_load_credential_free(ExecLoadCredential *lc); DEFINE_TRIVIAL_CLEANUP_FUNC(ExecLoadCredential*, exec_load_credential_free); +ExecImportCredential* exec_import_credential_free(ExecImportCredential *lc); +DEFINE_TRIVIAL_CLEANUP_FUNC(ExecImportCredential*, exec_import_credential_free); + int exec_context_put_load_credential(ExecContext *c, const char *id, const char *path, bool encrypted); int exec_context_put_set_credential( ExecContext *c, @@ -38,6 +46,7 @@ int exec_context_put_set_credential( void *data_consume, size_t size, bool encrypted); +int exec_context_put_import_credential(ExecContext *c, const char *glob, const char *rename); bool exec_params_need_credentials(const ExecParameters *p); diff --git a/src/core/execute-serialize.c b/src/core/execute-serialize.c index 69f79984a54..23816627690 100644 --- a/src/core/execute-serialize.c +++ b/src/core/execute-serialize.c @@ -2554,13 +2554,14 @@ static int exec_context_serialize(const ExecContext *c, FILE *f) { return r; } - if (!set_isempty(c->import_credentials)) { - char *ic; - SET_FOREACH(ic, c->import_credentials) { - r = serialize_item(f, "exec-context-import-credentials", ic); - if (r < 0) - return r; - } + ExecImportCredential *ic; + ORDERED_SET_FOREACH(ic, c->import_credentials) { + r = serialize_item_format(f, "exec-context-import-credentials", "%s%s%s", + ic->glob, + ic->rename ? " " : "", + strempty(ic->rename)); + if (r < 0) + return r; } r = serialize_image_policy(f, "exec-context-root-image-policy", c->root_image_policy); @@ -3688,11 +3689,15 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { if (r < 0) return r; } else if ((val = startswith(l, "exec-context-import-credentials="))) { - r = set_ensure_allocated(&c->import_credentials, &string_hash_ops); + _cleanup_free_ char *glob = NULL, *rename = NULL; + + r = extract_many_words(&val, " ", EXTRACT_DONT_COALESCE_SEPARATORS, &glob, &rename); if (r < 0) return r; + if (r == 0) + return -EINVAL; - r = set_put_strdup(&c->import_credentials, val); + r = exec_context_put_import_credential(c, glob, rename); if (r < 0) return r; } else if ((val = startswith(l, "exec-context-root-image-policy="))) { diff --git a/src/core/execute.c b/src/core/execute.c index 26b2c0fb4af..56e6d4b1ca8 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -630,7 +630,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_free(c->import_credentials); + c->import_credentials = ordered_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 b801bfe8532..22d72c8959f 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -26,6 +26,7 @@ typedef struct Manager Manager; #include "nsflags.h" #include "numa-util.h" #include "open-file.h" +#include "ordered-set.h" #include "path-util.h" #include "runtime-scope.h" #include "set.h" @@ -363,7 +364,7 @@ struct ExecContext { Hashmap *set_credentials; /* output id → ExecSetCredential */ Hashmap *load_credentials; /* output id → ExecLoadCredential */ - Set *import_credentials; + OrderedSet *import_credentials; /* ExecImportCredential */ 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 e04450d869d..9a78e22fb52 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -153,7 +153,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}}.ImportCredential, config_parse_import_credential, 0, offsetof({{type}}, exec_context) {{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 97b719f52aa..7633589b0d4 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -4895,8 +4895,7 @@ int config_parse_import_credential( void *data, void *userdata) { - _cleanup_free_ char *s = NULL; - Set** import_credentials = ASSERT_PTR(data); + ExecContext *context = ASSERT_PTR(data); Unit *u = userdata; int r; @@ -4906,23 +4905,40 @@ int config_parse_import_credential( if (isempty(rvalue)) { /* Empty assignment resets the list */ - *import_credentials = set_free_free(*import_credentials); + context->import_credentials = ordered_set_free(context->import_credentials); return 0; } - r = unit_cred_printf(u, rvalue, &s); + const char *p = rvalue; + _cleanup_free_ char *word = NULL, *glob = NULL; + + r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r == -ENOMEM) + return log_oom(); + if (r <= 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", rvalue); + return 0; + } + + r = unit_cred_printf(u, word, &glob); if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in \"%s\", ignoring: %m", s); - return 0; - } - if (!credential_glob_valid(s)) { - log_syntax(unit, LOG_WARNING, filename, line, 0, "Credential name or glob \"%s\" not valid, ignoring.", s); + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in \"%s\", ignoring: %m", word); return 0; } - r = set_put_strdup(import_credentials, s); + if (!credential_glob_valid(glob)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Credential name or glob \"%s\" not valid, ignoring.", glob); + return 0; + } + + if (!isempty(p) && !credential_name_valid(p)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Credential name \"%s\" not valid, ignoring.", p); + return 0; + } + + r = exec_context_put_import_credential(context, glob, p); if (r < 0) - return log_error_errno(r, "Failed to store credential name '%s': %m", rvalue); + return log_error_errno(r, "Failed to store import credential '%s': %m", rvalue); return 0; } diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 259fbeaf5c4..6ce76ded431 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -1310,6 +1310,49 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con return 1; } + if (streq(field, "ImportCredentialEx")) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_basic(m, 's', field); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'v', "a(ss)"); + if (r < 0) + return bus_log_create_error(r); + + if (isempty(eq)) + r = sd_bus_message_append(m, "a(ss)", 0); + else { + _cleanup_free_ char *word = NULL; + const char *p = eq; + + r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + return log_error_errno(r, "Failed to parse %s= parameter: %s", field, eq); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing argument to %s=.", field); + + r = sd_bus_message_append(m, "a(ss)", 1, word, p); + } + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + return 1; + } + if (streq(field, "LogExtraFields")) { r = sd_bus_message_open_container(m, 'r', "sv"); if (r < 0) diff --git a/test/units/TEST-54-CREDS.sh b/test/units/TEST-54-CREDS.sh index 89d6dcdf034..9f7f4b11606 100755 --- a/test/units/TEST-54-CREDS.sh +++ b/test/units/TEST-54-CREDS.sh @@ -289,6 +289,36 @@ systemd-run -p "ImportCredential=test.creds.*" \ '${CREDENTIALS_DIRECTORY}/test.creds.third' >/tmp/ts54-concat cmp /tmp/ts54-concat <(echo -n abc) +# Check that renaming with globs works as expected. +systemd-run -p "ImportCredentialEx=test.creds.*:renamed.creds." \ + --unit=test-54-ImportCredential.service \ + -p DynamicUser=1 \ + --wait \ + --pipe \ + cat '${CREDENTIALS_DIRECTORY}/renamed.creds.first' \ + '${CREDENTIALS_DIRECTORY}/renamed.creds.second' \ + '${CREDENTIALS_DIRECTORY}/renamed.creds.third' >/tmp/ts54-concat +cmp /tmp/ts54-concat <(echo -n abc) + +# Check that renaming without globs works as expected. +systemd-run -p "ImportCredentialEx=test.creds.first:renamed.creds.first" \ + --unit=test-54-ImportCredential.service \ + -p DynamicUser=1 \ + --wait \ + --pipe \ + cat '${CREDENTIALS_DIRECTORY}/renamed.creds.first' >/tmp/ts54-concat +cmp /tmp/ts54-concat <(echo -n a) + +# Test that multiple renames are processed in the correct order. +systemd-run -p "ImportCredentialEx=test.creds.first:renamed.creds.first" \ + -p "ImportCredentialEx=test.creds.second:renamed.creds.first" \ + --unit=test-54-ImportCredential.service \ + -p DynamicUser=1 \ + --wait \ + --pipe \ + cat '${CREDENTIALS_DIRECTORY}/renamed.creds.first' >/tmp/ts54-concat +cmp /tmp/ts54-concat <(echo -n a) + # Now test encrypted credentials (only supported when built with OpenSSL though) if systemctl --version | grep -q -- +OPENSSL ; then echo -n $RANDOM >/tmp/test-54-plaintext