]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
pid1: add mechanism for conditionalizing units/network/netdev/link based on credentia...
authorLennart Poettering <lennart@poettering.net>
Wed, 13 Jul 2022 08:38:53 +0000 (10:38 +0200)
committerLennart Poettering <lennart@poettering.net>
Fri, 15 Jul 2022 08:53:45 +0000 (10:53 +0200)
This is useful when provisioning systems via nspawn/qemu and running
specific services only if specific data is passed into the system.

13 files changed:
docs/CREDENTIALS.md
man/systemd.link.xml
man/systemd.netdev.xml
man/systemd.network.xml
man/systemd.unit.xml
src/core/load-fragment-gperf.gperf.in
src/network/netdev/netdev-gperf.gperf
src/network/networkd-network-gperf.gperf
src/shared/condition.c
src/shared/condition.h
src/test/test-condition.c
src/udev/net/link-config-gperf.gperf
test/units/testsuite-54.sh

index bbd92ad3c9f41cb3e056424ca60183c01877cb68..4ba37844696e4e3dd439bcfb5e757b00ea41fd91 100644 (file)
@@ -395,3 +395,9 @@ in `/etc/credstore/`, `/run/credstore/`,
 `/usr/lib/credstore/`. `LoadCredentialEncrypted=` will also search
 `/etc/credstore.encrypted/` and similar directories. These directories are
 hence a great place to store credentials to load on the system.
+
+## Conditionalizing Services
+
+Sometimes it makes sense to conditionalize system services and invoke them only
+if the right system credential is passed to the system. use the
+`ConditionCredential=` and `AssertCredential=` unit file settings for that.
index de23b941ad2dcf40527e15eb6c2f638e3e386b50..d9336323937e2e6243dc699a373da8dd17e720c9 100644 (file)
         </listitem>
       </varlistentry>
 
+      <varlistentry id='credential'>
+        <term><varname>Credential=</varname></term>
+        <listitem>
+          <para>Checks whether the specified credential was passed to the
+          <filename>systemd-networkd.service</filename> service. See <ulink
+          url="https://systemd.io/CREDENTIALS">System and Service Credentials</ulink> for details. When
+          prefixed with an exclamation mark (<literal>!</literal>), the result is negated.  If an empty
+          string is assigned, the previously assigned value is cleared.
+          </para>
+        </listitem>
+      </varlistentry>
+
       <varlistentry id='architecture'>
         <term><varname>Architecture=</varname></term>
         <listitem>
index 7d3a4f95c858eee6120f8af0796009ca502d9c11..c3578fc2dae5d3d7ff1dd5e5e9134eb7b24e2298 100644 (file)
       <xi:include href="systemd.link.xml" xpointer="virtualization" />
       <xi:include href="systemd.link.xml" xpointer="kernel-command-line" />
       <xi:include href="systemd.link.xml" xpointer="kernel-version" />
+      <xi:include href="systemd.link.xml" xpointer="credential" />
       <xi:include href="systemd.link.xml" xpointer="architecture" />
       <xi:include href="systemd.link.xml" xpointer="firmware" />
     </variablelist>
index 516a42e25a0090656a5061964db3d5f749d912ab..70d2c34a40472531df2f9e4ab8856997de4dcf76 100644 (file)
       <xi:include href="systemd.link.xml" xpointer="virtualization" />
       <xi:include href="systemd.link.xml" xpointer="kernel-command-line" />
       <xi:include href="systemd.link.xml" xpointer="kernel-version" />
+      <xi:include href="systemd.link.xml" xpointer="credential" />
       <xi:include href="systemd.link.xml" xpointer="architecture" />
       <xi:include href="systemd.link.xml" xpointer="firmware" />
     </variablelist>
index 55f32f32728f58df7d4afdf2cfaf01803418f76b..ea95ba886927c7b558f5ae56ab94d9377d01b5df 100644 (file)
           </listitem>
         </varlistentry>
 
+        <varlistentry>
+          <term><varname>ConditionCredential=</varname></term>
+
+          <listitem><para><varname>ConditionCredential=</varname> may be used to check whether a credential
+          by the specified name was passed into the service manager. See <ulink
+          url="https://systemd.io/CREDENTIALS">System and Service Credentials</ulink> for details about
+          credentials. If used in services for the system service manager this may be used to conditionalize
+          services based on system credentials passed in. If used in services for the per-user service
+          manager this may be used to conditionalize services based on credentials passed into the
+          <filename>unit@.service</filename> service instance belonging to the user. The argument must be a
+          valid credential name.</para></listitem>
+        </varlistentry>
+
         <varlistentry>
           <term><varname>ConditionEnvironment=</varname></term>
 
index 7817c20c0ba9d5a42bbd556ca2cdc4dde75e2075..54c1c0bb56bfe81d4770da5ac3c99c551dd92944 100644 (file)
@@ -332,6 +332,7 @@ Unit.ConditionVirtualization,            config_parse_unit_condition_string,
 Unit.ConditionHost,                      config_parse_unit_condition_string,          CONDITION_HOST,                     offsetof(Unit, conditions)
 Unit.ConditionKernelCommandLine,         config_parse_unit_condition_string,          CONDITION_KERNEL_COMMAND_LINE,      offsetof(Unit, conditions)
 Unit.ConditionKernelVersion,             config_parse_unit_condition_string,          CONDITION_KERNEL_VERSION,           offsetof(Unit, conditions)
+Unit.ConditionCredential,                config_parse_unit_condition_string,          CONDITION_CREDENTIAL,               offsetof(Unit, conditions)
 Unit.ConditionSecurity,                  config_parse_unit_condition_string,          CONDITION_SECURITY,                 offsetof(Unit, conditions)
 Unit.ConditionCapability,                config_parse_unit_condition_string,          CONDITION_CAPABILITY,               offsetof(Unit, conditions)
 Unit.ConditionACPower,                   config_parse_unit_condition_string,          CONDITION_AC_POWER,                 offsetof(Unit, conditions)
@@ -363,6 +364,7 @@ Unit.AssertVirtualization,               config_parse_unit_condition_string,
 Unit.AssertHost,                         config_parse_unit_condition_string,          CONDITION_HOST,                     offsetof(Unit, asserts)
 Unit.AssertKernelCommandLine,            config_parse_unit_condition_string,          CONDITION_KERNEL_COMMAND_LINE,      offsetof(Unit, asserts)
 Unit.AssertKernelVersion,                config_parse_unit_condition_string,          CONDITION_KERNEL_VERSION,           offsetof(Unit, asserts)
+Unit.AssertCredential,                   config_parse_unit_condition_string,          CONDITION_CREDENTIAL,               offsetof(Unit, asserts)
 Unit.AssertSecurity,                     config_parse_unit_condition_string,          CONDITION_SECURITY,                 offsetof(Unit, asserts)
 Unit.AssertCapability,                   config_parse_unit_condition_string,          CONDITION_CAPABILITY,               offsetof(Unit, asserts)
 Unit.AssertACPower,                      config_parse_unit_condition_string,          CONDITION_AC_POWER,                 offsetof(Unit, asserts)
index 55ad60ddc8b8661eb4ba8b23e71e1abb3af79805..162664ecf131afae5c85d4a19c9b6811b0d005e5 100644 (file)
@@ -45,6 +45,7 @@ Match.Host,                               config_parse_net_condition,
 Match.Virtualization,                     config_parse_net_condition,                CONDITION_VIRTUALIZATION,      offsetof(NetDev, conditions)
 Match.KernelCommandLine,                  config_parse_net_condition,                CONDITION_KERNEL_COMMAND_LINE, offsetof(NetDev, conditions)
 Match.KernelVersion,                      config_parse_net_condition,                CONDITION_KERNEL_VERSION,      offsetof(NetDev, conditions)
+Match.Credential,                         config_parse_net_condition,                CONDITION_CREDENTIAL,          offsetof(NetDev, conditions)
 Match.Architecture,                       config_parse_net_condition,                CONDITION_ARCHITECTURE,        offsetof(NetDev, conditions)
 Match.Firmware,                           config_parse_net_condition,                CONDITION_FIRMWARE,            offsetof(NetDev, conditions)
 NetDev.Description,                       config_parse_string,                       0,                             offsetof(NetDev, description)
index 0b0c8da27b5c57725571d1c76334b4af9126868f..13d521e37a2910f8c4f7e4681b87f9a3db5adab2 100644 (file)
@@ -62,6 +62,7 @@ Match.Host,                                  config_parse_net_condition,
 Match.Virtualization,                        config_parse_net_condition,                               CONDITION_VIRTUALIZATION,      offsetof(Network, conditions)
 Match.KernelCommandLine,                     config_parse_net_condition,                               CONDITION_KERNEL_COMMAND_LINE, offsetof(Network, conditions)
 Match.KernelVersion,                         config_parse_net_condition,                               CONDITION_KERNEL_VERSION,      offsetof(Network, conditions)
+Match.Credential,                            config_parse_net_condition,                               CONDITION_CREDENTIAL,          offsetof(Network, conditions)
 Match.Architecture,                          config_parse_net_condition,                               CONDITION_ARCHITECTURE,        offsetof(Network, conditions)
 Match.Firmware,                              config_parse_net_condition,                               CONDITION_FIRMWARE,            offsetof(Network, conditions)
 Link.MACAddress,                             config_parse_hw_addr,                                     0,                             offsetof(Network, hw_addr)
index 640dd96eb2bdbedaa612a92bafd4136faafd7185..2fc22c3714065eb54038f85cb2c96dd6920c6366 100644 (file)
@@ -22,6 +22,7 @@
 #include "cgroup-util.h"
 #include "condition.h"
 #include "cpu-set-util.h"
+#include "creds-util.h"
 #include "efi-api.h"
 #include "env-file.h"
 #include "env-util.h"
@@ -140,6 +141,46 @@ static int condition_test_kernel_command_line(Condition *c, char **env) {
         return false;
 }
 
+static int condition_test_credential(Condition *c, char **env) {
+        int (*gd)(const char **ret);
+        int r;
+
+        assert(c);
+        assert(c->parameter);
+        assert(c->type == CONDITION_CREDENTIAL);
+
+        /* For now we'll do a very simple existance check and are happy with either a regular or an encrypted
+         * credential. Given that we check the syntax of the argument we have the option to later maybe allow
+         * contents checks too without breaking compatibility, but for now let's be minimalistic. */
+
+        if (!credential_name_valid(c->parameter)) /* credentials with invalid names do not exist */
+                return false;
+
+        FOREACH_POINTER(gd, get_credentials_dir, get_encrypted_credentials_dir) {
+                _cleanup_free_ char *j = NULL;
+                const char *cd;
+
+                r = gd(&cd);
+                if (r == -ENXIO) /* no env var set */
+                        continue;
+                if (r < 0)
+                        return r;
+
+                j = path_join(cd, c->parameter);
+                if (!j)
+                        return -ENOMEM;
+
+                if (laccess(j, F_OK) >= 0)
+                        return true; /* yay! */
+                if (errno != ENOENT)
+                        return -errno;
+
+                /* not found in this dir */
+        }
+
+        return false;
+}
+
 typedef enum {
         /* Listed in order of checking. Note that some comparators are prefixes of others, hence the longest
          * should be listed first. */
@@ -1099,6 +1140,7 @@ int condition_test(Condition *c, char **env) {
                 [CONDITION_FILE_IS_EXECUTABLE]       = condition_test_file_is_executable,
                 [CONDITION_KERNEL_COMMAND_LINE]      = condition_test_kernel_command_line,
                 [CONDITION_KERNEL_VERSION]           = condition_test_kernel_version,
+                [CONDITION_CREDENTIAL]               = condition_test_credential,
                 [CONDITION_VIRTUALIZATION]           = condition_test_virtualization,
                 [CONDITION_SECURITY]                 = condition_test_security,
                 [CONDITION_CAPABILITY]               = condition_test_capability,
@@ -1218,6 +1260,7 @@ static const char* const condition_type_table[_CONDITION_TYPE_MAX] = {
         [CONDITION_HOST] = "ConditionHost",
         [CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine",
         [CONDITION_KERNEL_VERSION] = "ConditionKernelVersion",
+        [CONDITION_CREDENTIAL] = "ConditionCredential",
         [CONDITION_SECURITY] = "ConditionSecurity",
         [CONDITION_CAPABILITY] = "ConditionCapability",
         [CONDITION_AC_POWER] = "ConditionACPower",
@@ -1255,6 +1298,7 @@ static const char* const assert_type_table[_CONDITION_TYPE_MAX] = {
         [CONDITION_HOST] = "AssertHost",
         [CONDITION_KERNEL_COMMAND_LINE] = "AssertKernelCommandLine",
         [CONDITION_KERNEL_VERSION] = "AssertKernelVersion",
+        [CONDITION_CREDENTIAL] = "AssertCredential",
         [CONDITION_SECURITY] = "AssertSecurity",
         [CONDITION_CAPABILITY] = "AssertCapability",
         [CONDITION_AC_POWER] = "AssertACPower",
index 2bbb7fa7f46d388460a0d24fd4df8d5419c27446..54cc904feb55e4c052f287a558e23cb280668bb4 100644 (file)
@@ -14,6 +14,7 @@ typedef enum ConditionType {
         CONDITION_HOST,
         CONDITION_KERNEL_COMMAND_LINE,
         CONDITION_KERNEL_VERSION,
+        CONDITION_CREDENTIAL,
         CONDITION_SECURITY,
         CONDITION_CAPABILITY,
         CONDITION_AC_POWER,
index fb82f44d04a5b0b1b962633426279395bbf54dea..56b5ad88a251588b88dd94cee279bd453858cea5 100644 (file)
@@ -15,7 +15,9 @@
 #include "condition.h"
 #include "cpu-set-util.h"
 #include "efi-loader.h"
+#include "env-util.h"
 #include "errno-util.h"
+#include "fs-util.h"
 #include "hostname-util.h"
 #include "id128-util.h"
 #include "ima-util.h"
 #include "macro.h"
 #include "nulstr-util.h"
 #include "os-util.h"
+#include "path-util.h"
 #include "process-util.h"
 #include "psi-util.h"
+#include "rm-rf.h"
 #include "selinux-util.h"
 #include "set.h"
 #include "smack-util.h"
 #include "string-util.h"
 #include "strv.h"
 #include "tests.h"
+#include "tmpfile-util.h"
 #include "tomoyo-util.h"
 #include "udev-util.h"
 #include "uid-alloc-range.h"
@@ -460,6 +465,60 @@ TEST(condition_test_kernel_version) {
         condition_free(condition);
 }
 
+TEST(condition_test_credential) {
+        _cleanup_(rm_rf_physical_and_freep) char *n1 = NULL, *n2 = NULL;
+        _cleanup_free_ char *d1 = NULL, *d2 = NULL, *j = NULL;
+        Condition *condition;
+
+        assert_se(free_and_strdup(&d1, getenv("CREDENTIALS_DIRECTORY")) >= 0);
+        assert_se(free_and_strdup(&d2, getenv("ENCRYPTED_CREDENTIALS_DIRECTORY")) >= 0);
+
+        assert_se(unsetenv("CREDENTIALS_DIRECTORY") >= 0);
+        assert_se(unsetenv("ENCRYPTED_CREDENTIALS_DIRECTORY") >= 0);
+
+        condition = condition_new(CONDITION_CREDENTIAL, "definitelymissing", /* trigger= */ false, /* negate= */ false);
+        assert_se(condition);
+        assert_se(condition_test(condition, environ) == 0);
+        condition_free(condition);
+
+        /* invalid */
+        condition = condition_new(CONDITION_CREDENTIAL, "..", /* trigger= */ false, /* negate= */ false);
+        assert_se(condition);
+        assert_se(condition_test(condition, environ) == 0);
+        condition_free(condition);
+
+        assert_se(mkdtemp_malloc(NULL, &n1) >= 0);
+        assert_se(mkdtemp_malloc(NULL, &n2) >= 0);
+
+        assert_se(setenv("CREDENTIALS_DIRECTORY", n1, /* overwrite= */ true) >= 0);
+        assert_se(setenv("ENCRYPTED_CREDENTIALS_DIRECTORY", n2, /* overwrite= */ true) >= 0);
+
+        condition = condition_new(CONDITION_CREDENTIAL, "stillmissing", /* trigger= */ false, /* negate= */ false);
+        assert_se(condition);
+        assert_se(condition_test(condition, environ) == 0);
+        condition_free(condition);
+
+        assert_se(j = path_join(n1, "existing"));
+        assert_se(touch(j) >= 0);
+        assert_se(j);
+        condition = condition_new(CONDITION_CREDENTIAL, "existing", /* trigger= */ false, /* negate= */ false);
+        assert_se(condition);
+        assert_se(condition_test(condition, environ) > 0);
+        condition_free(condition);
+        free(j);
+
+        assert_se(j = path_join(n2, "existing-encrypted"));
+        assert_se(touch(j) >= 0);
+        assert_se(j);
+        condition = condition_new(CONDITION_CREDENTIAL, "existing-encrypted", /* trigger= */ false, /* negate= */ false);
+        assert_se(condition);
+        assert_se(condition_test(condition, environ) > 0);
+        condition_free(condition);
+
+        assert_se(set_unset_env("CREDENTIALS_DIRECTORY", d1, /* overwrite= */ true) >= 0);
+        assert_se(set_unset_env("ENCRYPTED_CREDENTIALS_DIRECTORY", d2, /* overwrite= */ true) >= 0);
+}
+
 #if defined(__i386__) || defined(__x86_64__)
 TEST(condition_test_cpufeature) {
         Condition *condition;
index 96280148c7bc78e4867a40e746f4fc42bfb811e3..240f16e251130ceeb0daafb1def7d44ef5401ee1 100644 (file)
@@ -34,6 +34,7 @@ Match.Host,                                config_parse_net_condition,
 Match.Virtualization,                      config_parse_net_condition,            CONDITION_VIRTUALIZATION,      offsetof(LinkConfig, conditions)
 Match.KernelCommandLine,                   config_parse_net_condition,            CONDITION_KERNEL_COMMAND_LINE, offsetof(LinkConfig, conditions)
 Match.KernelVersion,                       config_parse_net_condition,            CONDITION_KERNEL_VERSION,      offsetof(LinkConfig, conditions)
+Match.Credential,                          config_parse_net_condition,            CONDITION_CREDENTIAL,          offsetof(LinkConfig, conditions)
 Match.Architecture,                        config_parse_net_condition,            CONDITION_ARCHITECTURE,        offsetof(LinkConfig, conditions)
 Match.Firmware,                            config_parse_net_condition,            CONDITION_FIRMWARE,            offsetof(LinkConfig, conditions)
 Link.Description,                          config_parse_string,                   0,                             offsetof(LinkConfig, description)
index 151a7987d5a42362242d0af8f6bc5d4208da11d3..771b041bf1de400bdc2a5e90c25df2c8278d1d0d 100755 (executable)
@@ -58,6 +58,12 @@ if [ "$expected_credential" != "" ] ; then
 
     # Combine it with a fallback (which should have no effect, given the cred should be passed down)
     [ "$(systemd-run -p LoadCredential="$expected_credential" -p SetCredential="$expected_credential":zzz --pipe --wait systemd-creds cat "$expected_credential")" = "$expected_value" ]
+
+    # This should succeed
+    systemd-run -p AssertCredential="$expected_credential" -p Type=oneshot true
+
+    # And this should fail
+    systemd-run -p AssertCredential="undefinedcredential" -p Type=oneshot true && { echo 'unexpected success'; exit 1; }
 fi
 
 # Verify that the creds are immutable