]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/core/import-creds.c
tree-wide: use -EBADF for fd initialization
[thirdparty/systemd.git] / src / core / import-creds.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <sys/mount.h>
4
5 #include "copy.h"
6 #include "creds-util.h"
7 #include "escape.h"
8 #include "fileio.h"
9 #include "format-util.h"
10 #include "fs-util.h"
11 #include "hexdecoct.h"
12 #include "initrd-util.h"
13 #include "import-creds.h"
14 #include "io-util.h"
15 #include "mkdir-label.h"
16 #include "mount-util.h"
17 #include "mountpoint-util.h"
18 #include "parse-util.h"
19 #include "path-util.h"
20 #include "proc-cmdline.h"
21 #include "recurse-dir.h"
22 #include "strv.h"
23
24 /* This imports credentials passed in from environments higher up (VM manager, boot loader, …) and rearranges
25 * them so that later code can access them using our regular credential protocol
26 * (i.e. $CREDENTIALS_DIRECTORY). It's supposed to be minimal glue to unify behaviour how PID 1 (and
27 * generators invoked by it) can acquire credentials from outside, to mimic how we support it for containers,
28 * but on VM/physical environments.
29 *
30 * This does four things:
31 *
32 * 1. It imports credentials picked up by sd-boot (and placed in the /.extra/credentials/ dir in the initrd)
33 * and puts them in /run/credentials/@encrypted/. Note that during the initrd→host transition the initrd root
34 * file system is cleaned out, thus it is essential we pick up these files before they are deleted. Note
35 * that these credentials originate from an untrusted source, i.e. the ESP and are not
36 * pre-authenticated. They still have to be authenticated before use.
37 *
38 * 2. It imports credentials from /proc/cmdline and puts them in /run/credentials/@system/. These come from a
39 * trusted environment (i.e. the boot loader), and are typically authenticated (if authentication is done
40 * at all). However, they are world-readable, which might be less than ideal. Hence only use this for data
41 * that doesn't require trust.
42 *
43 * 3. It imports credentials passed in through qemu's fw_cfg logic. Specifically, credential data passed in
44 * /sys/firmware/qemu_fw_cfg/by_name/opt/io.systemd.credentials/ is picked up and also placed in
45 * /run/credentials/@system/.
46 *
47 * 4. It imports credentials passed in via the DMI/SMBIOS OEM string tables, quite similar to fw_cfg. It
48 * looks for strings starting with "io.systemd.credential:" and "io.systemd.credential.binary:". Both
49 * expect a key=value assignment, but in the latter case the value is Base64 decoded, allowing binary
50 * credentials to be passed in.
51 *
52 * If it picked up any credentials it will set the $CREDENTIALS_DIRECTORY and
53 * $ENCRYPTED_CREDENTIALS_DIRECTORY environment variables to point to these directories, so that processes
54 * can find them there later on. If "ramfs" is available $CREDENTIALS_DIRECTORY will be backed by it (but
55 * $ENCRYPTED_CREDENTIALS_DIRECTORY is just a regular tmpfs).
56 *
57 * Net result: the service manager can pick up trusted credentials from $CREDENTIALS_DIRECTORY afterwards,
58 * and untrusted ones from $ENCRYPTED_CREDENTIALS_DIRECTORY. */
59
60 typedef struct ImportCredentialContext {
61 int target_dir_fd;
62 size_t size_sum;
63 unsigned n_credentials;
64 } ImportCredentialContext;
65
66 static void import_credentials_context_free(ImportCredentialContext *c) {
67 assert(c);
68
69 c->target_dir_fd = safe_close(c->target_dir_fd);
70 }
71
72 static int acquire_encrypted_credential_directory(ImportCredentialContext *c) {
73 int r;
74
75 assert(c);
76
77 if (c->target_dir_fd >= 0)
78 return c->target_dir_fd;
79
80 r = mkdir_safe_label(ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, 0700, 0, 0, MKDIR_WARN_MODE);
81 if (r < 0)
82 return log_error_errno(r, "Failed to create " ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY ": %m");
83
84 c->target_dir_fd = open(ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
85 if (c->target_dir_fd < 0)
86 return log_error_errno(errno, "Failed to open " ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY ": %m");
87
88 return c->target_dir_fd;
89 }
90
91 static int open_credential_file_for_write(int target_dir_fd, const char *dir_name, const char *n) {
92 int fd;
93
94 assert(target_dir_fd >= 0);
95 assert(dir_name);
96 assert(n);
97
98 fd = openat(target_dir_fd, n, O_WRONLY|O_CLOEXEC|O_CREAT|O_EXCL|O_NOFOLLOW, 0400);
99 if (fd < 0) {
100 if (errno == EEXIST) /* In case of EEXIST we'll only debug log! */
101 return log_debug_errno(errno, "Credential '%s' set twice, ignoring.", n);
102
103 return log_error_errno(errno, "Failed to create %s/%s: %m", dir_name, n);
104 }
105
106 return fd;
107 }
108
109 static bool credential_size_ok(ImportCredentialContext *c, const char *name, uint64_t size) {
110 assert(c);
111 assert(name);
112
113 if (size > CREDENTIAL_SIZE_MAX) {
114 log_warning("Credential '%s' is larger than allowed limit (%s > %s), skipping.", name, FORMAT_BYTES(size), FORMAT_BYTES(CREDENTIAL_SIZE_MAX));
115 return false;
116 }
117
118 if (size > CREDENTIALS_TOTAL_SIZE_MAX - c->size_sum) {
119 log_warning("Accumulated credential size would be above allowed limit (%s+%s > %s), skipping '%s'.",
120 FORMAT_BYTES(c->size_sum), FORMAT_BYTES(size), FORMAT_BYTES(CREDENTIALS_TOTAL_SIZE_MAX), name);
121 return false;
122 }
123
124 return true;
125 }
126
127 static int finalize_credentials_dir(const char *dir, const char *envvar) {
128 int r;
129
130 assert(dir);
131 assert(envvar);
132
133 /* Try to make the credentials directory read-only now */
134
135 r = make_mount_point(dir);
136 if (r < 0)
137 log_warning_errno(r, "Failed to make '%s' a mount point, ignoring: %m", dir);
138 else
139 (void) mount_nofollow_verbose(LOG_WARNING, NULL, dir, NULL, MS_BIND|MS_NODEV|MS_NOEXEC|MS_NOSUID|MS_RDONLY|MS_REMOUNT, NULL);
140
141 if (setenv(envvar, dir, /* overwrite= */ true) < 0)
142 return log_error_errno(errno, "Failed to set $%s environment variable: %m", envvar);
143
144 return 0;
145 }
146
147 static int import_credentials_boot(void) {
148 _cleanup_(import_credentials_context_free) ImportCredentialContext context = {
149 .target_dir_fd = -EBADF,
150 };
151 int r;
152
153 /* systemd-stub will wrap sidecar *.cred files from the UEFI kernel image directory into initrd
154 * cpios, so that they unpack into /.extra/. We'll pick them up from there and copy them into /run/
155 * so that we can access them during the entire runtime (note that the initrd file system is erased
156 * during the initrd → host transition). Note that these credentials originate from an untrusted
157 * source (i.e. the ESP typically) and thus need to be authenticated later. We thus put them in a
158 * directory separate from the usual credentials which are from a trusted source. */
159
160 if (!in_initrd())
161 return 0;
162
163 FOREACH_STRING(p,
164 "/.extra/credentials/", /* specific to this boot menu */
165 "/.extra/global_credentials/") { /* boot partition wide */
166
167 _cleanup_free_ DirectoryEntries *de = NULL;
168 _cleanup_close_ int source_dir_fd = -EBADF;
169
170 source_dir_fd = open(p, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
171 if (source_dir_fd < 0) {
172 if (errno == ENOENT) {
173 log_debug("No credentials passed via %s.", p);
174 continue;
175 }
176
177 log_warning_errno(errno, "Failed to open '%s', ignoring: %m", p);
178 continue;
179 }
180
181 r = readdir_all(source_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de);
182 if (r < 0) {
183 log_warning_errno(r, "Failed to read '%s' contents, ignoring: %m", p);
184 continue;
185 }
186
187 for (size_t i = 0; i < de->n_entries; i++) {
188 const struct dirent *d = de->entries[i];
189 _cleanup_close_ int cfd = -EBADF, nfd = -EBADF;
190 _cleanup_free_ char *n = NULL;
191 const char *e;
192 struct stat st;
193
194 e = endswith(d->d_name, ".cred");
195 if (!e)
196 continue;
197
198 /* drop .cred suffix (which we want in the ESP sidecar dir, but not for our internal
199 * processing) */
200 n = strndup(d->d_name, e - d->d_name);
201 if (!n)
202 return log_oom();
203
204 if (!credential_name_valid(n)) {
205 log_warning("Credential '%s' has invalid name, ignoring.", d->d_name);
206 continue;
207 }
208
209 cfd = openat(source_dir_fd, d->d_name, O_RDONLY|O_CLOEXEC);
210 if (cfd < 0) {
211 log_warning_errno(errno, "Failed to open %s, ignoring: %m", d->d_name);
212 continue;
213 }
214
215 if (fstat(cfd, &st) < 0) {
216 log_warning_errno(errno, "Failed to stat %s, ignoring: %m", d->d_name);
217 continue;
218 }
219
220 r = stat_verify_regular(&st);
221 if (r < 0) {
222 log_warning_errno(r, "Credential file %s is not a regular file, ignoring: %m", d->d_name);
223 continue;
224 }
225
226 if (!credential_size_ok(&context, n, st.st_size))
227 continue;
228
229 r = acquire_encrypted_credential_directory(&context);
230 if (r < 0)
231 return r;
232
233 nfd = open_credential_file_for_write(context.target_dir_fd, ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, n);
234 if (nfd == -EEXIST)
235 continue;
236 if (nfd < 0)
237 return nfd;
238
239 r = copy_bytes(cfd, nfd, st.st_size, 0);
240 if (r < 0) {
241 (void) unlinkat(context.target_dir_fd, n, 0);
242 return log_error_errno(r, "Failed to create credential '%s': %m", n);
243 }
244
245 context.size_sum += st.st_size;
246 context.n_credentials++;
247
248 log_debug("Successfully copied boot credential '%s'.", n);
249 }
250 }
251
252 if (context.n_credentials > 0) {
253 log_debug("Imported %u credentials from boot loader.", context.n_credentials);
254
255 r = finalize_credentials_dir(ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, "ENCRYPTED_CREDENTIALS_DIRECTORY");
256 if (r < 0)
257 return r;
258 }
259
260 return 0;
261 }
262
263 static int acquire_credential_directory(ImportCredentialContext *c) {
264 int r;
265
266 assert(c);
267
268 if (c->target_dir_fd >= 0)
269 return c->target_dir_fd;
270
271 r = path_is_mount_point(SYSTEM_CREDENTIALS_DIRECTORY, NULL, 0);
272 if (r < 0) {
273 if (r != -ENOENT)
274 return log_error_errno(r, "Failed to determine if " SYSTEM_CREDENTIALS_DIRECTORY " is a mount point: %m");
275
276 r = mkdir_safe_label(SYSTEM_CREDENTIALS_DIRECTORY, 0700, 0, 0, MKDIR_WARN_MODE);
277 if (r < 0)
278 return log_error_errno(r, "Failed to create " SYSTEM_CREDENTIALS_DIRECTORY " mount point: %m");
279
280 r = 0; /* Now it exists and is not a mount point */
281 }
282 if (r == 0)
283 /* If not a mountpoint yet, try to mount a ramfs there (so that this stuff isn't swapped
284 * out), but if that doesn't work, let's just use the regular tmpfs it already is. */
285 (void) mount_nofollow_verbose(LOG_WARNING, "ramfs", SYSTEM_CREDENTIALS_DIRECTORY, "ramfs", MS_NODEV|MS_NOEXEC|MS_NOSUID, "mode=0700");
286
287 c->target_dir_fd = open(SYSTEM_CREDENTIALS_DIRECTORY, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
288 if (c->target_dir_fd < 0)
289 return log_error_errno(errno, "Failed to open " SYSTEM_CREDENTIALS_DIRECTORY ": %m");
290
291 return c->target_dir_fd;
292 }
293
294 static int proc_cmdline_callback(const char *key, const char *value, void *data) {
295 ImportCredentialContext *c = ASSERT_PTR(data);
296 _cleanup_free_ char *n = NULL;
297 _cleanup_close_ int nfd = -EBADF;
298 const char *colon;
299 size_t l;
300 int r;
301
302 assert(key);
303
304 if (!proc_cmdline_key_streq(key, "systemd.set_credential"))
305 return 0;
306
307 colon = value ? strchr(value, ':') : NULL;
308 if (!colon) {
309 log_warning("Credential assignment through kernel command line lacks ':' character, ignoring: %s", value);
310 return 0;
311 }
312
313 n = strndup(value, colon - value);
314 if (!n)
315 return log_oom();
316
317 if (!credential_name_valid(n)) {
318 log_warning("Credential name '%s' is invalid, ignoring.", n);
319 return 0;
320 }
321
322 colon++;
323 l = strlen(colon);
324
325 if (!credential_size_ok(c, n, l))
326 return 0;
327
328 r = acquire_credential_directory(c);
329 if (r < 0)
330 return r;
331
332 nfd = open_credential_file_for_write(c->target_dir_fd, SYSTEM_CREDENTIALS_DIRECTORY, n);
333 if (nfd == -EEXIST)
334 return 0;
335 if (nfd < 0)
336 return nfd;
337
338 r = loop_write(nfd, colon, l, /* do_poll= */ false);
339 if (r < 0) {
340 (void) unlinkat(c->target_dir_fd, n, 0);
341 return log_error_errno(r, "Failed to write credential: %m");
342 }
343
344 c->size_sum += l;
345 c->n_credentials++;
346
347 log_debug("Successfully processed kernel command line credential '%s'.", n);
348
349 return 0;
350 }
351
352 static int import_credentials_proc_cmdline(ImportCredentialContext *c) {
353 int r;
354
355 assert(c);
356
357 r = proc_cmdline_parse(proc_cmdline_callback, c, 0);
358 if (r < 0)
359 return log_error_errno(r, "Failed to parse /proc/cmdline: %m");
360
361 return 0;
362 }
363
364 #define QEMU_FWCFG_PATH "/sys/firmware/qemu_fw_cfg/by_name/opt/io.systemd.credentials"
365
366 static int import_credentials_qemu(ImportCredentialContext *c) {
367 _cleanup_free_ DirectoryEntries *de = NULL;
368 _cleanup_close_ int source_dir_fd = -EBADF;
369 int r;
370
371 assert(c);
372
373 source_dir_fd = open(QEMU_FWCFG_PATH, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
374 if (source_dir_fd < 0) {
375 if (errno == ENOENT) {
376 log_debug("No credentials passed via fw_cfg.");
377 return 0;
378 }
379
380 log_warning_errno(errno, "Failed to open '" QEMU_FWCFG_PATH "', ignoring: %m");
381 return 0;
382 }
383
384 r = readdir_all(source_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de);
385 if (r < 0) {
386 log_warning_errno(r, "Failed to read '" QEMU_FWCFG_PATH "' contents, ignoring: %m");
387 return 0;
388 }
389
390 for (size_t i = 0; i < de->n_entries; i++) {
391 const struct dirent *d = de->entries[i];
392 _cleanup_close_ int vfd = -EBADF, rfd = -EBADF, nfd = -EBADF;
393 _cleanup_free_ char *szs = NULL;
394 uint64_t sz;
395
396 if (!credential_name_valid(d->d_name)) {
397 log_warning("Credential '%s' has invalid name, ignoring.", d->d_name);
398 continue;
399 }
400
401 vfd = openat(source_dir_fd, d->d_name, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
402 if (vfd < 0) {
403 log_warning_errno(errno, "Failed to open '" QEMU_FWCFG_PATH "'/%s/, ignoring: %m", d->d_name);
404 continue;
405 }
406
407 r = read_virtual_file_at(vfd, "size", LINE_MAX, &szs, NULL);
408 if (r < 0) {
409 log_warning_errno(r, "Failed to read '" QEMU_FWCFG_PATH "'/%s/size, ignoring: %m", d->d_name);
410 continue;
411 }
412
413 r = safe_atou64(strstrip(szs), &sz);
414 if (r < 0) {
415 log_warning_errno(r, "Failed to parse size of credential '%s', ignoring: %s", d->d_name, szs);
416 continue;
417 }
418
419 if (!credential_size_ok(c, d->d_name, sz))
420 continue;
421
422 /* Ideally we'd just symlink the data here. Alas the kernel driver exports the raw file as
423 * having size zero, and we'd rather not have applications support such credential
424 * files. Let's hence copy the files to make them regular. */
425
426 rfd = openat(vfd, "raw", O_RDONLY|O_CLOEXEC);
427 if (rfd < 0) {
428 log_warning_errno(errno, "Failed to open '" QEMU_FWCFG_PATH "'/%s/raw, ignoring: %m", d->d_name);
429 continue;
430 }
431
432 r = acquire_credential_directory(c);
433 if (r < 0)
434 return r;
435
436 nfd = open_credential_file_for_write(c->target_dir_fd, SYSTEM_CREDENTIALS_DIRECTORY, d->d_name);
437 if (nfd == -EEXIST)
438 continue;
439 if (nfd < 0)
440 return nfd;
441
442 r = copy_bytes(rfd, nfd, sz, 0);
443 if (r < 0) {
444 (void) unlinkat(c->target_dir_fd, d->d_name, 0);
445 return log_error_errno(r, "Failed to create credential '%s': %m", d->d_name);
446 }
447
448 c->size_sum += sz;
449 c->n_credentials++;
450
451 log_debug("Successfully copied qemu fw_cfg credential '%s'.", d->d_name);
452 }
453
454 return 0;
455 }
456
457 static int parse_smbios_strings(ImportCredentialContext *c, const char *data, size_t size) {
458 size_t left, skip;
459 const char *p;
460 int r;
461
462 assert(c);
463 assert(data || size == 0);
464
465 /* Unpacks a packed series of SMBIOS OEM vendor strings. These are a series of NUL terminated
466 * strings, one after the other. */
467
468 for (p = data, left = size; left > 0; p += skip, left -= skip) {
469 _cleanup_free_ void *buf = NULL;
470 _cleanup_free_ char *cn = NULL;
471 _cleanup_close_ int nfd = -EBADF;
472 const char *nul, *n, *eq;
473 const void *cdata;
474 size_t buflen, cdata_len;
475 bool unbase64;
476
477 nul = memchr(p, 0, left);
478 if (nul)
479 skip = (nul - p) + 1;
480 else {
481 nul = p + left;
482 skip = left;
483 }
484
485 if (nul - p == 0) /* Skip empty strings */
486 continue;
487
488 /* Only care about strings starting with either of these two prefixes */
489 if ((n = memory_startswith(p, nul - p, "io.systemd.credential:")))
490 unbase64 = false;
491 else if ((n = memory_startswith(p, nul - p, "io.systemd.credential.binary:")))
492 unbase64 = true;
493 else {
494 _cleanup_free_ char *escaped = NULL;
495
496 escaped = cescape_length(p, nul - p);
497 log_debug("Ignoring OEM string: %s", strnull(escaped));
498 continue;
499 }
500
501 eq = memchr(n, '=', nul - n);
502 if (!eq) {
503 log_warning("SMBIOS OEM string lacks '=' character, ignoring.");
504 continue;
505 }
506
507 cn = memdup_suffix0(n, eq - n);
508 if (!cn)
509 return log_oom();
510
511 if (!credential_name_valid(cn)) {
512 log_warning("SMBIOS credential name '%s' is not valid, ignoring: %m", cn);
513 continue;
514 }
515
516 /* Optionally base64 decode the data, if requested, to allow binary credentials */
517 if (unbase64) {
518 r = unbase64mem(eq + 1, nul - (eq + 1), &buf, &buflen);
519 if (r < 0) {
520 log_warning_errno(r, "Failed to base64 decode credential '%s', ignoring: %m", cn);
521 continue;
522 }
523
524 cdata = buf;
525 cdata_len = buflen;
526 } else {
527 cdata = eq + 1;
528 cdata_len = nul - (eq + 1);
529 }
530
531 if (!credential_size_ok(c, cn, cdata_len))
532 continue;
533
534 r = acquire_credential_directory(c);
535 if (r < 0)
536 return r;
537
538 nfd = open_credential_file_for_write(c->target_dir_fd, SYSTEM_CREDENTIALS_DIRECTORY, cn);
539 if (nfd == -EEXIST)
540 continue;
541 if (nfd < 0)
542 return nfd;
543
544 r = loop_write(nfd, cdata, cdata_len, /* do_poll= */ false);
545 if (r < 0) {
546 (void) unlinkat(c->target_dir_fd, cn, 0);
547 return log_error_errno(r, "Failed to write credential: %m");
548 }
549
550 c->size_sum += cdata_len;
551 c->n_credentials++;
552
553 log_debug("Successfully processed SMBIOS credential '%s'.", cn);
554 }
555
556 return 0;
557 }
558
559 static int import_credentials_smbios(ImportCredentialContext *c) {
560 int r;
561
562 /* Parses DMI OEM strings fields (SMBIOS type 11), as settable with qemu's -smbios type=11,value=… switch. */
563
564 for (unsigned i = 0;; i++) {
565 struct dmi_field_header {
566 uint8_t type;
567 uint8_t length;
568 uint16_t handle;
569 uint8_t count;
570 char contents[];
571 } _packed_ *dmi_field_header;
572 _cleanup_free_ char *p = NULL;
573 _cleanup_free_ void *data = NULL;
574 size_t size;
575
576 assert_cc(offsetof(struct dmi_field_header, contents) == 5);
577
578 if (asprintf(&p, "/sys/firmware/dmi/entries/11-%u/raw", i) < 0)
579 return log_oom();
580
581 r = read_virtual_file(p, sizeof(dmi_field_header) + CREDENTIALS_TOTAL_SIZE_MAX, (char**) &data, &size);
582 if (r < 0) {
583 /* Once we reach ENOENT there are no more DMI Type 11 fields around. */
584 log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r, "Failed to open '%s', ignoring: %m", p);
585 break;
586 }
587
588 if (size < offsetof(struct dmi_field_header, contents))
589 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "DMI field header of '%s' too short.", p);
590
591 dmi_field_header = data;
592 if (dmi_field_header->type != 11 ||
593 dmi_field_header->length != offsetof(struct dmi_field_header, contents))
594 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Invalid DMI field header.");
595
596 r = parse_smbios_strings(c, dmi_field_header->contents, size - offsetof(struct dmi_field_header, contents));
597 if (r < 0)
598 return r;
599
600 if (i == UINT_MAX) /* Prevent overflow */
601 break;
602 }
603
604 return 0;
605 }
606
607 static int import_credentials_trusted(void) {
608 _cleanup_(import_credentials_context_free) ImportCredentialContext c = {
609 .target_dir_fd = -EBADF,
610 };
611 int q, w, r;
612
613 r = import_credentials_qemu(&c);
614 w = import_credentials_smbios(&c);
615 q = import_credentials_proc_cmdline(&c);
616
617 if (c.n_credentials > 0) {
618 int z;
619
620 log_debug("Imported %u credentials from kernel command line/smbios/fw_cfg.", c.n_credentials);
621
622 z = finalize_credentials_dir(SYSTEM_CREDENTIALS_DIRECTORY, "CREDENTIALS_DIRECTORY");
623 if (z < 0)
624 return z;
625 }
626
627 return r < 0 ? r : w < 0 ? w : q;
628 }
629
630 static int symlink_credential_dir(const char *envvar, const char *path, const char *where) {
631 int r;
632
633 assert(envvar);
634 assert(path);
635 assert(where);
636
637 if (!path_is_valid(path) || !path_is_absolute(path))
638 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "String specified via $%s is not a valid absolute path, refusing: %s", envvar, path);
639
640 /* If the env var already points to where we intend to create the symlink, then most likely we
641 * already imported some creds earlier, and thus set the env var, and hence don't need to do
642 * anything. */
643 if (path_equal(path, where))
644 return 0;
645
646 r = symlink_idempotent(path, where, /* make_relative= */ true);
647 if (r < 0)
648 return log_error_errno(r, "Failed to link $%s to %s: %m", envvar, where);
649
650 return 0;
651 }
652
653 int import_credentials(void) {
654 const char *received_creds_dir = NULL, *received_encrypted_creds_dir = NULL;
655 bool envvar_set = false;
656 int r, q;
657
658 r = get_credentials_dir(&received_creds_dir);
659 if (r < 0 && r != -ENXIO) /* ENXIO → env var not set yet */
660 log_warning_errno(r, "Failed to determine credentials directory, ignoring: %m");
661
662 envvar_set = r >= 0;
663
664 r = get_encrypted_credentials_dir(&received_encrypted_creds_dir);
665 if (r < 0 && r != -ENXIO) /* ENXIO → env var not set yet */
666 log_warning_errno(r, "Failed to determine encrypted credentials directory, ignoring: %m");
667
668 envvar_set = envvar_set || r >= 0;
669
670 if (envvar_set) {
671 /* Maybe an earlier stage initrd already set this up? If so, don't try to import anything again. */
672 log_debug("Not importing credentials, $CREDENTIALS_DIRECTORY or $ENCRYPTED_CREDENTIALS_DIRECTORY already set.");
673
674 /* But, let's make sure the creds are available from our regular paths. */
675 if (received_creds_dir)
676 r = symlink_credential_dir("CREDENTIALS_DIRECTORY", received_creds_dir, SYSTEM_CREDENTIALS_DIRECTORY);
677 else
678 r = 0;
679
680 if (received_encrypted_creds_dir) {
681 q = symlink_credential_dir("ENCRYPTED_CREDENTIALS_DIRECTORY", received_encrypted_creds_dir, ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY);
682 if (r >= 0)
683 r = q;
684 }
685
686 } else {
687 _cleanup_free_ char *v = NULL;
688
689 r = proc_cmdline_get_key("systemd.import_credentials", PROC_CMDLINE_STRIP_RD_PREFIX, &v);
690 if (r < 0)
691 log_debug_errno(r, "Failed to check if 'systemd.import_credentials=' kernel command line option is set, ignoring: %m");
692 else if (r > 0) {
693 r = parse_boolean(v);
694 if (r < 0)
695 log_debug_errno(r, "Failed to parse 'systemd.import_credentials=' parameter, ignoring: %m");
696 else if (r == 0) {
697 log_notice("systemd.import_credentials=no is set, skipping importing of credentials.");
698 return 0;
699 }
700 }
701
702 r = import_credentials_boot();
703
704 q = import_credentials_trusted();
705 if (r >= 0)
706 r = q;
707 }
708
709 return r;
710 }