diff --git a/TODO b/TODO
index c44f106fe35..d7b7c39c0d9 100644
--- a/TODO
+++ b/TODO
@@ -310,11 +310,6 @@ Features:
cloud-init/ignitation and similar can parameterize the host with data they
acquire.
-* Add ConditionCredentialExists= or so, that allows conditionalizing services
- depending on whether a specific system credential is set. Usecase: a service
- similar to the ssh keygen service that installs any SSH host key supplied via
- system credentials into /etc/ssh.
-
* drop support for kernels that lack ambient capabilities support (i.e. make
4.3 new baseline). Then drop support for "!!" modifier for ExecStart= which
is only supported for such old kernels
diff --git a/docs/CREDENTIALS.md b/docs/CREDENTIALS.md
index bbd92ad3c9f..4ba37844696 100644
--- a/docs/CREDENTIALS.md
+++ b/docs/CREDENTIALS.md
@@ -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.
diff --git a/man/systemd.link.xml b/man/systemd.link.xml
index de23b941ad2..bb4cd227e6b 100644
--- a/man/systemd.link.xml
+++ b/man/systemd.link.xml
@@ -228,7 +228,7 @@
Matches against the hostname or machine ID of the host. See ConditionHost= in
systemd.unit5
for details. When prefixed with an exclamation mark (!), the result is negated.
- If an empty string is assigned, then previously assigned value is cleared.
+ If an empty string is assigned, the previously assigned value is cleared.
@@ -240,7 +240,7 @@
whether it is a specific implementation. See ConditionVirtualization= in
systemd.unit5
for details. When prefixed with an exclamation mark (!), the result is negated.
- If an empty string is assigned, then previously assigned value is cleared.
+ If an empty string is assigned, the previously assigned value is cleared.
@@ -252,7 +252,7 @@
ConditionKernelCommandLine= in
systemd.unit5
for details. When prefixed with an exclamation mark (!), the result is negated.
- If an empty string is assigned, then previously assigned value is cleared.
+ If an empty string is assigned, the previously assigned value is cleared.
@@ -264,7 +264,19 @@
expression. See ConditionKernelVersion= in
systemd.unit5 for
details. When prefixed with an exclamation mark (!), the result is negated.
- If an empty string is assigned, then previously assigned value is cleared.
+ If an empty string is assigned, the previously assigned value is cleared.
+
+
+
+
+
+ Credential=
+
+ Checks whether the specified credential was passed to the
+ systemd-networkd.service service. See System and Service Credentials for details. When
+ prefixed with an exclamation mark (!), the result is negated. If an empty
+ string is assigned, the previously assigned value is cleared.
@@ -276,7 +288,7 @@
ConditionArchitecture= in
systemd.unit5
for details. When prefixed with an exclamation mark (!), the result is negated.
- If an empty string is assigned, then previously assigned value is cleared.
+ If an empty string is assigned, the previously assigned value is cleared.
@@ -288,7 +300,7 @@
ConditionFirmware= in
systemd.unit5
for details. When prefixed with an exclamation mark (!), the result is negated.
- If an empty string is assigned, then previously assigned value is cleared.
+ If an empty string is assigned, the previously assigned value is cleared.
diff --git a/man/systemd.netdev.xml b/man/systemd.netdev.xml
index 7d3a4f95c85..c3578fc2dae 100644
--- a/man/systemd.netdev.xml
+++ b/man/systemd.netdev.xml
@@ -217,6 +217,7 @@
+
diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index 516a42e25a0..70d2c34a404 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -135,6 +135,7 @@
+
diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml
index 55f32f32728..ea95ba88692 100644
--- a/man/systemd.unit.xml
+++ b/man/systemd.unit.xml
@@ -1330,6 +1330,19 @@
+
+ ConditionCredential=
+
+ ConditionCredential= may be used to check whether a credential
+ by the specified name was passed into the service manager. See System and Service Credentials 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
+ unit@.service service instance belonging to the user. The argument must be a
+ valid credential name.
+
+
ConditionEnvironment=
diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in
index 7817c20c0ba..54c1c0bb56b 100644
--- a/src/core/load-fragment-gperf.gperf.in
+++ b/src/core/load-fragment-gperf.gperf.in
@@ -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)
diff --git a/src/network/netdev/netdev-gperf.gperf b/src/network/netdev/netdev-gperf.gperf
index 55ad60ddc8b..162664ecf13 100644
--- a/src/network/netdev/netdev-gperf.gperf
+++ b/src/network/netdev/netdev-gperf.gperf
@@ -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)
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index 0b0c8da27b5..13d521e37a2 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -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)
diff --git a/src/shared/condition.c b/src/shared/condition.c
index 640dd96eb2b..2fc22c37140 100644
--- a/src/shared/condition.c
+++ b/src/shared/condition.c
@@ -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",
diff --git a/src/shared/condition.h b/src/shared/condition.h
index 2bbb7fa7f46..54cc904feb5 100644
--- a/src/shared/condition.h
+++ b/src/shared/condition.h
@@ -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,
diff --git a/src/test/test-condition.c b/src/test/test-condition.c
index fb82f44d04a..56b5ad88a25 100644
--- a/src/test/test-condition.c
+++ b/src/test/test-condition.c
@@ -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"
@@ -24,14 +26,17 @@
#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;
diff --git a/src/udev/net/link-config-gperf.gperf b/src/udev/net/link-config-gperf.gperf
index 96280148c7b..240f16e2511 100644
--- a/src/udev/net/link-config-gperf.gperf
+++ b/src/udev/net/link-config-gperf.gperf
@@ -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)
diff --git a/test/fuzz/fuzz-link-parser/directives.link b/test/fuzz/fuzz-link-parser/directives.link
index d6c6cc6f2ef..a1c797a297b 100644
--- a/test/fuzz/fuzz-link-parser/directives.link
+++ b/test/fuzz/fuzz-link-parser/directives.link
@@ -1,18 +1,19 @@
[Match]
-MACAddress=
-PermanentMACAddress=
-OriginalName=
-Path=
+Architecture=
+Credential=
Driver=
-Type=
-Kind=
-Property=
+Firmware=
Host=
-Virtualization=
KernelCommandLine=
KernelVersion=
-Architecture=
-Firmware=
+Kind=
+MACAddress=
+OriginalName=
+Path=
+PermanentMACAddress=
+Property=
+Type=
+Virtualization=
[Link]
Description=
MACAddressPolicy=
diff --git a/test/fuzz/fuzz-netdev-parser/directives.netdev b/test/fuzz/fuzz-netdev-parser/directives.netdev
index d97f81512bb..d5f3228065c 100644
--- a/test/fuzz/fuzz-netdev-parser/directives.netdev
+++ b/test/fuzz/fuzz-netdev-parser/directives.netdev
@@ -24,11 +24,12 @@ Mode=
SourceMACAddress=
[Match]
Architecture=
+Credential=
Firmware=
Host=
+KernelCommandLine=
KernelVersion=
Virtualization=
-KernelCommandLine=
[GENEVE]
DestinationPort=
TTL=
diff --git a/test/fuzz/fuzz-network-parser/directives b/test/fuzz/fuzz-network-parser/directives
index ea0de6660c7..b7e8f168348 100644
--- a/test/fuzz/fuzz-network-parser/directives
+++ b/test/fuzz/fuzz-network-parser/directives
@@ -15,23 +15,24 @@ ProxyARP=
ProxyARPWiFi=
MulticastRouter=
[Match]
-KernelVersion=
-Type=
-Kind=
-Driver=
Architecture=
-Firmware=
-Path=
-WLANInterfaceType=
-SSID=
BSSID=
-Name=
-Property=
-Virtualization=
-KernelCommandLine=
+Credential=
+Driver=
+Firmware=
Host=
+KernelCommandLine=
+KernelVersion=
+Kind=
MACAddress=
+Name=
+Path=
PermanentMACAddress=
+Property=
+SSID=
+Type=
+Virtualization=
+WLANInterfaceType=
[Link]
ActivationPolicy=
RequiredForOnline=
diff --git a/test/fuzz/fuzz-unit-file/directives-all.service b/test/fuzz/fuzz-unit-file/directives-all.service
index 487001b6b78..621fb1cf1b6 100644
--- a/test/fuzz/fuzz-unit-file/directives-all.service
+++ b/test/fuzz/fuzz-unit-file/directives-all.service
@@ -10,9 +10,10 @@ Also=
AmbientCapabilities=
AssertACPower=
AssertArchitecture=
+AssertCPUPressure=
AssertCapability=
AssertControlGroupController=
-AssertCPUPressure=
+AssertCredential=
AssertDirectoryNotEmpty=
AssertFileIsExecutable=
AssertFileNotEmpty=
@@ -59,6 +60,7 @@ ConditionACPower=
ConditionArchitecture=
ConditionCapability=
ConditionControlGroupController=
+ConditionCredential=
ConditionCPUPressure=
ConditionDirectoryNotEmpty=
ConditionFileIsExecutable=
@@ -481,6 +483,7 @@ InitialCongestionWindow=
InputKey=
InvertRule=
KernelCommandLine=
+Credential=
KernelVersion=
Key=
Kind=
diff --git a/test/fuzz/fuzz-unit-file/directives.service b/test/fuzz/fuzz-unit-file/directives.service
index 3c33d947fe2..056edb9f172 100644
--- a/test/fuzz/fuzz-unit-file/directives.service
+++ b/test/fuzz/fuzz-unit-file/directives.service
@@ -9,6 +9,7 @@ AssertCPUPressure=
AssertCPUs=
AssertCapability=
AssertControlGroupController=
+AssertCredential=
AssertDirectoryNotEmpty=
AssertEnvironment=
AssertFileIsExecutable=
@@ -47,6 +48,7 @@ ConditionCPUs=
ConditionFirmware=
ConditionCapability=
ConditionControlGroupController=
+ConditionCredential=
ConditionDirectoryNotEmpty=
ConditionEnvironment=
ConditionFileIsExecutable=
diff --git a/test/units/testsuite-54.sh b/test/units/testsuite-54.sh
index 151a7987d5a..771b041bf1d 100755
--- a/test/units/testsuite-54.sh
+++ b/test/units/testsuite-54.sh
@@ -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