]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: add SetCredentials{,Encrypted} to varlink Unit.StartTransient 41874/head
authorMichael Vogt <michael@amutable.com>
Tue, 5 May 2026 08:16:11 +0000 (10:16 +0200)
committerMichael Vogt <michael@amutable.com>
Wed, 13 May 2026 17:28:35 +0000 (19:28 +0200)
This commit adds support to set `SetCredentials` and
`SetCredentialsEncrypted` in the `io.systemd.Unit.StartTransient`
varlink call.

src/core/varlink-unit.c
src/shared/varlink-io.systemd.Unit.c
test/units/TEST-26-SYSTEMCTL.sh

index 10fbc1d7bcf4f4883a778ec5e4195a372bab3b13..3b289e46458a2847bb6e53e3e490f781394653b5 100644 (file)
@@ -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;
 }
 
index ae0f78db9c2e8d450dff3d78cf201119b7cca639..f4ff0b648a840a15ae98e8756ea2f96b9522e7bd 100644 (file)
@@ -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"),
index 6f615eb1c3ab640c78ff34d0dd616fc749be300a..a98ef7646d33bdab414d87ed48d14dc4e5b17abc 100755 (executable)
@@ -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