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