From 2c2ed3272b992b6eb13864e49847c943e42be00f Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 31 Jul 2024 15:21:17 +0200 Subject: [PATCH 1/5] Drop EEXIST handling in load_credential_glob() Credentials are written to a temporary file and renamed to the destination with renameat() which will replace existing files so EEXIST should not happen so drop the handling for EEXIST. --- src/core/exec-credential.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/exec-credential.c b/src/core/exec-credential.c index b210850cb1..8731d74666 100644 --- a/src/core/exec-credential.c +++ b/src/core/exec-credential.c @@ -442,8 +442,6 @@ static int load_credential_glob( ownership_ok, data, size, left); - if (r == -EEXIST) - continue; if (r < 0) return r; } From 590348e2bf8415053487324d47d0083b49dfdeb0 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 31 Jul 2024 15:01:40 +0200 Subject: [PATCH 2/5] exec-credential: Log if we skip duplicate credential --- src/core/exec-credential.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/exec-credential.c b/src/core/exec-credential.c index 8731d74666..a70cf23eaa 100644 --- a/src/core/exec-credential.c +++ b/src/core/exec-credential.c @@ -779,8 +779,10 @@ static int acquire_credentials( * EEXIST if the credential already exists. That's because the TPM2-based decryption is kinda * slow and involved, hence it's nice to be able to skip that if the credential already * exists anyway. */ - if (faccessat(dfd, sc->id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) + if (faccessat(dfd, sc->id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) { + log_debug("Skipping credential with duplicated ID %s", sc->id); continue; + } if (errno != ENOENT) return log_debug_errno(errno, "Failed to test if credential %s exists: %m", sc->id); From 3de13e6148731ae9c36885afd78b1421e6f16305 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 31 Jul 2024 15:02:07 +0200 Subject: [PATCH 3/5] exec-credential: Skip duplicate credentials in load_credential_glob() We document that when multiple credentials of the same name are found, we use the first one found so let's actually implement that behavior. --- src/core/exec-credential.c | 15 +++++++++++---- test/units/TEST-54-CREDS.sh | 5 ++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/core/exec-credential.c b/src/core/exec-credential.c index a70cf23eaa..75eca830f8 100644 --- a/src/core/exec-credential.c +++ b/src/core/exec-credential.c @@ -417,6 +417,17 @@ static int load_credential_glob( _cleanup_(erase_and_freep) char *data = NULL; size_t size; + r = path_extract_filename(*p, &fn); + if (r < 0) + return log_debug_errno(r, "Failed to extract filename from '%s': %m", *p); + + if (faccessat(write_dfd, fn, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) { + log_debug("Skipping credential with duplicated ID %s at %s", fn, *p); + continue; + } + if (errno != ENOENT) + return log_debug_errno(errno, "Failed to test if credential %s exists: %m", fn); + /* path is absolute, hence pass AT_FDCWD as nop dir fd here */ r = read_full_file_full( AT_FDCWD, @@ -429,10 +440,6 @@ static int load_credential_glob( if (r < 0) return log_debug_errno(r, "Failed to read credential '%s': %m", *p); - r = path_extract_filename(*p, &fn); - if (r < 0) - return log_debug_errno(r, "Failed to extract filename from '%s': %m", *p); - r = maybe_decrypt_and_write_credential( write_dfd, fn, diff --git a/test/units/TEST-54-CREDS.sh b/test/units/TEST-54-CREDS.sh index fe410d567e..89d6dcdf03 100755 --- a/test/units/TEST-54-CREDS.sh +++ b/test/units/TEST-54-CREDS.sh @@ -273,8 +273,11 @@ rm -rf /tmp/ts54-creds # Check that globs work as expected mkdir -p /run/credstore echo -n a >/run/credstore/test.creds.first -echo -n b >/run/credstore/test.creds.second +# Make sure that when multiple credentials of the same name are found, the first one is used (/etc/credstore +# is searched before /run/credstore). +echo -n ignored >/run/credstore/test.creds.second mkdir -p /etc/credstore +echo -n b >/etc/credstore/test.creds.second echo -n c >/etc/credstore/test.creds.third systemd-run -p "ImportCredential=test.creds.*" \ --unit=test-54-ImportCredential.service \ From 831f208783aeac443e6f2fc2efc3119535a032ef Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 30 Jul 2024 16:16:26 +0200 Subject: [PATCH 4/5] 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 31e6194bec..b9120cc222 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 c79cf67445..fe19c8a657 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 c9b118a1a0..feafa15e43 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 75eca830f8..e1b09f6718 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 87b85d7709..88d7f71580 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 69f79984a5..2381662769 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 26b2c0fb4a..56e6d4b1ca 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 b801bfe853..22d72c8959 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 e04450d869..9a78e22fb5 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 97b719f52a..7633589b0d 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 259fbeaf5c..6ce76ded43 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 89d6dcdf03..9f7f4b1160 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 From e97429902dd75744e6df8d147e7ddea8d69a2e91 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 30 Jul 2024 16:22:34 +0200 Subject: [PATCH 5/5] units: Import tty specific credentials for each getty unit As explained in the previous commit, this allows us to configure agetty and login for individual ttys instead of globally. --- units/console-getty.service.in | 2 ++ units/container-getty@.service.in | 2 ++ units/getty@.service.in | 2 ++ units/serial-getty@.service.in | 2 ++ 4 files changed, 8 insertions(+) diff --git a/units/console-getty.service.in b/units/console-getty.service.in index 2de31dd01a..841c4496f0 100644 --- a/units/console-getty.service.in +++ b/units/console-getty.service.in @@ -37,6 +37,8 @@ KillMode=process {% endif %} IgnoreSIGPIPE=no SendSIGHUP=yes +ImportCredential=tty.console.agetty.*:agetty. +ImportCredential=tty.console.login.*:login. ImportCredential=agetty.* ImportCredential=login.* diff --git a/units/container-getty@.service.in b/units/container-getty@.service.in index 7e277a49bc..1f63383641 100644 --- a/units/container-getty@.service.in +++ b/units/container-getty@.service.in @@ -43,5 +43,7 @@ KillMode=process {% endif %} IgnoreSIGPIPE=no SendSIGHUP=yes +ImportCredential=tty.container.%I.agetty.*:agetty. +ImportCredential=tty.container.%I.login.*:login. ImportCredential=agetty.* ImportCredential=login.* diff --git a/units/getty@.service.in b/units/getty@.service.in index ce8db9764e..983f6dba49 100644 --- a/units/getty@.service.in +++ b/units/getty@.service.in @@ -53,6 +53,8 @@ KillMode=process {% endif %} IgnoreSIGPIPE=no SendSIGHUP=yes +ImportCredential=tty.virtual.%I.agetty.*:agetty. +ImportCredential=tty.virtual.%I.login.*:login. ImportCredential=agetty.* ImportCredential=login.* diff --git a/units/serial-getty@.service.in b/units/serial-getty@.service.in index 5669b19aff..afb8ad06a7 100644 --- a/units/serial-getty@.service.in +++ b/units/serial-getty@.service.in @@ -47,6 +47,8 @@ KillMode=process {% endif %} IgnoreSIGPIPE=no SendSIGHUP=yes +ImportCredential=tty.serial.%I.agetty.*:agetty. +ImportCredential=tty.serial.%I.login.*:login. ImportCredential=agetty.* ImportCredential=login.*