File: remote-unlocking

package info (click to toggle)
dropbear 2025.89-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 12,580 kB
  • sloc: ansic: 108,210; sh: 4,765; perl: 774; python: 763; makefile: 715; java: 177
file content (190 lines) | stat: -rw-r--r-- 8,597 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#!/bin/sh

# Copyright 2019 Johannes 'josch' Schauer <josch@debian.org>
# Copyright 2021 Guilhem Moulin <guilhem@debian.org>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

set -eux
PATH="/usr/bin:/bin"
export PATH

TESTNAME="$(basename -- "$0")"

# get package version/distribution and local autopkgtest repo URI to
# pass along to mmdebstrap
VERSION="$(dpkg-parsechangelog -SVersion)"
apt-cache show "dropbear=$VERSION" >/dev/null || exit 1

# try to create /dev/kvm if missing, for instance in a schroot where /dev isn't managed by udev
if [ ! -c /dev/kvm ] && mknod -m0600 /dev/kvm c 10 232; then
    echo "INFO: Created character special file /dev/kvm" >&2
    if getent group kvm >/dev/null && chgrp kvm /dev/kvm; then
        # kvm group is created by udev.postinst
        chmod 0640 /dev/kvm
    fi
fi

if [ -c /dev/kvm ] && dd if=/dev/kvm count=0 status=none; then
    CPU_MODEL="host"
else
    CPU_MODEL="max"
    echo "WARN: KVM is not available, guests will be slow!" >&2
fi


# mkinitramfs(8) uses the default kernel by default, but we might be running an
# old kernel and need to use what's defined as dependency for this test anyway
DEB_HOST_ARCH="$(dpkg-architecture -qDEB_HOST_ARCH)"
KERNEL_VERSION="$(find /lib/modules -mindepth 1 -maxdepth 1 -name "*-$DEB_HOST_ARCH" -type d -printf "%P\\n" | sort -V | tail -n1)"
KERNEL="/boot/vmlinuz-$KERNEL_VERSION"
if [ -z "$KERNEL_VERSION" ] || [ ! -f "$KERNEL" ]; then
    echo "ERROR: Could not determine kernel to use, aborting" >&2
    exit 1
fi

# generate key file used to format and unlock the root FS
head -c32 </dev/urandom >"$AUTOPKGTEST_TMP/rootfs.key"

# rebuild initramfs with custom hook and boot scripts
# (note: cryptsetup-initramfs might spew warnings depending on the host's device stack -- these are irrelevant here)
install -m0755 "$0.d/initramfs-hook"   "/etc/initramfs-tools/hooks/autopkgtest-$TESTNAME-setup"
install -m0755 "$0.d/initramfs-script" "/etc/initramfs-tools/scripts/init-premount/autopkgtest-$TESTNAME-setup"
/usr/sbin/mkinitramfs -o "$AUTOPKGTEST_TMP/initrd.img" "$KERNEL_VERSION"
rm -f "/etc/initramfs-tools/hooks/autopkgtest-$TESTNAME-setup" "/etc/initramfs-tools/scripts/init-premount/autopkgtest-$TESTNAME-setup"


# generate SSH key used to log in to the guest
ssh-keygen -t rsa -C "$TESTNAME" -f "$AUTOPKGTEST_TMP/id_rsa" -N ""

# prepare chroot tarball with a minimal system (to find out missing
# dependencies); we do not use debootstrap because it's unable to create
# a system containing only essential:yes packages and their dependencies,
# and don't use multistrap because it's unmaintained
#
# aside from obious packages (kernel, boot loader, cryptsetup-initramfs, dropbear*)
# we need the following:
#   - e2fsprogs: for /sbin/fsck.ext4
#   - systemd-sysv: for /sbin/init
#   - systemd-cryptsetup: to unlock the swap device at boot time
#   - ifupdown: for networking
# (note: cryptsetup-initramfs and/or dropbear-initramfs might spew warnings depending on
# the host's device stack -- these are irrelevant here)
apt-get indextargets --format "deb \$(SITE) \$(SUITE) \$(COMPONENT)" \
    "Created-By: Packages" "Origin: Debian" >"$AUTOPKGTEST_TMP/sources.list"
AUTOPKGTEST_LOCAL_ARCHIVE="$(apt-get indextargets --format "\$(REPO_URI)" "Created-By: Packages" | \
    sed -nr '\,^(file|copy):((/+[^/[:blank:]]+)*/autopkgtest[.-].*[^/])/?$, {s//\2/p;q}')"
if [ -n "$AUTOPKGTEST_LOCAL_ARCHIVE" ]; then
    echo "deb [trusted=yes] file://$AUTOPKGTEST_LOCAL_ARCHIVE /" >>"$AUTOPKGTEST_TMP/sources.list"
fi
mmdebstrap --verbose --variant="apt" \
    --include="linux-image-$KERNEL_VERSION" \
    --include="grub2" \
    --include="cryptsetup-initramfs" \
    --include="dropbear=$VERSION,dropbear-initramfs=$VERSION" \
    --include="e2fsprogs" \
    --include="systemd-sysv" \
    --include="systemd-cryptsetup" \
    --include="ifupdown" \
    --customize-hook="upload \"$AUTOPKGTEST_TMP/id_rsa.pub\" /etc/dropbear/initramfs/authorized_keys" \
    ${AUTOPKGTEST_LOCAL_ARCHIVE:+--setup-hook="mkdir -p -- \"\$1${AUTOPKGTEST_LOCAL_ARCHIVE%/*}\"" \
                                 --setup-hook="copy-in $AUTOPKGTEST_LOCAL_ARCHIVE ${AUTOPKGTEST_LOCAL_ARCHIVE%/*}" \
                                 --customize-hook="rm -rf -- \"\$1${AUTOPKGTEST_LOCAL_ARCHIVE%/*}\"" \
                                 --customize-hook="sed -i 's,.*\\bfile://,#&,' \"\$1/etc/apt/sources.list\"" } \
    <"$AUTOPKGTEST_TMP/sources.list" >"$AUTOPKGTEST_TMP/debian.tar"


# pre-allocate disk image for the guest
DRIVE_IMG="$AUTOPKGTEST_TMP/disk.img"
dd if=/dev/zero of="$DRIVE_IMG" bs=1M count=2048 conv=fsync,excl status=none

# wrapper around qemu-system, passing common options and an optional timeout
SMP="cpus=2"
MEMORY="1G"
unset TIMEOUT
qemu_system() {
    local exe="qemu-system-x86_64" rv
    ${TIMEOUT:+timeout -k60s -sTERM "$TIMEOUT"} "$exe" -no-user-config -nodefaults \
        -name "autopkgtest-$TESTNAME" -machine "type=q35,accel=kvm:tcg,graphics=off" \
        -cpu "$CPU_MODEL" -smp "$SMP" -m "$MEMORY" \
        -serial stdio -vga virtio -display none -monitor none \
        -object "rng-random,id=rng0,filename=/dev/urandom" -device "virtio-rng-pci,rng=rng0" \
        -drive "file=$DRIVE_IMG,format=raw,if=virtio,cache=unsafe" \
        "$@" </dev/null && return 0 || rv=$?

    # timeout(1) exits with status 124 on timeout, or with status 128+9 if
    # the command is still running after 60s and a SIGKILL needs to be sent
    if [ -n ${TIMEOUT:+x} ] && [ $rv -eq 124 -o $rv -eq $((128+9)) ]; then
        echo "ERROR: $TIMEOUT timeout reached while running $exe" >&2
    else
        echo "ERROR: $exe exited with status $rv" >&2
    fi
    trap - EXIT # don't try to kill the terminated QEMU process
    exit 1
}

# prepare the target system from our custom initrd, passing the chroot tarball as secondary disk
TIMEOUT=1800s qemu_system \
    -drive "file=$AUTOPKGTEST_TMP/debian.tar,format=raw,if=virtio,readonly=on" \
    -kernel "$KERNEL" -initrd "$AUTOPKGTEST_TMP/initrd.img" -append "console=ttyS0"

# boot the target system -- without the chroot tarball but with a network card instead, forward
# connections from host port 12222 resp. 10022 to the guest's SSHd at initramfs stage resp.
# after pivoting
HOST_FWD="hostfwd=tcp:127.0.0.1:10022-10.0.2.129:22,hostfwd=tcp:127.0.0.1:12222-10.0.2.130:2222"
TIMEOUT=600s qemu_system \
    -netdev "user,restrict=on,id=net0,$HOST_FWD" -device "virtio-net-pci,netdev=net0" &

QEMU_PID=$!
trap "kill $QEMU_PID" EXIT # terminate the guest if this script exits first


# wrapper around ssh(1), passing common options
ssh() {
    command ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no \
                -oConnectTimeout=5 -i "$AUTOPKGTEST_TMP/id_rsa" -l root "$@"
}

# keep trying to ssh into QEMU up to $MAX_TRIES times
unset MAX_TRIES
ssh_retry() {
    local i=${MAX_TRIES:-24} rv
    while :; do
        ssh "$@" && rv=0 || rv=$?
        if [ $rv -ne 255 ]; then
            # the remote command was run, returns its exit status
            return $rv
        elif [ $i -le 1 ]; then
            echo "ERROR: Couldn't SSH into QEMU: maximum number of tries exceeded" >&2
            exit 1
        fi
        i=$((i - 1))
        sleep 5
    done
}

# wait for dropbear to start on the guest, then remotely unlock the system
ssh_retry -Tp 12222 "127.0.0.1" /nonexistent <"$AUTOPKGTEST_TMP/rootfs.key"

# wait for the main system to finish booting and its SSHd to start, then
# make sure the root FS and swap are held by dm-crypt devices
# XXX we have to use cryptsetup(8)'s full path here, see #903403
ssh_retry -nTp 10022 "127.0.0.1" echo ping
ssh -nTp 10022 "127.0.0.1" lsblk -no"MOUNTPOINT" /dev/mapper/root_crypt | grep -Fx "/"
#ssh -nTp 10022 "127.0.0.1" lsblk -no"MOUNTPOINT" /dev/mapper/swap_crypt | grep -Fx "[SWAP]"
ssh  -Tp 10022 "127.0.0.1" /sbin/cryptsetup luksOpen --key-file=- \
    --test-passphrase /dev/vda4 <"$AUTOPKGTEST_TMP/rootfs.key"

# power the guest down and wait for QEMU to gracefully terminate
ssh -nTp 10022 "127.0.0.1" systemctl poweroff

trap - EXIT
wait $QEMU_PID