diff options
-rw-r--r-- | docs/CONTAINER_INTERFACE.md | 15 | ||||
-rw-r--r-- | docs/CREDENTIALS.md | 18 | ||||
-rw-r--r-- | man/org.freedesktop.systemd1.xml | 24 | ||||
-rw-r--r-- | man/systemd-ask-password.xml | 4 | ||||
-rw-r--r-- | man/systemd-creds.xml | 6 | ||||
-rw-r--r-- | man/systemd-firstboot.xml | 4 | ||||
-rw-r--r-- | man/systemd-resolved.service.xml | 4 | ||||
-rw-r--r-- | man/systemd-sysctl.service.xml | 4 | ||||
-rw-r--r-- | man/systemd-sysusers.xml | 4 | ||||
-rw-r--r-- | man/systemd-tmpfiles.xml | 4 | ||||
-rw-r--r-- | man/systemd-vconsole-setup.service.xml | 4 | ||||
-rw-r--r-- | man/systemd.exec.xml | 40 | ||||
-rw-r--r-- | man/systemd.link.xml | 2 | ||||
-rw-r--r-- | man/systemd.xml | 2 | ||||
-rw-r--r-- | src/ask-password/ask-password.c | 4 | ||||
-rw-r--r-- | src/core/dbus-execute.c | 101 | ||||
-rw-r--r-- | src/core/execute.c | 178 | ||||
-rw-r--r-- | src/core/execute.h | 1 | ||||
-rw-r--r-- | src/core/load-fragment-gperf.gperf.in | 1 | ||||
-rw-r--r-- | src/core/load-fragment.c | 106 | ||||
-rw-r--r-- | src/core/load-fragment.h | 3 | ||||
-rw-r--r-- | src/shared/bus-unit-util.c | 11 | ||||
-rwxr-xr-x | test/units/testsuite-54.sh | 15 |
23 files changed, 433 insertions, 122 deletions
diff --git a/docs/CONTAINER_INTERFACE.md b/docs/CONTAINER_INTERFACE.md index ddeaf8ea4f..2435d4ae97 100644 --- a/docs/CONTAINER_INTERFACE.md +++ b/docs/CONTAINER_INTERFACE.md @@ -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 diff --git a/docs/CREDENTIALS.md b/docs/CREDENTIALS.md index 083c7ecc3c..2f6bdd44b2 100644 --- a/docs/CREDENTIALS.md +++ b/docs/CREDENTIALS.md @@ -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 diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 70273bbf64..fc9a79d796 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -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"/> diff --git a/man/systemd-ask-password.xml b/man/systemd-ask-password.xml index 27f77751fe..ffd6c1c4d8 100644 --- a/man/systemd-ask-password.xml +++ b/man/systemd-ask-password.xml @@ -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 diff --git a/man/systemd-creds.xml b/man/systemd-creds.xml index fbe62262af..a5cfe0901a 100644 --- a/man/systemd-creds.xml +++ b/man/systemd-creds.xml @@ -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> diff --git a/man/systemd-firstboot.xml b/man/systemd-firstboot.xml index 9984683967..becb5f52ac 100644 --- a/man/systemd-firstboot.xml +++ b/man/systemd-firstboot.xml @@ -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> diff --git a/man/systemd-resolved.service.xml b/man/systemd-resolved.service.xml index 7cc143fd41..05d20bbf35 100644 --- a/man/systemd-resolved.service.xml +++ b/man/systemd-resolved.service.xml @@ -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> diff --git a/man/systemd-sysctl.service.xml b/man/systemd-sysctl.service.xml index fede8b092d..4174184c15 100644 --- a/man/systemd-sysctl.service.xml +++ b/man/systemd-sysctl.service.xml @@ -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> diff --git a/man/systemd-sysusers.xml b/man/systemd-sysusers.xml index f7ee5e79d9..34d3cab5c7 100644 --- a/man/systemd-sysusers.xml +++ b/man/systemd-sysusers.xml @@ -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> diff --git a/man/systemd-tmpfiles.xml b/man/systemd-tmpfiles.xml index 735f59cc11..3a9699ff4b 100644 --- a/man/systemd-tmpfiles.xml +++ b/man/systemd-tmpfiles.xml @@ -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> diff --git a/man/systemd-vconsole-setup.service.xml b/man/systemd-vconsole-setup.service.xml index 98d9e2ad01..f9f8327a68 100644 --- a/man/systemd-vconsole-setup.service.xml +++ b/man/systemd-vconsole-setup.service.xml @@ -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> diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 9fb6c9b908..b70b90d667 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -3287,6 +3287,25 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX </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> diff --git a/man/systemd.link.xml b/man/systemd.link.xml index cc851d31f9..1384b1acb3 100644 --- a/man/systemd.link.xml +++ b/man/systemd.link.xml @@ -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., diff --git a/man/systemd.xml b/man/systemd.xml index 95dc1fef83..2b1e990846 100644 --- a/man/systemd.xml +++ b/man/systemd.xml @@ -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 diff --git a/src/ask-password/ask-password.c b/src/ask-password/ask-password.c index de41d7b641..b45842f1cb 100644 --- a/src/ask-password/ask-password.c +++ b/src/ask-password/ask-password.c @@ -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" diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index fb22a9769d..57e9eb8254 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -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,41 +2343,54 @@ 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; + r = hashmap_put_credential(&c->load_credentials, id, source, encrypted); + if (r < 0) + return r; - 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; + (void) unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "%s=%s:%s", name, id, source); + } + } - lc = new(ExecLoadCredential, 1); - if (!lc) - return -ENOMEM; + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; - *lc = (ExecLoadCredential) { - .id = strdup(id), - .path = TAKE_PTR(copy), - .encrypted = streq(name, "LoadCredentialEncrypted"), - }; + if (!UNIT_WRITE_FLAGS_NOOP(flags) && isempty) { + c->load_credentials = hashmap_free(c->load_credentials); + (void) unit_write_settingf(u, flags, name, "%s=", name); + } - if (!lc->id) - return -ENOMEM; + return 1; - r = hashmap_ensure_put(&c->load_credentials, &exec_load_credential_hash_ops, lc->id, lc); - if (r < 0) - return r; + } else if (streq(name, "ImportCredential")) { + bool isempty = true; - TAKE_PTR(lc); - } + r = sd_bus_message_enter_container(message, 'a', "s"); + if (r < 0) + return r; - (void) unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "%s=%s:%s", name, id, source); + 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); } } @@ -2354,7 +2399,7 @@ int bus_exec_context_set_transient_property( return r; if (!UNIT_WRITE_FLAGS_NOOP(flags) && isempty) { - c->load_credentials = hashmap_free(c->load_credentials); + c->import_credentials = set_free(c->import_credentials); (void) unit_write_settingf(u, flags, name, "%s=", name); } diff --git a/src/core/execute.c b/src/core/execute.c index 2d1538be85..29e06e837e 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -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); diff --git a/src/core/execute.h b/src/core/execute.h index 1c8378c8b0..953dc9e7f7 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -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; }; diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index 64a00fef28..ae318dae89 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -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) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 554180f1e3..6d129af7b2 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -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; } diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index 59f02a3207..3cfb50969a 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -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); diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 8b1a353a9b..6e93d0ca43 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -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) diff --git a/test/units/testsuite-54.sh b/test/units/testsuite-54.sh index d243d93d81..4affd128cc 100755 --- a/test/units/testsuite-54.sh +++ b/test/units/testsuite-54.sh @@ -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 |