diff --git a/man/systemd-tmpfiles.xml b/man/systemd-tmpfiles.xml
index 425ed23dd3..92ab322ba0 100644
--- a/man/systemd-tmpfiles.xml
+++ b/man/systemd-tmpfiles.xml
@@ -230,6 +230,31 @@
systemd-tmpfiles --remove --create
+
+ Credentials
+
+ systemd-tmpfiles supports the service credentials logic as implemented by
+ LoadCredential=/SetCredential= (see
+ systemd.exec1 for
+ details). The following credentials are used when passed in:
+
+
+
+ tmpfiles.extra
+
+ The contents of this credential may contain additional lines to operate on. The
+ credential contents should follow the same format as any other tmpfiles.d/
+ drop-in configuration file. If this credential is passed it is processed after all of the drop-in
+ files read from the file system. The lines in the credential can hence augment existing lines of the
+ OS, but not override them.
+
+
+
+ Note that by default the systemd-tmpfiles-setup.service unit file (and related
+ unit files) is set up to inherit the tmpfiles.extra credential from the service
+ manager.
+
+
Environment
diff --git a/man/tmpfiles.d.xml b/man/tmpfiles.d.xml
index 79d0ff6bdd..04617bc532 100644
--- a/man/tmpfiles.d.xml
+++ b/man/tmpfiles.d.xml
@@ -160,9 +160,10 @@ L /tmp/foobar - - - - /dev/null
Type
- The type consists of a single letter and optionally a plus sign (+),
- exclamation mark (!), minus sign (-), equals sign
- (=) and/or tilde character (~).
+ The type consists of a single letter and optionally one or emore modifier characters: a plus sign
+ (+), exclamation mark (!), minus sign (-),
+ equals sign (=), tilde character (~) and/or caret
+ (^).
The following line types are understood:
@@ -461,6 +462,10 @@ L /tmp/foobar - - - - /dev/null
symlinks.
+
+
+
+ Type Modifiers
If the exclamation mark (!) is used, this line is only safe to execute during
boot, and can break a running system. Lines without the exclamation mark are presumed to be safe to
@@ -493,9 +498,19 @@ w- /proc/sys/vm/swappiness - - - - 10
If the tilde character (~) is used, the argument (i.e. 6th) column is Base64 decoded before use. This modifier is
only supported on line types that can write file contents, i.e. f,
- f+, w. This is useful for writing arbitrary binary data
- (including newlines and NUL bytes) to files. Note that if this switch is used, the argument is not
- subject to specifier expansion, neither before nor after Base64 decoding.
+ f+, w, +. This is useful for writing arbitrary
+ binary data (including newlines and NUL bytes) to files. Note that if this switch is used, the argument
+ is not subject to specifier expansion, neither before nor after Base64 decoding.
+
+ If the caret character (^) is used, the argument (i.e. 6th) column takes a
+ service credential name to read the argument data from. See System and Service Credentials for details about the
+ credentials concept. This modifier is only supported on line types that can write file contents,
+ i.e. f, f+, w, w+. This is
+ useful for writing arbitrary files with contents sourced from elsewhere, including from VM or container
+ managers further up. If the specified credential is not set for the systemd-tmpfiles
+ service, the line is silently skipped. If ^ and ~ are combined
+ Base64 decoding is applied to the credential contents.
Note that for all line types that result in creation of any kind of file node
(i.e. f/F,
diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c
index 0c50c8e1ee..07432a1e51 100644
--- a/src/tmpfiles/tmpfiles.c
+++ b/src/tmpfiles/tmpfiles.c
@@ -25,6 +25,7 @@
#include "chattr-util.h"
#include "conf-files.h"
#include "copy.h"
+#include "creds-util.h"
#include "def.h"
#include "devnum-util.h"
#include "dirent-util.h"
@@ -2977,7 +2978,7 @@ static int parse_line(
ItemArray *existing;
OrderedHashmap *h;
int r, pos;
- bool append_or_force = false, boot = false, allow_failure = false, try_replace = false, unbase64 = false;
+ bool append_or_force = false, boot = false, allow_failure = false, try_replace = false, unbase64 = false, from_cred = false;
assert(fname);
assert(line >= 1);
@@ -3050,6 +3051,8 @@ static int parse_line(
try_replace = true;
else if (action[pos] == '~' && !unbase64)
unbase64 = true;
+ else if (action[pos] == '^' && !from_cred)
+ from_cred = true;
else {
*invalid_config = true;
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "Unknown modifiers in command '%s'", action);
@@ -3239,13 +3242,8 @@ static int parse_line(
if (!should_include_path(i.path))
return 0;
- if (unbase64) {
- if (i.argument) {
- r = unbase64mem(i.argument, SIZE_MAX, &i.binary_argument, &i.binary_argument_size);
- if (r < 0)
- return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to base64 decode specified argument '%s': %m", i.argument);
- }
- } else {
+ if (!unbase64) {
+ /* Do specifier expansion except if base64 mode is enabled */
r = specifier_expansion_from_arg(specifier_table, &i);
if (r == -ENXIO)
return log_unresolvable_specifier(fname, line);
@@ -3256,6 +3254,35 @@ static int parse_line(
}
}
+ if (from_cred) {
+ if (!i.argument)
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL), "Reading from credential requested, but no credential name specified.");
+ if (!credential_name_valid(i.argument))
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL), "Credential name not valid: %s", i.argument);
+
+ r = read_credential(i.argument, &i.binary_argument, &i.binary_argument_size);
+ if (IN_SET(r, -ENXIO, -ENOENT)) {
+ /* Silently skip over lines that have no credentials passed */
+ log_syntax(NULL, LOG_INFO, fname, line, 0, "Credential '%s' not specified, skipping line.", i.argument);
+ return 0;
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to read credential '%s': %m", i.argument);
+ }
+
+ /* If base64 decoding is requested, do so now */
+ if (unbase64 && item_binary_argument(&i)) {
+ _cleanup_free_ void *data = NULL;
+ size_t data_size = 0;
+
+ r = unbase64mem(item_binary_argument(&i), item_binary_argument_size(&i), &data, &data_size);
+ if (r < 0)
+ return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to base64 decode specified argument '%s': %m", i.argument);
+
+ free_and_replace(i.binary_argument, data);
+ i.binary_argument_size = data_size;
+ }
+
if (!empty_or_root(arg_root)) {
char *p;
@@ -3594,7 +3621,12 @@ static int parse_argv(int argc, char *argv[]) {
return 1;
}
-static int read_config_file(char **config_dirs, const char *fn, bool ignore_enoent, bool *invalid_config) {
+static int read_config_file(
+ char **config_dirs,
+ const char *fn,
+ bool ignore_enoent,
+ bool *invalid_config) {
+
_cleanup_(hashmap_freep) Hashmap *uid_cache = NULL, *gid_cache = NULL;
_cleanup_fclose_ FILE *_f = NULL;
_cleanup_free_ char *pp = NULL;
@@ -3736,6 +3768,25 @@ static int read_config_files(char **config_dirs, char **args, bool *invalid_conf
return 0;
}
+static int read_credential_lines(bool *invalid_config) {
+ _cleanup_free_ char *j = NULL;
+ const char *d;
+ int r;
+
+ r = get_credentials_dir(&d);
+ if (r == -ENXIO)
+ return 0;
+ if (r < 0)
+ return log_error_errno(r, "Failed to get credentials directory: %m");
+
+ j = path_join(d, "tmpfiles.extra");
+ if (!j)
+ return log_oom();
+
+ (void) read_config_file(/* config_dirs= */ NULL, j, /* ignore_enoent= */ true, invalid_config);
+ return 0;
+}
+
static int link_parent(ItemArray *a) {
const char *path;
char *prefix;
@@ -3892,6 +3943,10 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return r;
+ r = read_credential_lines(&invalid_config);
+ if (r < 0)
+ return r;
+
/* Let's now link up all child/parent relationships */
ORDERED_HASHMAP_FOREACH(a, items) {
r = link_parent(a);
diff --git a/test/TEST-54-CREDS/test.sh b/test/TEST-54-CREDS/test.sh
index 8f66f1c7b8..5269eaa437 100755
--- a/test/TEST-54-CREDS/test.sh
+++ b/test/TEST-54-CREDS/test.sh
@@ -3,9 +3,29 @@
set -e
TEST_DESCRIPTION="test credentials"
-NSPAWN_ARGUMENTS="${NSPAWN_ARGUMENTS:-} --set-credential=mynspawncredential:strangevalue"
-QEMU_OPTIONS="${QEMU_OPTIONS:-} -fw_cfg name=opt/io.systemd.credentials/myqemucredential,string=othervalue -smbios type=11,value=io.systemd.credential:smbioscredential=magicdata -smbios type=11,value=io.systemd.credential.binary:binarysmbioscredential=bWFnaWNiaW5hcnlkYXRh -smbios type=11,value=io.systemd.credential.binary:sysusers.extra=dSBjcmVkdGVzdHVzZXIK"
-KERNEL_APPEND="${KERNEL_APPEND:-} systemd.set_credential=kernelcmdlinecred:uff systemd.set_credential=sysctl.extra:kernel.domainname=sysctltest rd.systemd.import_credentials=no"
+
+NSPAWN_CREDS=(
+ "--set-credential=mynspawncredential:strangevalue"
+)
+NSPAWN_ARGUMENTS="${NSPAWN_ARGUMENTS:-} ${NSPAWN_CREDS[*]}"
+
+QEMU_CREDS=(
+ "-fw_cfg name=opt/io.systemd.credentials/myqemucredential,string=othervalue"
+ "-smbios type=11,value=io.systemd.credential:smbioscredential=magicdata"
+ "-smbios type=11,value=io.systemd.credential.binary:binarysmbioscredential=bWFnaWNiaW5hcnlkYXRh"
+ "-smbios type=11,value=io.systemd.credential.binary:sysusers.extra=dSBjcmVkdGVzdHVzZXIK"
+ "-smbios type=11,value=io.systemd.credential.binary:tmpfiles.extra=ZiAvdG1wL3NvdXJjZWRmcm9tY3JlZGVudGlhbCAtIC0gLSAtIHRtcGZpbGVzc2VjcmV0Cg=="
+)
+QEMU_OPTIONS="${QEMU_OPTIONS:-} ${QEMU_CREDS[*]}"
+
+KERNEL_CREDS=(
+ "systemd.set_credential=kernelcmdlinecred:uff"
+ "systemd.set_credential=sysctl.extra:kernel.domainname=sysctltest"
+ "systemd.set_credential=login.motd:hello"
+ "systemd.set_credential=login.issue:welcome"
+ "rd.systemd.import_credentials=no"
+)
+KERNEL_APPEND="${KERNEL_APPEND:-} ${KERNEL_CREDS[*]}"
# shellcheck source=test/test-functions
. "${TEST_BASE_DIR:?}/test-functions"
diff --git a/test/units/testsuite-54.sh b/test/units/testsuite-54.sh
index a7ccdca032..dc0c5f554f 100755
--- a/test/units/testsuite-54.sh
+++ b/test/units/testsuite-54.sh
@@ -43,6 +43,11 @@ elif [ -d /sys/firmware/qemu_fw_cfg/by_name ]; then
# Verify that creating a user via sysusers via the kernel cmdline worked
grep -q ^credtestuser: /etc/passwd
+
+ # Verify that writing a file via tmpfiles worked
+ [ "$(cat /tmp/sourcedfromcredential)" = "tmpfilessecret" ]
+ [ "$(cat /etc/motd.d/50-provision.conf)" = "hello" ]
+ [ "$(cat /etc/issue.d/50-provision.conf)" = "welcome" ]
else
echo "qemu_fw_cfg support missing in kernel. Sniff!"
expected_credential=""
diff --git a/tmpfiles.d/meson.build b/tmpfiles.d/meson.build
index 4546169694..ca1abbf3fe 100644
--- a/tmpfiles.d/meson.build
+++ b/tmpfiles.d/meson.build
@@ -14,6 +14,7 @@ files = [['README', ''],
['systemd-tmp.conf', ''],
['tmp.conf', ''],
['x11.conf', ''],
+ ['provision.conf', ''],
]
foreach pair : files
diff --git a/tmpfiles.d/provision.conf b/tmpfiles.d/provision.conf
new file mode 100644
index 0000000000..9a4783868f
--- /dev/null
+++ b/tmpfiles.d/provision.conf
@@ -0,0 +1,17 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+# See tmpfiles.d(5) for details
+
+# Provision additional loging messages from credentials, if they are set. Note
+# that these lines are NOPs if the credentials are not set or if the files
+# already exist.
+f^ /etc/motd.d/50-provision.conf - - - - login.motd
+f^ /etc/issue.d/50-provision.conf - - - - login.issue
+
+# Provision a /etc/hosts file from credentials.
+f^ /etc/hosts - - - - network.hosts
diff --git a/units/systemd-tmpfiles-clean.service b/units/systemd-tmpfiles-clean.service
index 7aee6463bd..6ae4e74ddd 100644
--- a/units/systemd-tmpfiles-clean.service
+++ b/units/systemd-tmpfiles-clean.service
@@ -20,3 +20,4 @@ Type=oneshot
ExecStart=systemd-tmpfiles --clean
SuccessExitStatus=DATAERR
IOSchedulingClass=idle
+LoadCredential=tmpfiles.extra
diff --git a/units/systemd-tmpfiles-setup-dev.service b/units/systemd-tmpfiles-setup-dev.service
index 0babe78767..ad0e54fcc4 100644
--- a/units/systemd-tmpfiles-setup-dev.service
+++ b/units/systemd-tmpfiles-setup-dev.service
@@ -20,3 +20,4 @@ Type=oneshot
RemainAfterExit=yes
ExecStart=systemd-tmpfiles --prefix=/dev --create --boot
SuccessExitStatus=DATAERR CANTCREAT
+LoadCredential=tmpfiles.extra
diff --git a/units/systemd-tmpfiles-setup.service b/units/systemd-tmpfiles-setup.service
index bc29dbc8c9..fc6a4a0e0b 100644
--- a/units/systemd-tmpfiles-setup.service
+++ b/units/systemd-tmpfiles-setup.service
@@ -21,3 +21,7 @@ Type=oneshot
RemainAfterExit=yes
ExecStart=systemd-tmpfiles --create --remove --boot --exclude-prefix=/dev
SuccessExitStatus=DATAERR CANTCREAT
+LoadCredential=tmpfiles.extra
+LoadCredential=login.motd
+LoadCredential=login.issue
+LoadCredential=network.hosts