]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: Add support for renaming credentials with ImportCredential=
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Tue, 30 Jul 2024 14:16:26 +0000 (16:16 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 31 Jul 2024 13:52:27 +0000 (15:52 +0200)
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.

12 files changed:
man/org.freedesktop.systemd1.xml
man/systemd.exec.xml
src/core/dbus-execute.c
src/core/exec-credential.c
src/core/exec-credential.h
src/core/execute-serialize.c
src/core/execute.c
src/core/execute.h
src/core/load-fragment-gperf.gperf.in
src/core/load-fragment.c
src/shared/bus-unit-util.c
test/units/TEST-54-CREDS.sh

index 31e6194bec35587a3d71f73d71bebcad58d29f0e..b9120cc2228324101d329312ada59e606390efde 100644 (file)
@@ -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 {
 
     <!--property ImportCredential is not documented!-->
 
+    <!--property ImportCredentialEx is not documented!-->
+
     <!--property SupplementaryGroups is not documented!-->
 
     <!--property PAMName is not documented!-->
@@ -4488,6 +4492,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <variablelist class="dbus-property" generated="True" extra-ref="ImportCredential"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="ImportCredentialEx"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="SupplementaryGroups"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="PAMName"/>
@@ -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 {
 
     <!--property ImportCredential is not documented!-->
 
+    <!--property ImportCredentialEx is not documented!-->
+
     <!--property SupplementaryGroups is not documented!-->
 
     <!--property PAMName is not documented!-->
@@ -6603,6 +6613,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
 
     <variablelist class="dbus-property" generated="True" extra-ref="ImportCredential"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="ImportCredentialEx"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="SupplementaryGroups"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="PAMName"/>
@@ -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 {
 
     <!--property ImportCredential is not documented!-->
 
+    <!--property ImportCredentialEx is not documented!-->
+
     <!--property SupplementaryGroups is not documented!-->
 
     <!--property PAMName is not documented!-->
@@ -8420,6 +8436,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
 
     <variablelist class="dbus-property" generated="True" extra-ref="ImportCredential"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="ImportCredentialEx"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="SupplementaryGroups"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="PAMName"/>
@@ -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 {
 
     <!--property ImportCredential is not documented!-->
 
+    <!--property ImportCredentialEx is not documented!-->
+
     <!--property SupplementaryGroups is not documented!-->
 
     <!--property PAMName is not documented!-->
@@ -10332,6 +10354,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
 
     <variablelist class="dbus-property" generated="True" extra-ref="ImportCredential"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="ImportCredentialEx"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="SupplementaryGroups"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="PAMName"/>
@@ -12099,8 +12123,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
       <varname>ExecMainHandoffTimestampMonotonic</varname>, and
       <varname>ExecMainHandoffTimestamp</varname> were added in version 256.</para>
       <para><varname>StatusBusError</varname>,
-      <varname>StatusVarlinkError</varname>, and
-      <varname>PrivateTmpEx</varname> were added in version 257.</para>
+      <varname>StatusVarlinkError</varname>,
+      <varname>PrivateTmpEx</varname>, and
+      <varname>ImportCredentialEx</varname> were added in version 257.</para>
     </refsect2>
     <refsect2>
       <title>Socket Unit Objects</title>
@@ -12137,7 +12162,8 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
       <varname>EffectiveTasksMax</varname>,
       <varname>MemoryZSwapWriteback</varname>, and
       <varname>PassFileDescriptorsToExec</varname> were added in version 256.</para>
-      <para><varname>PrivateTmpEx</varname> was added in version 257.</para>
+      <para><varname>PrivateTmpEx</varname>, and
+      <varname>ImportCredentialEx</varname> were added in version 257.</para>
     </refsect2>
     <refsect2>
       <title>Mount Unit Objects</title>
@@ -12171,7 +12197,8 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
       <varname>EffectiveMemoryMax</varname>,
       <varname>EffectiveTasksMax</varname>, and
       <varname>MemoryZSwapWriteback</varname> were added in version 256.</para>
-      <para><varname>PrivateTmpEx</varname> was added in version 257.</para>
+      <para><varname>PrivateTmpEx</varname>, and
+      <varname>ImportCredentialEx</varname> were added in version 257.</para>
     </refsect2>
     <refsect2>
       <title>Swap Unit Objects</title>
@@ -12205,7 +12232,8 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
       <varname>EffectiveMemoryMax</varname>,
       <varname>EffectiveTasksMax</varname>, and
       <varname>MemoryZSwapWriteback</varname> were added in version 256.</para>
-      <para><varname>PrivateTmpEx</varname> was added in version 257.</para>
+      <para><varname>PrivateTmpEx</varname>, and
+      <varname>ImportCredentialEx</varname> were added in version 257.</para>
     </refsect2>
     <refsect2>
       <title>Slice Unit Objects</title>
index c79cf6744583d1fcdd07a0b86fc29dfc2b79eb8e..fe19c8a657cb2db5acf2d903b42d1173b6e8a490 100644 (file)
@@ -3520,6 +3520,18 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX
         <literal>[]</literal> wildcards are not permitted, nor are <literal>*</literal> wildcards anywhere
         except at the end of the glob expression.</para>
 
+        <para>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 <literal>ImportCredential=my.original.cred:my.renamed.cred</literal> is
+        specified, the service manager will read the <literal>my.original.cred</literal> credential and make
+        it available as the <literal>my.renamed.cred</literal> credential to the service. Similarly, if
+        <literal>ImportCredential=my.original.*:my.renamed.</literal> is specified, the service manager will
+        read all credentials starting with <literal>my.original.</literal> and make them available as
+        <literal>my.renamed.xxx</literal> to the service.</para>
+
+        <para>If <varname>ImportCredential=</varname> 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.</para>.
+
         <para>When multiple credentials of the same name are found, credentials found by
         <varname>LoadCredential=</varname> and <varname>LoadCredentialEncrypted=</varname> take priority over
         credentials found by <varname>ImportCredential=</varname>.</para>
index c9b118a1a04c78b2c91641e29ea2ed0b54fee8f7..feafa15e4303a1b2960ba8f164a244846be16f26 100644 (file)
@@ -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);
                 }
 
index 75eca830f80c080dba4fccb30a992fc4bf9cb36e..e1b09f671893a5fcd1fb468df92a5e034d53c6f2 100644 (file)
@@ -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);
index 87b85d770993c21be097e9f30ded650b55971fbe..88d7f715801f5caa3745133be105db734679eb7f 100644 (file)
@@ -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);
 
index 69f79984a54411026c2a1f2d76574cab7d9d153b..238166276904ecff992ea24853a5179eb2758b27 100644 (file)
@@ -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="))) {
index 26b2c0fb4afd18a4b0065e69394cb9f74760e078..56e6d4b1ca8a48b6c400046b21c7e93d980c7373 100644 (file)
@@ -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);
index b801bfe8532e15e1fe92574625a5c2e5f0a5db3c..22d72c8959ff3bd59eb963b0c872650e35a1b053 100644 (file)
@@ -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;
 };
index e04450d869dd2a0cdfa12be8ee621b623c70eeba..9a78e22fb528586dfa903f9be38fbd420bbf19c8 100644 (file)
 {{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)
index 97b719f52aa886c46a6a6fcc5e66149e002e90b7..7633589b0d49fbf54af20c53424d2dac7aeb0f1f 100644 (file)
@@ -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);
+                log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in \"%s\", ignoring: %m", word);
                 return 0;
         }
-        if (!credential_glob_valid(s)) {
-                log_syntax(unit, LOG_WARNING, filename, line, 0, "Credential name or glob \"%s\" not valid, ignoring.", 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 = set_put_strdup(import_credentials, s);
+        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;
 }
index 259fbeaf5c4f52307211835cd2561fb152cff98c..6ce76ded4319337776e7474cd3125e3f4ddd6c5f 100644 (file)
@@ -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)
index 89d6dcdf034de81c849dee724b890e9fd5bb79d4..9f7f4b11606f477d4937173b0a0399b65307e2cc 100755 (executable)
@@ -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