pid1: add mechanism for conditionalizing units/network/netdev/link based on credentials passed in

This is useful when provisioning systems via nspawn/qemu and running
specific services only if specific data is passed into the system.
This commit is contained in:
Lennart Poettering 2022-07-13 10:38:53 +02:00
parent 5eab88a569
commit 4f80cfca5e
13 changed files with 148 additions and 0 deletions

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

@ -269,6 +269,18 @@
</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>

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

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