diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md
index d9453b5d3ce..5f6ff5d7316 100644
--- a/docs/ENVIRONMENT.md
+++ b/docs/ENVIRONMENT.md
@@ -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
diff --git a/man/systemd-dissect.xml b/man/systemd-dissect.xml
index 670fb2a5741..b5fc5293c1b 100644
--- a/man/systemd-dissect.xml
+++ b/man/systemd-dissect.xml
@@ -152,7 +152,14 @@
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.
+ unmounted. LUKS volumes carrying a TPM2 token (as enrolled via the
+ option of
+ systemd-cryptenroll1)
+ are unlocked via the local TPM2 automatically, before a passphrase is requested. This may be turned
+ off via the $SYSTEMD_DISSECT_TPM2_UNLOCK environment variable. Note that this
+ requires privileges: when invoked unprivileged, images are mounted via
+ systemd-mountfsd.service8,
+ which does not currently support unlocking via TPM2.
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
diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml
index 58d4c84b36c..44983b72108 100644
--- a/man/systemd-nspawn.xml
+++ b/man/systemd-nspawn.xml
@@ -341,7 +341,12 @@
/efi (or /boot as fallback) in case a directory by this name exists
and is empty.
- Partitions encrypted with LUKS are automatically decrypted. Also, on GPT images dm-verity data integrity
+ 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
+ $SYSTEMD_DISSECT_TPM2_UNLOCK environment variable), or otherwise by prompting for a
+ passphrase. Unlocking via TPM2 requires privileges: when invoked unprivileged, images are mounted via
+ systemd-mountfsd.service8,
+ 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.
diff --git a/man/systemd-sysext.xml b/man/systemd-sysext.xml
index e9d275fac49..984441add5b 100644
--- a/man/systemd-sysext.xml
+++ b/man/systemd-sysext.xml
@@ -101,7 +101,11 @@
systemd-nspawn1
supports via its / switches and those that the
service manager supports via /. Similar to
- them they may optionally carry Verity authentication information.
+ them they may optionally carry Verity authentication information. LUKS encrypted extension images
+ carrying a TPM2 token (as enrolled via the option of
+ systemd-cryptenroll1)
+ are unlocked via the local TPM2 automatically, without any passphrase. This may be turned off via the
+ $SYSTEMD_DISSECT_TPM2_UNLOCK environment variable.
System extensions are searched for in the directories
/etc/extensions/, /run/extensions/ and
diff --git a/man/systemd-tpm2-generator.xml b/man/systemd-tpm2-generator.xml
index b45cf29be86..57aee889b94 100644
--- a/man/systemd-tpm2-generator.xml
+++ b/man/systemd-tpm2-generator.xml
@@ -53,6 +53,16 @@
systemd-tpm2-swtpm.service8
service.
+ In the initrd, the generator also orders
+ systemd-sysext-initrd.service8
+ and
+ systemd-confext-initrd.service8
+ after tpm2.target and
+ systemd-pcrphase-initrd.service8,
+ 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.
+
systemd-tpm2-generator implements
systemd.generator7.
diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c
index 7056b7b1d15..a1266eeebfb 100644
--- a/src/dissect/dissect.c
+++ b/src/dissect/dissect.c
@@ -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;
diff --git a/src/machine/image-dbus.c b/src/machine/image-dbus.c
index 4a61b48f3f7..27b1cc15740 100644
--- a/src/machine/image-dbus.c
+++ b/src/machine/image-dbus.c
@@ -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");
}
diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c
index ae121395c01..4b449ff2656 100644
--- a/src/machine/machined-varlink.c
+++ b/src/machine/machined-varlink.c
@@ -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)
diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c
index 4709928384b..e2b02ece7f4 100644
--- a/src/nspawn/nspawn.c
+++ b/src/nspawn/nspawn.c
@@ -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);
diff --git a/src/portable/portabled-image-bus.c b/src/portable/portabled-image-bus.c
index 5a5393e986a..935f0e619bb 100644
--- a/src/portable/portabled-image-bus.c
+++ b/src/portable/portabled-image-bus.c
@@ -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");
}
diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c
index fa0fd1928f6..7c2a7901a9e 100644
--- a/src/shared/discover-image.c
+++ b/src/shared/discover-image.c
@@ -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,
diff --git a/src/shared/discover-image.h b/src/shared/discover-image.h
index 71d00cbdb07..63fc9a65c7b 100644
--- a/src/shared/discover-image.h
+++ b/src/shared/discover-image.h
@@ -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);
diff --git a/src/shared/forward.h b/src/shared/forward.h
index f70a9a12753..d508f24ccda 100644
--- a/src/shared/forward.h
+++ b/src/shared/forward.h
@@ -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;
diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c
index d4478d8c981..28545481c7f 100644
--- a/src/sysext/sysext.c
+++ b/src/sysext/sysext.c
@@ -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);
}
diff --git a/src/tpm2-setup/tpm2-generator.c b/src/tpm2-setup/tpm2-generator.c
index 6c27fa106af..a9dc1462ab7 100644
--- a/src/tpm2-setup/tpm2-generator.c
+++ b/src/tpm2-setup/tpm2-generator.c
@@ -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;
diff --git a/units/systemd-confext-initrd.service b/units/systemd-confext-initrd.service
index 9984bd70652..d3961e37a77 100644
--- a/units/systemd-confext-initrd.service
+++ b/units/systemd-confext-initrd.service
@@ -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
diff --git a/units/systemd-confext-sysroot.service b/units/systemd-confext-sysroot.service
index e30193e17e4..6fa5f602b2f 100644
--- a/units/systemd-confext-sysroot.service
+++ b/units/systemd-confext-sysroot.service
@@ -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
diff --git a/units/systemd-confext.service b/units/systemd-confext.service
index ffbf8345b8d..2e3f89f8fb7 100644
--- a/units/systemd-confext.service
+++ b/units/systemd-confext.service
@@ -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
diff --git a/units/systemd-nspawn@.service.in b/units/systemd-nspawn@.service.in
index d540a071a48..2b9984bf231 100644
--- a/units/systemd-nspawn@.service.in
+++ b/units/systemd-nspawn@.service.in
@@ -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
diff --git a/units/systemd-sysext-initrd.service b/units/systemd-sysext-initrd.service
index de106abad3e..d40dfdf82dc 100644
--- a/units/systemd-sysext-initrd.service
+++ b/units/systemd-sysext-initrd.service
@@ -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
diff --git a/units/systemd-sysext-sysroot.service b/units/systemd-sysext-sysroot.service
index b6a586925e2..787a51a9e70 100644
--- a/units/systemd-sysext-sysroot.service
+++ b/units/systemd-sysext-sysroot.service
@@ -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
diff --git a/units/systemd-sysext.service b/units/systemd-sysext.service
index 1ee16be6172..c0847c722d5 100644
--- a/units/systemd-sysext.service
+++ b/units/systemd-sysext.service
@@ -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