]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
import-creds: define a new dir where initrd configurators can pass credentials to...
authorLennart Poettering <lennart@poettering.net>
Fri, 23 Jun 2023 15:49:44 +0000 (17:49 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 4 Jul 2023 20:59:07 +0000 (22:59 +0200)
man/systemd.exec.xml
src/core/import-creds.c

index 5a917d83492f0c62c015e605f73893486879d007..ccec6ec4235de2f4cce9f11fcafa0bed9b90533a 100644 (file)
@@ -3274,18 +3274,20 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX
         11) with a prefix of <literal>io.systemd.credential:</literal> or
         <literal>io.systemd.credential.binary:</literal>. In both cases a key/value pair separated by
         <literal>=</literal> is expected, in the latter case the right-hand side is Base64 decoded when
-        parsed (thus permitting binary data to be passed in). Example
-        <ulink url="https://www.qemu.org/docs/master/system/index.html">qemu</ulink>
-        switch: <literal>-smbios
+        parsed (thus permitting binary data to be passed in). Example <ulink
+        url="https://www.qemu.org/docs/master/system/index.html">qemu</ulink> switch: <literal>-smbios
         type=11,value=io.systemd.credential:xx=yy</literal>, or <literal>-smbios
         type=11,value=io.systemd.credential.binary:rick=TmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXA=</literal>. Alternatively,
         use the <command>qemu</command> <literal>fw_cfg</literal> node
-        <literal>opt/io.systemd.credentials/</literal>. Example <command>qemu</command> switch: <literal>-fw_cfg
-        name=opt/io.systemd.credentials/mycred,string=supersecret</literal>. They may also be specified on
-        the kernel command line using the <literal>systemd.set_credential=</literal> switch (see
-        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>) and from
-        the UEFI firmware environment via
-        <citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
+        <literal>opt/io.systemd.credentials/</literal>. Example <command>qemu</command> switch:
+        <literal>-fw_cfg name=opt/io.systemd.credentials/mycred,string=supersecret</literal>. They may also
+        be passed from the UEFI firmware environment via
+        <citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+        from the initrd (see
+        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>), or be
+        specified on the kernel command line using the <literal>systemd.set_credential=</literal> switch (see
+        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry> – this is
+        not recommended since unprivileged userspace can read the kernel command line). </para>
 
         <para>If referencing an <constant>AF_UNIX</constant> stream socket to connect to, the connection will
         originate from an abstract namespace socket, that includes information about the unit and the
index ade509be348c11031465626b6171c2c0961cc9a0..6b1fca6f59a9b84dd75665a729a8d0b55f3e7614 100644 (file)
@@ -611,27 +611,157 @@ static int import_credentials_smbios(ImportCredentialContext *c) {
         return 0;
 }
 
+static int import_credentials_initrd(ImportCredentialContext *c) {
+        _cleanup_free_ DirectoryEntries *de = NULL;
+        _cleanup_close_ int source_dir_fd = -EBADF;
+        int r;
+
+        assert(c);
+
+        /* This imports credentials from /run/credentials/@initrd/ into our credentials directory and deletes
+         * the source directory afterwards. This is run once after the initrd → host transition. This is
+         * supposed to establish a well-defined avenue for initrd-based host configurators to pass
+         * credentials into the main system. */
+
+        if (in_initrd())
+                return 0;
+
+        source_dir_fd = open("/run/credentials/@initrd", O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+        if (source_dir_fd < 0) {
+                if (errno == ENOENT)
+                        log_debug_errno(errno, "No credentials passed from initrd.");
+                else
+                        log_warning_errno(errno, "Failed to open '/run/credentials/@initrd', ignoring: %m");
+                return 0;
+        }
+
+        r = readdir_all(source_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de);
+        if (r < 0) {
+                log_warning_errno(r, "Failed to read '/run/credentials/@initrd' contents, ignoring: %m");
+                return 0;
+        }
+
+        FOREACH_ARRAY(entry, de->entries, de->n_entries) {
+                _cleanup_close_ int cfd = -EBADF, nfd = -EBADF;
+                const struct dirent *d = *entry;
+                struct stat st;
+
+                if (!credential_name_valid(d->d_name)) {
+                        log_warning("Credential '%s' has invalid name, ignoring.", d->d_name);
+                        continue;
+                }
+
+                cfd = openat(source_dir_fd, d->d_name, O_RDONLY|O_CLOEXEC);
+                if (cfd < 0) {
+                        log_warning_errno(errno, "Failed to open %s, ignoring: %m", d->d_name);
+                        continue;
+                }
+
+                if (fstat(cfd, &st) < 0) {
+                        log_warning_errno(errno, "Failed to stat %s, ignoring: %m", d->d_name);
+                        continue;
+                }
+
+                r = stat_verify_regular(&st);
+                if (r < 0) {
+                        log_warning_errno(r, "Credential file %s is not a regular file, ignoring: %m", d->d_name);
+                        continue;
+                }
+
+                if (!credential_size_ok(c, d->d_name, st.st_size))
+                        continue;
+
+                r = acquire_credential_directory(c);
+                if (r < 0)
+                        return r;
+
+                nfd = open_credential_file_for_write(c->target_dir_fd, SYSTEM_CREDENTIALS_DIRECTORY, d->d_name);
+                if (nfd == -EEXIST)
+                        continue;
+                if (nfd < 0)
+                        return nfd;
+
+                r = copy_bytes(cfd, nfd, st.st_size, 0);
+                if (r < 0) {
+                        (void) unlinkat(c->target_dir_fd, d->d_name, 0);
+                        return log_error_errno(r, "Failed to create credential '%s': %m", d->d_name);
+                }
+
+                c->size_sum += st.st_size;
+                c->n_credentials++;
+
+                log_debug("Successfully copied initrd credential '%s'.", d->d_name);
+
+                (void) unlinkat(source_dir_fd, d->d_name, 0);
+        }
+
+        source_dir_fd = safe_close(source_dir_fd);
+
+        if (rmdir("/run/credentials/@initrd") < 0)
+                log_warning_errno(errno, "Failed to remove /run/credentials/@initrd after import, ignoring: %m");
+
+        return 0;
+}
+
 static int import_credentials_trusted(void) {
         _cleanup_(import_credentials_context_free) ImportCredentialContext c = {
                 .target_dir_fd = -EBADF,
         };
-        int q, w, r;
+        int q, w, r, y;
+
+        /* This is invoked during early boot when no credentials have been imported so far. (Specifically, if
+         * the $CREDENTIALS_DIRECTORY or $ENCRYPTED_CREDENTIALS_DIRECTORY environment variables are not set
+         * yet.) */
 
         r = import_credentials_qemu(&c);
         w = import_credentials_smbios(&c);
         q = import_credentials_proc_cmdline(&c);
+        y = import_credentials_initrd(&c);
 
         if (c.n_credentials > 0) {
                 int z;
 
-                log_debug("Imported %u credentials from kernel command line/smbios/fw_cfg.", c.n_credentials);
+                log_debug("Imported %u credentials from kernel command line/smbios/fw_cfg/initrd.", c.n_credentials);
 
                 z = finalize_credentials_dir(SYSTEM_CREDENTIALS_DIRECTORY, "CREDENTIALS_DIRECTORY");
                 if (z < 0)
                         return z;
         }
 
-        return r < 0 ? r : w < 0 ? w : q;
+        return r < 0 ? r : w < 0 ? w : q < 0 ? q : y;
+}
+
+static int merge_credentials_trusted(const char *creds_dir) {
+        _cleanup_(import_credentials_context_free) ImportCredentialContext c = {
+                .target_dir_fd = -EBADF,
+        };
+        int r;
+
+        /* This is invoked after the initrd → host transitions, when credentials already have been imported,
+         * but we might want to import some more from the initrd. */
+
+        if (in_initrd())
+                return 0;
+
+        /* Do not try to merge initrd credentials into foreign credentials directories */
+        if (!path_equal_ptr(creds_dir, SYSTEM_CREDENTIALS_DIRECTORY)) {
+                log_debug("Not importing initrd credentials, as foreign $CREDENTIALS_DIRECTORY has been set.");
+                return 0;
+        }
+
+        r = import_credentials_initrd(&c);
+
+        if (c.n_credentials > 0) {
+                int z;
+
+                log_debug("Merged %u credentials from initrd.", c.n_credentials);
+
+                z = finalize_credentials_dir(SYSTEM_CREDENTIALS_DIRECTORY, "CREDENTIALS_DIRECTORY");
+                if (z < 0)
+                        return z;
+        }
+
+        return r;
 }
 
 static int symlink_credential_dir(const char *envvar, const char *path, const char *where) {
@@ -690,6 +820,10 @@ int import_credentials(void) {
                                 r = q;
                 }
 
+                q = merge_credentials_trusted(received_creds_dir);
+                if (r >= 0)
+                        r = q;
+
         } else {
                 _cleanup_free_ char *v = NULL;