diff --git a/.gitignore b/.gitignore index c910e48..b4a4654 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.o *~ /build +config.mk diff --git a/Makefile b/Makefile index 3d1b229..68959bd 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,14 @@ export VERSION=1.0.2 -export OLDDRACUT=0 -export LIBDIR=/lib -export ROOTHOME=/root/ +include config.mk export DESTDIR= -export MODULEDIR=${DESTDIR}/usr/share/dracut/modules.d/ +export MODULEDIR=${DESTDIR}/usr/$(DRACUT_MODULEDIR) SUBDIRS=modules/earlyssh modules/cryptsettle-patch src -ifeq ($(OLDDRACUT),1) +ifeq ($(NEED_CRYPTSETTLE),1) CRYPTSETTLE=modules/cryptsettle-patch else CRYPTSETTLE= @@ -18,7 +16,7 @@ endif .PHONY: src dist $(SUBDIRS) -all: $(CRYPTSETTLE) modules/earlyssh +all: src $(CRYPTSETTLE) modules/earlyssh install: src modules/earlyssh $(CRYPTSETTLE) mkdir -p $(DESTDIR)/etc/dracut.conf.d/ @@ -29,7 +27,7 @@ clean: src modules/earlyssh modules/cryptsettle-patch dist: $(MAKE) clean mkdir -p tmp/dracut-earlyssh-${VERSION} - cp -R src modules Makefile earlyssh.conf COPYING README.md tmp/dracut-earlyssh-${VERSION} + cp -R configure src modules Makefile earlyssh.conf COPYING README.md tmp/dracut-earlyssh-${VERSION} tar -C tmp -czf ../dracut-earlyssh-${VERSION}.tgz dracut-earlyssh-${VERSION} rm -rf tmp diff --git a/README.md b/README.md index bcfa449..01b9776 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,12 @@ for automating the unlock process. Finally, there is a RPM spec file which shou it very easy to deploy (and doesn't introduce a runtime dependency on a compiler). Users are strictly authenticated by provided SSH public keys. These can be either: -root's ~/.ssh/authorized_keys or a custom file ("dropbear_acl" option). +root's ~/.ssh/authorized_keys or a custom file ("dropbear_acl" option). Depending +on your environment, it may make sense to make the preboot authorized_keys file +quite different to the normal one. See dropbear(8) manpage for full list of supported restrictions there (which are -fairly similar to openssh). If using in combination with the unlock utility (see below), a useful -restriction may be to make /bin/unlock a 'forced command' in SSH. +fairly similar to openssh). If using in combination with the unlock utility (see below), a useful restriction may be to make /bin/unlock a 'forced command' in SSH. ### Usage @@ -32,15 +33,8 @@ First of all, you must have dropbear. CentOS/RHEL users can get this from EPEL. You will need gcc and libblkid(-devel) installed to build console_auth and the unlock tools. -- You should be able to build everything by running make/make install. There - are some additional variables to make that you may need to override. - These are - OLDDRACUT Defaults to 0, set to 1 if using an old version of Dracut (e.g. 004) - ROOTHOME root's home on the initrd. On RHEL6 this is /, Exherbo apparently /root - Note that this is NOT the same as root's home in the normal system! - LIBDIR On RHEL6 and above /lib64, otherwise /lib - For example (RHEL6): - make OLDDRACUT=1 ROOTHOME=/ LIBDIR=/lib64 install +- You should be able to build everything by running configure, make, make install as usual. + The configure script should detect and compensate for the various differences in dracut versions. - The provided RPM spec file should take care of these things for RHEL6/7 @@ -58,10 +52,10 @@ You will need gcc and libblkid(-devel) installed to build console_auth and the u Simplest way might be just passing `ip=dhcp rd.neednet=1` on cmdline, if dhcp can assign predictable ip and pass proper routes. - On older Dracut's, a more tortuous route is required. Networking is only configured - if you have configured a network root. In order to work around this, the system will - install a dummyroot script (if OLDDRACUT=1 at build-time). The cmdline for these versions - should be `ip=dhcp netroot=dummy`. + On older Dracut versions (e.g. 004 in RHEL6), networking is only configured + if you have configured a network root. In order to work around this, dracut-earlyssh + system will install a dummyroot script (if it detects dracut v004 at build-time). + The cmdline for these versions should be `ip=dhcp netroot=dummy`. - Run dracut to build initramfs with the thing. @@ -120,7 +114,7 @@ The `unlock` binary takes a passphrase in stdin, reads `/etc/crypttab` and attem call `cryptsetup luksOpen` on all luks-encrypted drives that don't have a keyfile, passing the passphrase that unlock got in stdin to luksOpen. -What this means in practise is you can do: +What this means in practice is you can do: ```console % ssh root@remote.server -p 2222 unlock < passwordFile ``` @@ -149,10 +143,11 @@ successfully. This means: In short, if you have more than one volume in /etc/crypttab, you will need to be careful about how use this tool. -If the process is successful, `unlock` will kill cryptroot-ask, which, at least on RHEL, -forces the boot process to proceed. Note that the plymouth splash screen (if you happen to be -watching the console...) will still appear to ask for your password, but this is an artificat. -Disable plymouth (rhgb command line) if this annoys you. +If the process is successful, `unlock` will launch the script `/sbin/unlock-reap-success`. +This can be found in the modules.d/earlyssh folder. This will attempt to kill systemd-cryptsetup, +and failing that, attempt to kill cryptroot-ask. On RHEL6 & 7, this aborts the builtin decrypt +password request processes and allows the boot process to proceed. +Note that the plymouth splash screen on RHEL6 (if you happen to be watching the console...) will still appear to ask for your password, but this is an artificat. Disable plymouth (rhgb command line) if this annoys you. ### dracut.conf parameters @@ -253,12 +248,14 @@ either enable "debug" dracut module or add `dracut_install netstat ip` line to naming mixup, no traffic (e.g. unrelated connection issue), etc. -### Bad things +### TODO +- Limited testing. Original (before fork) only tested with customized source-based distro + ([Exherbo](http://exherbo.org/)), current version only tested with CentOS 6.5 and CentOS 7.0. + However, the configure script should allow it to be fairly adaptable to a range of distro's. -- Only tested with customized source-based distro - ([Exherbo](http://exherbo.org/)), no idea how easy it is to use with generic - debian or ubuntu. +- Need to document & form recommendations on how to unlock multiple systems using the unlock script. + Something with gpg-agent seems like it may work well. - `check()` in module_setup.sh should probably not be empty no-op. @@ -270,6 +267,12 @@ naming mixup, no traffic (e.g. unrelated connection issue), etc. - Some notes on threat model where such thing might be useful would be nice, so people won't assume too much. +- Remote initramfs hash verification, etc. See above point, a determined attacker could potentially + circumvent or fake the outputs of various commands in order to pretend that the verification had succeeded. + One possibility would be to dynamically upload a special hashing binary that has a compiled-in nonce. This + would be hard to fake, I think. However, it would require a compiler for each supported architecture on the + verification machine. + ### Based on code, examples and ideas from diff --git a/configure b/configure new file mode 100755 index 0000000..55409a6 --- /dev/null +++ b/configure @@ -0,0 +1,128 @@ +#!/bin/bash + +while [[ $# -gt 0 ]]; do + case $1 in + --print-moduledir) + [[ -f config.mk ]] && source config.mk + [[ -z $DRACUT_MODULEDIR ]] && exit 1 + echo $DRACUT_MODULEDIR + exit 0 + ;; + --dracut) + DRACUT=$2 + shift + ;; + --root-home) + ROOTHOME=$2 + shift + ;; + esac + shift +done + +echo -n "Searching for dracut... " +if [[ -z $DRACUT ]]; then + DRACUT=$(which dracut) + if [[ $? -eq 0 ]]; then + echo $DRACUT + else + echo "not found" + exit 1 + fi +else + echo "[provided] $DRACUT" +fi + +echo -n "Checking dracut version... " +DRACUT_VERSION=$(${DRACUT} --help | egrep "Version:[ \t]+[0-9]+" | egrep -o [0-9]+) +if [[ $? -ne 0 ]]; then + echo -n "[old version - fallback] " + DRACUT_VERSION=004 +else + DRACUT_VERSION=$(echo ${DRACUT_VERSION} | cut -f 1 -d " ") +fi +echo ${DRACUT_VERSION} + +echo -n "Checking dracut module prefix... " +DRACUT_MODULEDIR="" +if [[ -d /usr/lib/dracut/modules.d ]]; then + DRACUT_MODULEDIR=lib/dracut/modules.d +elif [[ -d /usr/share/dracut/modules.d ]]; then + DRACUT_MODULEDIR=share/dracut/modules.d +else + echo "failed" + exit 1 +fi +echo ${DRACUT_MODULEDIR} + +NEED_CRYPTSETTLE=0 +CRYPTPARSE=/usr/${DRACUT_MODULEDIR}/90crypt/parse-crypt.sh +if [[ -f $CRYPTPARSE ]]; then + echo -n "Checking whether crypt-settle patch is needed... " + grep -c -- "--settled" ${CRYPTPARSE} >/dev/null + if [[ $? -ne 0 ]]; then + NEED_CRYPTSETTLE=1 + echo "yes" + else + echo "no" + fi +fi + +echo -n "Checking for old-style modules... " +OLDSTYLE=1 +if [[ -f /usr/${DRACUT_MODULEDIR}/99base/module-setup.sh ]]; then + OLDSTYLE=0 + echo "no" +else + echo "yes" +fi + +echo -n "Checking for root's home... " +if [[ -z ${ROOTHOME} ]]; then + ROOTHOME=/ + module=module-setup.sh + [[ ${OLDSTYLE} -eq 1 ]] && module=install + MODULEBASE=/usr/${DRACUT_MODULEDIR}/99base/${module} + if [[ -f $MODULEBASE ]]; then + grep -c "root:" ${MODULEBASE} >/dev/null + if [[ $? -eq 0 ]]; then + ROOTHOME=/root + fi + fi +else + echo -n "[provided] " +fi +echo ${ROOTHOME} + + +echo -n "Checking for libnss_files.so ... " +DIRS="/usr/lib /usr/lib64 /lib64 /lib" +NSSFILES="" +for dir in ${DIRS}; do + for check in ${dir}/libnss_files.so*; do + if [[ -f $check ]]; then + NSSFILES=${check} + break + fi + done +done +if [[ -z $NSSFILES ]]; then + echo "not found" + exit 1 +else + echo $NSSFILES +fi + +OLDDRACUT=0 +[[ $DRACUT_VERSION -le 4 ]] && OLDDRACUT=1 + +cat >config.mk < BuildRequires: dracut @@ -24,17 +18,18 @@ Requires: openssh A dracut module that includes dropbear in the boot image, along with some helper utilities for unlocking encrypted drives over a remote connection. -Please read the README and configuration parameters in /etc/dracut.conf.d/earlyssh.conf -before use. +Please read the README and configuration parameters in +/etc/dracut.conf.d/earlyssh.conf before use. %prep %setup +./configure %build -make LIBDIR=/lib64 ROOTHOME=/ OLDDRACUT=%{olddracut} +make %install -make install OLDDRACUT=%{olddracut} DESTDIR=$RPM_BUILD_ROOT +make install DESTDIR=$RPM_BUILD_ROOT cp README.md README %files @@ -42,10 +37,7 @@ cp README.md README %doc README COPYING %config(noreplace) %{_sysconfdir}/dracut.conf.d/earlyssh.conf %{_libexecdir}/dracut-earlyssh -%{_datadir}/dracut/modules.d/60earlyssh -%if %{olddracut} -%{_datadir}/dracut/modules.d/91cryptsettle-patch -%endif +%{_prefix}/*/dracut/modules.d/* diff --git a/modules/check.sh b/modules/check.sh new file mode 100644 index 0000000..39287b7 --- /dev/null +++ b/modules/check.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +source $(dirname $0)/module-setup.sh + +check +exit $? diff --git a/modules/cryptsettle-patch/Makefile b/modules/cryptsettle-patch/Makefile index abe7a83..a854274 100644 --- a/modules/cryptsettle-patch/Makefile +++ b/modules/cryptsettle-patch/Makefile @@ -7,9 +7,10 @@ all: clean: -install: install.sh check.sh +install: ../install.sh ../check.sh module-setup.sh mkdir -p $(INSTDIR) - cp install.sh $(INSTDIR)/install - cp check.sh $(INSTDIR)/check - chmod 755 $(INSTDIR)/{install,check} - + install -m 0755 module-setup.sh $(INSTDIR)/ +ifeq ($(OLDSTYLE),1) + install -m 0755 ../install.sh $(INSTDIR)/install + install -m 0755 ../check.sh $(INSTDIR)/check +endif diff --git a/modules/cryptsettle-patch/check.sh b/modules/cryptsettle-patch/check.sh deleted file mode 100644 index 6112234..0000000 --- a/modules/cryptsettle-patch/check.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -exit 0 - diff --git a/modules/cryptsettle-patch/install.sh b/modules/cryptsettle-patch/install.sh deleted file mode 100644 index 09ce494..0000000 --- a/modules/cryptsettle-patch/install.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -for file in ${initdir}/cmdline/*parse-crypt.sh; do - dinfo "Patching ${file}" - sed -i -e "s!/sbin/initqueue!/sbin/initqueue --settled!g" ${file} -done - diff --git a/modules/cryptsettle-patch/module-setup.sh b/modules/cryptsettle-patch/module-setup.sh new file mode 100644 index 0000000..1f00d19 --- /dev/null +++ b/modules/cryptsettle-patch/module-setup.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +check() { + return 0 +} + +install() { + for file in ${initdir}/cmdline/*parse-crypt.sh; do + dinfo "Patching ${file}" + sed -i -e "s!/sbin/initqueue!/sbin/initqueue --settled!g" ${file} + done + + return 0 +} + diff --git a/modules/earlyssh/Makefile b/modules/earlyssh/Makefile index 228a8ef..99e6f3c 100644 --- a/modules/earlyssh/Makefile +++ b/modules/earlyssh/Makefile @@ -7,19 +7,18 @@ clean: module-setup.sh: module-setup.sh.in sed -e "s!@ROOTHOME@!${ROOTHOME}!g" \ - -e "s!@OLDDRACUT@!${OLDDRACUT}!g" \ - -e "s!@LIBDIR@!${LIBDIR}!g" \ + -e "s!@OLDSTYLE@!${OLDSTYLE}!g" \ + -e "s!@NSSFILES@!${NSSFILES}!g" \ < module-setup.sh.in > module-setup.sh -install: 50-udev-pty.rules console_peek.sh +install: 50-udev-pty.rules console_peek.sh ../install.sh ../check.sh mkdir -p $(INSTDIR) - cp 50-udev-pty.rules $(INSTDIR)/ - cp console_peek.sh $(INSTDIR)/ - chmod 755 $(INSTDIR)/console_peek.sh -ifeq ($(OLDDRACUT),1) - cp install.sh $(INSTDIR)/install - cp check.sh $(INSTDIR)/check - cp dummyroot $(INSTDIR)/ - chmod 755 $(INSTDIR)/{install,check,dummyroot} + install -m 0644 50-udev-pty.rules $(INSTDIR)/ + install -m 0755 unlock-reap-success.sh console_peek.sh $(INSTDIR)/ + install -m 0755 module-setup.sh $(INSTDIR) +ifeq ($(OLDSTYLE),1) + install -m 0755 ../install.sh $(INSTDIR)/install + install -m 0755 ../check.sh $(INSTDIR)/check + install -m 0755 dummyroot $(INSTDIR)/ endif diff --git a/modules/earlyssh/check.sh b/modules/earlyssh/check.sh deleted file mode 100755 index 7a693aa..0000000 --- a/modules/earlyssh/check.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - - diff --git a/modules/earlyssh/install.sh b/modules/earlyssh/install.sh deleted file mode 100755 index 80e498a..0000000 --- a/modules/earlyssh/install.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -source $moddir/module-setup.sh - -install - - diff --git a/modules/earlyssh/module-setup.sh.in b/modules/earlyssh/module-setup.sh.in index 38404d2..eac1fdc 100755 --- a/modules/earlyssh/module-setup.sh.in +++ b/modules/earlyssh/module-setup.sh.in @@ -35,14 +35,13 @@ depends() { install() { local tmp=$(mktemp -d --tmpdir dracut-crypt-sshd.XXXX) local ROOTHOME=@ROOTHOME@ - local OLDDRACUT=@OLDDRACUT@ # Defaults [[ -z "${dropbear_rsa_key}" ]] && dropbear_rsa_key=GENERATE [[ -z "${dropbear_port}" ]] && dropbear_port=2222 [[ -z "${dropbear_acl}" ]] && dropbear_acl=/root/.ssh/authorized_keys - dracut_install pkill setterm /@LIBDIR@/libnss_files.so.2 + dracut_install pkill setterm @NSSFILES@ inst $(which dropbear) /sbin/dropbear # Helper to safely send password to cryptsetup on /dev/console without echoing it. @@ -51,6 +50,7 @@ install() { # Designed for use with automated ssh helpers inst /usr/libexec/dracut-earlyssh/unlock /bin/unlock inst ${moddir}/console_peek.sh /bin/console_peek + inst ${moddir}/unlock-reap-success.sh /sbin/unlock-reap-success # Don't bother with DSA, as it's either much more fragile or broken anyway local rsa_key="$tmp"/key @@ -70,7 +70,7 @@ install() { return 255 } - mv "${rsa_key}".ossh + mv "${rsa_key}" "${rsa_key}".ossh dinfo "Generated ad-hoc rsa key for dropbear sshd in initramfs" ;; SYSTEM ) @@ -112,7 +112,7 @@ install() { # should be provided by 99base; /bin/sh will be run regardless of /etc/shells presence. # It can do without nsswitch.conf, resolv.conf or whatever other stuff it usually has. - [[ ${OLDDRACUT} -eq 1 ]] && inst $moddir/dummyroot /sbin/dummyroot + [[ @OLDSTYLE@ -eq 1 ]] && inst $moddir/dummyroot /sbin/dummyroot inst_rules 50-udev-pty.rules # Generate hooks right here, with parameters baked-in diff --git a/modules/earlyssh/unlock-reap-success.sh b/modules/earlyssh/unlock-reap-success.sh new file mode 100644 index 0000000..291d707 --- /dev/null +++ b/modules/earlyssh/unlock-reap-success.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +SYSTEMD_CRYPTSETUP="$(ps -C systemd-cryptsetup -o pid=)" +if [[ $? -eq 0 ]]; then + # SystemD method + kill -9 ${SYSTEMD_CRYPTSETUP} +else + # Older method + pkill cryptroot-ask +fi + diff --git a/modules/install.sh b/modules/install.sh new file mode 100644 index 0000000..5a79acf --- /dev/null +++ b/modules/install.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +source ${moddir}/module-setup.sh + +install + diff --git a/src/Makefile b/src/Makefile index bf5cf24..7699e51 100644 --- a/src/Makefile +++ b/src/Makefile @@ -14,7 +14,7 @@ clean: install: console_auth unlock mkdir -p $(INSTDIR) - cp console_auth unlock $(INSTDIR)/ + install -m 0755 console_auth unlock $(INSTDIR)/ console_auth: auth.c gcc $(CFLAGS) $^ -o $@ diff --git a/src/unlock.c b/src/unlock.c index 34cbd0d..e756671 100644 --- a/src/unlock.c +++ b/src/unlock.c @@ -170,7 +170,11 @@ int main( int argc, const char ** argv ) memset( password, '\0', kPasswordSize ); if( !errorExit ) { - system( "pkill cryptroot-ask" ); + // Fork ourselves and run the cleanup script + // Fork so that our return code isn't affected by the script + if( fork() == 0 ) { + system( "/sbin/unlock-reap-success" ); + } } return errorExit;