LUKS Encrypted Hetzner VX6 vServer With Ubuntu 14.04 LTS
This is a guide to installing Ubuntu 14.04 LTS on an Hetzner VX6 vServer using LUKS encryption of the root partition. The guide is inspiried by blog posts of Martin Carpella and Oliver Feiler. On reboot you will be able to log in via ssh to provide the password for the LUKS partition (using dropbear and busybox).
As pointed out by Martin Carpella it may sound stupid to encrypt the disk of a virtual machine as the private key can be pulled from memory by the host (i.e. Hetzner). However, encrypting all data on the disk protects it in case you cancel the vServer, or the vServer gets moved to a new host, or a failed disk is returned to the manufacturer or discarded etc. So it is more a matter of protecting the data if the VM is cancelled/offline rather than online. If you do not want Hetzner to be able to pull the private key, go for something other than a virtual machine (e.g. a root server).
Anyway, here we go!
When signing up for the vServer select the Hetzner Ubuntu 14.04 LTS 64-bit minimal-install image. Once the server is setup log in via SSH and make backups of these files:
/etc/hostname
/etc/hosts
/etc/resolv.conf
/etc/network/interfaces
Log into the Hetzner robot interface and select 64-bit Linux rescue system, take note of the password for rescue system ssh login displayed on the page. Reboot your vServer in rescue mode and log in.
Setup disk partitions using fdisk:
1
fdisk /dev/vda
Create a boot partition of 256 MB (/dev/vda1) and a root partition of the remaining free space (/dev/vda2). Mark the boot partition as bootable. My partition table looks like this:
12345678910
Disk /dev/vda: 26.8 GB, 26843545600 bytes
255 heads, 63 sectors/track, 3263 cylinders, total 52428800 sectors
Units= sectors of 1 * 512=512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00044f0a
Device Boot Start End Blocks Id System
/dev/vda1 * 204852633526214483 Linux
/dev/vda2 526336524287992595123283 Linux
Create the encrypted partition (enter passphrase when prompted):
blkid /dev/vda1 /dev/vda2 /dev/vg-encrypted/root /dev/vg-encrypted/swap
# These are my UUIDs/dev/vda1: UUID="409938b1-7244-4c34-a665-8f086dd1c5f9"TYPE="ext2"/dev/vda2: UUID="b360acf3-b392-417f-bc08-90f378a5d26b"TYPE="crypto_LUKS"/dev/vg-encrypted/root: UUID="efc68b96-de3d-4cb9-a884-127dca8c97d5"TYPE="ext4"/dev/vg-encrypted/swap: UUID="6556fbea-0e34-4f4e-92cc-c18b3ccb0ece"TYPE="swap"
MAke a chroot directory (/mnt/ubuntu) and mount volumes:
To enter the LUKS passphrase upon boot, first save the following script as unlock-cryptroot on your computer (not the vServer) and make it executable (chmod +x unlock-cryptroot):
#!/bin/shusage(){ cat <<EOF${0##*/}: Remotely unlock a LUKS-encrypted Ubuntu root filesystemWorks around bug #595648 <https://bugs.launchpad.net/bugs/595648>Usage: $0 [options] [--] <host>Arguments: -h, --help Display this usage message and exit -i <identity_file>, --identity <identity_file>, --identity=<identity_file> Path to the SSH private key. Default: ${idbase}<host> -k <knownhosts>, --known-hosts <knownhosts>, --known-hosts=<knownhosts> Path to the 'known_hosts' file. Default: ${knownhosts} -l, --login Just log in, don't try to unlock the system. -- End of options; treat the next argument as the hostname even if it begins with '-'. <host> The name of the remote system to unlockEOF}# handy logging and error handling functionslog(){printf'%s\n'"$*";}error(){ log "ERROR: $@" >&2;}fatal(){ error "$@";exit 1;}try(){"$@"|| fatal "'$@' failed";}usage_fatal(){ usage >&2;printf'\n' >&2; fatal "$@";}# quote special characters so that:# eval "set -- $(shell_quote "$@")"# is always a no-op no matter what values are in the positional# parameters. note that it is run in a subshell to protect the# caller's environment.shell_quote()(sep=for i in "$@";doiesc=$(printf %s\\n "${i}eoi"| sed -e "s/'/'\\\\''/g")iesc=\'${iesc%eoi}\'printf %s "${sep}${iesc}"sep=" "done)# parse argumentsknownhosts=~/.ssh/known_hosts.initramfs
idbase=~/.ssh/id_rsa.initramfs_
unset id
runscript=truewhile["$#" -gt 0];doarg=$1case$1 in
# convert "--opt=the value" to --opt "the value". --*'='*)shift;set -- "${arg%%=*}""${arg#*=}""$@";continue;; -h|--help) usage;exit 0;; -i|--identity)shift;id=$1;; -k|--known-hosts)shift;knownhosts=$1;; -l|--login)runscript=false;; --)shift;break;; -*) usage_fatal "unknown option: '$1'";; *)break;;esacshift|| usage_fatal "option '${arg}' requires a value"done["$#" -ge 1]|| usage_fatal "no hostname specified"host=$1;shift["$#" -eq 0]|| fatal "unknown argument: '$1'"[ -n "${id+set}"]||id=${idbase}${host%%.*}[ -r "${id}"]|| fatal "can't read ssh key ${id}"script='#!/bin/shPATH=/sbin:${PATH}p() { printf %s\\n "$*"; }log() { p "$@"; }warn() { log "WARNING: $@" >&2; }error() { log "ERROR: $@" >&2; }fatal() { error "$@"; exit 1; }try() { "$@" || fatal "'\''$@'\'' failed"; }getpid() { psout=$(try ps) || exit 1 psout=$(p "${psout}" | grep "$1") || return 0 pswc=$(p "${psout}" | try wc -l) || exit 1 [ "${pswc}" -eq 1 ] || fatal "more than one instance of $1:${psout}" p "${psout}" | try awk '\''{print$1}'\'' || exit 1}# if cryptroot is not running, then there is no password prompt so# there is nothing to dolog "checking if /scripts/local-top/cryptroot is running..."cr_pid=$(getpid "/scripts/local-top/[c]ryptroot") || exit 1[ -n "${cr_pid}" ] || fatal "/scripts/local-top/cryptroot is not running"unset pw# keep prompting for a password over and over until cryptroot has# finished runningwhile true; do cs_pid=$(getpid "/sbin/[c]ryptsetup") || exit 1 [ -n "${cs_pid}" ] || { log "waiting to see if there will be another passphrase prompt..." # the next commands are all on one line so that they are still # in busybox memory if the root filesystem is mounted during # the sleep (which would cause this script to disappear during # execution) sleep 1; [ -d /proc/"${cr_pid}" ] || { log "done"; exit 0; } continue } log "getting /sbin/cryptsetup command-line arguments..." cs_args=$(try tr "\\0" "\\n" </proc/${cs_pid}/cmdline) || exit 1 set -- while IFS= read -r line; do set -- "$@" "${line}" done <<EOF${cs_args}EOF log "command: $@" [ -n "${pw+set}" ] && { log "trying previously entered passphrase..." printf %s "${pw}" | try "$@" } || { pw=$(try /lib/cryptsetup/askpass "Enter passphrase: " && echo x) \ || exit 1 pw=${pw%x} printf %s "${pw}" | try "$@" || exit 1 } log "passphrase accepted; killing passphrase prompt..." for ap in \ "/lib/cryptsetup/[a]skpass" \ "[a]sk-for-password" \ ; do log " checking for ${ap}..." ap_pid=$(getpid "${ap}") || exit 1 [ -n "${ap_pid}" ] || continue log " killing PID ${ap_pid}..." try kill "${ap_pid}" donedone'run_ssh(){unset forcetty
case$1 in -t)forcetty=$1;shift;;esac ssh -o UserKnownHostsFile="${knownhosts}"\ -i "${id}"\${forcetty}\ root@"${host}""$@"}# $1 is the shell command to run; must be < 1024 characters (busybox# limitation?)run_ssh_cmd(){unset forcetty
case$1 in -t)forcetty=$1;shift;;esacsshcmd='sh -c '$(shell_quote "$1")' -' run_ssh ${forcetty}"${sshcmd}"}"${runscript}"||{whileIFS=read -r line;do log "${line}"done<<\EOFAfter you are logged in: 1. use 'ps -l' to get cryptsetup's command-line arguments 2. run: /lib/cryptsetup/askpass "Enter passphrase: " \ | /sbin/cryptsetup <args go here> 3. kill 'plymouth ask-for-password' or 'askpass' as appropriate 4. log outEOF run_ssh
exit$?}log "sending script to ${host}..."printf %s\\n "${script}"|run_ssh_cmd 'cat >tmp.sh && chmod +x tmp.sh'\|| fatal "unable to create script"log "running script on ${host}..."run_ssh_cmd -t './tmp.sh'
Use the script like this (enter passphrase when prompted, use the IP address of your own vServer)