From: Lennart Poettering Date: Fri, 23 Jun 2023 15:49:44 +0000 (+0200) Subject: import-creds: define a new dir where initrd configurators can pass credentials to... X-Git-Tag: v254-rc1~33^2~19 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=0dea5b7719b6dc0e2026923fc6ad0a80a8fd1db5;p=thirdparty%2Fsystemd.git import-creds: define a new dir where initrd configurators can pass credentials to host --- diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 5a917d83492..ccec6ec4235 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -3274,18 +3274,20 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX 11) with a prefix of io.systemd.credential: or io.systemd.credential.binary:. In both cases a key/value pair separated by = is expected, in the latter case the right-hand side is Base64 decoded when - parsed (thus permitting binary data to be passed in). Example - qemu - switch: -smbios + parsed (thus permitting binary data to be passed in). Example qemu switch: -smbios type=11,value=io.systemd.credential:xx=yy, or -smbios type=11,value=io.systemd.credential.binary:rick=TmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXA=. Alternatively, use the qemu fw_cfg node - opt/io.systemd.credentials/. Example qemu switch: -fw_cfg - name=opt/io.systemd.credentials/mycred,string=supersecret. They may also be specified on - the kernel command line using the systemd.set_credential= switch (see - systemd1) and from - the UEFI firmware environment via - systemd-stub7. + opt/io.systemd.credentials/. Example qemu switch: + -fw_cfg name=opt/io.systemd.credentials/mycred,string=supersecret. They may also + be passed from the UEFI firmware environment via + systemd-stub7, + from the initrd (see + systemd1), or be + specified on the kernel command line using the systemd.set_credential= switch (see + systemd1 – this is + not recommended since unprivileged userspace can read the kernel command line). If referencing an AF_UNIX stream socket to connect to, the connection will originate from an abstract namespace socket, that includes information about the unit and the diff --git a/src/core/import-creds.c b/src/core/import-creds.c index ade509be348..6b1fca6f59a 100644 --- a/src/core/import-creds.c +++ b/src/core/import-creds.c @@ -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;