]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
creds: Add ImportCredential=
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 13 Jan 2023 15:22:46 +0000 (16:22 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 8 Jun 2023 12:09:18 +0000 (14:09 +0200)
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.

23 files changed:
docs/CONTAINER_INTERFACE.md
docs/CREDENTIALS.md
man/org.freedesktop.systemd1.xml
man/systemd-ask-password.xml
man/systemd-creds.xml
man/systemd-firstboot.xml
man/systemd-resolved.service.xml
man/systemd-sysctl.service.xml
man/systemd-sysusers.xml
man/systemd-tmpfiles.xml
man/systemd-vconsole-setup.service.xml
man/systemd.exec.xml
man/systemd.link.xml
man/systemd.xml
src/ask-password/ask-password.c
src/core/dbus-execute.c
src/core/execute.c
src/core/execute.h
src/core/load-fragment-gperf.gperf.in
src/core/load-fragment.c
src/core/load-fragment.h
src/shared/bus-unit-util.c
test/units/testsuite-54.sh

index ddeaf8ea4fa09df4b25d16501a0e74eec81606bd..2435d4ae97c6728cff1a0b7eb6f69ed37acb7db4 100644 (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
 
index 083c7ecc3c532409622b15128e64cf00827dc604..2f6bdd44b2e936320b903671538565c611fc7723 100644 (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
index 70273bbf64bf06343b617854645c79c8e77d6fd5..fc9a79d79626c4f0bdb47bfbcb97bee8f55c116e 100644 (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"/>
index 27f77751fef9574dc40387841a10b70eb1ff9713..ffd6c1c4d866c0344a1df7335fe05157b9778412 100644 (file)
       <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
index fbe62262af08555b950c27570ec7c8c46eed47ed..a5cfe0901a7e488a65bace090d5b239e663b65c2 100644 (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>
 
index 99846839678e8dac5c3459cb9d7dbde492679876..becb5f52ace9c51139ac6bed7daf078182ebc897 100644 (file)
     <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>
index 7cc143fd41df05c7183e44c5bdc53b96f6224a98..05d20bbf35502da8731143aa440bce5ec223eeb0 100644 (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>
index fede8b092d730036c662bbb6f34f8c87f7d06582..4174184c152e63ba8e1cf9dff32b481bb4769789 100644 (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>
index f7ee5e79d91fe083dd5b29febd72759818e9c4f1..34d3cab5c7e6582e913a81bab8afe58d8b04c20b 100644 (file)
     <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>
index 735f59cc11acc8b5a6d679b7edcfb36fd0269bdf..3a9699ff4b12e832acfd6e9ac9fee9f8dce43461 100644 (file)
     <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>
index 98d9e2ad01b2e14827a41c14f2d01479fa818ef7..f9f8327a68cdf2566d45ba390edec4b624444cc2 100644 (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>
index 9fb6c9b90841f2c79a5d21257c710c991d9c3bb1..b70b90d6671954be6fc4fa98fd0acc8c536508b7 100644 (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>
index cc851d31f98a710b276f17d971ea897059a4f9d7..1384b1acb3cde7f50d1174d123958180b9ded22f 100644 (file)
                 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.,
index 95dc1fef83c641bd6066370b0a04c55a6ca8cd87..2b1e9908463f4e2ceca63aa90bba7dd6e426cc87 100644 (file)
         <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
index de41d7b641d24cc07f2696c97ec6a65bb81cba9c..b45842f1cbc3df5a09c0f3d8e76d6bfc0c0002b8 100644 (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"
index fb22a9769d8acc2336ab80c9a43c88a3241a94d9..57e9eb825464f89ff0ae02d50543ee3febce0327 100644 (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,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);
                 }
 
index 2d1538be857e4bf3bea1f6289d41a7aba397caef..29e06e837e9a51963dbb986e1293dd5e137a16a0 100644 (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);
index 1c8378c8b09de94e552978f46a498f7acaedf94c..953dc9e7f778aef9cac7f5f4faaf047619eb4159 100644 (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;
 };
index 64a00fef28d8a7984f84f4b0ec21c7a47d583001..ae318dae895a3a4c60d32bd9b86ec888cf1a0eb4 100644 (file)
 {{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)
index 554180f1e3152360c11d1f1b6a2ce59a37866cec..6d129af7b2aec4f1139970d8fcac87c2179be9ce 100644 (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;
 }
 
index 59f02a32072f24a2adb10d20e5c7e987196aed2d..3cfb50969a91a4404e2a7843636bdd2ced0c8945 100644 (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);
index 8b1a353a9b3df512542191e5b7b230d51c3b8f18..6e93d0ca434bfb9441bbc9f33273cbb6555b81b3 100644 (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)
index d243d93d8132e5b57cddb31c136e94ab5476865e..4affd128ccd4a0af2aed5ca8ee9f59a9266a2c07 100755 (executable)
@@ -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