#!/bin/bash
# start-up a remote machine which has cryptroot and dropbear, for more info run with -h
VERSION="0.8 [14 Nov 2018]"
THIS=`basename $0`
COLUMNS=$(stty size 2>/dev/null||echo 80); COLUMNS=${COLUMNS##* }
REMOTEIP=192.168.100.143
PORT=22
HIDEPWD="-s"
set -o pipefail
# process command line options
while getopts ":dfhi:lp:s:tvw" optname; do
case "$optname" in
"d") DEBUG="-d";;
"h") HELP="y";;
"i") IDFILE="$OPTARG"; IDOPT="-i";;
"l") CHANGELOG="y";;
"p") PORT=$OPTARG;;
"s") TEST="y"; STATUSFILE="$OPTARG";;
"t") TEST="y";;
"v") unset HIDEPWD;;
"w") COLUMNS=30000;;
"?") echo "Unknown option $OPTARG">&2; exit 1;;
":") echo "No argument value for option $OPTARG">&2; exit 1;;
*) # Should not occur
echo "Unknown error while processing options">&2; exit 1;;
esac
done
shift $(($OPTIND-1))
[[ -z $TEST ]] && echo -e "\n$THIS v$VERSION - by Dominic (-h for help)\n${THIS//?/=}\n"
# show help and/or changelog
[[ -z $1 && -z $CHANGELOG ]] && HELP="y"
if [ -n "$HELP" ]; then
echo -e "This utility provides an easy way to enter the decrypt passphrase on a remote machine which has root encryption with dm-crypt+LUKS (e.g. as set up at \
Debian or Ubuntu installation if you select 'encrypted LVM') - so that local access is not required when booting the machine.
When the encrypted machine boots it loads an initramfs image from a small unencrypted boot partition and then waits for the encryption \
passphrase, without which it cannot read the main partition. If it has previously been suitably modified (see below) \
you can reach it remotely (by ssh) at this boot stage and enter the passphrase, which allows booting to complete.
From a remote client you can use $THIS to enter the passphrase, provided your public key (i.e. matching the private key \
used for ssh connection by $THIS) has previously been added to the encrypted machine's /etc/dropbear-initramfs/authorized_keys file \
and its initramfs has then been updated (see below). Please note this means there are two requirements for ability to remote boot the encrypted \
machine: you must know the passphrase *and* your public key must be pre-loaded on the encrypted machine.
You can also use $THIS in test mode (-t) in a cron job to monitor the encrypted machine and warn you if it ceases to be fully available: \
if all is well then running $THIS -t generates no text output, otherwise it will show an appropriate message.
Usage: ./$THIS [options] ip.address.of.remote.encrypted.machine
Options : -d - debug mode (implementation may vary)
-h - show this help and exit
-i file - specify private key identity file (default: selected automatically by ssh)
-l - show changelog and exit
-p n - where 'n' is the ssh port used by dropbear in initramfs on the encrypted machine (default: 22)
-s file - test status of remote machine and output text if status has changed since the preceding run of '$THIS -s' to the specified 'file'
-t - test status of remote machine and exit with code - silent if running normally
-v - show passphrase on console as you enter it
Exit Codes: 0 - remote machine is running normally
1 - some error occurred or remote machine is off/unresponsive
2 - remote machine is still awaiting passphrase
Prerequisites: \
$THIS is designed for a remote machine that has dm-crypt + LUKS on the root system so that it cannot be started up without the pre-set passphrase being entered. \
(The process of setting up a machine for dm-crypt + LUKS is not covered here, but it can most easily be done on Debian or Ubuntu if you use the alternate \
or netboot installer [file: mini.iso] and select 'Guided - use entire disk and set up encrypted LVM'.) By default, booting such an encrypted machine requires local \
access in order to enter the passphrase, but remote access at this stage is possible by setting up the encrypted machine thus (tested under Ubuntu 18.04):
sudo -i # become root (if not already)
apt-get install dropbear-initramfs # check/install necessary software
# add public keys for remote users who could run $THIS here, one per line:
nano /etc/dropbear-initramfs/authorized_keys
# make sure the file terminates with EOL
[[ \$(tail -c1 /etc/dropbear-initramfs/authorized_keys|wc -l) -eq 1 ]] && echo OK || echo FAIL
update-initramfs -u -k all # update boot-time filesystem
hostname -I # note the ip address, please ensure it won't change on reboot
Dependencies: bash grep sed ssh
Notes: If you do not know the passphrase, or if you do not have a private key \
that matches a public key previously set up for the encrypted machine's initramfs, then $THIS cannot help you; \
you can get remote access only to the initial boot stage of the encrypted machine and it will be impossible to access the main system or data. \
If you have the passphrase but not a suitable private key, you will require local access to the encrypted machine in order to start it up fully.
More information about remote booting with dmcrypt + LUKS can be found at:
https://ubuntuforums.org/archive/index.php/t-2085267.html
http://blog.neutrino.es/2011/unlocking-a-luks-encrypted-root-partition-remotely-via-ssh/
https://www.adfinis-sygroup.ch/blog/en/decrypt-luks-devices-remotely-via-dropbear-ssh/
For a tool for converting an existing unencrypted partition to dm-crypt+LUKS (must be offline) see:
http://johndoe31415.github.io/luksipc
You can test a passphrase on an *already-mounted* dm-crypt + LUKS partition. In this example, /dev/sda5 is \
encrypted (as /dev/sda5_crypt), and the 'x' can be anything (required but ignored). A non-zero \
exit code indicates a wrong passphrase:
cryptsetup open /dev/sda5 x --test-passphrase --tries 1; echo \$?
Depending on the remote machine's network configuration (a) when booting and \
(b) after booting has completed, these two states may have different ips or \
accept connections from different ports; \
if so, after you have successfully entered the passphrase $THIS will report \
that it was unable to connect and ask you to check if the remote machine is \
switched on - the remote machine may be working fine but with \
a different ip address or port. So try to ensure that the same ip address \
is allocated when booting (see below) as when fully booted (e.g. per \
/etc/network/interfaces), and is allocated in the same way.
To specify ip parameters at boot time (i.e. running from initramfs) set 'ip=' in variable GRUB_CMDLINE_LINUX in \
/etc/default/grub and then run update-grub. The parameters are ip=client-ip:[server-ip]:gateway-ip:netmask:[hostname]:device:autoconf \
- for more info see https://www.kernel.org/doc/Documentation/filesystems/nfs/nfsroot.txt and \
https://www.eugenemdavis.com/set-static-ip-initramfs.html. Never specify server-ip; and do \
not specify a hostname because $THIS depends on the hostname when booting being '(none)'. Examples:
GRUB_CMDLINE_LINUX=\"ip=:::::eth0:dhcp\"
GRUB_CMDLINE_LINUX=\"ip=192.0.2.62::192.0.2.1:255.255.255.192::eth0:none\"
And afterwards run
update-grub
To specify a non-standard ssh port at boot time (i.e. not 22), add a line in /etc/dropbear-initramfs/config like
DROPBEAR_OPTIONS="-p 4748"
And then update initramfs
update-initramfs -u -k all
Cryptroot-unlock: Whereas $THIS runs under bash on the client machine, \
cryptroot-unlock resides by default in the \
initramfs package on the encrypted machine and can be run using SSH \
from a client with any OS. As with $THIS, cryptroot-unlock can only be run \
successfully if the appropriate public key has been stored beforehand in the \
encrypted machine's initramfs (see above). Subject to that, here are \
examples of how cryptroot-unlock can \
be run from a remote client logging in with SSH (instead of using $THIS):
One-line example to run remotely (i.e. on client machine) to remote machine 192.168.20.88 under Linux or Cygwin or Bash-on-ubuntu-on-Windows:
ssh -t -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@192.168.20.88 cryptroot-unlock
Another one-line example but using a non-default private key file:
ssh -ti /path/to/private_key_file -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@192.168.20.88 cryptroot-unlock
or under Windows using plink:
plink.exe -t -i C:\path\private_key_file.ppk root@192.168.20.88 cryptroot-unlock
"|fold -s -w $COLUMNS
fi
if [ -n "$CHANGELOG" ]; then
[ -n "$HELP" ] && echo "Changelog:"
echo -e "\
0.8 [14 Nov 2018] - silently correct if user supplies ip address in form name@n.n.n.n
0.7 [09 Nov 2018] - modify instructions to reference cryptroot-unlock, remove references to pass.sh
0.6 [07 Nov 2018] - correct instructions for location of authorized_keys file
0.5 [06 Nov 2018] - bugfix -i option
0.4 [30 May 2017] - bugfix -t option
0.3 [20 Apr 2017] - add -s option, other fixes
0.2 [12 Apr 2017] - updated help, add -i and -t options, several other fixes
0.1 [04 Apr 2017] - initial version
"|fold -s -w $COLUMNS
fi
[ -n "$HELP$CHANGELOG" ] && exit 0
# main code starts here
REMOTEIP=$1
# if user has inadvertently provided address in the form name@n.n.n.n, strip the 'name@' part
REMOTEIP=${REMOTEIP##*@}
if [[ -n $STATUSFILE ]]; then
[[ -f "$STATUSFILE" ]] && PRIORSTATUS="$(cat "$STATUSFILE")" || PRIORSTATUS="x"
else
STATUSFILE=/dev/null
fi
for (( T=1; T<=30; T++ )); do
REMOTENAME=$(ssh $IDOPT $IDFILE -o Port=$PORT -o ConnectTimeout=6 -o PasswordAuthentication=no -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@$REMOTEIP "uname -n" 2>&1|sed '/Permanently added/d;s/\r//')
SSH_ERR=$?
[[ -n $DEBUG ]] && echo "\$REMOTENAME: '$REMOTENAME'"
if [[ $SSH_ERR -gt 0 || $REMOTENAME != "(none)" ]]; then
echo $REMOTENAME|grep -q "Permission denied (publickey,password"
if [[ $? -eq 0 || $SSH_ERR -eq 0 ]]; then
# if we gained ssh access (and remote machine is not called '(none)'), or we were explictly denied, then it is ok
if [[ $T -eq 1 ]]; then
NEWSTATUS="Remote machine $REMOTEIP is running normally"
# if testing mode 't', suppress message that all is ok
[[ -n $TEST && $STATUSFILE == "/dev/null" ]] && PRIORSTATUS="$NEWSTATUS"
[[ $NEWSTATUS != $PRIORSTATUS ]] && echo $NEWSTATUS | tee $STATUSFILE
else
echo -e "\nRemote machine $REMOTEIP has started ok"
fi
exit 0
fi
echo $REMOTENAME|grep -q "Permission denied (publickey"
if [[ $? -eq 0 ]]; then
NEWSTATUS="$REMOTEIP's dropbear rejected your private key."
if [[ $NEWSTATUS != $PRIORSTATUS ]]; then
echo $NEWSTATUS | tee $STATUSFILE
[[ -z $TEST ]] && echo -e "On $REMOTEIP did you previously:\n - install the matching public key at /etc/dropbear-initramfs/authorized_keys?\n - do 'update-initramfs -u -k all'?" >&2
fi
exit 1
fi
if [[ $T -gt 1 && $CANT_CONNECT_LOOP -le 2 ]]; then
# we can get a 'can't connect' message in the switchover from dropbear to openssh, so don't worry if it happens a couple of times
let CANT_CONNECT_LOOP++
else
NEWSTATUS="$REMOTENAME (code $SSH_ERR)"
if [[ $NEWSTATUS != $PRIORSTATUS ]]; then
echo $NEWSTATUS | tee $STATUSFILE
[[ -z $TEST ]] && echo -e " - is the address correct?\n - is the remote machine switched on?" >&2
fi
exit 1
fi
fi
[[ -z $TEST ]] || { echo -e "\nRemote machine is booting but requires passphrase"; exit 2; }
if [[ -z $REPLY ]]; then
echo "Access to remote machine running dropbear is confirmed"
read -t60 $HIDEPWD -p"Enter passphrase for remote machine $REMOTEIP, then press ENTER/RETURN: "
[[ -n $REPLY ]] || { echo "No passphrase entered, please try again" >&2; exit 1; }
echo -e "[done]\nSending start instruction to remote machine $REMOTEIP"
ssh $IDOPT $IDFILE -o Port=$PORT -o ConnectTimeout=3 -o PasswordAuthentication=no -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@$REMOTEIP "printf \"$REPLY\" >/lib/cryptsetup/passfifo" 2>/dev/null
echo -n "Please wait"
fi
sleep 6s
echo -n "."
done
echo -e "\nUnable to start remote machine $REMOTEIP\nMaybe try again?" >&2; exit 2