creds: Add ImportCredential=

ImportCredential= takes a credential name and searches for a matching
credential in all the credential stores we know about it. It supports
globs which are expanded so that all matching credentials are loaded.
This commit is contained in:
Daan De Meyer 2023-01-13 16:22:46 +01:00
parent 96df2bd84b
commit bbfb25f4b9
23 changed files with 437 additions and 126 deletions

View file

@ -138,15 +138,16 @@ manager, please consider supporting the following interfaces.
`$container_host_version_id=10`
5. systemd supports passing immutable binary data blobs with limited size and
restricted access to services via the `LoadCredential=` and `SetCredential=`
settings. The same protocol may be used to pass credentials from the
container manager to systemd itself. The credential data should be placed in
some location (ideally a read-only and non-swappable file system, like
'ramfs'), and the absolute path to this directory exported in the
restricted access to services via the `ImportCredential=`, `LoadCredential=`
and `SetCredential=` settings. The same protocol may be used to pass credentials
from the container manager to systemd itself. The credential data should be
placed in some location (ideally a read-only and non-swappable file system,
like 'ramfs'), and the absolute path to this directory exported in the
`$CREDENTIALS_DIRECTORY` environment variable. If the container managers
does this, the credentials passed to the service manager can be propagated
to services via `LoadCredential=` (see ...). The container manager can
choose any path, but `/run/host/credentials` is recommended.
to services via `LoadCredential=` or `ImportCredential=` (see ...). The
container manager can choose any path, but `/run/host/credentials` is
recommended.
## Advanced Integration

View file

@ -72,6 +72,9 @@ Within unit files, there are four settings to configure service credentials.
1. `LoadCredential=` may be used to load a credential from disk, from an
`AF_UNIX` socket, or propagate them from a system credential.
2. `ImportCredential=` may be used to load one or more (encrypted) credentials
from disk or from the credential stores.
2. `SetCredential=` may be used to set a credential to a literal string encoded
in the unit file. Because unit files are world-readable (both on disk and
via D-Bus), this should only be used for credentials that aren't sensitive,
@ -323,7 +326,7 @@ systemd-creds --system cat mycred
Or propagated to services further down:
```
systemd-run -p LoadCredential=mycred -P --wait systemd-creds cat mycred
systemd-run -p ImportCredential=mycred -P --wait systemd-creds cat mycred
```
## Well-Known Credentials
@ -430,13 +433,14 @@ a container manager or via qemu) and `/run/credentials/@encrypted/` (for
credentials that must be decrypted/validated before use, such as those from
`systemd-stub`).
The `LoadCredential=` and `LoadCredentialEncrypted=` settings when configured
with a relative source path will search for the source file to read the
credential from automatically. Primarily, these credentials are searched among
the credentials passed into the system. If not found there, they are searched
in `/etc/credstore/`, `/run/credstore/`,
The `ImportCredential=` setting (and the `LoadCredential=` and
`LoadCredentialEncrypted=` settings when configured with a relative source path)
will search for the source file to read the credential from automatically. Primarily,
these credentials are searched among the credentials passed into the system. If
not found there, they are searched in `/etc/credstore/`, `/run/credstore/`,
`/usr/lib/credstore/`. `LoadCredentialEncrypted=` will also search
`/etc/credstore.encrypted/` and similar directories. These directories are
`/etc/credstore.encrypted/` and similar directories. `ImportCredential` will search
both the non-encrypted and encrypted directories. These directories are
hence a great place to store credentials to load on the system.
## Conditionalizing Services

View file

@ -3042,6 +3042,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(ss) LoadCredentialEncrypted = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as ImportCredential = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as SupplementaryGroups = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s PAMName = '...';
@ -3627,6 +3629,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<!--property LoadCredentialEncrypted is not documented!-->
<!--property ImportCredential is not documented!-->
<!--property SupplementaryGroups is not documented!-->
<!--property PAMName is not documented!-->
@ -4277,6 +4281,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-property" generated="True" extra-ref="LoadCredentialEncrypted"/>
<variablelist class="dbus-property" generated="True" extra-ref="ImportCredential"/>
<variablelist class="dbus-property" generated="True" extra-ref="SupplementaryGroups"/>
<variablelist class="dbus-property" generated="True" extra-ref="PAMName"/>
@ -5058,6 +5064,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(ss) LoadCredentialEncrypted = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as ImportCredential = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as SupplementaryGroups = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s PAMName = '...';
@ -5655,6 +5663,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<!--property LoadCredentialEncrypted is not documented!-->
<!--property ImportCredential is not documented!-->
<!--property SupplementaryGroups is not documented!-->
<!--property PAMName is not documented!-->
@ -6285,6 +6295,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<variablelist class="dbus-property" generated="True" extra-ref="LoadCredentialEncrypted"/>
<variablelist class="dbus-property" generated="True" extra-ref="ImportCredential"/>
<variablelist class="dbus-property" generated="True" extra-ref="SupplementaryGroups"/>
<variablelist class="dbus-property" generated="True" extra-ref="PAMName"/>
@ -6941,6 +6953,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(ss) LoadCredentialEncrypted = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as ImportCredential = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as SupplementaryGroups = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s PAMName = '...';
@ -7466,6 +7480,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<!--property LoadCredentialEncrypted is not documented!-->
<!--property ImportCredential is not documented!-->
<!--property SupplementaryGroups is not documented!-->
<!--property PAMName is not documented!-->
@ -8014,6 +8030,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<variablelist class="dbus-property" generated="True" extra-ref="LoadCredentialEncrypted"/>
<variablelist class="dbus-property" generated="True" extra-ref="ImportCredential"/>
<variablelist class="dbus-property" generated="True" extra-ref="SupplementaryGroups"/>
<variablelist class="dbus-property" generated="True" extra-ref="PAMName"/>
@ -8797,6 +8815,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(ss) LoadCredentialEncrypted = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as ImportCredential = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as SupplementaryGroups = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s PAMName = '...';
@ -9308,6 +9328,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
<!--property LoadCredentialEncrypted is not documented!-->
<!--property ImportCredential is not documented!-->
<!--property SupplementaryGroups is not documented!-->
<!--property PAMName is not documented!-->
@ -9842,6 +9864,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
<variablelist class="dbus-property" generated="True" extra-ref="LoadCredentialEncrypted"/>
<variablelist class="dbus-property" generated="True" extra-ref="ImportCredential"/>
<variablelist class="dbus-property" generated="True" extra-ref="SupplementaryGroups"/>
<variablelist class="dbus-property" generated="True" extra-ref="PAMName"/>

View file

@ -141,8 +141,8 @@
<varlistentry>
<term><option>--credential=</option></term>
<listitem><para>Configure a credential to read the password from if it exists. This may be used in
conjunction with the <varname>LoadCredential=</varname> and <varname>SetCredential=</varname>
settings in unit files. See
conjunction with the <varname>ImportCredential=</varname>, <varname>LoadCredential=</varname> and
<varname>SetCredential=</varname> settings in unit files. See
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details. If not specified, defaults to <literal>password</literal>. This option has no effect if no
credentials directory is passed to the program (i.e. <varname>$CREDENTIALS_DIRECTORY</varname> is not

View file

@ -38,9 +38,9 @@
processes. They are primarily used for passing cryptographic keys (both public and private) or
certificates, user account information or identity information from the host to services.</para>
<para>Credentials are configured in unit files via the <varname>LoadCredential=</varname>,
<varname>SetCredential=</varname>, <varname>LoadCredentialEncrypted=</varname> and
<varname>SetCredentialEncrypted=</varname> settings, see
<para>Credentials are configured in unit files via the <varname>ImportCredential></varname>,
<varname>LoadCredential=</varname>, <varname>SetCredential=</varname>,
<varname>LoadCredentialEncrypted=</varname> and <varname>SetCredentialEncrypted=</varname> settings, see
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details.</para>

View file

@ -304,8 +304,8 @@
<title>Credentials</title>
<para><command>systemd-firstboot</command> supports the service credentials logic as implemented by
<varname>LoadCredential=</varname>/<varname>SetCredential=</varname> (see
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
<varname>ImportCredential=</varname>/<varname>LoadCredential=</varname>/<varname>SetCredential=</varname>
(see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details). The following credentials are used when passed in:</para>
<variablelist>

View file

@ -403,8 +403,8 @@ search foobar.com barbar.com
<title>Credentials</title>
<para><command>systemd-resolved</command> supports the service credentials logic as implemented by
<varname>LoadCredential=</varname>/<varname>SetCredential=</varname> (see
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
<varname>ImportCredential=</varname>/<varname>LoadCredential=</varname>/<varname>SetCredential=</varname>
(see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details). The following credentials are used when passed in:</para>
<variablelist>

View file

@ -85,8 +85,8 @@
<title>Credentials</title>
<para><command>systemd-sysctl</command> supports the service credentials logic as implemented by
<varname>LoadCredential=</varname>/<varname>SetCredential=</varname> (see
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
<varname>ImportCredential=</varname>/<varname>LoadCredential=</varname>/<varname>SetCredential=</varname>
(see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details). The following credentials are used when passed in:</para>
<variablelist>

View file

@ -139,8 +139,8 @@
<title>Credentials</title>
<para><command>systemd-sysusers</command> supports the service credentials logic as implemented by
<varname>LoadCredential=</varname>/<varname>SetCredential=</varname> (see
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
<varname>ImportCredential=</varname><varname>LoadCredential=</varname>/<varname>SetCredential=</varname>
(see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details). The following credentials are used when passed in:</para>
<variablelist>

View file

@ -246,8 +246,8 @@
<title>Credentials</title>
<para><command>systemd-tmpfiles</command> supports the service credentials logic as implemented by
<varname>LoadCredential=</varname>/<varname>SetCredential=</varname> (see
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
<varname>ImportCredential=</varname>/<varname>LoadCredential=</varname>/<varname>SetCredential=</varname>
(see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details). The following credentials are used when passed in:</para>
<variablelist>

View file

@ -53,8 +53,8 @@
<title>Credentials</title>
<para><command>systemd-vconsole-setup</command> supports the service credentials logic as implemented by
<varname>LoadCredential=</varname>/<varname>SetCredential=</varname> (see
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
<varname>ImportCredential=</varname><varname>LoadCredential=</varname>/<varname>SetCredential=</varname>
(see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details). The following credentials are used when passed in:</para>
<variablelist>

View file

@ -3286,6 +3286,25 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX
Credentials</ulink> documentation.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>ImportCredential=</varname><replaceable>GLOB</replaceable></term>
<listitem><para>Pass one or more credentials to the unit. Takes a credential name for which we'll
attempt to find a credential that the service manager itself received under the specified name —
which may be used to propagate credentials from an invoking environment (e.g. a container manager
that invoked the service manager) into a service. If the credential name is a glob, all credentials
matching the glob are passed to the unit. Matching credentials are searched for in the system
credentials, the encrypted system credentials, and under <filename>/etc/credstore/</filename>,
<filename>/run/credstore/</filename>, <filename>/usr/lib/credstore/</filename>,
<filename>/run/credstore.encrypted/</filename>, <filename>/etc/credstore.encrypted/</filename>, and
<filename>/usr/lib/credstore.encrypted/</filename> in that order. When multiple credentials of the
same name are found, the first one found is used.</para>
<para>When multiple credentials of the same name are found, credentials found by
<varname>LoadCredential=</varname> and <varname>LoadCredentialEncrypted=</varname> take priority over
credentials found by <varname>ImportCredential=</varname></para></listitem>.
</varlistentry>
<varlistentry>
<term><varname>SetCredential=</varname><replaceable>ID</replaceable>:<replaceable>VALUE</replaceable></term>
<term><varname>SetCredentialEncrypted=</varname><replaceable>ID</replaceable>:<replaceable>VALUE</replaceable></term>
@ -3307,10 +3326,13 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX
directly from plaintext credentials. For further details see
<varname>LoadCredentialEncrypted=</varname> above.</para>
<para>If a credential of the same ID is listed in both <varname>LoadCredential=</varname> and
<varname>SetCredential=</varname>, the latter will act as default if the former cannot be
retrieved. In this case not being able to retrieve the credential from the path specified in
<varname>LoadCredential=</varname> is not considered fatal.</para></listitem>
<para>When multiple credentials of the same name are found, credentials found by
<varname>LoadCredential=</varname>, <varname>LoadCredentialEncrypted=</varname> and
<varname>ImportCredential=</varname> take priority over credentials found by
<varname>SetCredential=</varname>. As such, <varname>SetCredential=</varname> will act as default if
no credentials are found by any of the former. In this case not being able to retrieve the credential
from the path specified in <varname>LoadCredential=</varname> or
<varname>LoadCredentialEncrypted=</varname> is not considered fatal.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
@ -3492,10 +3514,10 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX
<term><varname>$CREDENTIALS_DIRECTORY</varname></term>
<listitem><para>An absolute path to the per-unit directory with credentials configured via
<varname>LoadCredential=</varname>/<varname>SetCredential=</varname>. The directory is marked
read-only and is placed in unswappable memory (if supported and permitted), and is only accessible to
the UID associated with the unit via <varname>User=</varname> or <varname>DynamicUser=</varname> (and
the superuser).</para></listitem>
<varname>ImportCredential=</varname>/<varname>LoadCredential=</varname>/<varname>SetCredential=</varname>.
The directory is marked read-only and is placed in unswappable memory (if supported and permitted),
and is only accessible to the UID associated with the unit via <varname>User=</varname> or
<varname>DynamicUser=</varname> (and the superuser).</para></listitem>
</varlistentry>
<varlistentry>
@ -4184,7 +4206,7 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX
<row>
<entry>243</entry>
<entry><constant>EXIT_CREDENTIALS</constant></entry>
<entry>Failed to set up unit's credentials. See <varname>LoadCredential=</varname> and <varname>SetCredential=</varname> above.</entry>
<entry>Failed to set up unit's credentials. See <varname>ImportCredential=</varname>, <varname>LoadCredential=</varname> and <varname>SetCredential=</varname> above.</entry>
</row>
<row>
<entry>245</entry>

View file

@ -623,7 +623,7 @@
credential <literal><replaceable>LINK</replaceable>.link.wol.password</literal> (e.g.,
<literal>60-foo.link.wol.password</literal>), and if the credential not found, then
read from <literal>wol.password</literal>. See
<varname>LoadCredential=</varname>/<varname>SetCredential=</varname> in
<varname>ImportCredential=</varname>/<varname>LoadCredential=</varname>/<varname>SetCredential=</varname> in
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry>
for details. The password in the credential, must be 6 bytes in hex format with each
byte separated by a colon (<literal>:</literal>) like an Ethernet MAC address, e.g.,

View file

@ -934,7 +934,7 @@
<term><varname>systemd.set_credential=</varname></term>
<listitem><para>Sets a system credential, which can then be propagated to system services using the
<varname>LoadCredential=</varname> setting, see
<varname>ImportCredential=</varname> or <varname>LoadCredential=</varname> setting, see
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details. Takes a pair of credential name and value, separated by a colon. Note that the kernel
command line is typically accessible by unprivileged programs in

View file

@ -44,8 +44,8 @@ static int help(void) {
" --id=ID Query identifier (e.g. \"cryptsetup:/dev/sda5\")\n"
" --keyname=NAME Kernel key name for caching passwords (e.g. \"cryptsetup\")\n"
" --credential=NAME\n"
" Credential name for LoadCredential=/SetCredential=\n"
" credentials\n"
" Credential name for ImportCredential=, LoadCredential= or\n"
" SetCredential= credentials\n"
" --timeout=SEC Timeout in seconds\n"
" --echo=yes|no|masked\n"
" Control whether to show password while typing (echo)\n"

View file

@ -26,6 +26,7 @@
#include "io-util.h"
#include "ioprio-util.h"
#include "journal-file.h"
#include "load-fragment.h"
#include "memstream-util.h"
#include "missing_ioprio.h"
#include "mountpoint-util.h"
@ -928,6 +929,36 @@ static int property_get_load_credential(
return sd_bus_message_close_container(reply);
}
static int property_get_import_credential(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
ExecContext *c = ASSERT_PTR(userdata);
const char *s;
int r;
assert(bus);
assert(property);
assert(reply);
r = sd_bus_message_open_container(reply, 'a', "s");
if (r < 0)
return r;
SET_FOREACH(s, c->import_credentials) {
r = sd_bus_message_append(reply, "s", s);
if (r < 0)
return r;
}
return sd_bus_message_close_container(reply);
}
static int property_get_root_hash(
sd_bus *bus,
const char *path,
@ -1281,6 +1312,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("SetCredentialEncrypted", "a(say)", property_get_set_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LoadCredential", "a(ss)", property_get_load_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LoadCredentialEncrypted", "a(ss)", property_get_load_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ImportCredential", "as", property_get_import_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("SupplementaryGroups", "as", NULL, offsetof(ExecContext, supplementary_groups), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PAMName", "s", NULL, offsetof(ExecContext, pam_name), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ReadWritePaths", "as", NULL, offsetof(ExecContext, read_write_paths), SD_BUS_VTABLE_PROPERTY_CONST),
@ -2311,39 +2343,11 @@ int bus_exec_context_set_transient_property(
isempty = false;
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
_cleanup_free_ char *copy = NULL;
ExecLoadCredential *old;
bool encrypted = streq(name, "LoadCredentialEncrypted");
copy = strdup(source);
if (!copy)
return -ENOMEM;
old = hashmap_get(c->load_credentials, id);
if (old) {
free_and_replace(old->path, copy);
old->encrypted = streq(name, "LoadCredentialEncrypted");
} else {
_cleanup_(exec_load_credential_freep) ExecLoadCredential *lc = NULL;
lc = new(ExecLoadCredential, 1);
if (!lc)
return -ENOMEM;
*lc = (ExecLoadCredential) {
.id = strdup(id),
.path = TAKE_PTR(copy),
.encrypted = streq(name, "LoadCredentialEncrypted"),
};
if (!lc->id)
return -ENOMEM;
r = hashmap_ensure_put(&c->load_credentials, &exec_load_credential_hash_ops, lc->id, lc);
if (r < 0)
return r;
TAKE_PTR(lc);
}
r = hashmap_put_credential(&c->load_credentials, id, source, encrypted);
if (r < 0)
return r;
(void) unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "%s=%s:%s", name, id, source);
}
@ -2360,6 +2364,47 @@ int bus_exec_context_set_transient_property(
return 1;
} else if (streq(name, "ImportCredential")) {
bool isempty = true;
r = sd_bus_message_enter_container(message, 'a', "s");
if (r < 0)
return r;
for (;;) {
const char *s;
r = sd_bus_message_read(message, "s", &s);
if (r < 0)
return r;
if (r == 0)
break;
if (!filename_is_valid(s))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Credential name is invalid: %s", s);
isempty = false;
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
r = set_put_strdup(&c->import_credentials, s);
if (r < 0)
return r;
(void) unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "%s=%s", name, s);
}
}
r = sd_bus_message_exit_container(message);
if (r < 0)
return r;
if (!UNIT_WRITE_FLAGS_NOOP(flags) && isempty) {
c->import_credentials = set_free(c->import_credentials);
(void) unit_write_settingf(u, flags, name, "%s=", name);
}
return 1;
} else if (streq(name, "SyslogLevel")) {
int32_t level;

View file

@ -1544,7 +1544,8 @@ bool exec_context_has_credentials(const ExecContext *context) {
assert(context);
return !hashmap_isempty(context->set_credentials) ||
!hashmap_isempty(context->load_credentials);
!hashmap_isempty(context->load_credentials) ||
!set_isempty(context->import_credentials);
}
#if HAVE_SECCOMP
@ -2802,6 +2803,111 @@ static char **credential_search_path(const ExecParameters *params, CredentialSea
return TAKE_PTR(l);
}
static int maybe_decrypt_and_write_credential(
int dir_fd,
const char *id,
bool encrypted,
uid_t uid,
bool ownership_ok,
const char *data,
size_t size,
uint64_t *left) {
_cleanup_free_ void *plaintext = NULL;
size_t add;
int r;
if (encrypted) {
size_t plaintext_size = 0;
r = decrypt_credential_and_warn(id, now(CLOCK_REALTIME), NULL, NULL, data, size,
&plaintext, &plaintext_size);
if (r < 0)
return r;
data = plaintext;
size = plaintext_size;
}
add = strlen(id) + size;
if (add > *left)
return -E2BIG;
r = write_credential(dir_fd, id, data, size, uid, ownership_ok);
if (r < 0)
return log_debug_errno(r, "Failed to write credential '%s': %m", id);
*left -= add;
return 0;
}
static int load_credential_glob(
const char *path,
bool encrypted,
char **search_path,
ReadFullFileFlags flags,
int write_dfd,
uid_t uid,
bool ownership_ok,
uint64_t *left) {
int r;
STRV_FOREACH(d, search_path) {
_cleanup_globfree_ glob_t pglob = {};
_cleanup_free_ char *j = NULL;
j = path_join(*d, path);
if (!j)
return -ENOMEM;
r = safe_glob(j, 0, &pglob);
if (r == -ENOENT)
continue;
if (r < 0)
return r;
for (unsigned n = 0; n < pglob.gl_pathc; n++) {
_cleanup_free_ char *fn = NULL;
_cleanup_(erase_and_freep) char *data = NULL;
size_t size;
/* path is absolute, hence pass AT_FDCWD as nop dir fd here */
r = read_full_file_full(
AT_FDCWD,
pglob.gl_pathv[n],
UINT64_MAX,
encrypted ? CREDENTIAL_ENCRYPTED_SIZE_MAX : CREDENTIAL_SIZE_MAX,
flags,
NULL,
&data, &size);
if (r < 0)
return log_debug_errno(r, "Failed to read credential '%s': %m",
pglob.gl_pathv[n]);
r = path_extract_filename(pglob.gl_pathv[n], &fn);
if (r < 0)
return log_debug_errno(r, "Failed to extract filename from '%s': %m",
pglob.gl_pathv[n]);
r = maybe_decrypt_and_write_credential(
write_dfd,
fn,
encrypted,
uid,
ownership_ok,
data, size,
left);
if (r == -EEXIST)
continue;
if (r < 0)
return r;
}
}
return 0;
}
static int load_credential(
const ExecContext *context,
const ExecParameters *params,
@ -2821,7 +2927,7 @@ static int load_credential(
_cleanup_free_ char *bindname = NULL;
const char *source = NULL;
bool missing_ok = true;
size_t size, add, maxsz;
size_t size, maxsz;
int r;
assert(context);
@ -2923,28 +3029,7 @@ static int load_credential(
if (r < 0)
return log_debug_errno(r, "Failed to read credential '%s': %m", path);
if (encrypted) {
_cleanup_free_ void *plaintext = NULL;
size_t plaintext_size = 0;
r = decrypt_credential_and_warn(id, now(CLOCK_REALTIME), NULL, NULL, data, size, &plaintext, &plaintext_size);
if (r < 0)
return r;
free_and_replace(data, plaintext);
size = plaintext_size;
}
add = strlen(id) + size;
if (add > *left)
return -E2BIG;
r = write_credential(write_dfd, id, data, size, uid, ownership_ok);
if (r < 0)
return log_debug_errno(r, "Failed to write credential '%s': %m", id);
*left -= add;
return 0;
return maybe_decrypt_and_write_credential(write_dfd, id, encrypted, uid, ownership_ok, data, size, left);
}
struct load_cred_args {
@ -3019,6 +3104,7 @@ static int acquire_credentials(
uint64_t left = CREDENTIALS_TOTAL_SIZE_MAX;
_cleanup_close_ int dfd = -EBADF;
const char *ic;
ExecLoadCredential *lc;
ExecSetCredential *sc;
int r;
@ -3084,8 +3170,47 @@ static int acquire_credentials(
return r;
}
/* Second, we add in literally specified credentials. If the credentials already exist, we'll not add
* them, so that they can act as a "default" if the same credential is specified multiple times. */
/* Next, look for system credentials and credentials in the credentials store. Note that these do not
* override any credentials found earlier. */
SET_FOREACH(ic, context->import_credentials) {
_cleanup_free_ char **search_path = NULL;
search_path = credential_search_path(params, CREDENTIAL_SEARCH_PATH_TRUSTED);
if (!search_path)
return -ENOMEM;
r = load_credential_glob(
ic,
/* encrypted = */ false,
search_path,
READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER,
dfd,
uid,
ownership_ok,
&left);
if (r < 0)
return r;
search_path = strv_free(search_path);
search_path = credential_search_path(params, CREDENTIAL_SEARCH_PATH_ENCRYPTED);
if (!search_path)
return -ENOMEM;
r = load_credential_glob(
ic,
/* encrypted = */ true,
search_path,
READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER|READ_FULL_FILE_UNBASE64,
dfd,
uid,
ownership_ok,
&left);
if (r < 0)
return r;
}
/* Finally, we add in literally specified credentials. If the credentials already exist, we'll not
* add them, so that they can act as a "default" if the same credential is specified multiple times. */
HASHMAP_FOREACH(sc, context->set_credentials) {
_cleanup_(erase_and_freep) void *plaintext = NULL;
const char *data;
@ -5883,6 +6008,7 @@ void exec_context_done(ExecContext *c) {
c->load_credentials = hashmap_free(c->load_credentials);
c->set_credentials = hashmap_free(c->set_credentials);
c->import_credentials = set_free(c->import_credentials);
c->root_image_policy = image_policy_free(c->root_image_policy);
c->mount_image_policy = image_policy_free(c->mount_image_policy);

View file

@ -361,6 +361,7 @@ struct ExecContext {
Hashmap *set_credentials; /* output id → ExecSetCredential */
Hashmap *load_credentials; /* output id → ExecLoadCredential */
Set *import_credentials;
ImagePolicy *root_image_policy, *mount_image_policy, *extension_image_policy;
};

View file

@ -151,6 +151,7 @@
{{type}}.SetCredentialEncrypted, config_parse_set_credential, 1, offsetof({{type}}, exec_context)
{{type}}.LoadCredential, config_parse_load_credential, 0, offsetof({{type}}, exec_context)
{{type}}.LoadCredentialEncrypted, config_parse_load_credential, 1, offsetof({{type}}, exec_context)
{{type}}.ImportCredential, config_parse_import_credential, 0, offsetof({{type}}, exec_context.import_credentials)
{{type}}.TimeoutCleanSec, config_parse_sec, 0, offsetof({{type}}, exec_context.timeout_clean_usec)
{% if HAVE_PAM %}
{{type}}.PAMName, config_parse_unit_string_printf, 0, offsetof({{type}}, exec_context.pam_name)

View file

@ -4840,6 +4840,46 @@ int config_parse_set_credential(
return 0;
}
int hashmap_put_credential(Hashmap **h, const char *id, const char *path, bool encrypted) {
ExecLoadCredential *old;
int r;
assert(h);
assert(id);
assert(path);
old = hashmap_get(*h, id);
if (old) {
r = free_and_strdup(&old->path, path);
if (r < 0)
return r;
old->encrypted = encrypted;
} else {
_cleanup_(exec_load_credential_freep) ExecLoadCredential *lc = NULL;
lc = new(ExecLoadCredential, 1);
if (!lc)
return log_oom();
*lc = (ExecLoadCredential) {
.id = strdup(id),
.path = strdup(path),
.encrypted = encrypted,
};
if (!lc->id || !lc->path)
return -ENOMEM;
r = hashmap_ensure_put(h, &exec_load_credential_hash_ops, lc->id, lc);
if (r < 0)
return r;
TAKE_PTR(lc);
}
return 0;
}
int config_parse_load_credential(
const char *unit,
const char *filename,
@ -4854,7 +4894,6 @@ int config_parse_load_credential(
_cleanup_free_ char *word = NULL, *k = NULL, *q = NULL;
ExecContext *context = ASSERT_PTR(data);
ExecLoadCredential *old;
bool encrypted = ltype;
Unit *u = userdata;
const char *p;
@ -4907,35 +4946,54 @@ int config_parse_load_credential(
}
}
old = hashmap_get(context->load_credentials, k);
if (old) {
free_and_replace(old->path, q);
old->encrypted = encrypted;
} else {
_cleanup_(exec_load_credential_freep) ExecLoadCredential *lc = NULL;
r = hashmap_put_credential(&context->load_credentials, k, q, encrypted);
if (r < 0)
return log_error_errno(r, "Failed to store load credential '%s': %m", rvalue);
lc = new(ExecLoadCredential, 1);
if (!lc)
return log_oom();
return 0;
}
*lc = (ExecLoadCredential) {
.id = TAKE_PTR(k),
.path = TAKE_PTR(q),
.encrypted = encrypted,
};
int config_parse_import_credential(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
r = hashmap_ensure_put(&context->load_credentials, &exec_load_credential_hash_ops, lc->id, lc);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Duplicated credential value '%s', ignoring assignment: %s", lc->id, rvalue);
return 0;
}
_cleanup_free_ char *s = NULL;
Set** import_credentials = ASSERT_PTR(data);
Unit *u = userdata;
int r;
TAKE_PTR(lc);
assert(filename);
assert(lvalue);
assert(rvalue);
if (isempty(rvalue)) {
/* Empty assignment resets the list */
*import_credentials = set_free(*import_credentials);
return 0;
}
r = unit_cred_printf(u, rvalue, &s);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in \"%s\", ignoring: %m", s);
return 0;
}
if (!filename_is_valid(s)) {
log_syntax(unit, LOG_WARNING, filename, line, 0, "Credential name \"%s\" not valid, ignoring.", s);
return 0;
}
r = set_put_strdup(import_credentials, s);
if (r < 0)
return log_error_errno(r, "Failed to store credential name '%s': %m", rvalue);
return 0;
}

View file

@ -12,6 +12,8 @@ int unit_is_likely_recursive_template_dependency(Unit *u, const char *name, cons
int parse_crash_chvt(const char *value, int *data);
int parse_confirm_spawn(const char *value, char **console);
int hashmap_put_credential(Hashmap **h, const char *id, const char *path, bool encrypted);
/* Read service data from .desktop file style configuration fragments */
int unit_load_fragment(Unit *u);
@ -105,6 +107,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_preserve_mode);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_directories);
CONFIG_PARSER_PROTOTYPE(config_parse_set_credential);
CONFIG_PARSER_PROTOTYPE(config_parse_load_credential);
CONFIG_PARSER_PROTOTYPE(config_parse_import_credential);
CONFIG_PARSER_PROTOTYPE(config_parse_set_status);
CONFIG_PARSER_PROTOTYPE(config_parse_namespace_path_strv);
CONFIG_PARSER_PROTOTYPE(config_parse_temporary_filesystems);

View file

@ -1209,6 +1209,17 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
return 1;
}
if (streq(field, "ImportCredential")) {
if (isempty(eq))
r = sd_bus_message_append(m, "(sv)", field, "as", 0);
else
r = sd_bus_message_append(m, "(sv)", field, "as", 1, eq);
if (r < 0)
return bus_log_create_error(r);
return 1;
}
if (streq(field, "LogExtraFields")) {
r = sd_bus_message_open_container(m, 'r', "sv");
if (r < 0)

View file

@ -253,6 +253,21 @@ cmp /tmp/ts54-concat <(echo -n abcd)
rm /tmp/ts54-concat
rm -rf /tmp/ts54-creds
# Check that globs work as expected
mkdir -p /run/credstore
echo -n a >/run/credstore/test.creds.first
echo -n b >/run/credstore/test.creds.second
mkdir -p /etc/credstore
echo -n c >/etc/credstore/test.creds.third
systemd-run -p "ImportCredential=test.creds.*" \
-p DynamicUser=1 \
--wait \
--pipe \
cat '${CREDENTIALS_DIRECTORY}/test.creds.first' \
'${CREDENTIALS_DIRECTORY}/test.creds.second' \
'${CREDENTIALS_DIRECTORY}/test.creds.third' >/tmp/ts54-concat
cmp /tmp/ts54-concat <(echo -n abc)
# Now test encrypted credentials (only supported when built with OpenSSL though)
if systemctl --version | grep -q -- +OPENSSL ; then
echo -n $RANDOM >/tmp/test-54-plaintext