mirror of
https://github.com/systemd/systemd.git
synced 2026-11-06 05:17:24 +00:00
dissect,nspawn,sysext: enable TPM2 unlocking of DDIs, document it
Set DISSECT_IMAGE_TPM2_UNLOCK where unlocking an image's LUKS partitions via the local TPM2 is wanted and the caller is trusted: * systemd-dissect, an admin tool operating on an image the invoking user can read anyway; * systemd-nspawn --image=, which already prompts for a passphrase interactivly, so TPM2 first and passphrase second is the natural order; * systemd-sysext/systemd-confext, which decrypt without any passphrase, so extension images sealed to the host can be activated without user interaction. For sysext/confext setting the flag at merge time is not enough: before merging, the extension-release metadata is read via image_read_metadata() in discover-image.c, which uses its own flag set and would already fail with -ENOKEY on an encrypted image. Give it an extra_flags parameter and pass the flag from there, too; the other callers (machined, portabled) pass 0 and keep their behaviour. Two units need adjusting for this to work when started by the service manager: systemd-nspawn@.service has DevicePolicy=closed, so allow /dev/tpmrm0 there, and the sysext/confext services are not ordered against the TPM2 device showing up, so order them after tpm2.target like the pcr* services. The initrd variants cannot be ordered the same way: they run before local-fs-pre.target, while the software TPM fallback only becomes available after the early ESP mount, which is ordered after local-fs-pre.target, so a static After=tpm2.target would be an ordering cycle with systemd.tpm2_software_fallback=1. Hence systemd-tpm2-generator adds the ordering as a drop-in instead, unless the fallback is in use. That drop-in also orders them after systemd-pcrphase-initrd.service: nothing else guarantees the "enter-initrd" phase has been measured into PCR 11 before the initrd extension units merge their images (the barrier's Before=systemd-sysext.service is a leftover from when that unit was also used in the initrd), so tokens bound to a signed PCR 11 policy or a pcrlock policy would race the measurement. Deliberately not enabled for now: RootImage=/ExtensionImages=/MountImages= in units, portabled and gpt-auto. For those an unconditional default is probably not wanted but a per-unit or per-image switch, which is left for a follow-up. systemd-mountfsd, which unprivileged systemd-dissect and systemd-nspawn delegate to, keeps its own flag set and does not honour the flag, hence TPM2 unlocking is only available when running privileged; the man pages say so. Document the behaviour and $SYSTEMD_DISSECT_TPM2_UNLOCK= in the dissect, nspawn and sysext man pages and in docs/ENVIRONMENT.md.
This commit is contained in:
1 parent
972ceb2258
commit
600fcc6dcb
22 files changed
+111
-16
No files matched your search
@@ -146,6 +146,14 @@ All tools:
|
||||
RootImage=/ExtensionImages=/MountImages= in unit files default to enabled,
|
||||
while other uses default to disabled for this.
|
||||
|
||||
* `$SYSTEMD_DISSECT_TPM2_UNLOCK=` — takes a boolean. If false, turns off
|
||||
unlocking of LUKS encrypted partitions in disk images via a TPM2 token
|
||||
enrolled in the LUKS2 header (e.g. via `systemd-cryptenroll --tpm2-device=…`)
|
||||
for the tools that do this by default if no passphrase is supplied, i.e.
|
||||
`systemd-dissect`, `systemd-nspawn` and `systemd-sysext`/`systemd-confext`.
|
||||
It cannot turn this on for other uses (including `systemd-mountfsd`),
|
||||
which act on behalf of unprivileged clients.
|
||||
|
||||
`systemctl`:
|
||||
|
||||
* `$SYSTEMCTL_FORCE_BUS=1` — if set, do not connect to PID 1's private D-Bus
|
||||
|
||||
@@ -152,7 +152,14 @@
|
||||
|
||||
<para>When the OS image contains LUKS encrypted or Verity integrity protected file systems
|
||||
appropriate volumes are automatically set up and marked for automatic disassembly when the image is
|
||||
unmounted.</para>
|
||||
unmounted. LUKS volumes carrying a TPM2 token (as enrolled via the <option>--tpm2-device=</option>
|
||||
option of
|
||||
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>)
|
||||
are unlocked via the local TPM2 automatically, before a passphrase is requested. This may be turned
|
||||
off via the <varname>$SYSTEMD_DISSECT_TPM2_UNLOCK</varname> environment variable. Note that this
|
||||
requires privileges: when invoked unprivileged, images are mounted via
|
||||
<citerefentry><refentrytitle>systemd-mountfsd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
which does not currently support unlocking via TPM2.</para>
|
||||
|
||||
<para>The OS image may either be specified as path to an OS image stored in a regular file or may
|
||||
refer to block device node (in the latter case, the block device must be the "whole" device, i.e. not
|
||||
|
||||
@@ -341,7 +341,12 @@
|
||||
<filename>/efi</filename> (or <filename>/boot</filename> as fallback) in case a directory by this name exists
|
||||
and is empty.</para>
|
||||
|
||||
<para>Partitions encrypted with LUKS are automatically decrypted. Also, on GPT images dm-verity data integrity
|
||||
<para>Partitions encrypted with LUKS are automatically decrypted, via a TPM2 token enrolled in the LUKS2
|
||||
header if one matches the local TPM2 (this may be turned off via the
|
||||
<varname>$SYSTEMD_DISSECT_TPM2_UNLOCK</varname> environment variable), or otherwise by prompting for a
|
||||
passphrase. Unlocking via TPM2 requires privileges: when invoked unprivileged, images are mounted via
|
||||
<citerefentry><refentrytitle>systemd-mountfsd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
which does not currently support it. Also, on GPT images dm-verity data integrity
|
||||
hash partitions are set up if the root hash for them is specified using the <option>--root-hash=</option>
|
||||
option.</para>
|
||||
|
||||
|
||||
@@ -101,7 +101,11 @@
|
||||
<citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
supports via its <option>--directory=</option>/<option>--image=</option> switches and those that the
|
||||
service manager supports via <option>RootDirectory=</option>/<option>RootImage=</option>. Similar to
|
||||
them they may optionally carry Verity authentication information.</para>
|
||||
them they may optionally carry Verity authentication information. LUKS encrypted extension images
|
||||
carrying a TPM2 token (as enrolled via the <option>--tpm2-device=</option> option of
|
||||
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>)
|
||||
are unlocked via the local TPM2 automatically, without any passphrase. This may be turned off via the
|
||||
<varname>$SYSTEMD_DISSECT_TPM2_UNLOCK</varname> environment variable.</para>
|
||||
|
||||
<para>System extensions are searched for in the directories
|
||||
<filename>/etc/extensions/</filename>, <filename>/run/extensions/</filename> and
|
||||
|
||||
@@ -53,6 +53,16 @@
|
||||
<citerefentry><refentrytitle>systemd-tpm2-swtpm.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
service.</para>
|
||||
|
||||
<para>In the initrd, the generator also orders
|
||||
<citerefentry><refentrytitle>systemd-sysext-initrd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
and
|
||||
<citerefentry><refentrytitle>systemd-confext-initrd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
after <filename>tpm2.target</filename> and
|
||||
<citerefentry><refentrytitle>systemd-pcrphase-initrd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
so that LUKS encrypted extension images sealed to the TPM2 can be unlocked. This is not done if the
|
||||
software TPM fallback is in use, as the software TPM only becomes available after the extension images
|
||||
are merged, hence such images cannot be unlocked in the initrd in that case.</para>
|
||||
|
||||
<para><command>systemd-tpm2-generator</command> implements
|
||||
<citerefentry><refentrytitle>systemd.generator</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
|
||||
</refsect1>
|
||||
|
||||
@@ -92,7 +92,8 @@ static DissectImageFlags arg_flags =
|
||||
DISSECT_IMAGE_GROWFS |
|
||||
DISSECT_IMAGE_PIN_PARTITION_DEVICES |
|
||||
DISSECT_IMAGE_ADD_PARTITION_DEVICES |
|
||||
DISSECT_IMAGE_ALLOW_USERSPACE_VERITY;
|
||||
DISSECT_IMAGE_ALLOW_USERSPACE_VERITY |
|
||||
DISSECT_IMAGE_TPM2_UNLOCK;
|
||||
static VeritySettings arg_verity_settings = VERITY_SETTINGS_DEFAULT;
|
||||
static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
|
||||
static PagerFlags arg_pager_flags = 0;
|
||||
|
||||
@@ -295,7 +295,7 @@ int bus_image_method_get_hostname(
|
||||
int r;
|
||||
|
||||
if (!image->metadata_valid) {
|
||||
r = image_read_metadata(image, /* root= */ NULL, &image_policy_container, m->runtime_scope);
|
||||
r = image_read_metadata(image, /* root= */ NULL, &image_policy_container, m->runtime_scope, /* extra_flags= */ 0);
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
|
||||
}
|
||||
@@ -314,7 +314,7 @@ int bus_image_method_get_machine_id(
|
||||
int r;
|
||||
|
||||
if (!image->metadata_valid) {
|
||||
r = image_read_metadata(image, /* root= */ NULL, &image_policy_container, m->runtime_scope);
|
||||
r = image_read_metadata(image, /* root= */ NULL, &image_policy_container, m->runtime_scope, /* extra_flags= */ 0);
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
|
||||
}
|
||||
@@ -343,7 +343,7 @@ int bus_image_method_get_machine_info(
|
||||
int r;
|
||||
|
||||
if (!image->metadata_valid) {
|
||||
r = image_read_metadata(image, /* root= */ NULL, &image_policy_container, m->runtime_scope);
|
||||
r = image_read_metadata(image, /* root= */ NULL, &image_policy_container, m->runtime_scope, /* extra_flags= */ 0);
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
|
||||
}
|
||||
@@ -361,7 +361,7 @@ int bus_image_method_get_os_release(
|
||||
int r;
|
||||
|
||||
if (!image->metadata_valid) {
|
||||
r = image_read_metadata(image, /* root= */ NULL, &image_policy_container, m->runtime_scope);
|
||||
r = image_read_metadata(image, /* root= */ NULL, &image_policy_container, m->runtime_scope, /* extra_flags= */ 0);
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
|
||||
}
|
||||
|
||||
@@ -633,7 +633,7 @@ static int list_image_one_and_maybe_read_metadata(Manager *m, sd_varlink *link,
|
||||
assert(image);
|
||||
|
||||
if (should_acquire_metadata(am) && !image->metadata_valid) {
|
||||
r = image_read_metadata(image, /* root= */ NULL, &image_policy_container, m->runtime_scope);
|
||||
r = image_read_metadata(image, /* root= */ NULL, &image_policy_container, m->runtime_scope, /* extra_flags= */ 0);
|
||||
if (r < 0 && am != ACQUIRE_METADATA_GRACEFUL)
|
||||
return log_debug_errno(r, "Failed to read image metadata: %m");
|
||||
if (r < 0)
|
||||
|
||||
@@ -3877,6 +3877,7 @@ static DissectImageFlags determine_dissect_image_flags(void) {
|
||||
(arg_read_only ? DISSECT_IMAGE_READ_ONLY : DISSECT_IMAGE_FSCK|DISSECT_IMAGE_GROWFS) |
|
||||
DISSECT_IMAGE_ALLOW_USERSPACE_VERITY |
|
||||
DISSECT_IMAGE_VERITY_SHARE |
|
||||
DISSECT_IMAGE_TPM2_UNLOCK |
|
||||
(arg_console_mode == CONSOLE_INTERACTIVE && arg_ask_password ? DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH : 0) |
|
||||
((arg_userns_ownership == USER_NAMESPACE_OWNERSHIP_FOREIGN) ? DISSECT_IMAGE_FOREIGN_UID :
|
||||
(arg_userns_ownership != USER_NAMESPACE_OWNERSHIP_AUTO) ? DISSECT_IMAGE_IDENTITY_UID : 0);
|
||||
|
||||
@@ -61,7 +61,7 @@ int bus_image_common_get_os_release(
|
||||
return 1;
|
||||
|
||||
if (!image->metadata_valid) {
|
||||
r = image_read_metadata(image, /* root= */ NULL, &image_policy_service, m->runtime_scope);
|
||||
r = image_read_metadata(image, /* root= */ NULL, &image_policy_service, m->runtime_scope, /* extra_flags= */ 0);
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
|
||||
}
|
||||
|
||||
@@ -2228,7 +2228,7 @@ int image_setup_pool(RuntimeScope scope, ImageClass class, bool use_btrfs_subvol
|
||||
return 0;
|
||||
}
|
||||
|
||||
int image_read_metadata(Image *i, const char *root, const ImagePolicy *image_policy, RuntimeScope scope) {
|
||||
int image_read_metadata(Image *i, const char *root, const ImagePolicy *image_policy, RuntimeScope scope, DissectImageFlags extra_flags) {
|
||||
_cleanup_(release_lock_file) LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT;
|
||||
int r;
|
||||
|
||||
@@ -2316,7 +2316,8 @@ int image_read_metadata(Image *i, const char *root, const ImagePolicy *image_pol
|
||||
DISSECT_IMAGE_PIN_PARTITION_DEVICES |
|
||||
DISSECT_IMAGE_VALIDATE_OS |
|
||||
DISSECT_IMAGE_VALIDATE_OS_EXT |
|
||||
DISSECT_IMAGE_ALLOW_USERSPACE_VERITY;
|
||||
DISSECT_IMAGE_ALLOW_USERSPACE_VERITY |
|
||||
extra_flags;
|
||||
|
||||
r = loop_device_make_by_path(
|
||||
i->path,
|
||||
|
||||
@@ -76,7 +76,7 @@ int image_get_pool_usage(RuntimeScope scope, ImageClass class, uint64_t *ret);
|
||||
int image_get_pool_limit(RuntimeScope scope, ImageClass class, uint64_t *ret);
|
||||
int image_setup_pool(RuntimeScope scope, ImageClass class, bool use_btrfs_subvol, bool use_btrfs_quota);
|
||||
|
||||
int image_read_metadata(Image *i, const char *root, const ImagePolicy *image_policy, RuntimeScope scope);
|
||||
int image_read_metadata(Image *i, const char *root, const ImagePolicy *image_policy, RuntimeScope scope, DissectImageFlags extra_flags);
|
||||
|
||||
bool image_in_search_path(RuntimeScope scope, ImageClass class, const char *root, const char *image);
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ typedef enum BusTransport BusTransport;
|
||||
typedef enum CatFlags CatFlags;
|
||||
typedef enum CertificateSourceType CertificateSourceType;
|
||||
typedef enum ConditionType ConditionType;
|
||||
typedef enum DissectImageFlags DissectImageFlags;
|
||||
typedef enum DnsAnswerFlags DnsAnswerFlags;
|
||||
typedef enum DnsCacheMode DnsCacheMode;
|
||||
typedef enum DnsOverTlsMode DnsOverTlsMode;
|
||||
|
||||
+3
-2
@@ -2068,7 +2068,8 @@ static int merge_subprocess(
|
||||
DISSECT_IMAGE_USR_NO_ROOT |
|
||||
DISSECT_IMAGE_ADD_PARTITION_DEVICES |
|
||||
DISSECT_IMAGE_PIN_PARTITION_DEVICES |
|
||||
DISSECT_IMAGE_ALLOW_USERSPACE_VERITY;
|
||||
DISSECT_IMAGE_ALLOW_USERSPACE_VERITY |
|
||||
DISSECT_IMAGE_TPM2_UNLOCK;
|
||||
|
||||
r = verity_settings_load(&verity_settings, img->path, NULL, NULL);
|
||||
if (r < 0)
|
||||
@@ -2651,7 +2652,7 @@ static int image_discover_and_read_metadata(const Context *c, Hashmap **ret_imag
|
||||
return log_error_errno(r, "Failed to discover images: %m");
|
||||
|
||||
HASHMAP_FOREACH(img, images) {
|
||||
r = image_read_metadata(img, c->root, image_class_info[c->image_class].default_image_policy, RUNTIME_SCOPE_SYSTEM);
|
||||
r = image_read_metadata(img, c->root, image_class_info[c->image_class].default_image_policy, RUNTIME_SCOPE_SYSTEM, DISSECT_IMAGE_TPM2_UNLOCK);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read metadata for image %s: %m", img->name);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "path-util.h"
|
||||
#include "proc-cmdline.h"
|
||||
#include "special.h"
|
||||
#include "strv.h"
|
||||
#include "tpm2-util.h"
|
||||
|
||||
/* A small generator that enqueues tpm2.target as synchronization point if the TPM2 device hasn't shown up
|
||||
@@ -74,6 +75,39 @@ static int generate_tpm_target_symlink(Tpm2Support support, bool software_fallba
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int generate_initrd_extension_ordering(bool software_fallback_enabled) {
|
||||
int r;
|
||||
|
||||
/* systemd-sysext-initrd.service and systemd-confext-initrd.service might have to unlock LUKS
|
||||
* encrypted extension images via the TPM2, hence order them after tpm2.target and after the
|
||||
* "enter-initrd" PCR measurement. This cannot be done statically in the units: they run before
|
||||
* local-fs-pre.target, but the software TPM fallback only becomes available after the early ESP
|
||||
* mount, which is ordered after local-fs-pre.target, so with the fallback the ordering would be
|
||||
* a cycle. Hence add it here, and only if the fallback is not in use. */
|
||||
|
||||
if (!in_initrd())
|
||||
return 0;
|
||||
|
||||
if (software_fallback_enabled) {
|
||||
log_debug("Not ordering initrd extension image units after tpm2.target, as the software TPM fallback is in use.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
FOREACH_STRING(u, "systemd-sysext-initrd.service", "systemd-confext-initrd.service") {
|
||||
r = write_drop_in(
|
||||
arg_dest,
|
||||
u,
|
||||
50, "tpm2",
|
||||
"# Automatically generated by systemd-tpm2-generator\n\n"
|
||||
"[Unit]\n"
|
||||
"After=tpm2.target systemd-pcrphase-initrd.service\n");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to order %s after tpm2.target: %m", u);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int generate_swtpm_symlink(Tpm2Support support) {
|
||||
int r;
|
||||
|
||||
@@ -158,7 +192,13 @@ static int generate_now(void) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = generate_tpm_target_symlink(support, /* software_fallback_enabled= */ r > 0);
|
||||
bool software_fallback_enabled = r > 0;
|
||||
|
||||
r = generate_tpm_target_symlink(support, software_fallback_enabled);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = generate_initrd_extension_ordering(software_fallback_enabled);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ Conflicts=initrd-switch-root.target
|
||||
Before=initrd-switch-root.target
|
||||
Wants=modprobe@loop.service modprobe@dm_mod.service
|
||||
After=modprobe@loop.service modprobe@dm_mod.service
|
||||
# Extension images might be LUKS encrypted and sealed to the local TPM2, but ordering after tpm2.target here
|
||||
# would be a cycle with the software TPM fallback, hence systemd-tpm2-generator adds it where possible
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
|
||||
@@ -23,6 +23,8 @@ Conflicts=shutdown.target
|
||||
Before=initrd-root-fs.target shutdown.target
|
||||
Wants=modprobe@loop.service modprobe@dm_mod.service
|
||||
After=modprobe@loop.service modprobe@dm_mod.service sysroot.mount sysroot-usr.mount systemd-volatile-root.service
|
||||
# Extension images might be LUKS encrypted and sealed to the local TPM2
|
||||
After=tpm2.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
|
||||
@@ -21,6 +21,8 @@ ConditionKernelCommandLine=!systemd.confext=0
|
||||
|
||||
DefaultDependencies=no
|
||||
After=local-fs.target
|
||||
# Extension images might be LUKS encrypted and sealed to the local TPM2
|
||||
After=tpm2.target
|
||||
Before=sysinit.target systemd-tmpfiles-setup.service
|
||||
Conflicts=shutdown.target
|
||||
Before=shutdown.target
|
||||
|
||||
@@ -51,5 +51,9 @@ DeviceAllow=block-blkext rw
|
||||
DeviceAllow=/dev/mapper/control rw
|
||||
DeviceAllow=block-device-mapper rw
|
||||
|
||||
# nspawn unlocks LUKS encrypted images via a TPM2 token enrolled in them, in
|
||||
# which case it needs access to the TPM2 resource manager device.
|
||||
DeviceAllow=/dev/tpmrm0 rw
|
||||
|
||||
[Install]
|
||||
WantedBy=machines.target
|
||||
@@ -22,6 +22,8 @@ ConditionKernelCommandLine=!rd.systemd.sysext=0
|
||||
|
||||
DefaultDependencies=no
|
||||
After=systemd-confext-initrd.service
|
||||
# Extension images might be LUKS encrypted and sealed to the local TPM2, but ordering after tpm2.target here
|
||||
# would be a cycle with the software TPM fallback, hence systemd-tpm2-generator adds it where possible
|
||||
Before=local-fs-pre.target cryptsetup-pre.target systemd-tmpfiles-setup.service
|
||||
Wants=local-fs-pre.target cryptsetup-pre.target
|
||||
Conflicts=initrd-switch-root.target
|
||||
|
||||
@@ -23,6 +23,8 @@ Conflicts=shutdown.target
|
||||
Before=initrd-root-fs.target shutdown.target
|
||||
Wants=modprobe@loop.service modprobe@dm_mod.service
|
||||
After=modprobe@loop.service modprobe@dm_mod.service sysroot.mount sysroot-usr.mount systemd-volatile-root.service
|
||||
# Extension images might be LUKS encrypted and sealed to the local TPM2
|
||||
After=tpm2.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
|
||||
@@ -22,6 +22,8 @@ ConditionKernelCommandLine=!systemd.sysext=0
|
||||
DefaultDependencies=no
|
||||
After=systemd-confext.service
|
||||
After=local-fs.target
|
||||
# Extension images might be LUKS encrypted and sealed to the local TPM2
|
||||
After=tpm2.target
|
||||
Before=sysinit.target systemd-tmpfiles-setup.service
|
||||
Conflicts=shutdown.target
|
||||
Before=shutdown.target
|
||||
|
||||
Reference in new issue
Block a user