├── README.md ├── do-boot.sh ├── etc ├── default │ ├── kexec │ └── kexec-cryptroot └── initramfs-tools │ └── scripts │ └── local-top │ └── cryptroot └── kexec-load.sh /README.md: -------------------------------------------------------------------------------- 1 | # keyexec 2 | 3 | Collection of scripts to automatically unlock LUKS devices on kexec reboot. 4 | 5 | ## Compatibility 6 | 7 | Scripts were tested on Ubuntu 16.04 LTS (Xenial Xerus) and 18.04 LTS (Bionic Beaver) 8 | 9 | ## Usage 10 | 11 | 1. Install kexec-tools 12 | 1. Copy the files from `etc` to the corresponding directories in the root filesystem 13 | 1. Run `update-initramfs -u -k all` 14 | 15 | ### Ubuntu 16.04 16 | 17 | 1. reboot 18 | 19 | ### Ubuntu 18.04 20 | 21 | Ubuntu 16.04 used to allow simply rebooting now, and it would happily load and kexec the latest kernel, and the automagically-generated initramfs with LUKS key material. This seems to no longer work with Ubuntu 18.04. While systemd allows you to `systemctl kexec`, this doesn't seem to properly work either, at least not on non-UEFI systems. Untested are UEFI-based systems, but it is not unlikely that systemd will just kexec-reboot with the system standard initramfs. However, `systemctl kexec` will happily kexec-reboot a pre-loaded kernel and initramfs: 22 | 23 | 1. Use `kexec-load.sh` to generate a temporary intramfs with key material, and kexec load it with the latest kernel. 24 | 1. Use `systemctl kexec` to gracefully shutdown and kexec execute the preloaded kernel and initramfs. 25 | 26 | ## How does this work? 27 | 28 | ### Preparation 29 | Recent versions of Ubuntu support rebooting through kexec. This is implemented through two scripts in `/etc/init.d`: 30 | 31 | * `/etc/init.d/kexec-load` takes care of loading a kernel and initramfs with `kexec -l` 32 | * `/etc/init.d/kexec` executes `kexec -e` to reboot 33 | 34 | Both scripts source `/etc/default/kexec`. That script was adapted to also source `/etc/default/kexec-cryptroot`, if it exists. That script: 35 | 36 | * Creates a new temporary directory in /dev/shm 37 | * Sets up a trap handler to wipe that directory on exit 38 | * Copies the existing initramfs image to that directory 39 | * Iterates over a list of all block devices of type `crypto_LUKS` (i.e. the LUKS backing devices) and for each 40 | * Obtains the UUID 41 | * Finds the corresponding unlocked LUKS device 42 | * Obtains the LUKS master key using `dmsetup --showkeys table` 43 | * Writes that key to a file `etc/${UUID}.key` in the temporary directory 44 | * Appends the key files to the initramfs image 45 | * Points `$CRYPTROOT_INITRD` to the temporary initramfs image 46 | * If `$APPEND` is unset, sets `$CRYPTROOT_APPEND` to the current kernel command line and appends `panic=10`. The latter forces the initramfs to reboot on error, instead of spawning an emergency shell. 47 | 48 | `$CRYPTROOT_INITRD` and `$CRYPTROOT_APPEND` are then used to overwrite `$INITRD` and `$APPEND` in `/etc/default/kexec`. This will cause `/etc/init.d/kexec-load` to invoke kexec to load the default kernel with the temporary initramfs and the new kernel command line: 49 | 50 | ```sh 51 | /sbin/kexec -l "$KERNEL_IMAGE" --initrd="$INITRD" --append="$REAL_APPEND" 52 | ``` 53 | When `kexec-load` exits, the temporary initramfs image has been loaded to memory and the trap handler wipes the keys and initramfs from the temporary directory. 54 | 55 | ### Unlocking 56 | 57 | The default `cryptroot` script from the initramfs-tools package was slightly adapted to use the key files in `/etc` to unlock matching devices with `cryptsetup luksOpen --master-key-file=/etc/${UUID}.key`. Keys are wiped after use to minimize exposure. 58 | 59 | ## Security Considerations 60 | 61 | The scripts try to minimize exposure of key material, e.g. by setting a restrictive umask before creating any directories or files, only storing them in RAM (/dev/shm should hopefully be a tmpfs) and removing keys from filesystems when they are no longer needed. Adding `panic=10` to the kernel command line *should* prevent the initramfs from dropping to an emergency shell, where the keys could still be accessible in the initramfs /etc directory. 62 | 63 | ## Copyright and License 64 | 65 | Copyright 2017, Lutz Wolf 66 | 67 | Licensed under GPLv2 or later. 68 | 69 | The cryptroot script is an adaptation of the script from the Debian/Ubuntu cryptsetup package, which is licensed GPL v2 or later. Copyright and licensing information can be found in that package, or at: 70 | 71 | https://anonscm.debian.org/cgit/pkg-cryptsetup/cryptsetup.git 72 | -------------------------------------------------------------------------------- /do-boot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Manual reboot script 4 | 5 | . /etc/default/kexec 6 | 7 | APPEND="" 8 | APPEND="${APPEND} root=$(blkid -o export "$(findmnt -o SOURCE -n -T /)" | grep "^UUID=") ro quiet panic=10" 9 | 10 | /sbin/kexec -l "${KERNEL_IMAGE}" --initrd="${INITRD}" --append="${APPEND}" 11 | 12 | echo -e "Now run\n kexec -e" 13 | -------------------------------------------------------------------------------- /etc/default/kexec: -------------------------------------------------------------------------------- 1 | # Defaults for kexec initscript 2 | # sourced by /etc/init.d/kexec and /etc/init.d/kexec-load 3 | 4 | PATH=/sbin:/bin:/usr/sbin:/usr/bin 5 | 6 | # Load a kexec kernel (true/false) 7 | LOAD_KEXEC=true 8 | 9 | # Kernel and initrd image 10 | KERNEL_IMAGE="/vmlinuz" 11 | INITRD="/initrd.img" 12 | 13 | # If empty, use current /proc/cmdline 14 | APPEND="" 15 | 16 | # Load the default kernel from grub config (true/false) 17 | USE_GRUB_CONFIG=false 18 | 19 | if [ -e /etc/default/kexec-cryptroot ] ; then 20 | # Automatic LUKS unlock on kexec reboot 21 | . /etc/default/kexec-cryptroot 22 | 23 | # Override INITRD 24 | INITRD="${CRYPTROOT_INITRD:-$INITRD}" 25 | 26 | # If append is empty, override it 27 | APPEND="${APPEND:-$CRYPTROOT_APPEND}" 28 | fi 29 | -------------------------------------------------------------------------------- /etc/default/kexec-cryptroot: -------------------------------------------------------------------------------- 1 | # Generate temporary intrd.img with LUKS master keys for kexec reboot 2 | 3 | PATH=/sbin:/bin:/usr/sbin:/usr/bin 4 | 5 | umask 0077 6 | CRYPTROOT_TMPDIR="$(mktemp -d --tmpdir=/dev/shm)" 7 | 8 | cleanup() { 9 | shred -fu "${CRYPTROOT_TMPDIR}/etc/"*.key || true 10 | shred -fu "${CRYPTROOT_TMPDIR}/initrd.img" || true 11 | rm -rf "${CRYPTROOT_TMPDIR}" 12 | } 13 | 14 | mkdir -p "${CRYPTROOT_TMPDIR}/etc" 15 | 16 | trap cleanup INT TERM EXIT 17 | 18 | cp "${INITRD}" "${CRYPTROOT_TMPDIR}/initrd.img" 19 | 20 | cd "${CRYPTROOT_TMPDIR}" 21 | 22 | blkid -t TYPE=crypto_LUKS -s UUID -o value | while read UUID ; do 23 | mapped="$(dmsetup info --columns --separator , | grep -F "$(echo "${UUID}" | tr -d -)" | cut -d , -f1)" 24 | if [ -n "${mapped}" ] ; then 25 | dmsetup --showkeys table "${mapped}" | cut -d ' ' -f5 | xxd -ps -g1 -r > "etc/${UUID}.key" 26 | fi 27 | done 28 | 29 | find etc | cpio -H newc -o | gzip >> "${CRYPTROOT_TMPDIR}/initrd.img" 30 | 31 | cd - 32 | 33 | CRYPTROOT_INITRD="${CRYPTROOT_TMPDIR}/initrd.img" 34 | 35 | # If APPEND is empty, use current kernel command line, and force reboot on error 36 | if [ -z "${APPEND}" ] ; then 37 | CRYPTROOT_APPEND="$(cat /proc/cmdline | sed -e 's,\s*panic=10,,' -e 's,BOOT_IMAGE=[^ ]* ,,') panic=10" 38 | fi 39 | -------------------------------------------------------------------------------- /etc/initramfs-tools/scripts/local-top/cryptroot: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PREREQ="cryptroot-prepare" 4 | 5 | # 6 | # Standard initramfs preamble 7 | # 8 | prereqs() 9 | { 10 | # Make sure that cryptroot is run last in local-top 11 | for req in $(dirname $0)/*; do 12 | script=${req##*/} 13 | if [ $script != cryptroot ]; then 14 | echo $script 15 | fi 16 | done 17 | } 18 | 19 | case $1 in 20 | prereqs) 21 | prereqs 22 | exit 0 23 | ;; 24 | esac 25 | 26 | # source for log_*_msg() functions, see LP: #272301 27 | . /scripts/functions 28 | 29 | # 30 | # Helper functions 31 | # 32 | message() 33 | { 34 | if [ -x /bin/plymouth ] && plymouth --ping; then 35 | plymouth message --text="$@" 36 | else 37 | echo "$@" >&2 38 | fi 39 | return 0 40 | } 41 | 42 | udev_settle() 43 | { 44 | # Wait for udev to be ready, see https://launchpad.net/bugs/85640 45 | if command -v udevadm >/dev/null 2>&1; then 46 | udevadm settle --timeout=30 47 | elif command -v udevsettle >/dev/null 2>&1; then 48 | udevsettle --timeout=30 49 | fi 50 | return 0 51 | } 52 | 53 | parse_options() 54 | { 55 | local cryptopts 56 | cryptopts="$1" 57 | 58 | if [ -z "$cryptopts" ]; then 59 | return 1 60 | fi 61 | 62 | # Defaults 63 | cryptcipher=aes-cbc-essiv:sha256 64 | cryptsize=256 65 | crypthash=ripemd160 66 | crypttarget=cryptroot 67 | cryptsource="" 68 | cryptheader="" 69 | cryptlvm="" 70 | cryptkeyscript="" 71 | cryptkey="" # This is only used as an argument to an eventual keyscript 72 | crypttries=3 73 | crypttcrypt="" 74 | cryptrootdev="" 75 | cryptdiscard="" 76 | CRYPTTAB_OPTIONS="" 77 | 78 | local IFS=" ," 79 | for x in $cryptopts; do 80 | case $x in 81 | hash=*) 82 | crypthash=${x#hash=} 83 | ;; 84 | size=*) 85 | cryptsize=${x#size=} 86 | ;; 87 | cipher=*) 88 | cryptcipher=${x#cipher=} 89 | ;; 90 | target=*) 91 | crypttarget=${x#target=} 92 | export CRYPTTAB_NAME="$crypttarget" 93 | ;; 94 | source=*) 95 | cryptsource=${x#source=} 96 | if [ ${cryptsource#UUID=} != $cryptsource ]; then 97 | cryptsource="/dev/disk/by-uuid/${cryptsource#UUID=}" 98 | elif [ ${cryptsource#LABEL=} != $cryptsource ]; then 99 | cryptsource="/dev/disk/by-label/${cryptsource#LABEL=}" 100 | fi 101 | export CRYPTTAB_SOURCE="$cryptsource" 102 | cryptuuid="$(blkid -o export "${cryptsource}" | grep "^UUID=")" 103 | export CRYPTTAB_UUID="${cryptuuid#UUID=}" 104 | ;; 105 | header=*) 106 | cryptheader=${x#header=} 107 | if [ ! -e "$cryptheader" ] && [ -e "/conf/conf.d/cryptheader/$cryptheader" ]; then 108 | cryptheader="/conf/conf.d/cryptheader/$cryptheader" 109 | fi 110 | export CRYPTTAB_HEADER="$cryptheader" 111 | ;; 112 | lvm=*) 113 | cryptlvm=${x#lvm=} 114 | ;; 115 | keyscript=*) 116 | cryptkeyscript=${x#keyscript=} 117 | ;; 118 | key=*) 119 | if [ "${x#key=}" != "none" ]; then 120 | cryptkey=${x#key=} 121 | fi 122 | export CRYPTTAB_KEY="$cryptkey" 123 | ;; 124 | tries=*) 125 | crypttries="${x#tries=}" 126 | case "$crypttries" in 127 | *[![:digit:].]*) 128 | crypttries=3 129 | ;; 130 | esac 131 | ;; 132 | tcrypt) 133 | crypttcrypt="yes" 134 | ;; 135 | rootdev) 136 | cryptrootdev="yes" 137 | ;; 138 | discard) 139 | cryptdiscard="yes" 140 | ;; 141 | esac 142 | PARAM="${x%=*}" 143 | if [ "$PARAM" = "$x" ]; then 144 | VALUE="yes" 145 | else 146 | VALUE="${x#*=}" 147 | fi 148 | CRYPTTAB_OPTIONS="$CRYPTTAB_OPTIONS $PARAM" 149 | eval export CRYPTTAB_OPTION_$PARAM="\"$VALUE\"" 150 | done 151 | export CRYPTTAB_OPTIONS 152 | 153 | if [ -z "$cryptsource" ]; then 154 | message "cryptsetup: source parameter missing" 155 | return 1 156 | fi 157 | return 0 158 | } 159 | 160 | activate_vg() 161 | { 162 | # Sanity checks 163 | if [ ! -x /sbin/lvm ]; then 164 | message "cryptsetup: lvm is not available" 165 | return 1 166 | fi 167 | 168 | # Detect and activate available volume groups 169 | /sbin/lvm vgscan 170 | /sbin/lvm vgchange -a y --sysinit 171 | return $? 172 | } 173 | 174 | setup_mapping() 175 | { 176 | local opts count cryptopen cryptremove NEWROOT 177 | opts="$1" 178 | 179 | if [ -z "$opts" ]; then 180 | return 0 181 | fi 182 | 183 | parse_options "$opts" || return 1 184 | 185 | if [ -n "$cryptkeyscript" ] && ! type "$cryptkeyscript" >/dev/null; then 186 | message "cryptsetup: error - script \"$cryptkeyscript\" missing" 187 | return 1 188 | fi 189 | 190 | if [ -n "$cryptheader" ] && ! type "$cryptheader" >/dev/null; then 191 | message "cryptsetup: error - LUKS header \"$cryptheader\" missing" 192 | return 1 193 | fi 194 | 195 | # The same target can be specified multiple times 196 | # e.g. root and resume lvs-on-lvm-on-crypto 197 | if [ -e "/dev/mapper/$crypttarget" ]; then 198 | return 0 199 | fi 200 | 201 | modprobe -q dm_crypt 202 | 203 | # Make sure the cryptsource device is available 204 | if [ ! -e $cryptsource ]; then 205 | activate_vg 206 | fi 207 | 208 | # If the encrypted source device hasn't shown up yet, give it a 209 | # little while to deal with removable devices 210 | 211 | # the following lines below have been taken from 212 | # /usr/share/initramfs-tools/scripts/local, as suggested per 213 | # https://launchpad.net/bugs/164044 214 | if [ ! -e "$cryptsource" ]; then 215 | log_begin_msg "Waiting for encrypted source device..." 216 | 217 | # Default delay is 180s 218 | if [ -z "${ROOTDELAY}" ]; then 219 | slumber=180 220 | else 221 | slumber=${ROOTDELAY} 222 | fi 223 | 224 | slumber=$(( ${slumber} * 10 )) 225 | while [ ! -e "$cryptsource" ]; do 226 | # retry for LVM devices every 10 seconds 227 | if [ ${slumber} -eq $(( ${slumber}/100*100 )) ]; then 228 | activate_vg 229 | fi 230 | 231 | /bin/sleep 0.1 232 | slumber=$(( ${slumber} - 1 )) 233 | [ ${slumber} -gt 0 ] || break 234 | done 235 | 236 | if [ ${slumber} -gt 0 ]; then 237 | log_end_msg 0 238 | else 239 | log_end_msg 1 || true 240 | fi 241 | fi 242 | udev_settle 243 | 244 | # We've given up, but we'll let the user fix matters if they can 245 | if [ ! -e "${cryptsource}" ]; then 246 | 247 | echo " ALERT! ${cryptsource} does not exist." 248 | echo " Check cryptopts=source= bootarg: cat /proc/cmdline" 249 | echo " or missing modules, devices: cat /proc/modules; ls /dev" 250 | panic -r "Dropping to a shell. Will skip ${cryptsource} if you can't fix." 251 | fi 252 | 253 | if [ ! -e "${cryptsource}" ]; then 254 | return 1 255 | fi 256 | 257 | 258 | # Prepare commands 259 | cryptopen="/sbin/cryptsetup -T 1" 260 | if [ "$cryptdiscard" = "yes" ]; then 261 | cryptopen="$cryptopen --allow-discards" 262 | fi 263 | if [ -n "$cryptheader" ]; then 264 | cryptopen="$cryptopen --header=$cryptheader" 265 | fi 266 | if /sbin/cryptsetup isLuks ${cryptheader:-$cryptsource} >/dev/null 2>&1; then 267 | if [ -n "${CRYPTTAB_UUID}" -a -e "/etc/${CRYPTTAB_UUID}.key" ] ; then 268 | cryptopen="$cryptopen open --type luks $cryptsource $crypttarget --master-key-file=/etc/${CRYPTTAB_UUID}.key" 269 | # Override cryptkeyscript 270 | cryptkeyscript="/bin/true" 271 | else 272 | cryptopen="$cryptopen open --type luks $cryptsource $crypttarget --key-file=-" 273 | fi 274 | elif [ "$crypttcrypt" = "yes" ]; then 275 | cryptopen="$cryptopen open --type tcrypt $cryptsource $crypttarget" 276 | else 277 | cryptopen="$cryptopen -c $cryptcipher -s $cryptsize -h $crypthash open --type plain $cryptsource $crypttarget --key-file=-" 278 | fi 279 | cryptremove="/sbin/cryptsetup remove $crypttarget" 280 | NEWROOT="/dev/mapper/$crypttarget" 281 | 282 | # Try to get a satisfactory password $crypttries times 283 | count=0 284 | while [ $crypttries -le 0 ] || [ $count -lt $crypttries ]; do 285 | export CRYPTTAB_TRIED="$count" 286 | count=$(( $count + 1 )) 287 | 288 | if [ -z "$cryptkeyscript" ]; then 289 | if [ ${cryptsource#/dev/disk/by-uuid/} != $cryptsource ]; then 290 | # UUIDs are not very helpful 291 | diskname="$crypttarget" 292 | else 293 | diskname="$cryptsource ($crypttarget)" 294 | fi 295 | 296 | if [ -x /bin/plymouth ] && plymouth --ping; then 297 | cryptkeyscript="plymouth ask-for-password --prompt" 298 | # Plymouth will add a : if it is a non-graphical prompt 299 | cryptkey="Please unlock disk $diskname" 300 | else 301 | cryptkeyscript="/lib/cryptsetup/askpass" 302 | cryptkey="Please unlock disk $diskname: " 303 | fi 304 | fi 305 | 306 | 307 | if [ ! -e "$NEWROOT" ]; then 308 | if ! crypttarget="$crypttarget" cryptsource="$cryptsource" \ 309 | $cryptkeyscript "$cryptkey" | $cryptopen; then 310 | message "cryptsetup: cryptsetup failed, bad password or options?" 311 | continue 312 | fi 313 | fi 314 | if [ -e "/etc/${CRYPTTAB_UUID}.key" ] ; then 315 | /bin/dd if=/dev/urandom of="/etc/${CRYPTTAB_UUID}.key" bs=4096 count=1 316 | fi 317 | 318 | if [ ! -e "$NEWROOT" ]; then 319 | message "cryptsetup: unknown error setting up device mapping" 320 | return 1 321 | fi 322 | 323 | #FSTYPE='' 324 | #eval $(fstype < "$NEWROOT") 325 | FSTYPE="$(/sbin/blkid -s TYPE -o value "$NEWROOT")" 326 | 327 | # See if we need to setup lvm on the crypto device 328 | #if [ "$FSTYPE" = "lvm" ] || [ "$FSTYPE" = "lvm2" ]; then 329 | if [ "$FSTYPE" = "LVM_member" ] || [ "$FSTYPE" = "LVM2_member" ]; then 330 | if [ -z "$cryptlvm" ]; then 331 | message "cryptsetup: lvm fs found but no lvm configured" 332 | return 1 333 | elif ! activate_vg; then 334 | # disable error message, LP: #151532 335 | #message "cryptsetup: failed to setup lvm device" 336 | return 1 337 | fi 338 | 339 | # Apparently ROOT is already set in /conf/param.conf for 340 | # flashed kernels at least. See bugreport #759720. 341 | if [ -f /conf/param.conf ] && grep -q "^ROOT=" /conf/param.conf; then 342 | NEWROOT=$(sed -n 's/^ROOT=//p' /conf/param.conf) 343 | else 344 | NEWROOT=${cmdline_root:-/dev/mapper/$cryptlvm} 345 | if [ "$cryptrootdev" = "yes" ]; then 346 | # required for lilo to find the root device 347 | echo "ROOT=$NEWROOT" >>/conf/param.conf 348 | fi 349 | fi 350 | #eval $(fstype < "$NEWROOT") 351 | FSTYPE="$(/sbin/blkid -s TYPE -o value "$NEWROOT")" 352 | fi 353 | 354 | #if [ -z "$FSTYPE" ] || [ "$FSTYPE" = "unknown" ]; then 355 | if [ -z "$FSTYPE" ]; then 356 | message "cryptsetup: unknown fstype, bad password or options?" 357 | udev_settle 358 | $cryptremove 359 | continue 360 | fi 361 | 362 | message "cryptsetup: $crypttarget set up successfully" 363 | break 364 | done 365 | 366 | if [ $crypttries -gt 0 ] && [ $count -gt $crypttries ]; then 367 | message "cryptsetup: maximum number of tries exceeded for $crypttarget" 368 | return 1 369 | fi 370 | 371 | udev_settle 372 | return 0 373 | } 374 | 375 | # 376 | # Begin real processing 377 | # 378 | 379 | # Do we have any kernel boot arguments? 380 | cmdline_cryptopts='' 381 | unset cmdline_root 382 | for opt in $(cat /proc/cmdline); do 383 | case $opt in 384 | cryptopts=*) 385 | opt="${opt#cryptopts=}" 386 | if [ -n "$opt" ]; then 387 | if [ -n "$cmdline_cryptopts" ]; then 388 | cmdline_cryptopts="$cmdline_cryptopts $opt" 389 | else 390 | cmdline_cryptopts="$opt" 391 | fi 392 | fi 393 | ;; 394 | root=*) 395 | opt="${opt#root=}" 396 | case $opt in 397 | /*) # Absolute path given. Not lilo major/minor number. 398 | cmdline_root=$opt 399 | ;; 400 | *) # lilo major/minor number (See #398957). Ignore 401 | esac 402 | ;; 403 | esac 404 | done 405 | 406 | if [ -n "$cmdline_cryptopts" ]; then 407 | # Call setup_mapping separately for each possible cryptopts= setting 408 | for cryptopt in $cmdline_cryptopts; do 409 | setup_mapping "$cryptopt" 410 | done 411 | exit 0 412 | fi 413 | 414 | # Do we have any settings from the /conf/conf.d/cryptroot file? 415 | if [ -r /conf/conf.d/cryptroot ]; then 416 | while read mapping <&3; do 417 | setup_mapping "$mapping" 3<&- 418 | done 3< /conf/conf.d/cryptroot 419 | fi 420 | 421 | exit 0 422 | -------------------------------------------------------------------------------- /kexec-load.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -x 3 | set -e 4 | 5 | . /etc/default/kexec 6 | 7 | /sbin/kexec -l "$KERNEL_IMAGE" --initrd="$INITRD" --append="$APPEND" 8 | --------------------------------------------------------------------------------