From de2ad965c2ef6f775f61019ad86899bd5a5bf410 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 5 May 2026 10:16:11 +0200 Subject: [PATCH] core: add SetCredentials{,Encrypted} to varlink Unit.StartTransient This commit adds support to set `SetCredentials` and `SetCredentialsEncrypted` in the `io.systemd.Unit.StartTransient` varlink call. --- src/core/varlink-unit.c | 120 ++++++++++++++++++++++++++- src/shared/varlink-io.systemd.Unit.c | 7 +- test/units/TEST-26-SYSTEMCTL.sh | 20 ++++- 3 files changed, 141 insertions(+), 6 deletions(-) diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index 10fbc1d7bcf..3b289e46458 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -11,6 +11,7 @@ #include "execute.h" #include "format-util.h" #include "install.h" +#include "iovec-util.h" #include "job.h" #include "json-util.h" #include "locale-util.h" @@ -708,17 +709,38 @@ typedef struct TransientWorkingDirectory { bool missing_ok; } TransientWorkingDirectory; +typedef struct TransientSetCredential { + const char *id; + struct iovec value; +} TransientSetCredential; + typedef struct TransientExecContextParameters { bool present; bool working_directory_set; TransientWorkingDirectory working_directory; bool environment_set; char **environment; + + bool set_credentials_set; + TransientSetCredential *set_credentials; + size_t n_set_credentials; + + bool set_credentials_encrypted_set; + TransientSetCredential *set_credentials_encrypted; + size_t n_set_credentials_encrypted; } TransientExecContextParameters; +static void transient_set_credential_array_free(TransientSetCredential *items, size_t n) { + FOREACH_ARRAY(item, items, n) + iovec_done_erase(&item->value); + free(items); +} + static void transient_exec_context_parameters_done(TransientExecContextParameters *p) { assert(p); strv_free(p->environment); + transient_set_credential_array_free(p->set_credentials, p->n_set_credentials); + transient_set_credential_array_free(p->set_credentials_encrypted, p->n_set_credentials_encrypted); } typedef struct TransientServiceParameters { @@ -827,11 +849,67 @@ static int dispatch_transient_environment(const char *name, sd_json_variant *var return sd_json_dispatch_strv(name, variant, flags, &p->environment); } +static int dispatch_transient_set_credential_array( + sd_json_variant *variant, + TransientSetCredential **ret_items, + size_t *ret_n) { + + static const sd_json_dispatch_field item_dispatch[] = { + { "id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(TransientSetCredential, id), SD_JSON_MANDATORY }, + { "value", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(TransientSetCredential, value), SD_JSON_MANDATORY }, + {} + }; + + TransientSetCredential *items = NULL; + size_t n = 0; + int r; + + assert(ret_items); + assert(ret_n); + + CLEANUP_ARRAY(items, n, transient_set_credential_array_free); + + size_t n_items = sd_json_variant_elements(variant); + if (n_items == 0) { + *ret_items = NULL; + *ret_n = 0; + return 0; + } + + items = new0(TransientSetCredential, n_items); + if (!items) + return -ENOMEM; + + for (; n < n_items; n++) { + r = sd_json_dispatch(sd_json_variant_by_index(variant, n), item_dispatch, /* flags= */ 0, &items[n]); + if (r < 0) + return r; + } + + *ret_n = n; + *ret_items = TAKE_PTR(items); + return 0; +} + +static int dispatch_transient_set_credential(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + TransientExecContextParameters *p = ASSERT_PTR(userdata); + p->set_credentials_set = true; + return dispatch_transient_set_credential_array(variant, &p->set_credentials, &p->n_set_credentials); +} + +static int dispatch_transient_set_credential_encrypted(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + TransientExecContextParameters *p = ASSERT_PTR(userdata); + p->set_credentials_encrypted_set = true; + return dispatch_transient_set_credential_array(variant, &p->set_credentials_encrypted, &p->n_set_credentials_encrypted); +} + static int dispatch_transient_exec_context(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { /* Key names compatible with D-Bus property names */ static const sd_json_dispatch_field exec_dispatch[] = { - { "WorkingDirectory", SD_JSON_VARIANT_OBJECT, dispatch_transient_working_directory, 0, 0 }, - { "Environment", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_transient_environment, 0, 0 }, + { "WorkingDirectory", SD_JSON_VARIANT_OBJECT, dispatch_transient_working_directory, 0, 0 }, + { "Environment", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_transient_environment, 0, 0 }, + { "SetCredential", SD_JSON_VARIANT_ARRAY, dispatch_transient_set_credential, 0, 0 }, + { "SetCredentialEncrypted", SD_JSON_VARIANT_ARRAY, dispatch_transient_set_credential_encrypted, 0, 0 }, {} }; @@ -900,6 +978,32 @@ static int transient_unit_apply_properties(Unit *u, StartTransientContextParamet return 0; } +static int transient_apply_set_credentials( + Unit *u, + ExecContext *c, + const TransientSetCredential *items, + size_t n_items, + bool encrypted) { + + int r; + + assert(u); + assert(c); + + FOREACH_ARRAY(item, items, n_items) { + const char *err = NULL; + + r = exec_context_apply_set_credential(u, c, item->id, item->value.iov_base, item->value.iov_len, + encrypted, UNIT_RUNTIME|UNIT_PRIVATE, &err); + if (r == -EINVAL) + return log_debug_errno(r, "%s: %s", err, item->id); + if (r < 0) + return r; + } + + return 0; +} + static int transient_exec_context_apply_properties(Unit *u, ExecContext *c, TransientExecContextParameters *p) { int r; @@ -950,6 +1054,18 @@ static int transient_exec_context_apply_properties(Unit *u, ExecContext *c, Tran return r; } + if (p->set_credentials_set) { + r = transient_apply_set_credentials(u, c, p->set_credentials, p->n_set_credentials, /* encrypted= */ false); + if (r < 0) + return r; + } + + if (p->set_credentials_encrypted_set) { + r = transient_apply_set_credentials(u, c, p->set_credentials_encrypted, p->n_set_credentials_encrypted, /* encrypted= */ true); + if (r < 0) + return r; + } + return 0; } diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index ae0f78db9c2..f4ff0b648a8 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -1184,9 +1184,10 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(parameter, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); /* UnitContext is used both as input to StartTransient (subset settable at creation time: ID, - * Description, Service, and the Exec subset {WorkingDirectory, Environment}) and as output from - * List/StartTransient (full unit configuration). Fields that are not settable at creation time are - * rejected with PropertyNotSupported when supplied as input. */ + * Description, Service, and the Exec subset {WorkingDirectory, Environment, SetCredential, + * SetCredentialEncrypted}) and as output from List/StartTransient (full unit configuration). Fields + * that are not settable at creation time are rejected with PropertyNotSupported when supplied as + * input. */ static SD_VARLINK_DEFINE_STRUCT_TYPE( UnitContext, SD_VARLINK_FIELD_COMMENT("The unit type"), diff --git a/test/units/TEST-26-SYSTEMCTL.sh b/test/units/TEST-26-SYSTEMCTL.sh index 6f615eb1c3a..a98ef7646d3 100755 --- a/test/units/TEST-26-SYSTEMCTL.sh +++ b/test/units/TEST-26-SYSTEMCTL.sh @@ -646,6 +646,16 @@ varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ timeout 30 bash -c 'until systemctl is-active varlink-transient-wd-home.service; do sleep 0.5; done' systemctl show -P WorkingDirectory varlink-transient-wd-home.service | grep '^~$' >/dev/null +# Exec.SetCredential: pass a credential and verify the running process can read it +defer_transient_cleanup varlink-transient-cred.service +CRED_VALUE_B64=$(printf 'secret-value' | base64 -w0) +CRED_OUTPUT=$(mktemp) +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + "{\"context\":{\"ID\":\"varlink-transient-cred.service\",\"Exec\":{\"SetCredential\":[{\"id\":\"mycred\",\"value\":\"${CRED_VALUE_B64}\"}]},\"Service\":{\"Type\":\"oneshot\",\"RemainAfterExit\":true,\"ExecStart\":[{\"path\":\"/bin/sh\",\"arguments\":[\"/bin/sh\",\"-c\",\"cat \$CREDENTIALS_DIRECTORY/mycred > ${CRED_OUTPUT}\"]}]}}}" +timeout 30 bash -c "until systemctl is-active varlink-transient-cred.service; do sleep 0.5; done" +grep '^secret-value$' "$CRED_OUTPUT" >/dev/null +rm -f "$CRED_OUTPUT" + # Error cases: verify specific varlink error types set +o pipefail varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ @@ -667,7 +677,15 @@ varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ defer_transient_cleanup varlink-transient-bad-env.service varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ '{"context":{"ID":"varlink-transient-bad-env.service","Exec":{"Environment":["not_an_env_var"]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "io.systemd.Unit.BadUnitSetting" -# Exec on a unit type without an exec context (.target) is rejected +# Invalid credential ID +defer_transient_cleanup varlink-transient-bad-cred-id.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-bad-cred-id.service","Exec":{"SetCredential":[{"id":"bad/id","value":"YWJj"}]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "io.systemd.Unit.BadUnitSetting" +# Invalid base64 value for credential (rejected at JSON dispatch time as a parameter error) +defer_transient_cleanup varlink-transient-bad-cred-value.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-bad-cred-value.service","Exec":{"SetCredential":[{"id":"mycred","value":"!!!not_base64!!!"}]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "Invalid argument" +# Exec on a unit type without an exec context (.slice) is rejected varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ '{"context":{"ID":"varlink-transient-exec.slice","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false}}}}' |& grep "io.systemd.Unit.UnitTypeNotSupported" # Unknown field in Exec is rejected as PropertyNotSupported -- 2.47.3