-
Notifications
You must be signed in to change notification settings - Fork 188
feat(install): defer LUKS encryption to first boot #2096
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
andrewdunndev
wants to merge
2
commits into
bootc-dev:main
from
andrewdunndev:feat/luks-firstboot-encrypt
+287
−47
Closed
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| [Unit] | ||
| Description=bootc first-boot LUKS encryption | ||
| Documentation=man:bootc(1) | ||
| DefaultDependencies=no | ||
| ConditionKernelCommandLine=rd.bootc.luks.encrypt | ||
| ConditionPathExists=/etc/initrd-release | ||
|
|
||
| # Run before the root filesystem is mounted. We need the root block device | ||
| # to be available but not yet mounted, so we can encrypt it in-place. | ||
| # After encryption, cryptsetup reencrypt auto-opens the device as | ||
| # /dev/mapper/cr_root, and udev creates the by-uuid symlink so that the | ||
| # root=UUID= karg resolves to the encrypted device. | ||
| Before=sysroot.mount | ||
| Before=initrd-root-fs.target | ||
|
|
||
| # We need block devices to be available and udev to have settled | ||
| After=systemd-udev-settle.service | ||
| After=dracut-initqueue.service | ||
| Wants=systemd-udev-settle.service | ||
|
|
||
| # If we fail, drop to emergency shell -- do not leave the system | ||
| # with a half-encrypted root partition | ||
| OnFailure=emergency.target | ||
| OnFailureJobMode=isolate | ||
|
|
||
| [Service] | ||
| Type=oneshot | ||
| ExecStart=/usr/lib/bootc/bootc-luks-firstboot.sh | ||
| StandardInput=null | ||
| StandardOutput=journal+console | ||
| StandardError=journal+console | ||
| RemainAfterExit=yes | ||
| # Encryption of a large root partition can take several minutes | ||
| TimeoutStartSec=900 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
187 changes: 187 additions & 0 deletions
187
crates/initramfs/luks-firstboot/bootc-luks-firstboot.sh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,187 @@ | ||
| #!/bin/bash | ||
| # bootc-luks-firstboot -- encrypt root partition on first boot | ||
| # | ||
| # This script runs in the initrd before sysroot.mount. It checks for the | ||
| # rd.bootc.luks.encrypt kernel argument and, if present, encrypts the root | ||
| # partition in-place using cryptsetup reencrypt --encrypt. | ||
| # | ||
| # The root partition must have been created with 32MB of trailing free space | ||
| # (filesystem smaller than partition) by bootc install to-disk. | ||
| # | ||
| # After encryption: | ||
| # - The root device is available as /dev/mapper/cr_root | ||
| # - TPM2 is enrolled via systemd-cryptenroll | ||
| # - A recovery key is generated and printed to the console | ||
| # - /etc/crypttab is written inside the encrypted root | ||
| # - BLS entries are updated with rd.luks.uuid kargs | ||
| # - The rd.bootc.luks.encrypt trigger karg is removed | ||
| # | ||
| # The root=UUID=<ext4-uuid> karg does NOT need to change. Once the initrd | ||
| # unlocks LUKS via rd.luks.uuid on subsequent boots, the ext4 UUID becomes | ||
| # visible on /dev/mapper/cr_root and systemd resolves root= normally. | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 OR MIT | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| ENCRYPT_KARG="" | ||
| ROOT_DEV="" | ||
| LUKS_NAME="cr_root" | ||
|
|
||
| log() { | ||
| echo "bootc-luks-firstboot: $*" >&2 | ||
| } | ||
|
|
||
| die() { | ||
| log "FATAL: $*" | ||
| exit 1 | ||
| } | ||
|
|
||
| parse_cmdline() { | ||
| local cmdline | ||
| cmdline=$(< /proc/cmdline) | ||
|
|
||
| for arg in $cmdline; do | ||
| case "$arg" in | ||
| rd.bootc.luks.encrypt=*) | ||
| ENCRYPT_KARG="${arg#rd.bootc.luks.encrypt=}" | ||
| ;; | ||
| root=UUID=*) | ||
| local uuid="${arg#root=UUID=}" | ||
| ROOT_DEV=$(blkid -U "$uuid" 2>/dev/null) || true | ||
| ;; | ||
| root=/dev/*) | ||
| ROOT_DEV="${arg#root=}" | ||
| ;; | ||
| esac | ||
| done | ||
| } | ||
|
|
||
| should_encrypt() { | ||
| [ -n "$ENCRYPT_KARG" ] || return 1 | ||
|
|
||
| if [ -z "$ROOT_DEV" ]; then | ||
| die "rd.bootc.luks.encrypt set but no root= device found" | ||
| fi | ||
|
|
||
| # Already encrypted? Skip. This makes the script idempotent and | ||
| # handles the case where encryption succeeded but BLS update failed. | ||
| if cryptsetup isLuks "$ROOT_DEV" 2>/dev/null; then | ||
| log "Root device $ROOT_DEV is already LUKS. Skipping encryption." | ||
| return 1 | ||
| fi | ||
|
|
||
| return 0 | ||
| } | ||
|
|
||
| encrypt_root() { | ||
| log "Encrypting root device $ROOT_DEV (method: $ENCRYPT_KARG)" | ||
|
|
||
| # Generate a temporary passphrase for initial encryption. This will be | ||
| # replaced by TPM2 enrollment below. | ||
| local tmp_passphrase | ||
| tmp_passphrase=$(cat /proc/sys/kernel/random/uuid) | ||
|
|
||
| # Encrypt in-place. The filesystem was created 32MB smaller than the | ||
| # partition by bootc, so cryptsetup uses the trailing space for the | ||
| # LUKS2 header. The device is auto-opened as /dev/mapper/$LUKS_NAME. | ||
| log "Running cryptsetup reencrypt --encrypt --reduce-device-size 32M ..." | ||
| echo -n "$tmp_passphrase" | cryptsetup reencrypt \ | ||
| --encrypt \ | ||
| --reduce-device-size 32M \ | ||
| --batch-mode \ | ||
| "$ROOT_DEV" "$LUKS_NAME" \ | ||
| --key-file=- | ||
|
|
||
| log "Encryption complete. Device: /dev/mapper/$LUKS_NAME" | ||
|
|
||
| # Enroll TPM2. --wipe-slot=all removes the temporary passphrase and | ||
| # binds unlock to the local TPM2 device with default PCR policy. | ||
| if [ "$ENCRYPT_KARG" = "tpm2" ]; then | ||
| log "Enrolling TPM2..." | ||
| echo -n "$tmp_passphrase" | systemd-cryptenroll \ | ||
| --unlock-key-file=/dev/stdin \ | ||
| --tpm2-device=auto \ | ||
| --wipe-slot=all \ | ||
| "$ROOT_DEV" | ||
| log "TPM2 enrolled, temporary passphrase removed" | ||
|
|
||
| # Add a recovery key. systemd-cryptenroll --recovery-key generates | ||
| # a high-entropy key and prints it to stdout. We capture and display | ||
| # it on the console for the user to record. | ||
| log "Generating recovery key..." | ||
| local recovery_output | ||
| recovery_output=$(systemd-cryptenroll \ | ||
| --tpm2-device=auto \ | ||
| --recovery-key \ | ||
| "$ROOT_DEV" 2>&1) || { | ||
| log "WARNING: Could not add recovery key: $recovery_output" | ||
| } | ||
| # Print the recovery key prominently so the user can record it | ||
| echo "" | ||
| echo "========================================================" | ||
| echo " LUKS RECOVERY KEY -- RECORD THIS NOW" | ||
| echo " $recovery_output" | ||
| echo "========================================================" | ||
| echo "" | ||
| fi | ||
| } | ||
|
|
||
| configure_system() { | ||
| local luks_uuid | ||
| luks_uuid=$(cryptsetup luksDump "$ROOT_DEV" | awk '/^UUID:/{print $2; exit}') | ||
| log "LUKS UUID: $luks_uuid" | ||
|
|
||
| # Mount the encrypted root to update its configuration | ||
| local mnt="/run/bootc-luks-mnt" | ||
| mkdir -p "$mnt" | ||
| mount /dev/mapper/"$LUKS_NAME" "$mnt" | ||
|
|
||
| # Write crypttab inside the ostree deploy directory | ||
| local deploy_etc | ||
| deploy_etc=$(find "$mnt/ostree/deploy" -maxdepth 4 -name "etc" -type d | head -1) | ||
| if [ -n "$deploy_etc" ]; then | ||
| echo "$LUKS_NAME UUID=$luks_uuid - tpm2-device=auto" > "$deploy_etc/crypttab" | ||
| log "Written crypttab: $deploy_etc/crypttab" | ||
| else | ||
| log "WARNING: Could not find ostree deploy etc directory" | ||
| fi | ||
|
|
||
| # Update BLS entries. These may be on /boot (separate partition, already | ||
| # mounted by the initrd) or inside the encrypted root at /boot/loader/. | ||
| # Check both locations. | ||
| local updated=0 | ||
| local entry | ||
| for entry in /boot/loader/entries/*.conf "$mnt"/boot/loader/entries/*.conf; do | ||
| [ -f "$entry" ] || continue | ||
| if grep -q "rd.bootc.luks.encrypt" "$entry"; then | ||
| # Remove the first-boot trigger karg | ||
| sed -i 's/ rd.bootc.luks.encrypt=[^ ]*//' "$entry" | ||
| # Add LUKS unlock kargs. The root=UUID= karg stays unchanged -- | ||
| # once systemd-cryptsetup unlocks LUKS via rd.luks.uuid, the | ||
| # ext4 UUID inside becomes visible and root= resolves normally. | ||
| sed -i "s|^options |options rd.luks.uuid=$luks_uuid rd.luks.name=$luks_uuid=$LUKS_NAME rd.luks.options=$luks_uuid=tpm2-device=auto,headless=true |" "$entry" | ||
| updated=$((updated + 1)) | ||
| log "Updated BLS entry: $entry" | ||
| fi | ||
| done | ||
|
|
||
| if [ "$updated" -eq 0 ]; then | ||
| log "WARNING: No BLS entries found to update" | ||
| fi | ||
|
|
||
| umount "$mnt" | ||
| } | ||
|
|
||
| # Main | ||
| parse_cmdline | ||
|
|
||
| if ! should_encrypt; then | ||
| log "No encryption requested or already encrypted. Exiting." | ||
| exit 0 | ||
| fi | ||
|
|
||
| encrypt_root | ||
| configure_system | ||
andrewdunndev marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| log "First-boot encryption complete." | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.