|
#!/usr/bin/env bash |
|
set -eo pipefail |
|
|
|
# ============================================================================= |
|
# luks-tpm-enroll.sh |
|
# |
|
# Manage TPM2 auto-unlock for a LUKS2+LVM encrypted USB drive across multiple |
|
# machines, using systemd-cryptenroll (no clevis). |
|
# |
|
# Modes |
|
# --config-os Patch /etc/crypttab and rebuild initramfs — only if not |
|
# already done. Idempotent: exits cleanly when nothing to do. |
|
# |
|
# --add-this-tpm Check whether TPM2 enrollment is needed for this machine, |
|
# then PRINT the required commands (with all UUIDs/paths |
|
# already expanded) for manual inspection and execution. |
|
# Makes zero modifications itself. |
|
# |
|
# Enrollment record (tab-separated, first line is a # comment header) |
|
# /etc/luks-tpm-enrollments — written on the USB root filesystem. |
|
# Columns: machine_id, keyslot, hostname, timestamp_utc, luks_uuid, |
|
# tpm_device, kernel, dmi_vendor, dmi_product, dmi_board, cpu, os |
|
# |
|
# LUKS header backups (written by --config-os only, never by --add-this-tpm) |
|
# /etc/luks-header-backups/luks-backup.<YYYYMMDD-HHMMSS> pre-change |
|
# /etc/luks-header-backups/luks-backup.<YYYYMMDD-HHMMSS>.<host> post-change |
|
# Both backups share the same timestamp so they are unambiguously paired. |
|
# Mode 400, directory mode 700. |
|
# |
|
# PCR policy |
|
# PCR 7 = Secure Boot state. Survives kernel/package updates but will |
|
# require re-enrollment after a BIOS/firmware upgrade that alters SB state. |
|
# ============================================================================= |
|
|
|
readonly SCRIPT="$(basename "$0")" |
|
readonly ENROLLMENT_DB="/etc/luks-tpm-enrollments" |
|
readonly LUKS_BACKUP_DIR="/etc/luks-header-backups" |
|
readonly TPM2_PCRS="7" |
|
|
|
# ── Colour helpers ───────────────────────────────────────────────────────────── |
|
if [[ -t 1 ]]; then |
|
RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' |
|
CYAN='\033[0;36m' BOLD='\033[1m' DIM='\033[2m' NC='\033[0m' |
|
else |
|
RED='' GREEN='' YELLOW='' CYAN='' BOLD='' DIM='' NC='' |
|
fi |
|
|
|
log() { printf "${GREEN}[+]${NC} %s\n" "$*"; } |
|
info() { printf "${CYAN}[i]${NC} %s\n" "$*"; } |
|
warn() { printf "${YELLOW}[!]${NC} %s\n" "$*"; } |
|
ok() { printf "${GREEN}[✓]${NC} %s\n" "$*"; } |
|
die() { printf "${RED}[✗] %s${NC}\n" "$*" >&2; exit 1; } |
|
hr() { printf "${DIM}%s${NC}\n" \ |
|
"────────────────────────────────────────────────────────────"; } |
|
cmd_echo() { |
|
# Print a command line the user should inspect/run, visually distinct |
|
printf "${BOLD} %s${NC}\n" "$*" |
|
} |
|
|
|
# Single-quote a value for safe embedding in a shell command string printed to |
|
# the user. Replaces each ' with '\'' so the output is always copy-paste safe. |
|
sq() { printf "%s" "$*" | sed "s/'/'\\\\''/g; s/^/'/; s/$/'/"; } |
|
|
|
# ── Usage ────────────────────────────────────────────────────────────────────── |
|
usage() { |
|
cat <<EOF |
|
${BOLD}Usage:${NC} $SCRIPT [--config-os | --add-this-tpm] [-h | --help] |
|
|
|
${BOLD}--config-os${NC} |
|
Patch /etc/crypttab to enable TPM2 auto-unlock with passphrase |
|
fallback, then rebuild initramfs. Exits immediately (no changes) |
|
if the system is already configured. |
|
|
|
${BOLD}--add-this-tpm${NC} |
|
Check whether this machine's TPM2 needs enrolling. If so, prints |
|
the exact commands (with UUIDs and paths already resolved) to be |
|
reviewed and run manually. Makes zero changes itself. |
|
Requires the OS to already be configured (--config-os, or manually). |
|
|
|
${BOLD}-h, --help${NC} |
|
Show this help. |
|
|
|
PCR policy : ${BOLD}PCR${TPM2_PCRS}${NC} (Secure Boot state) |
|
Enrollment DB : ${ENROLLMENT_DB} |
|
Header backups: ${LUKS_BACKUP_DIR}/luks-backup.<timestamp>[.<hostname>] |
|
EOF |
|
} |
|
|
|
# ── Argument parsing ─────────────────────────────────────────────────────────── |
|
MODE="" |
|
while [[ $# -gt 0 ]]; do |
|
case "$1" in |
|
--config-os) MODE="config-os" ;; |
|
--add-this-tpm) MODE="add-tpm" ;; |
|
-h|--help) usage; exit 0 ;; |
|
*) die "Unknown argument: '$1' (run $SCRIPT --help)" ;; |
|
esac |
|
shift |
|
done |
|
[[ -z "$MODE" ]] && { usage >&2; exit 1; } |
|
[[ $EUID -ne 0 ]] && die "Must be run as root (try: sudo $SCRIPT --${MODE})" |
|
|
|
# ── Locate the raw LUKS partition via device ancestry ───────────────────────── |
|
# |
|
# Device stack for a LUKS2+LVM USB layout: |
|
# |
|
# /dev/sdX |
|
# └─ /dev/sdX1 FSTYPE=crypto_LUKS ← we want this block device |
|
# └─ dm-N TYPE=crypt |
|
# └─ dm-M TYPE=lvm |
|
# └─ / (root) |
|
# |
|
# lsblk -s (inverse tree from a given device) + filter FSTYPE=crypto_LUKS |
|
# gives the raw partition that cryptsetup / systemd-cryptenroll operate on. |
|
# Its stable reference is then /dev/disk/by-uuid/<LUKS_UUID>. |
|
# ───────────────────────────────────────────────────────────────────────────── |
|
find_luks_partition() { |
|
local root_src luks_dev |
|
|
|
# findmnt -o SOURCE: real block device backing /, resolving overlays etc. |
|
root_src=$(findmnt -n -o SOURCE /) |
|
|
|
# Strip any subvolume/offset annotations (e.g. /dev/sda1[/subvol]) |
|
root_src="${root_src%%\[*}" |
|
|
|
[[ -b "$root_src" ]] || die \ |
|
"findmnt returned '$root_src' which is not a block device. |
|
Are you running this from the USB's own booted environment?" |
|
|
|
luks_dev=$(lsblk -snpo NAME,FSTYPE "$root_src" 2>/dev/null \ |
|
| awk '$2 == "crypto_LUKS" { print $1; exit }') |
|
|
|
[[ -z "$luks_dev" ]] && die \ |
|
"No crypto_LUKS layer found in the ancestor chain of '$root_src'. |
|
Are you booted from the correct encrypted USB drive?" |
|
|
|
printf '%s' "$luks_dev" |
|
} |
|
|
|
# ── Persistent LUKS UUID → /dev/disk/by-uuid reference ─────────────────────── |
|
luks_uuid_of() { |
|
local dev="$1" |
|
local uuid |
|
uuid=$(blkid -s UUID -o value "$dev") |
|
[[ -z "$uuid" ]] && die "blkid could not read a UUID from $dev" |
|
printf '%s' "$uuid" |
|
} |
|
|
|
# Canonical, kernel-stable path for the LUKS partition (never /dev/sdXN) |
|
luks_byuuid_path() { printf '/dev/disk/by-uuid/%s' "$1"; } |
|
|
|
# ── LUKS2 version check ──────────────────────────────────────────────────────── |
|
require_luks2() { |
|
local dev="$1" |
|
local ver |
|
ver=$(cryptsetup luksDump "$dev" | awk '/^Version:/{print $2}') |
|
[[ "$ver" == "2" ]] || die \ |
|
"LUKS${ver} detected on $dev — TPM2 enrollment requires LUKS2. |
|
To convert (run from a live session with the volume closed): |
|
cryptsetup convert $dev --type luks2" |
|
} |
|
|
|
# ── TPM2 availability ────────────────────────────────────────────────────────── |
|
have_tpm2() { |
|
# Returns 0 only if systemd-cryptenroll can actually list a TPM2 device |
|
systemd-cryptenroll --tpm2-device=list 2>/dev/null | grep -q '.' |
|
} |
|
|
|
# ── Stable machine identifier ────────────────────────────────────────────────── |
|
# 1. DMI product_uuid — hardware level, survives OS reinstalls |
|
# 2. /etc/machine-id — OS level, fallback for VMs / unusual hardware |
|
get_machine_id() { |
|
local id |
|
id=$(cat /sys/class/dmi/id/product_uuid 2>/dev/null \ |
|
| tr '[:upper:]' '[:lower:]' | tr -d '[:space:]') |
|
|
|
if [[ -z "$id" || "$id" == "00000000-0000-0000-0000-000000000000" ]]; then |
|
warn "DMI product_uuid absent or all-zeros — using /etc/machine-id" |
|
id=$(cat /etc/machine-id 2>/dev/null | tr -d '[:space:]') |
|
fi |
|
|
|
[[ -z "$id" ]] && die "Cannot determine a stable machine identifier." |
|
printf '%s' "$id" |
|
} |
|
|
|
# ── Machine info collector ───────────────────────────────────────────────────── |
|
# Populates a set of MI_* variables in the caller's scope. |
|
# Every field falls back to "unknown" so the record is always complete. |
|
# Call this once per mode; all subsequent helpers read the MI_* vars. |
|
collect_machine_info() { |
|
# Hardware identity |
|
MI_MACHINE_ID=$(get_machine_id) |
|
MI_HOSTNAME=$(hostname 2>/dev/null || echo "unknown") |
|
|
|
# DMI / firmware |
|
MI_DMI_VENDOR=$( cat /sys/class/dmi/id/sys_vendor 2>/dev/null \ |
|
| tr -s ' ' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') |
|
MI_DMI_PRODUCT=$(cat /sys/class/dmi/id/product_name 2>/dev/null \ |
|
| tr -s ' ' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') |
|
MI_DMI_BOARD=$( cat /sys/class/dmi/id/board_name 2>/dev/null \ |
|
| tr -s ' ' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') |
|
[[ -z "$MI_DMI_VENDOR" ]] && MI_DMI_VENDOR="unknown" |
|
[[ -z "$MI_DMI_PRODUCT" ]] && MI_DMI_PRODUCT="unknown" |
|
[[ -z "$MI_DMI_BOARD" ]] && MI_DMI_BOARD="unknown" |
|
|
|
# CPU — first "model name" line in /proc/cpuinfo, collapsed whitespace |
|
MI_CPU=$(grep -m1 'model name' /proc/cpuinfo 2>/dev/null \ |
|
| sed 's/.*: //;s/ */ /g;s/^[[:space:]]*//;s/[[:space:]]*$//') |
|
[[ -z "$MI_CPU" ]] && MI_CPU="unknown" |
|
|
|
# OS — PRETTY_NAME from os-release |
|
MI_OS=$(. /etc/os-release 2>/dev/null && printf '%s' "${PRETTY_NAME:-}") |
|
[[ -z "$MI_OS" ]] && MI_OS="unknown" |
|
|
|
# Kernel |
|
MI_KERNEL=$(uname -r 2>/dev/null || echo "unknown") |
|
|
|
# TPM2 device path (first entry from systemd-cryptenroll --tpm2-device=list) |
|
MI_TPM_DEVICE=$(systemd-cryptenroll --tpm2-device=list 2>/dev/null \ |
|
| awk 'NR==2{print $1}') |
|
[[ -z "$MI_TPM_DEVICE" ]] && MI_TPM_DEVICE="unknown" |
|
} |
|
|
|
# ── Enrollment log display ───────────────────────────────────────────────────── |
|
# Pretty-prints every non-comment record in $ENROLLMENT_DB, one block per row. |
|
# Called read-only from both modes; never modifies the file. |
|
show_enrollment_log() { |
|
if [[ ! -f "$ENROLLMENT_DB" ]] || \ |
|
! grep -q $'^[^\t#]' "$ENROLLMENT_DB" 2>/dev/null; then |
|
info "No machines enrolled yet (${ENROLLMENT_DB} is empty or absent)." |
|
return 0 |
|
fi |
|
|
|
# Column indices (0-based after awk splits on TAB): |
|
# 0 machine_id 1 keyslot 2 hostname 3 timestamp 4 luks_uuid |
|
# 5 tpm_device 6 kernel 7 dmi_vendor 8 dmi_product 9 dmi_board |
|
# 10 cpu 11 os |
|
local count=0 |
|
while IFS=$'\t' read -r \ |
|
f_mid f_slot f_host f_ts f_luuid \ |
|
f_tpm f_kern f_vendor f_prod f_board f_cpu f_os; do |
|
[[ "$f_mid" == \#* || -z "$f_mid" ]] && continue |
|
(( count++ )) || true |
|
printf "${BOLD} ── Enrolled machine #%d ──────────────────────────────${NC}\n" "$count" |
|
printf " %-14s %s\n" "Hostname:" "$f_host" |
|
printf " %-14s %s\n" "Machine ID:" "$f_mid" |
|
printf " %-14s %s\n" "Keyslot:" "$f_slot" |
|
printf " %-14s %s\n" "Enrolled:" "$f_ts" |
|
printf " %-14s %s\n" "LUKS UUID:" "$f_luuid" |
|
printf " %-14s %s\n" "TPM device:" "$f_tpm" |
|
printf " %-14s %s\n" "Kernel:" "$f_kern" |
|
printf " %-14s %s %s (%s)\n" "Hardware:" "$f_vendor" "$f_prod" "$f_board" |
|
printf " %-14s %s\n" "CPU:" "$f_cpu" |
|
printf " %-14s %s\n" "OS:" "$f_os" |
|
printf '\n' |
|
done < "$ENROLLMENT_DB" |
|
|
|
[[ $count -eq 0 ]] && info "No machines enrolled yet." |
|
} |
|
|
|
# ── crypttab helpers ─────────────────────────────────────────────────────────── |
|
|
|
# Returns 0 if the crypttab entry for <uuid> already has tpm2-device= |
|
crypttab_has_tpm2() { |
|
local uuid="$1" |
|
grep -E "(UUID=)?${uuid}" /etc/crypttab 2>/dev/null \ |
|
| grep -q 'tpm2-device' |
|
} |
|
|
|
# Rewrite the crypttab entry for <uuid> to add TPM2 options. |
|
# Preserves any existing custom options; drops bare conflicting tokens. |
|
# Switches keyfile field to '-' (no static keyfile; systemd tries TPM2 first, |
|
# falls back to passphrase prompt if TPM2 fails). |
|
# Creates a new entry if none found. Backs up the original first. |
|
update_crypttab() { |
|
local uuid="$1" |
|
local tpm_opts="tpm2-device=auto,tpm2-pcrs=${TPM2_PCRS}" |
|
local crypttab="/etc/crypttab" |
|
local tmp found=0 |
|
tmp=$(mktemp /tmp/crypttab.XXXXXX) |
|
trap 'rm -f "$tmp"' EXIT |
|
|
|
while IFS= read -r line || [[ -n "$line" ]]; do |
|
# Pass comments and blank lines through unchanged |
|
if [[ "$line" =~ ^[[:space:]]*(#|$) ]]; then |
|
printf '%s\n' "$line" >> "$tmp"; continue |
|
fi |
|
|
|
if printf '%s' "$line" | grep -qE "(UUID=)?${uuid}"; then |
|
found=1 |
|
read -r ct_name ct_dev _ct_key ct_opts <<< "$line" |
|
|
|
# Strip tokens that conflict with '-' keyfile or duplicate tpm2 |
|
local clean_opts new_opts |
|
clean_opts=$(printf '%s' "${ct_opts-}" \ |
|
| tr ',' '\n' \ |
|
| grep -v -E '^(luks|none|password|keyscript=.*)$' \ |
|
| paste -sd ',' - \ |
|
| sed 's/^,//;s/,$//') |
|
new_opts="${clean_opts:+${clean_opts},}${tpm_opts}" |
|
|
|
printf '%s\tUUID=%s\t-\t%s\n' "$ct_name" "$uuid" "$new_opts" >> "$tmp" |
|
log " crypttab entry updated:" |
|
info " $ct_name UUID=$uuid - $new_opts" |
|
else |
|
printf '%s\n' "$line" >> "$tmp" |
|
fi |
|
done < "$crypttab" |
|
|
|
if [[ $found -eq 0 ]]; then |
|
local ct_name="luks-${uuid}" |
|
printf '%s\tUUID=%s\t-\t%s\n' "$ct_name" "$uuid" "$tpm_opts" >> "$tmp" |
|
log " crypttab new entry:" |
|
info " $ct_name UUID=$uuid - $tpm_opts" |
|
fi |
|
|
|
cp "$crypttab" "${crypttab}.bak" |
|
mv "$tmp" "$crypttab" |
|
trap - EXIT |
|
info " Backup saved: ${crypttab}.bak" |
|
} |
|
|
|
# ── Enrollment DB helpers ────────────────────────────────────────────────────── |
|
|
|
machine_is_enrolled() { |
|
local machine_id="$1" |
|
[[ -f "$ENROLLMENT_DB" ]] && \ |
|
grep -q "^${machine_id}"$'\t' "$ENROLLMENT_DB" |
|
} |
|
|
|
enrolled_slot() { |
|
local machine_id="$1" |
|
awk -F'\t' -v id="$machine_id" '$1==id { print $2 }' "$ENROLLMENT_DB" |
|
} |
|
|
|
# ── LUKS header backup ──────────────────────────────────────────────────────── |
|
# backup_luks_header <device> <timestamp> <suffix> |
|
# Writes a binary header backup to $LUKS_BACKUP_DIR using cryptsetup. |
|
# Pre-change backup: luks-backup.<timestamp> |
|
# Post-change backup: luks-backup.<timestamp>.<hostname> |
|
# |
|
# Both backups share the same <timestamp> so they are unambiguously paired. |
|
# The post-backup suffix carries the hostname because that is the machine that |
|
# made the keyslot change (relevant when multiple admins run --config-os from |
|
# different machines over the drive's lifetime). |
|
backup_luks_header() { |
|
local dev="$1" ts="$2" suffix="$3" |
|
local fname="${LUKS_BACKUP_DIR}/luks-backup.${ts}${suffix:+.${suffix}}" |
|
|
|
mkdir -p "$LUKS_BACKUP_DIR" |
|
chmod 700 "$LUKS_BACKUP_DIR" |
|
|
|
cryptsetup luksHeaderBackup "$dev" --header-backup-file "$fname" |
|
chmod 400 "$fname" |
|
ok " LUKS header backup: $fname" |
|
} |
|
|
|
# ══════════════════════════════════════════════════════════════════════════════ |
|
# MODE: --config-os |
|
# Idempotent. Makes changes only when the system is not yet configured. |
|
# ══════════════════════════════════════════════════════════════════════════════ |
|
do_config_os() { |
|
hr |
|
log "Checking OS configuration for TPM2 LUKS auto-unlock…" |
|
hr |
|
|
|
# ── 1. Identify the LUKS partition and its stable UUID ──────────────────── |
|
# backup_ts is generated once so pre- and post-change backups share the |
|
# same timestamp and are unambiguously paired in the backup directory. |
|
local luks_part luks_uuid luks_ref backup_ts |
|
backup_ts=$(date -u +"%Y%m%d-%H%M%S") |
|
collect_machine_info # populates MI_* variables |
|
luks_part=$(find_luks_partition) |
|
luks_uuid=$(luks_uuid_of "$luks_part") |
|
luks_ref=$(luks_byuuid_path "$luks_uuid") |
|
|
|
info "LUKS partition : $luks_part" |
|
info "LUKS UUID : $luks_uuid" |
|
info "Stable ref : $luks_ref" |
|
|
|
# ── 2. LUKS2 required ───────────────────────────────────────────────────── |
|
require_luks2 "$luks_part" |
|
ok "LUKS version 2 confirmed" |
|
|
|
# ── 3. Check crypttab — exit early if already configured ───────────────── |
|
if crypttab_has_tpm2 "$luks_uuid"; then |
|
ok "crypttab already has tpm2-device for UUID=${luks_uuid}" |
|
ok "initramfs is current (crypttab unchanged)" |
|
hr |
|
ok "OS already configured — nothing to do." |
|
info "On each TPM-equipped machine, run: sudo $SCRIPT --add-this-tpm" |
|
hr |
|
exit 0 |
|
fi |
|
|
|
# ── 4. Warn if this machine has no TPM2 (config valid for other machines) ─ |
|
if ! have_tpm2; then |
|
warn "No TPM2 found on this machine — /etc/crypttab will still be" |
|
warn "configured so other machines can enroll via --add-this-tpm." |
|
else |
|
ok "TPM2 device found on this machine" |
|
fi |
|
|
|
# ── 5. LUKS header backup — BEFORE any changes ─────────────────────────── |
|
log "Backing up LUKS header (pre-change)…" |
|
backup_luks_header "$luks_ref" "$backup_ts" "" |
|
|
|
# ── 6. Patch /etc/crypttab ──────────────────────────────────────────────── |
|
log "Patching /etc/crypttab…" |
|
update_crypttab "$luks_uuid" |
|
|
|
# ── 7. Rebuild initramfs — only reached because crypttab changed ───────── |
|
log "Rebuilding initramfs (crypttab was modified)…" |
|
update-initramfs -u -k all |
|
ok "initramfs rebuilt" |
|
|
|
# ── 8. LUKS header backup — AFTER changes ──────────────────────────────── |
|
# The post-backup is taken after initramfs rebuild (not after crypttab alone) |
|
# because update-initramfs can occasionally touch LUKS token metadata on |
|
# some systems. suffix = hostname to record which machine made the change. |
|
log "Backing up LUKS header (post-change)…" |
|
backup_luks_header "$luks_ref" "$backup_ts" "$MI_HOSTNAME" |
|
|
|
hr |
|
ok "OS configuration complete." |
|
info "On each TPM-equipped machine, run: sudo $SCRIPT --add-this-tpm" |
|
hr |
|
printf '\n' |
|
log "Current enrollment log:" |
|
show_enrollment_log |
|
hr |
|
} |
|
|
|
# ══════════════════════════════════════════════════════════════════════════════ |
|
# MODE: --add-this-tpm |
|
# Makes ZERO changes. Checks all preconditions, then prints the exact |
|
# commands (with all values expanded) for manual review and execution. |
|
# ══════════════════════════════════════════════════════════════════════════════ |
|
do_add_tpm() { |
|
hr |
|
log "Checking TPM2 enrollment for this machine…" |
|
hr |
|
|
|
# ── 1. TPM2 must be present on this machine ─────────────────────────────── |
|
have_tpm2 || die \ |
|
"No TPM2 device found on this machine. |
|
Only machines with a working TPM2 chip can be enrolled." |
|
ok "TPM2 device found" |
|
|
|
# ── 2. Collect full machine identity ───────────────────────────────────── |
|
collect_machine_info # populates MI_* variables |
|
|
|
info "Hostname : $MI_HOSTNAME" |
|
info "Machine ID : $MI_MACHINE_ID" |
|
info "Hardware : $MI_DMI_VENDOR $MI_DMI_PRODUCT ($MI_DMI_BOARD)" |
|
info "CPU : $MI_CPU" |
|
info "Kernel : $MI_KERNEL" |
|
info "OS : $MI_OS" |
|
info "TPM device : $MI_TPM_DEVICE" |
|
|
|
# ── 3. Locate the LUKS partition and its stable UUID ───────────────────── |
|
local luks_part luks_uuid luks_ref |
|
luks_part=$(find_luks_partition) |
|
luks_uuid=$(luks_uuid_of "$luks_part") |
|
luks_ref=$(luks_byuuid_path "$luks_uuid") |
|
|
|
info "LUKS partition: $luks_part" |
|
info "LUKS UUID : $luks_uuid" |
|
info "Stable ref : $luks_ref" |
|
|
|
require_luks2 "$luks_part" |
|
|
|
# ── 4. OS must already be configured ───────────────────────────────────── |
|
# Checks crypttab directly — works whether --config-os ran or the user |
|
# configured it manually, as long as the tpm2-device option is present. |
|
if ! crypttab_has_tpm2 "$luks_uuid"; then |
|
die \ |
|
"The OS is not yet configured for TPM2 auto-unlock (crypttab has no |
|
tpm2-device option for UUID=${luks_uuid}). |
|
|
|
Run first (from any machine, TPM not required): |
|
sudo $SCRIPT --config-os" |
|
fi |
|
ok "OS is configured (crypttab has tpm2-device for this drive)" |
|
|
|
# ── 5. Show current enrollment log ─────────────────────────────────────── |
|
printf '\n' |
|
log "Current enrollment log:" |
|
show_enrollment_log |
|
|
|
# ── 6. Check whether this machine is already enrolled ──────────────────── |
|
if machine_is_enrolled "$MI_MACHINE_ID"; then |
|
local slot; slot=$(enrolled_slot "$MI_MACHINE_ID") |
|
ok "This machine is already enrolled in keyslot ${slot} — no action needed." |
|
hr |
|
warn "To revoke and re-enroll, run these commands in order:" |
|
printf '\n' |
|
printf "${CYAN}# Wipe the existing TPM2 keyslot for this machine:${NC}\n" |
|
cmd_echo "sudo systemd-cryptenroll --wipe-slot=${slot} ${luks_ref}" |
|
printf '\n' |
|
printf "${CYAN}# Remove the enrollment record:${NC}\n" |
|
cmd_echo "sudo sed -i '/^${MI_MACHINE_ID}\t/d' ${ENROLLMENT_DB}" |
|
printf '\n' |
|
printf "${CYAN}# Then re-run this script to get fresh enrollment commands:${NC}\n" |
|
cmd_echo "sudo $SCRIPT --add-this-tpm" |
|
printf '\n' |
|
hr |
|
exit 0 |
|
fi |
|
|
|
# ── 7. Enrollment needed — print all commands for manual execution ──────── |
|
local ts |
|
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ") |
|
|
|
hr |
|
printf "${BOLD}This machine is not yet enrolled.${NC}\n" |
|
printf "Review the commands below carefully, then run them in order.\n" |
|
hr |
|
|
|
printf '\n' |
|
printf "${CYAN}# ── Step 1: Enroll this machine's TPM2 into a new LUKS keyslot ──────────────${NC}\n" |
|
printf "${DIM}# You will be prompted for the existing LUKS passphrase.${NC}\n" |
|
printf "${DIM}# systemd-cryptenroll will report the allocated slot number — note it.${NC}\n" |
|
printf '\n' |
|
cmd_echo "sudo systemd-cryptenroll \\" |
|
cmd_echo " --tpm2-device=auto \\" |
|
cmd_echo " --tpm2-pcrs=${TPM2_PCRS} \\" |
|
cmd_echo " ${luks_ref}" |
|
|
|
printf '\n' |
|
printf "${CYAN}# ── Step 2: Confirm the new keyslot in the LUKS header ───────────────────────${NC}\n" |
|
printf "${DIM}# The new slot should appear as the highest-numbered luks2 keyslot.${NC}\n" |
|
printf '\n' |
|
cmd_echo "sudo cryptsetup luksDump ${luks_ref} | awk '/^Keyslots:/{p=1} p && /^Tokens:/{p=0} p'" |
|
|
|
printf '\n' |
|
printf "${CYAN}# ── Step 3: Record the enrollment ────────────────────────────────────────────${NC}\n" |
|
printf "${DIM}# Replace SLOT with the actual keyslot number from Step 1.${NC}\n" |
|
printf "${DIM}# All other fields are already expanded — copy as-is.${NC}\n" |
|
printf '\n' |
|
|
|
# 3a — Create the header line if the file doesn't exist yet. |
|
local db_header='machine_id\tkeyslot\thostname\ttimestamp_utc\tluks_uuid\ttpmd_device\tkernel\tdmi_vendor\tdmi_product\tdmi_board\tcpu\tos' |
|
cmd_echo "sudo bash -c '[ -f ${ENROLLMENT_DB} ] || { printf \"# %s\\n\" \\" |
|
cmd_echo " $(sq "$db_header") > ${ENROLLMENT_DB} && chmod 640 ${ENROLLMENT_DB}; }'" |
|
|
|
# 3b — Append the record; pipe through tee so sudo applies to the write. |
|
# All field values are expanded now; only SLOT is a placeholder. |
|
# sq() has already escaped any embedded single quotes in each value. |
|
printf '\n' |
|
cmd_echo "printf '%s\\t%s\\t%s\\t%s\\t%s\\t%s\\t%s\\t%s\\t%s\\t%s\\t%s\\t%s\\n' \\" |
|
cmd_echo " $(sq "$MI_MACHINE_ID") \\" |
|
cmd_echo " 'SLOT' \\" |
|
cmd_echo " $(sq "$MI_HOSTNAME") \\" |
|
cmd_echo " $(sq "$ts") \\" |
|
cmd_echo " $(sq "$luks_uuid") \\" |
|
cmd_echo " $(sq "$MI_TPM_DEVICE") \\" |
|
cmd_echo " $(sq "$MI_KERNEL") \\" |
|
cmd_echo " $(sq "$MI_DMI_VENDOR") \\" |
|
cmd_echo " $(sq "$MI_DMI_PRODUCT") \\" |
|
cmd_echo " $(sq "$MI_DMI_BOARD") \\" |
|
cmd_echo " $(sq "$MI_CPU") \\" |
|
cmd_echo " $(sq "$MI_OS") \\" |
|
cmd_echo " | sudo tee -a ${ENROLLMENT_DB} > /dev/null" |
|
|
|
printf '\n' |
|
printf "${CYAN}# ── To revoke this machine later ─────────────────────────────────────────────${NC}\n" |
|
printf "${DIM}# Replace SLOT with the actual keyslot number.${NC}\n" |
|
printf '\n' |
|
cmd_echo "sudo systemd-cryptenroll --wipe-slot=SLOT ${luks_ref}" |
|
cmd_echo "sudo sed -i '/^${MI_MACHINE_ID}\t/d' ${ENROLLMENT_DB}" |
|
|
|
printf '\n' |
|
hr |
|
info "After running all steps, reboot to verify TPM2 auto-unlock." |
|
hr |
|
} |
|
|
|
# ── Dispatch ─────────────────────────────────────────────────────────────────── |
|
case "$MODE" in |
|
config-os) do_config_os ;; |
|
add-tpm) do_add_tpm ;; |
|
esac |