Unlock GELI partitions at boot through SSH

Hi fellas,

I bought a mini-computer and it is going to have sensible data and therefore I am going to encrypt the disk with GELI. However this mini-pc will be monitor-less and it will be connected to the (home) network, and therefore I need to unlock it at the boot with SSH.

I know how to do this with Linux and DropBear but I don't have any idea how to do that on FreeBSD, so my very first question is: is it possible?

Thanks,,, 🙏
 
Alternately, and traditionally, you do this through the serial console or a BMC.

On server grade hardware, you'll have an integrated BMC that will provide remote access to the main system's serial console and you can connect to the BMC via web or ssh. You'll need access the the network the BMC is connected to, though.

If your system doesn't have a BMC you can get an external device like the JetKVM or PiKVM devices. JetKVM is new and cheap, but still sorta in kickstarter phase. PiVKM is much more expensive.

Alternately, if your system has a serial port, you can get a serial console server like the cheap/consumer grade airconsole-ts-4n, or expensive server grade like those from Opengear. These will allow network base ssh access, and through that access to the serial console of the devices connected to them.
 
It really doesn't work as in Linux... I found something on line though:


Maybe FreeBSD is not suitable for my particular case... 😟
 

Maybe FreeBSD is not suitable for my particular case... 😟
For a home server the reboot -r (outerbase) will do fine. It needs a little disk space and you have to keep two systems up to date.

I have a setup like this for five years (see my notes).
 
Why not just put your sensitive data on dataset that are encrypted with zfs ? Then you send the data encrypted and never load the keys on the server itself ?
What is your use case ?
If you need to do some computation on the data, and require full data encryption, then you should look at homomorphic encryption algorithm.
 
Someone wrote me that he/she tried to adapt the script to work on a single disk system. I am not convincing I can do any better but maybe with the support of the forum it might possible to fix and polish his/her work...

 
Why not just put your sensitive data on dataset that are encrypted with zfs ? Then you send the data encrypted and never load the keys on the server itself ?
What is your use case ?
If you need to do some computation on the data, and require full data encryption, then you should look at homomorphic encryption algorithm.

This computer is headless I need to unlock it through SSH, I need to operate on this computer, but when is turned off I need its data encrypted.
 
You use zfs raw sends, that does not decrypt the snapshot before sending.

This computer is headless I need to unlock it through SSH, I need to operate on this computer, but when is turned off I need its data encrypted.
In that case, what about having a dataset that is encrypted, you just have to put the confidential stuff under it.
Then after a reboot, you login with ssh and use zfs mount -al to mount (and load the keys) the encrypted dataset by providing the keys.
This will be simpler than using an unencrypted preboot environment that you reroot into an encrypted root dataset.
If you also need the application to be encrypted, you could setup a jail inside the encrypted dataset, and do all the work in it.
In both scenario, meaning using a full encrypted zroot that you reroot from an unencrypted volume, or using an encrypted dataset, you will have the same level of security:
Anyone that have access to the server could in theory add a backdoor inside the unencrypted partition that would steal the keys when you enter it in your ssh connection.
 
Thank you, to unlock an OS from SSH you need a small portion that isn't encrypted, I am fine with this.
Anyway I have to test how good is using Dropbear with FreeBSD, I already have a similar setting but with Debian; the latter doesn't provide full-disc encryption therefore the boot partition must be clear.
 
You could question how much difference there really is between an unencrypted base sophisticated enough to decrypt a filesystem over ssh and leaving the whole base system (without user data) unencrypted.

Yes, the amount of unencrypted data in megabytes is drastic. But the attack pattern is basically the same.
 
You could question how much difference there really is between an unencrypted base sophisticated enough to decrypt a filesystem over ssh and leaving the whole base system (without user data) unencrypted.

Yes, the amount of unencrypted data in megabytes is drastic. But the attack pattern is basically the same.

I didn't really grasped what you tried to tell me. On Linux dropbear is built into the intiramfs, for what I understood on FreeBSD is run during the boot but the other partitions are locked. I only care if someone took this computer can't run or extract data because the OS and the other sensible partitions are encrypted, this computer doesn't have any SSH exposed to the internet.
 
You could question how much difference there really is between an unencrypted base sophisticated enough to decrypt a filesystem over ssh and leaving the whole base system (without user data) unencrypted.

Yes, the amount of unencrypted data in megabytes is drastic. But the attack pattern is basically the same.
I could never trust the unencrypted base, unless the computer stays with me 24/7 and in a safe while I sleep.
 
I didn't really grasped what you tried to tell me. On Linux dropbear is built into the intiramfs, for what I understood on FreeBSD is run during the boot but the other partitions are locked. I only care if someone took this computer can't run or extract data because the OS and the other sensible partitions are encrypted, this computer doesn't have any SSH exposed to the internet.

The attack scenario I am thinking of is that somebody manipulates the unencrypted parts and puts the computer back, waiting for you to enter the password.

In the Linux case you have a complete OS to manipulate in the initramfs.

What I'm saying is that it isn't that different to run a more complete unencryoted FreeBSD.
 
The remote server in the cloud of course cannot be secured against evil maid attacks. Still, it is better encrypted if the disks end at Ebay. And the casual admin of the cloud provider cannot look at your data.

But we're talking a home server here.

How probable is it that a burglar breaks in, attacks the unencrypted part and puts a backdoor in? Have a camera look at your home server.
 
The attack scenario I am thinking of is that somebody manipulates the unencrypted parts and puts the computer back, waiting for you to enter the password.

In the Linux case you have a complete OS to manipulate in the initramfs.

What I'm saying is that it isn't that different to run a more complete unencryoted FreeBSD.

I am not following by any malicious hacker... 😅
I only want two things: if someone take this computer I don't want him/her able to run it or to read the data from the drive.
 
I don't have any idea of what I am doing but this is the script resulted merging the two aforementioned scripts:

CRYPT
Code:
set -x
set -e

# the disk
geom0=ada0

# installation files be here:
bsd=/usr/freebsd-dist

if [ "$(uname -s)" != "FreeBSD" ] ; then
	echo "Only works on FreeBSD, Sorry." >&2
	exit 1
fi

if [ ! -d $bsd ] ; then
	set +x
	rdir="$(uname -m)/$(uname -r |sed 's/-.*/-RELEASE/')"
	echo "FreeBSD installation files missing..."
	echo ""
	echo "Enter to fetch, ^c to abort"
	read cont
	(
     mkdir $bsd
     cd $bsd
	 fetch ftp://ftp.freebsd.org/pub/FreeBSD/releases/$rdir/base.txz
	 fetch ftp://ftp.freebsd.org/pub/FreeBSD/releases/$rdir/lib32.txz
	 fetch ftp://ftp.freebsd.org/pub/FreeBSD/releases/$rdir/kernel.txz
	)
	set -x
fi

if [ ! -f $bsd/base.txz ] ; then
	set +x
	echo "Can't find FreeBSD installation files in $bsd" >&2
	exit 1
fi

bin=$(dirname $0)
[ "$bin" == "." ] && bin=$(pwd) || true

base=/mnt
ufsdir=/xboot

gpart destroy -F $geom0 || true
gpart create -s gpt $geom0
gpart add -b 64 -s 512k  -t freebsd-boot      $geom0
gpart add -l boot -s 1G -t freebsd-ufs  -a 4k $geom0 # 512M is absolute minimum, the kernel is so large.
gpart add -l swap -s 2G -t freebsd-swap -a 4k $geom0
gpart add -l zroot      -t freebsd-zfs  -a 4k $geom0

geli onetime -d -e AES-XTS -l 256 -s 4096 /dev/gpt/swap

set +x
echo -n Password:
stty -echo
read pw
stty echo
echo $pw > /tmp/pw
echo ""
set -x

geli init -s 4096 -J /tmp/pw /dev/gpt/zroot
geli attach -j /tmp/pw /dev/gpt/zroot

gpart bootcode -b /boot/pmbr -p /boot/gptboot -i 1 $geom0

newfs -U /dev/gpt/boot

pool=tank
zpool create -f -m none -o altroot=$base -o cachefile=/tmp/zpool.cache $pool /dev/gpt/zroot.eli

root=$pool/root
zfs create -o mountpoint=/                               $root
zfs create                                               $root/usr
zfs create -o compression=lzjb             -o setuid=off $root/usr/ports
zfs create -o compression=off  -o exec=off -o setuid=off $root/usr/ports/distfiles
zfs create -o compression=off  -o exec=off -o setuid=off $root/usr/ports/packages
zfs create -o compression=lzjb -o exec=off -o setuid=off $root/usr/src
zfs create                                               $root/home
zfs create                                               $root/var
zfs create -o compression=lzjb -o exec=off -o setuid=off $root/var/crash
zfs create                     -o exec=off -o setuid=off $root/var/db
zfs create -o compression=lzjb -o exec=on  -o setuid=off $root/var/db/pkg
zfs create                     -o exec=off -o setuid=off $root/var/empty
zfs create -o compression=lzjb -o exec=off -o setuid=off $root/var/log
zfs create -o compression=gzip -o exec=off -o setuid=off $root/var/mail
zfs create                     -o exec=off -o setuid=off $root/var/run
zfs create -o compression=lzjb -o exec=on  -o setuid=off $root/var/tmp

tar --unlink -xpJf $bsd/base.txz -C $base
tar --unlink -xpJf $bsd/lib32.txz -C $base

zfs set readonly=on $root/var/empty

if=$(ifconfig -ul ether |awk '{print $1}')
ip=$(ifconfig -f inet:cidr $if inet|awk '/inet/{print $2;exit}')
router=$(netstat -nrf inet|awk '/default/{print $2}')
name=$(hostname)
[ -z "$name" ] && name="setup"

cat << EOM >> $base/etc/rc.conf
# minimal rc.conf
# please set hostname and ip configuration
#

hostname="$name"

ifconfig_$if="$ip"
defaultrouter="$router"
# OR
#ifconfig_$if="DHCP"

zfs_enable="YES"
sshd_enable="YES"
sendmail_enable="NONE"

# existing rc.conf for reference:
EOM
[ -f /etc/rc.conf ] && cat /etc/rc.conf|sed 's/^/#/' >> $base/etc/rc.conf

zpool export $pool
zpool import -o altroot=$base -o cachefile=/tmp/zpool.cache $pool
cp /tmp/zpool.cache $base/boot/zfs/

[ ! -d $base$ufsdir ] && mkdir $base$ufsdir
mount /dev/gpt/boot $base$ufsdir
cp -pRP $base/boot $base$ufsdir
rm -rf $base/boot
ln -s ${ufsdir#/}/boot $base/boot
chflags -h sunlink $base/boot
tar -xpPJf $bsd/kernel.txz -C $base

echo /dev/gpt/boot $ufsdir ufs  rw 1 1 >> /mnt/etc/fstab
echo /dev/gpt/swap.eli none   swap sw 0 0 >> /mnt/etc/fstab

# If we used ssh, keep the keys
cp /etc/ssh/ssh_host* $base/etc/ssh/ || true

rpw=`grep '^root:[^*]' /etc/master.passwd`

if [ ! -z "$rpw" ] ; then
sed -i '' '/^root:/i\
'"$rpw"'
/^root:/d' $base/etc/master.passwd
	pwd_mkdb -p -d $base/etc $base/etc/master.passwd
else
	chroot $base passwd root
fi

sed -i '' -e 's/^#\(PermitRootLogin\).*/\1 yes/' $base/etc/ssh/sshd_config

ee $base/etc/rc.conf

echo 'geom_eli_load="YES"'           >> $base/boot/loader.conf
echo 'zfs_load="YES"'                >> $base/boot/loader.conf
echo vfs.root.mountfrom=\"ufs:/dev/gpt/boot\" >> $base/boot/loader.conf

export ufsdir pool
sh PREBOOT setup $base$ufsdir $base

[ -f bin/dropbear ] && cp bin/dropbear $base$ufsdir/sbin/
[ -d $base$ufsdir/root/.ssh ] || mkdir $base$ufsdir/root/.ssh
[ -f .ssh/authorized_keys ] && cp .ssh/authorized_keys $base$ufsdir/root/.ssh/

echo "All ok"
 
Same here... This is the code to fix Dropbear after a major upgrade.

PREBOOT
Code:
[ -z "$ufsdir" ] && ufsdir=/xboot
[ -z "$pool" ]   && pool=tank

if [ x"$1" != x"update" -a x"$1" != x"setup" ] ; then
    echo "ERROR: run me with setup or update"
    exit 1
fi

if [ "$1" = "setup" ] ; then
    src=${3-/}
    dst=${2-/xboot}
fi

if [ "$1" = "update" ] ; then
    src=${3-/}
    dst=${2-/xboot}
fi

if [ ! -d $src ] ; then
    echo "Source dir $src does not exist" >&2
    exit
fi

if [ ! -d $dst ] ; then
    echo "Destination dir $dst does not exist" >&2
    exit
fi

if [ x"$dst" = x"$src" ] ; then
    echo "srcdir and dstdir may not be equal" >&2
    exit
fi

if [ "$1" = "update" ] ; then
    schg=$(find ${dst} -flags +schg)
    chflags noschg ${schg}
fi
tar -C $src -cf - bin sbin lib libexec rescue | tar -C $dst -xf -
if [ "$1" = "update" ] ; then
    chflags schg ${schg}
fi

mkdir -p $dst/usr/share/misc
mkdir -p $dst/usr/local/etc
ln -fs /etc/dropbear $dst/usr/local/etc/dropbear
ln -fs /etc/termcap $dst/usr/share/misc/termcap

mkdir -p -m 1777 $dst/tmp
mkdir -p $dst/var
mkdir -p $dst/dev
mkdir -p $dst/var/empty
ln -fs /tmp $dst/var/db
ln -fs /tmp $dst/var/run
ln -fs /tmp $dst/var/tmp

mkdir -p $dst/root
mkdir -p /etc

for a in  \
./ntp \
./hosts \
./dhclient.conf \
./termcap.small \
./netconfig \
./protocols \
./devfs.conf \
./devd \
./devd/asus.conf \
./devd/uath.conf \
./devd/usb.conf \
./devd.conf \
./rc.d \
./rc.d/netif \
./rc.d/dhclient \
./rc.d/routing \
./rc.d/hostid \
./hostid \
./opieaccess \
./pam.d \
./pam.d/sshd \
./pam.d/system \
./pam.d/passwd \
./pam.d/login \
./services \
./mac.conf \
./pccard_ether \
./libmap32.conf \
./login.access \
./nsswitch.conf \
./netstart \
./shells \
./libalias.conf \
./rc.shutdown \
./hosts.allow \
./ftpusers \
./gettytab \
./sysctl.conf \
./rc.subr \
./locate.rc \
./rpc \
./defaults \
./defaults/devfs.rules \
./defaults/rc.conf \
./group \
./dumpdates \
./ssh \
./networks \
./profile \
./ttys \
./libmap.conf \
./login.conf.db \
./regdomain.xml \
./zfs \
./hosts.equiv \
./login.conf \
./network.subr \
./resolv.conf \
./rc.conf \
; do
if [ -d $src/etc/$a ] ; then
    mkdir -p $dst/etc/$a
else
    [ -f $src/etc/$a ] && cp $src/etc/$a $dst/etc/`dirname $a`
fi
done
ln -fs /etc/termcap.small $dst/etc/termcap

[ -f $dst/root/.profile ] || echo 'PATH=${PATH}:/bin:/sbin:/rescue:/usr/bin:/usr/sbin' > $dst/root/.profile

cat <<EOM > $dst/root/DWIM
PATH=${PATH}:/bin:/sbin:/rescue
set -ex
fsck -p
geli attach gpt/zroot
kenv vfs.root.mountfrom="zfs:$pool/root"
reboot -r

exit
EOM

tmpbase=/tmp/mnt
cat <<EOM > $dst/root/FIX
[ -z "\$TERM" ] && TERM=vt100 && export TERM
PATH=\${PATH}:/bin:/sbin:/rescue:${tmpbase}/usr/bin:${tmpbase}/usr/local/bin
fsck -p
geli attach gpt/zroot
kenv vfs.root.mountfrom="zfs:$pool/root"
mount -u /
[ ! -d $tmpbase ] && mkdir $tmpbase || true
zpool import -o altroot=$tmpbase -o cachefile=/tmp/zpool.cache $pool
mount -t devfs devfs ${tmpbase}/dev
echo "Mounted @ $tmpbase"
ldconfig $tmpbase/usr/lib
/etc/rc.d/hostid start
echo ''
echo 'Hope you did source me...'
echo ''
echo 'To continue boot:'
echo " - umount $tmpbase/dev"
echo " - zpool export $pool"
echo " - reboot -r"
EOM

#for fsck -p
[ -f $dst/etc/fstab ] || echo "/dev/gpt/boot / ufs rw 1 1" >$dst/etc/fstab
grep -E "^(root|nobody):" $src/etc/master.passwd |sed 's!/bin/csh!/bin/sh!'> $dst/etc/master.passwd

if [ "$1" = "setup" ] ; then

    if [ ! -f $src/etc/ssh/ssh_host_dsa_key ] ; then
        chroot $src /etc/rc.d/sshd onekeygen
    fi


cat << 'EOM' > $dst/etc/rc
#!/bin/sh
#

stty status '^T' 2> /dev/null
trap : 2
trap "echo 'Boot interrupted'; exit 1" 3
HOME=/
PATH=/sbin:/bin:/usr/sbin:/usr/bin
export HOME PATH

. /etc/rc.subr
load_rc_config
trap "_rc_conf_loaded=false; load_rc_config" ALRM


PORT=22

# To get dhclient working
synchronous_dhclient="YES"
export synchronous_dhclient
mdmfs -s16m -p 1777 -S md /tmp

# perhaps ipfw deny all except tcp/22 and icmp/echo
run_rc_script /etc/rc.d/netif start
run_rc_script /etc/rc.d/routing start
echo "Waiting for sshd to exit or press ^C ..."
/sbin/dropbear -E -F -p $PORT -R
echo " done!"
run_rc_script /etc/rc.d/routing stop
run_rc_script /etc/rc.d/netif stop

if [ -f /PREBOOT ] ; then
    echo "Running fallback /bin/sh"
    echo ""
    cat /etc/motd
    cd /root
    /bin/sh
fi

umount /tmp
echo 'Running real /etc/rc to go multiuser'
# remove "autoboot" because fsck might not work anymore here.
exec /bin/sh /etc/rc
EOM

# END OF SETUP
else

# update root password
(grep '^root:' $src/etc/master.passwd; grep -v '^root:' $dst/etc/master.passwd ) |sed 's!/bin/csh!/bin/sh!'> $dst/etc/master.passwd

fi

(
uname -v | sed -e 's,^\([^#]*\) #\(.* [1-2][0-9][0-9][0-9]\).*/\([^\]*\) $,\1 (\3) #\2,'
echo ""
echo "PREBOOT MAINTENANCE MODE"
echo ""
echo 'run "sh DWIM" to unlock disk and boot into system'
echo 'run ". FIX" to unlock disk and mount @ '$tmpbase' to fix/debug'
echo 'run "killall dropbear" to abort'
echo ""
)> $dst/etc/motd


# Convert (current) ssh keys to dropbear keys.

mkdir -p $dst/etc/dropbear
for alg in ecdsa ed25519 rsa; do
    dropbearconvert openssh dropbear $src/etc/ssh/ssh_host_${alg}_key $dst/etc/dropbear/dropbear_${alg}_host_key
done
cp $src/usr/local/sbin/dropbear $dst/sbin/

pwd_mkdb -p -d $dst/etc $dst/etc/master.passwd
touch $dst/PREBOOT
 
Back
Top