Merge pull request #24011 from poettering/condition-cred

pid1: add new condition type "ConditionCredential=" for checking for credentials passed into the system
This commit is contained in:
Lennart Poettering 2022-07-15 15:03:49 +02:00 committed by GitHub
commit 9fbb7df7bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 186 additions and 35 deletions

5
TODO
View file

@ -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

View 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.

View file

@ -228,7 +228,7 @@
<para>Matches against the hostname or machine ID of the host. See <varname>ConditionHost=</varname> in
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for details. When prefixed with an exclamation mark (<literal>!</literal>), 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.
</para>
</listitem>
</varlistentry>
@ -240,7 +240,7 @@
whether it is a specific implementation. See <varname>ConditionVirtualization=</varname> in
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for details. When prefixed with an exclamation mark (<literal>!</literal>), 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.
</para>
</listitem>
</varlistentry>
@ -252,7 +252,7 @@
<varname>ConditionKernelCommandLine=</varname> in
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for details. When prefixed with an exclamation mark (<literal>!</literal>), 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.
</para>
</listitem>
</varlistentry>
@ -264,7 +264,19 @@
expression. See <varname>ConditionKernelVersion=</varname> in
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details. When prefixed with an exclamation mark (<literal>!</literal>), 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.
</para>
</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>
@ -276,7 +288,7 @@
<varname>ConditionArchitecture=</varname> in
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for details. When prefixed with an exclamation mark (<literal>!</literal>), 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.
</para>
</listitem>
</varlistentry>
@ -288,7 +300,7 @@
<varname>ConditionFirmware=</varname> in
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for details. When prefixed with an exclamation mark (<literal>!</literal>), 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.
</para>
</listitem>
</varlistentry>

View file

@ -217,6 +217,7 @@
<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>

View file

@ -135,6 +135,7 @@
<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>

View file

@ -1330,6 +1330,19 @@
</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>

View 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)

View 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)

View 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)

View 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",

View 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,

View 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"
@ -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;

View 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)

View file

@ -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=

View file

@ -24,11 +24,12 @@ Mode=
SourceMACAddress=
[Match]
Architecture=
Credential=
Firmware=
Host=
KernelCommandLine=
KernelVersion=
Virtualization=
KernelCommandLine=
[GENEVE]
DestinationPort=
TTL=

View file

@ -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=

View file

@ -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=

View file

@ -9,6 +9,7 @@ AssertCPUPressure=
AssertCPUs=
AssertCapability=
AssertControlGroupController=
AssertCredential=
AssertDirectoryNotEmpty=
AssertEnvironment=
AssertFileIsExecutable=
@ -47,6 +48,7 @@ ConditionCPUs=
ConditionFirmware=
ConditionCapability=
ConditionControlGroupController=
ConditionCredential=
ConditionDirectoryNotEmpty=
ConditionEnvironment=
ConditionFileIsExecutable=

View file

@ -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