repart: add EncryptAddToken= to register custom LUKS2 tokens at provisioning time

Adds EncryptAddToken=<type>[:<source>:<value>] to repart.d partition
files, registering one or more LUKS2 token objects in the partition
header after the keyslot is created. This allows custom token plugins
(e.g. a TDX KBS plugin) to be wired up at provisioning time without a
separate cryptsetup token import step.

The type name is always explicit and extra token fields can be supplied as
a JSON file (:file:/path) or base64-encoded JSON (:data:<base64>). The
type and keyslots fields are always injected by repart. The option is
repeatable; an empty value clears all tokens for the partition.

Supported with Encrypt=key-file, tpm2, and key-file+tpm2.
This commit is contained in:
Nandakumar Raghavan
2026-09-22 21:26:20 +02:00
committed by Luca Boccassi
parent 06c2dfa73c
commit c99af7968e
3 changed files with 449 additions and 1 deletions
+186
View File
@@ -2940,6 +2940,192 @@ EOF
losetup -d "$loop"
}
testcase_encrypt_add_token() {
local defs imgs loop token_json metadata_json metadata_b64
defs="$(mktemp --directory "/tmp/test-repart.defs.XXXXXXXXXX")"
imgs="$(mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
chmod 0755 "$defs"
echo "*** testcase for EncryptAddToken= (plain type name, no extra data) ***"
tee "$defs/root.conf" <<EOF
[Partition]
Type=linux-generic
Format=ext4
Encrypt=key-file
EncryptAddToken=test-dummy-token
EOF
systemd-repart --pretty=yes \
--definitions "$defs" \
--empty=create \
--size=100M \
--seed="$seed" \
--dry-run=no \
--offline=no \
"$imgs/enctoken-plain.img"
loop="$(losetup -P --show --find "$imgs/enctoken-plain.img")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'; losetup -d '$loop'" RETURN
udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "${loop:?}p1"
cryptsetup luksDump "${loop}p1" | grep "test-dummy-token" >/dev/null
token_json="$(cryptsetup token export --token-id 0 "${loop}p1")"
echo "$token_json" | python3 -c "
import json, sys
d = json.load(sys.stdin)
assert d['type'] == 'test-dummy-token', 'Wrong token type: {}'.format(d['type'])
assert '0' in d.get('keyslots', []), 'keyslot 0 not referenced by token: {}'.format(d)
"
losetup -d "$loop"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
echo "*** testcase for EncryptAddToken= (type + extra data via :data: base64) ***"
rm -f "$defs/root.conf"
# Build base64-encoded JSON for the extra metadata (no type/keyslots)
metadata_json='{"trustee.kbs.url":"http://kbs-service:8080"}'
metadata_b64="$(echo -n "$metadata_json" | base64 -w0)"
tee "$defs/root.conf" <<EOF
[Partition]
Type=linux-generic
Format=ext4
Encrypt=key-file
EncryptAddToken=test-dummy-token:data:${metadata_b64}
EOF
systemd-repart --pretty=yes \
--definitions "$defs" \
--empty=create \
--size=100M \
--seed="$seed" \
--dry-run=no \
--offline=no \
"$imgs/enctoken-data.img"
loop="$(losetup -P --show --find "$imgs/enctoken-data.img")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'; losetup -d '$loop'" RETURN
udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "${loop:?}p1"
cryptsetup luksDump "${loop}p1" | grep "test-dummy-token" >/dev/null
token_json="$(cryptsetup token export --token-id 0 "${loop}p1")"
echo "$token_json" | python3 -c "
import json, sys
d = json.load(sys.stdin)
assert d['type'] == 'test-dummy-token', 'Wrong token type: {}'.format(d['type'])
assert d.get('trustee.kbs.url') == 'http://kbs-service:8080', 'Extra field not preserved: {}'.format(d)
assert '0' in d.get('keyslots', []), 'keyslot 0 not referenced by token: {}'.format(d)
"
losetup -d "$loop"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
echo "*** testcase for EncryptAddToken= (type + extra data via :file:) ***"
rm -f "$defs/root.conf"
echo -n '{"trustee.kbs.url":"http://kbs-service:9090"}' > "$imgs/token-meta.json"
tee "$defs/root.conf" <<EOF
[Partition]
Type=linux-generic
Format=ext4
Encrypt=key-file
EncryptAddToken=test-dummy-token:file:${imgs}/token-meta.json
EOF
systemd-repart --pretty=yes \
--definitions "$defs" \
--empty=create \
--size=100M \
--seed="$seed" \
--dry-run=no \
--offline=no \
"$imgs/enctoken-file.img"
loop="$(losetup -P --show --find "$imgs/enctoken-file.img")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'; losetup -d '$loop'" RETURN
udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "${loop:?}p1"
token_json="$(cryptsetup token export --token-id 0 "${loop}p1")"
echo "$token_json" | python3 -c "
import json, sys
d = json.load(sys.stdin)
assert d['type'] == 'test-dummy-token', 'Wrong token type: {}'.format(d['type'])
assert d.get('trustee.kbs.url') == 'http://kbs-service:9090', 'Extra field from file not preserved: {}'.format(d)
assert '0' in d.get('keyslots', []), 'keyslot 0 not referenced by token: {}'.format(d)
"
losetup -d "$loop"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
echo "*** testcase for EncryptAddToken= (multiple tokens on one partition) ***"
rm -f "$defs/root.conf"
metadata_b64="$(echo -n '{"extra-key":"extra-val"}' | base64 -w0)"
tee "$defs/root.conf" <<EOF
[Partition]
Type=linux-generic
Format=ext4
Encrypt=key-file
EncryptAddToken=first-token
EncryptAddToken=second-token:data:${metadata_b64}
EOF
systemd-repart --pretty=yes \
--definitions "$defs" \
--empty=create \
--size=100M \
--seed="$seed" \
--dry-run=no \
--offline=no \
"$imgs/enctoken-multi.img"
loop="$(losetup -P --show --find "$imgs/enctoken-multi.img")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'; losetup -d '$loop'" RETURN
udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "${loop:?}p1"
# Both tokens should appear in luksDump
cryptsetup luksDump "${loop}p1" | grep "first-token" >/dev/null
cryptsetup luksDump "${loop}p1" | grep "second-token" >/dev/null
# Verify each token individually
cryptsetup token export --token-id 0 "${loop}p1" | python3 -c "
import json, sys
d = json.load(sys.stdin)
assert d['type'] == 'first-token', 'Wrong first token type: {}'.format(d['type'])
assert '0' in d.get('keyslots', []), 'keyslot not in first token: {}'.format(d)
"
cryptsetup token export --token-id 1 "${loop}p1" | python3 -c "
import json, sys
d = json.load(sys.stdin)
assert d['type'] == 'second-token', 'Wrong second token type: {}'.format(d['type'])
assert d.get('extra-key') == 'extra-val', 'Extra field missing in second token: {}'.format(d)
assert '0' in d.get('keyslots', []), 'keyslot not in second token: {}'.format(d)
"
losetup -d "$loop"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
}
testcase_fstab_crypttab_in_repart() {
local defs imgs root volume