1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include "confidential-virt.h"
7 #include "creds-util.h"
10 #include "format-util.h"
12 #include "hexdecoct.h"
13 #include "import-creds.h"
14 #include "initrd-util.h"
16 #include "mkdir-label.h"
17 #include "mount-util.h"
18 #include "mountpoint-util.h"
19 #include "parse-util.h"
20 #include "path-util.h"
21 #include "proc-cmdline.h"
22 #include "recurse-dir.h"
26 /* This imports credentials passed in from environments higher up (VM manager, boot loader, …) and rearranges
27 * them so that later code can access them using our regular credential protocol
28 * (i.e. $CREDENTIALS_DIRECTORY). It's supposed to be minimal glue to unify behaviour how PID 1 (and
29 * generators invoked by it) can acquire credentials from outside, to mimic how we support it for containers,
30 * but on VM/physical environments.
32 * This does four things:
34 * 1. It imports credentials picked up by sd-boot (and placed in the /.extra/credentials/ dir in the initrd)
35 * and puts them in /run/credentials/@encrypted/. Note that during the initrd→host transition the initrd root
36 * file system is cleaned out, thus it is essential we pick up these files before they are deleted. Note
37 * that these credentials originate from an untrusted source, i.e. the ESP and are not
38 * pre-authenticated. They still have to be authenticated before use.
40 * 2. It imports credentials from /proc/cmdline and puts them in /run/credentials/@system/. These come from a
41 * trusted environment (i.e. the boot loader), and are typically authenticated (if authentication is done
42 * at all). However, they are world-readable, which might be less than ideal. Hence only use this for data
43 * that doesn't require trust.
45 * 3. It imports credentials passed in through qemu's fw_cfg logic. Specifically, credential data passed in
46 * /sys/firmware/qemu_fw_cfg/by_name/opt/io.systemd.credentials/ is picked up and also placed in
47 * /run/credentials/@system/.
49 * 4. It imports credentials passed in via the DMI/SMBIOS OEM string tables, quite similar to fw_cfg. It
50 * looks for strings starting with "io.systemd.credential:" and "io.systemd.credential.binary:". Both
51 * expect a key=value assignment, but in the latter case the value is Base64 decoded, allowing binary
52 * credentials to be passed in.
54 * If it picked up any credentials it will set the $CREDENTIALS_DIRECTORY and
55 * $ENCRYPTED_CREDENTIALS_DIRECTORY environment variables to point to these directories, so that processes
56 * can find them there later on. If "ramfs" is available $CREDENTIALS_DIRECTORY will be backed by it (but
57 * $ENCRYPTED_CREDENTIALS_DIRECTORY is just a regular tmpfs).
59 * Net result: the service manager can pick up trusted credentials from $CREDENTIALS_DIRECTORY afterwards,
60 * and untrusted ones from $ENCRYPTED_CREDENTIALS_DIRECTORY. */
62 typedef struct ImportCredentialContext
{
65 unsigned n_credentials
;
66 } ImportCredentialContext
;
68 static void import_credentials_context_free(ImportCredentialContext
*c
) {
71 c
->target_dir_fd
= safe_close(c
->target_dir_fd
);
74 static int acquire_credential_directory(ImportCredentialContext
*c
, const char *path
, bool with_mount
) {
80 if (c
->target_dir_fd
>= 0)
81 return c
->target_dir_fd
;
83 r
= path_is_mount_point(path
, NULL
, 0);
86 return log_error_errno(r
, "Failed to determine if %s is a mount point: %m", path
);
88 r
= mkdir_safe_label(path
, 0700, 0, 0, MKDIR_WARN_MODE
);
90 return log_error_errno(r
, "Failed to create %s mount point: %m", path
);
92 r
= 0; /* Now it exists and is not a mount point */
95 /* If already a mount point, then remount writable */
96 (void) mount_nofollow_verbose(LOG_WARNING
, NULL
, path
, NULL
, MS_BIND
|MS_REMOUNT
|credentials_fs_mount_flags(/* ro= */ false), NULL
);
98 /* If not a mount point yet, and the credentials are not encrypted, then let's try to mount a no-swap fs there */
99 (void) mount_credentials_fs(path
, CREDENTIALS_TOTAL_SIZE_MAX
, /* ro= */ false);
101 c
->target_dir_fd
= open(path
, O_RDONLY
|O_DIRECTORY
|O_CLOEXEC
);
102 if (c
->target_dir_fd
< 0)
103 return log_error_errno(errno
, "Failed to open %s: %m", path
);
105 return c
->target_dir_fd
;
108 static int open_credential_file_for_write(int target_dir_fd
, const char *dir_name
, const char *n
) {
111 assert(target_dir_fd
>= 0);
115 fd
= openat(target_dir_fd
, n
, O_WRONLY
|O_CLOEXEC
|O_CREAT
|O_EXCL
|O_NOFOLLOW
, 0400);
117 if (errno
== EEXIST
) /* In case of EEXIST we'll only debug log! */
118 return log_debug_errno(errno
, "Credential '%s' set twice, ignoring.", n
);
120 return log_error_errno(errno
, "Failed to create %s/%s: %m", dir_name
, n
);
126 static bool credential_size_ok(ImportCredentialContext
*c
, const char *name
, uint64_t size
) {
130 if (size
> CREDENTIAL_SIZE_MAX
) {
131 log_warning("Credential '%s' is larger than allowed limit (%s > %s), skipping.", name
, FORMAT_BYTES(size
), FORMAT_BYTES(CREDENTIAL_SIZE_MAX
));
135 if (size
> CREDENTIALS_TOTAL_SIZE_MAX
- c
->size_sum
) {
136 log_warning("Accumulated credential size would be above allowed limit (%s+%s > %s), skipping '%s'.",
137 FORMAT_BYTES(c
->size_sum
), FORMAT_BYTES(size
), FORMAT_BYTES(CREDENTIALS_TOTAL_SIZE_MAX
), name
);
144 static int finalize_credentials_dir(const char *dir
, const char *envvar
) {
150 /* Try to make the credentials directory read-only now */
152 r
= make_mount_point(dir
);
154 log_warning_errno(r
, "Failed to make '%s' a mount point, ignoring: %m", dir
);
156 (void) mount_nofollow_verbose(LOG_WARNING
, NULL
, dir
, NULL
, MS_BIND
|MS_REMOUNT
|credentials_fs_mount_flags(/* ro= */ true), NULL
);
158 if (setenv(envvar
, dir
, /* overwrite= */ true) < 0)
159 return log_error_errno(errno
, "Failed to set $%s environment variable: %m", envvar
);
164 static int import_credentials_boot(void) {
165 _cleanup_(import_credentials_context_free
) ImportCredentialContext context
= {
166 .target_dir_fd
= -EBADF
,
170 /* systemd-stub will wrap sidecar *.cred files from the UEFI kernel image directory into initrd
171 * cpios, so that they unpack into /.extra/. We'll pick them up from there and copy them into /run/
172 * so that we can access them during the entire runtime (note that the initrd file system is erased
173 * during the initrd → host transition). Note that these credentials originate from an untrusted
174 * source (i.e. the ESP typically) and thus need to be authenticated later. We thus put them in a
175 * directory separate from the usual credentials which are from a trusted source. */
181 "/.extra/credentials/", /* specific to this boot menu */
182 "/.extra/global_credentials/") { /* boot partition wide */
184 _cleanup_free_ DirectoryEntries
*de
= NULL
;
185 _cleanup_close_
int source_dir_fd
= -EBADF
;
187 source_dir_fd
= open(p
, O_RDONLY
|O_DIRECTORY
|O_CLOEXEC
|O_NOFOLLOW
);
188 if (source_dir_fd
< 0) {
189 if (errno
== ENOENT
) {
190 log_debug("No credentials passed via %s.", p
);
194 log_warning_errno(errno
, "Failed to open '%s', ignoring: %m", p
);
198 r
= readdir_all(source_dir_fd
, RECURSE_DIR_SORT
|RECURSE_DIR_IGNORE_DOT
, &de
);
200 log_warning_errno(r
, "Failed to read '%s' contents, ignoring: %m", p
);
204 for (size_t i
= 0; i
< de
->n_entries
; i
++) {
205 const struct dirent
*d
= de
->entries
[i
];
206 _cleanup_close_
int cfd
= -EBADF
, nfd
= -EBADF
;
207 _cleanup_free_
char *n
= NULL
;
211 e
= endswith(d
->d_name
, ".cred");
215 /* drop .cred suffix (which we want in the ESP sidecar dir, but not for our internal
217 n
= strndup(d
->d_name
, e
- d
->d_name
);
221 if (!credential_name_valid(n
)) {
222 log_warning("Credential '%s' has invalid name, ignoring.", d
->d_name
);
226 cfd
= openat(source_dir_fd
, d
->d_name
, O_RDONLY
|O_CLOEXEC
);
228 log_warning_errno(errno
, "Failed to open %s, ignoring: %m", d
->d_name
);
232 if (fstat(cfd
, &st
) < 0) {
233 log_warning_errno(errno
, "Failed to stat %s, ignoring: %m", d
->d_name
);
237 r
= stat_verify_regular(&st
);
239 log_warning_errno(r
, "Credential file %s is not a regular file, ignoring: %m", d
->d_name
);
243 if (!credential_size_ok(&context
, n
, st
.st_size
))
246 r
= acquire_credential_directory(&context
, ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY
, /* with_mount= */ false);
250 nfd
= open_credential_file_for_write(context
.target_dir_fd
, ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY
, n
);
256 r
= copy_bytes(cfd
, nfd
, st
.st_size
, 0);
258 (void) unlinkat(context
.target_dir_fd
, n
, 0);
259 return log_error_errno(r
, "Failed to create credential '%s': %m", n
);
262 context
.size_sum
+= st
.st_size
;
263 context
.n_credentials
++;
265 log_debug("Successfully copied boot credential '%s'.", n
);
269 if (context
.n_credentials
> 0) {
270 log_debug("Imported %u credentials from boot loader.", context
.n_credentials
);
272 r
= finalize_credentials_dir(ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY
, "ENCRYPTED_CREDENTIALS_DIRECTORY");
280 static int proc_cmdline_callback(const char *key
, const char *value
, void *data
) {
281 ImportCredentialContext
*c
= ASSERT_PTR(data
);
282 _cleanup_free_
void *binary
= NULL
;
283 _cleanup_free_
char *n
= NULL
;
284 _cleanup_close_
int nfd
= -EBADF
;
285 const char *colon
, *d
;
292 if (proc_cmdline_key_streq(key
, "systemd.set_credential"))
294 else if (proc_cmdline_key_streq(key
, "systemd.set_credential_binary"))
299 colon
= value
? strchr(value
, ':') : NULL
;
301 log_warning("Credential assignment through kernel command line lacks ':' character, ignoring: %s", value
);
305 n
= strndup(value
, colon
- value
);
309 if (!credential_name_valid(n
)) {
310 log_warning("Credential name '%s' is invalid, ignoring.", n
);
317 r
= unbase64mem(colon
, SIZE_MAX
, &binary
, &l
);
319 log_warning_errno(r
, "Failed to decode binary credential '%s' data, ignoring: %m", n
);
329 if (!credential_size_ok(c
, n
, l
))
332 r
= acquire_credential_directory(c
, SYSTEM_CREDENTIALS_DIRECTORY
, /* with_mount= */ true);
336 nfd
= open_credential_file_for_write(c
->target_dir_fd
, SYSTEM_CREDENTIALS_DIRECTORY
, n
);
342 r
= loop_write(nfd
, d
, l
);
344 (void) unlinkat(c
->target_dir_fd
, n
, 0);
345 return log_error_errno(r
, "Failed to write credential: %m");
351 log_debug("Successfully processed kernel command line credential '%s'.", n
);
356 static int import_credentials_proc_cmdline(ImportCredentialContext
*c
) {
361 r
= proc_cmdline_parse(proc_cmdline_callback
, c
, 0);
363 return log_error_errno(r
, "Failed to parse /proc/cmdline: %m");
368 #define QEMU_FWCFG_PATH "/sys/firmware/qemu_fw_cfg/by_name/opt/io.systemd.credentials"
370 static int import_credentials_qemu(ImportCredentialContext
*c
) {
371 _cleanup_free_ DirectoryEntries
*de
= NULL
;
372 _cleanup_close_
int source_dir_fd
= -EBADF
;
377 if (detect_container() > 0) /* don't access /sys/ in a container */
380 if (detect_confidential_virtualization() > 0) /* don't trust firmware if confidential VMs */
383 source_dir_fd
= open(QEMU_FWCFG_PATH
, O_RDONLY
|O_DIRECTORY
|O_CLOEXEC
);
384 if (source_dir_fd
< 0) {
385 if (errno
== ENOENT
) {
386 log_debug("No credentials passed via fw_cfg.");
390 log_warning_errno(errno
, "Failed to open '" QEMU_FWCFG_PATH
"', ignoring: %m");
394 r
= readdir_all(source_dir_fd
, RECURSE_DIR_SORT
|RECURSE_DIR_IGNORE_DOT
, &de
);
396 log_warning_errno(r
, "Failed to read '" QEMU_FWCFG_PATH
"' contents, ignoring: %m");
400 for (size_t i
= 0; i
< de
->n_entries
; i
++) {
401 const struct dirent
*d
= de
->entries
[i
];
402 _cleanup_close_
int vfd
= -EBADF
, rfd
= -EBADF
, nfd
= -EBADF
;
403 _cleanup_free_
char *szs
= NULL
;
406 if (!credential_name_valid(d
->d_name
)) {
407 log_warning("Credential '%s' has invalid name, ignoring.", d
->d_name
);
411 vfd
= openat(source_dir_fd
, d
->d_name
, O_RDONLY
|O_DIRECTORY
|O_CLOEXEC
);
413 log_warning_errno(errno
, "Failed to open '" QEMU_FWCFG_PATH
"'/%s/, ignoring: %m", d
->d_name
);
417 r
= read_virtual_file_at(vfd
, "size", LINE_MAX
, &szs
, NULL
);
419 log_warning_errno(r
, "Failed to read '" QEMU_FWCFG_PATH
"'/%s/size, ignoring: %m", d
->d_name
);
423 r
= safe_atou64(strstrip(szs
), &sz
);
425 log_warning_errno(r
, "Failed to parse size of credential '%s', ignoring: %s", d
->d_name
, szs
);
429 if (!credential_size_ok(c
, d
->d_name
, sz
))
432 /* Ideally we'd just symlink the data here. Alas the kernel driver exports the raw file as
433 * having size zero, and we'd rather not have applications support such credential
434 * files. Let's hence copy the files to make them regular. */
436 rfd
= openat(vfd
, "raw", O_RDONLY
|O_CLOEXEC
);
438 log_warning_errno(errno
, "Failed to open '" QEMU_FWCFG_PATH
"'/%s/raw, ignoring: %m", d
->d_name
);
442 r
= acquire_credential_directory(c
, SYSTEM_CREDENTIALS_DIRECTORY
, /* with_mount= */ true);
446 nfd
= open_credential_file_for_write(c
->target_dir_fd
, SYSTEM_CREDENTIALS_DIRECTORY
, d
->d_name
);
452 r
= copy_bytes(rfd
, nfd
, sz
, 0);
454 (void) unlinkat(c
->target_dir_fd
, d
->d_name
, 0);
455 return log_error_errno(r
, "Failed to create credential '%s': %m", d
->d_name
);
461 log_debug("Successfully copied qemu fw_cfg credential '%s'.", d
->d_name
);
467 static int parse_smbios_strings(ImportCredentialContext
*c
, const char *data
, size_t size
) {
473 assert(data
|| size
== 0);
475 /* Unpacks a packed series of SMBIOS OEM vendor strings. These are a series of NUL terminated
476 * strings, one after the other. */
478 for (p
= data
, left
= size
; left
> 0; p
+= skip
, left
-= skip
) {
479 _cleanup_free_
void *buf
= NULL
;
480 _cleanup_free_
char *cn
= NULL
;
481 _cleanup_close_
int nfd
= -EBADF
;
482 const char *nul
, *n
, *eq
;
484 size_t buflen
, cdata_len
;
487 nul
= memchr(p
, 0, left
);
489 skip
= (nul
- p
) + 1;
495 if (nul
- p
== 0) /* Skip empty strings */
498 /* Only care about strings starting with either of these two prefixes */
499 if ((n
= memory_startswith(p
, nul
- p
, "io.systemd.credential:")))
501 else if ((n
= memory_startswith(p
, nul
- p
, "io.systemd.credential.binary:")))
504 _cleanup_free_
char *escaped
= NULL
;
506 escaped
= cescape_length(p
, nul
- p
);
507 log_debug("Ignoring OEM string: %s", strnull(escaped
));
511 eq
= memchr(n
, '=', nul
- n
);
513 log_warning("SMBIOS OEM string lacks '=' character, ignoring.");
517 cn
= memdup_suffix0(n
, eq
- n
);
521 if (!credential_name_valid(cn
)) {
522 log_warning("SMBIOS credential name '%s' is not valid, ignoring: %m", cn
);
526 /* Optionally base64 decode the data, if requested, to allow binary credentials */
528 r
= unbase64mem(eq
+ 1, nul
- (eq
+ 1), &buf
, &buflen
);
530 log_warning_errno(r
, "Failed to base64 decode credential '%s', ignoring: %m", cn
);
538 cdata_len
= nul
- (eq
+ 1);
541 if (!credential_size_ok(c
, cn
, cdata_len
))
544 r
= acquire_credential_directory(c
, SYSTEM_CREDENTIALS_DIRECTORY
, /* with_mount= */ true);
548 nfd
= open_credential_file_for_write(c
->target_dir_fd
, SYSTEM_CREDENTIALS_DIRECTORY
, cn
);
554 r
= loop_write(nfd
, cdata
, cdata_len
);
556 (void) unlinkat(c
->target_dir_fd
, cn
, 0);
557 return log_error_errno(r
, "Failed to write credential: %m");
560 c
->size_sum
+= cdata_len
;
563 log_debug("Successfully processed SMBIOS credential '%s'.", cn
);
569 static int import_credentials_smbios(ImportCredentialContext
*c
) {
572 /* Parses DMI OEM strings fields (SMBIOS type 11), as settable with qemu's -smbios type=11,value=… switch. */
574 if (detect_container() > 0) /* don't access /sys/ in a container */
577 if (detect_confidential_virtualization() > 0) /* don't trust firmware if confidential VMs */
580 for (unsigned i
= 0;; i
++) {
581 struct dmi_field_header
{
587 } _packed_
*dmi_field_header
;
588 _cleanup_free_
char *p
= NULL
;
589 _cleanup_free_
void *data
= NULL
;
592 assert_cc(offsetof(struct dmi_field_header
, contents
) == 5);
594 if (asprintf(&p
, "/sys/firmware/dmi/entries/11-%u/raw", i
) < 0)
597 r
= read_virtual_file(p
, sizeof(dmi_field_header
) + CREDENTIALS_TOTAL_SIZE_MAX
, (char**) &data
, &size
);
599 /* Once we reach ENOENT there are no more DMI Type 11 fields around. */
600 log_full_errno(r
== -ENOENT
? LOG_DEBUG
: LOG_WARNING
, r
, "Failed to open '%s', ignoring: %m", p
);
604 if (size
< offsetof(struct dmi_field_header
, contents
))
605 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
), "DMI field header of '%s' too short.", p
);
607 dmi_field_header
= data
;
608 if (dmi_field_header
->type
!= 11 ||
609 dmi_field_header
->length
!= offsetof(struct dmi_field_header
, contents
))
610 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
), "Invalid DMI field header.");
612 r
= parse_smbios_strings(c
, dmi_field_header
->contents
, size
- offsetof(struct dmi_field_header
, contents
));
616 if (i
== UINT_MAX
) /* Prevent overflow */
623 static int import_credentials_initrd(ImportCredentialContext
*c
) {
624 _cleanup_free_ DirectoryEntries
*de
= NULL
;
625 _cleanup_close_
int source_dir_fd
= -EBADF
;
630 /* This imports credentials from /run/credentials/@initrd/ into our credentials directory and deletes
631 * the source directory afterwards. This is run once after the initrd → host transition. This is
632 * supposed to establish a well-defined avenue for initrd-based host configurators to pass
633 * credentials into the main system. */
638 source_dir_fd
= open("/run/credentials/@initrd", O_RDONLY
|O_DIRECTORY
|O_CLOEXEC
|O_NOFOLLOW
);
639 if (source_dir_fd
< 0) {
641 log_debug_errno(errno
, "No credentials passed from initrd.");
643 log_warning_errno(errno
, "Failed to open '/run/credentials/@initrd', ignoring: %m");
647 r
= readdir_all(source_dir_fd
, RECURSE_DIR_SORT
|RECURSE_DIR_IGNORE_DOT
, &de
);
649 log_warning_errno(r
, "Failed to read '/run/credentials/@initrd' contents, ignoring: %m");
653 FOREACH_ARRAY(entry
, de
->entries
, de
->n_entries
) {
654 _cleanup_close_
int cfd
= -EBADF
, nfd
= -EBADF
;
655 const struct dirent
*d
= *entry
;
658 if (!credential_name_valid(d
->d_name
)) {
659 log_warning("Credential '%s' has invalid name, ignoring.", d
->d_name
);
663 cfd
= openat(source_dir_fd
, d
->d_name
, O_RDONLY
|O_CLOEXEC
);
665 log_warning_errno(errno
, "Failed to open %s, ignoring: %m", d
->d_name
);
669 if (fstat(cfd
, &st
) < 0) {
670 log_warning_errno(errno
, "Failed to stat %s, ignoring: %m", d
->d_name
);
674 r
= stat_verify_regular(&st
);
676 log_warning_errno(r
, "Credential file %s is not a regular file, ignoring: %m", d
->d_name
);
680 if (!credential_size_ok(c
, d
->d_name
, st
.st_size
))
683 r
= acquire_credential_directory(c
, SYSTEM_CREDENTIALS_DIRECTORY
, /* with_mount= */ true);
687 nfd
= open_credential_file_for_write(c
->target_dir_fd
, SYSTEM_CREDENTIALS_DIRECTORY
, d
->d_name
);
693 r
= copy_bytes(cfd
, nfd
, st
.st_size
, 0);
695 (void) unlinkat(c
->target_dir_fd
, d
->d_name
, 0);
696 return log_error_errno(r
, "Failed to create credential '%s': %m", d
->d_name
);
699 c
->size_sum
+= st
.st_size
;
702 log_debug("Successfully copied initrd credential '%s'.", d
->d_name
);
704 (void) unlinkat(source_dir_fd
, d
->d_name
, 0);
707 source_dir_fd
= safe_close(source_dir_fd
);
709 if (rmdir("/run/credentials/@initrd") < 0)
710 log_warning_errno(errno
, "Failed to remove /run/credentials/@initrd after import, ignoring: %m");
715 static int import_credentials_trusted(void) {
716 _cleanup_(import_credentials_context_free
) ImportCredentialContext c
= {
717 .target_dir_fd
= -EBADF
,
721 /* This is invoked during early boot when no credentials have been imported so far. (Specifically, if
722 * the $CREDENTIALS_DIRECTORY or $ENCRYPTED_CREDENTIALS_DIRECTORY environment variables are not set
725 r
= import_credentials_qemu(&c
);
726 w
= import_credentials_smbios(&c
);
727 q
= import_credentials_proc_cmdline(&c
);
728 y
= import_credentials_initrd(&c
);
730 if (c
.n_credentials
> 0) {
733 log_debug("Imported %u credentials from kernel command line/smbios/fw_cfg/initrd.", c
.n_credentials
);
735 z
= finalize_credentials_dir(SYSTEM_CREDENTIALS_DIRECTORY
, "CREDENTIALS_DIRECTORY");
740 return r
< 0 ? r
: w
< 0 ? w
: q
< 0 ? q
: y
;
743 static int merge_credentials_trusted(const char *creds_dir
) {
744 _cleanup_(import_credentials_context_free
) ImportCredentialContext c
= {
745 .target_dir_fd
= -EBADF
,
749 /* This is invoked after the initrd → host transitions, when credentials already have been imported,
750 * but we might want to import some more from the initrd. */
755 /* Do not try to merge initrd credentials into foreign credentials directories */
756 if (!path_equal_ptr(creds_dir
, SYSTEM_CREDENTIALS_DIRECTORY
)) {
757 log_debug("Not importing initrd credentials, as foreign $CREDENTIALS_DIRECTORY has been set.");
761 r
= import_credentials_initrd(&c
);
763 if (c
.n_credentials
> 0) {
766 log_debug("Merged %u credentials from initrd.", c
.n_credentials
);
768 z
= finalize_credentials_dir(SYSTEM_CREDENTIALS_DIRECTORY
, "CREDENTIALS_DIRECTORY");
776 static int symlink_credential_dir(const char *envvar
, const char *path
, const char *where
) {
783 if (!path_is_valid(path
) || !path_is_absolute(path
))
784 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "String specified via $%s is not a valid absolute path, refusing: %s", envvar
, path
);
786 /* If the env var already points to where we intend to create the symlink, then most likely we
787 * already imported some creds earlier, and thus set the env var, and hence don't need to do
789 if (path_equal(path
, where
))
792 r
= symlink_idempotent(path
, where
, /* make_relative= */ true);
794 return log_error_errno(r
, "Failed to link $%s to %s: %m", envvar
, where
);
799 static int setenv_notify_socket(void) {
800 _cleanup_free_
char *address
= NULL
;
803 r
= read_credential_with_decryption("vmm.notify_socket", (void **)&address
, /* ret_size= */ NULL
);
805 return log_warning_errno(r
, "Failed to read 'vmm.notify_socket' credential, ignoring: %m");
807 if (isempty(address
))
810 if (setenv("NOTIFY_SOCKET", address
, /* replace= */ 1) < 0)
811 return log_warning_errno(errno
, "Failed to set $NOTIFY_SOCKET environment variable, ignoring: %m");
816 static int report_credentials_per_func(const char *title
, int (*get_directory_func
)(const char **ret
)) {
817 _cleanup_free_ DirectoryEntries
*de
= NULL
;
818 _cleanup_close_
int dir_fd
= -EBADF
;
819 _cleanup_free_
char *ll
= NULL
;
820 const char *d
= NULL
;
824 assert(get_directory_func
);
826 r
= get_directory_func(&d
);
828 if (r
== -ENXIO
) /* Env var not set */
831 return log_warning_errno(r
, "Failed to determine %s directory: %m", title
);
834 dir_fd
= open(d
, O_RDONLY
|O_DIRECTORY
|O_CLOEXEC
);
836 return log_warning_errno(errno
, "Failed to open credentials directory %s: %m", d
);
838 r
= readdir_all(dir_fd
, RECURSE_DIR_SORT
|RECURSE_DIR_IGNORE_DOT
, &de
);
840 return log_warning_errno(r
, "Failed to enumerate credentials directory %s: %m", d
);
842 FOREACH_ARRAY(entry
, de
->entries
, de
->n_entries
) {
843 const struct dirent
*e
= *entry
;
845 if (!credential_name_valid(e
->d_name
))
848 if (!strextend_with_separator(&ll
, ", ", e
->d_name
))
855 log_info("Received %s: %s", title
, ll
);
860 static void report_credentials(void) {
863 p
= report_credentials_per_func("regular credentials", get_credentials_dir
);
864 q
= report_credentials_per_func("untrusted credentials", get_encrypted_credentials_dir
);
866 log_full(p
> 0 || q
> 0 ? LOG_INFO
: LOG_DEBUG
,
867 "Acquired %i regular credentials, %i untrusted credentials.",
872 int import_credentials(void) {
873 const char *received_creds_dir
= NULL
, *received_encrypted_creds_dir
= NULL
;
874 bool envvar_set
= false;
877 r
= get_credentials_dir(&received_creds_dir
);
878 if (r
< 0 && r
!= -ENXIO
) /* ENXIO → env var not set yet */
879 log_warning_errno(r
, "Failed to determine credentials directory, ignoring: %m");
883 r
= get_encrypted_credentials_dir(&received_encrypted_creds_dir
);
884 if (r
< 0 && r
!= -ENXIO
) /* ENXIO → env var not set yet */
885 log_warning_errno(r
, "Failed to determine encrypted credentials directory, ignoring: %m");
887 envvar_set
= envvar_set
|| r
>= 0;
890 /* Maybe an earlier stage initrd already set this up? If so, don't try to import anything again. */
891 log_debug("Not importing credentials, $CREDENTIALS_DIRECTORY or $ENCRYPTED_CREDENTIALS_DIRECTORY already set.");
893 /* But, let's make sure the creds are available from our regular paths. */
894 if (received_creds_dir
)
895 r
= symlink_credential_dir("CREDENTIALS_DIRECTORY", received_creds_dir
, SYSTEM_CREDENTIALS_DIRECTORY
);
899 if (received_encrypted_creds_dir
) {
900 q
= symlink_credential_dir("ENCRYPTED_CREDENTIALS_DIRECTORY", received_encrypted_creds_dir
, ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY
);
905 q
= merge_credentials_trusted(received_creds_dir
);
910 _cleanup_free_
char *v
= NULL
;
912 r
= proc_cmdline_get_key("systemd.import_credentials", PROC_CMDLINE_STRIP_RD_PREFIX
, &v
);
914 log_debug_errno(r
, "Failed to check if 'systemd.import_credentials=' kernel command line option is set, ignoring: %m");
916 r
= parse_boolean(v
);
918 log_debug_errno(r
, "Failed to parse 'systemd.import_credentials=' parameter, ignoring: %m");
920 log_notice("systemd.import_credentials=no is set, skipping importing of credentials.");
925 r
= import_credentials_boot();
927 q
= import_credentials_trusted();
932 report_credentials();
934 /* Propagate vmm_notify_socket credential → $NOTIFY_SOCKET env var */
935 (void) setenv_notify_socket();