├── .github └── workflows │ ├── e2e-base.yml │ ├── e2e-schedule.yml │ └── e2e-trigger.yml ├── 46sshd ├── module-setup.sh ├── motd ├── profile ├── sshd.service └── sshd_config ├── README.md ├── dracut-sshd.spec ├── example ├── 20-wired.network └── 90-networkd.conf └── test ├── conf.sh ├── create-vm.sh ├── e2e.sh ├── encrypt-fedora.sh ├── encrypt-rhel.sh ├── get-alma.sh ├── get-fedora.sh ├── install-dracut-sshd.sh ├── unlock.sh ├── update-grub.guestf ├── update-grub.sh └── verify-boot.sh /.github/workflows/e2e-base.yml: -------------------------------------------------------------------------------- 1 | name: e2e-fedora 2 | run-name: end-to-end test latest Fedora (${{ github.sha }} ${{ github.ref }} ${{ github.actor }}) 3 | on: 4 | workflow_call: 5 | inputs: 6 | target: 7 | required: true 8 | type: string 9 | jobs: 10 | e2e-test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Check out repository code 14 | uses: actions/checkout@v4 15 | # XXX remove when https://github.com/actions/runner-images/issues/10977 is fixed ... 16 | - name: disable man-db trigger 17 | shell: sudo bash -e {0} 18 | run: | 19 | echo 'set man-db/auto-update false' | debconf-communicate 20 | dpkg-reconfigure man-db 21 | - name: install packages 22 | shell: sudo bash -e {0} 23 | run: | 24 | apt update 25 | apt -y install socat xterm libvirt-clients virtinst libvirt-daemon-system qemu-system-x86 goxkcdpwgen guestfish 26 | - name: fix libvirt, kvm, guestfish permissions 27 | shell: sudo bash -e {0} 28 | run: | 29 | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' > /etc/udev/rules.d/99-kvm4all.rules 30 | udevadm trigger --name-match=kvm 31 | 32 | setfacl -m user:$SUDO_USER:rw /var/run/libvirt/libvirt-sock 33 | chmod 755 /home/runner 34 | chmod 644 /boot/vmlinuz* 35 | - name: verify libvirt, kvm setup 36 | run: | 37 | if [ $(virt-host-validate | grep /kvm'.\+PASS$' -c) -ne 2 ]; then 38 | virt-host-validate 39 | echo 'kvm validation failed' >&2 40 | exit 23 41 | fi 42 | virsh --connect qemu:///system list --all 43 | - name: run test 44 | env: 45 | target: ${{ inputs.target }} 46 | run: | 47 | cd .. 48 | mkdir -p work 49 | set -x 50 | cd work 51 | case "$target" in 52 | fedora) 53 | ../dracut-sshd/test/get-fedora.sh 54 | ../dracut-sshd/test/e2e.sh $(cat f-release) 55 | ;; 56 | rawhide) 57 | ../dracut-sshd/test/get-fedora.sh rawhide 58 | ../dracut-sshd/test/e2e.sh $(cat f-release) 59 | ;; 60 | alma) 61 | ../dracut-sshd/test/get-alma.sh 62 | ../dracut-sshd/test/e2e.sh $(cat alma-release) alma 63 | ;; 64 | esac 65 | 66 | -------------------------------------------------------------------------------- /.github/workflows/e2e-schedule.yml: -------------------------------------------------------------------------------- 1 | name: e2e-schedule 2 | run-name: end-to-end test latest greatest fedora and alma each weak 3 | on: 4 | schedule: 5 | # minute hour dom month dow 6 | - cron: '29 22 * * 3' 7 | jobs: 8 | e2e-schedule: 9 | strategy: 10 | matrix: 11 | target: 12 | - fedora 13 | - rawhide 14 | - alma 15 | fail-fast: false 16 | uses: ./.github/workflows/e2e-base.yml 17 | with: 18 | target: ${{ matrix.target }} 19 | 20 | -------------------------------------------------------------------------------- /.github/workflows/e2e-trigger.yml: -------------------------------------------------------------------------------- 1 | name: e2e-trigger 2 | run-name: end-to-end test latest greatest fedora and alma 3 | on: 4 | push: 5 | pull_request: 6 | jobs: 7 | e2e-trigger: 8 | strategy: 9 | matrix: 10 | target: 11 | - fedora 12 | - rawhide 13 | - alma 14 | fail-fast: false 15 | uses: ./.github/workflows/e2e-base.yml 16 | with: 17 | target: ${{ matrix.target }} 18 | -------------------------------------------------------------------------------- /46sshd/module-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 2018, Georg Sauthoff 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | # called by dracut 7 | check() { 8 | require_binaries sshd || return 1 9 | # 0 enables by default, 255 only on request 10 | return 0 11 | } 12 | 13 | # called by dracut 14 | depends() { 15 | return 0 16 | } 17 | 18 | # called by dracut 19 | install() { 20 | local key_prefix key_type ssh_host_key authorized_keys 21 | key_prefix= 22 | if [ "$(find /etc/ssh -maxdepth 1 -name 'dracut_ssh_host_*_key')" ]; then 23 | key_prefix=dracut_ 24 | fi 25 | local found_host_key=no 26 | for key_type in dsa ecdsa ed25519 rsa; do 27 | ssh_host_key=/etc/ssh/"$key_prefix"ssh_host_"$key_type"_key 28 | if [ -f "$ssh_host_key" ]; then 29 | inst_simple "$ssh_host_key".pub /etc/ssh/ssh_host_"$key_type"_key.pub 30 | /usr/bin/install -m 600 "$ssh_host_key" \ 31 | "$initdir/etc/ssh/ssh_host_${key_type}_key" 32 | found_host_key=yes 33 | fi 34 | done 35 | if [ "$found_host_key" = no ]; then 36 | dfatal "Didn't find any SSH host key!" 37 | return 1 38 | fi 39 | 40 | if [ -e /root/.ssh/dracut_authorized_keys ]; then 41 | authorized_keys=/root/.ssh/dracut_authorized_keys 42 | elif [ -e /etc/dracut-sshd/authorized_keys ]; then 43 | authorized_keys=/etc/dracut-sshd/authorized_keys 44 | else 45 | authorized_keys=/root/.ssh/authorized_keys 46 | fi 47 | if [ ! -r "$authorized_keys" ]; then 48 | dfatal "No authorized_keys for root user found!" 49 | return 1 50 | fi 51 | 52 | mkdir -p -m 0700 "$initdir/root" 53 | mkdir -p -m 0700 "$initdir/root/.ssh" 54 | /usr/bin/install -m 600 "$authorized_keys" \ 55 | "$initdir/root/.ssh/authorized_keys" 56 | 57 | inst_binary /usr/sbin/sshd 58 | inst_multiple -o /etc/sysconfig/sshd /etc/sysconfig/ssh \ 59 | /etc/sysconfig/dracut-sshd 60 | 61 | # Copy ssh helper executables for OpenSSH 9.8+ 62 | # /usr/lib/ssh -> Arch 63 | # /usr/lib(64)/misc -> Gentoo 64 | # /usr/libexec/openssh -> Fedora 65 | # /usr/libexec/ssh -> openSUSE 66 | local d 67 | for d in /usr/lib/ssh /usr/lib64/misc /usr/lib/misc /usr/libexec/openssh /usr/libexec/ssh ; do 68 | if [ -f "$d"/sshd-session ]; then 69 | inst_multiple -o "$d"/{sshd-session,sftp-server,sshd-auth} 70 | break 71 | fi 72 | done 73 | 74 | # First entry for Fedora 28, second for Fedora 27 75 | inst_multiple -o /etc/crypto-policies/back-ends/opensshserver.config \ 76 | /etc/crypto-policies/back-ends/openssh-server.config 77 | inst_simple "${moddir}/sshd.service" "$systemdsystemunitdir/sshd.service" 78 | inst_simple "${moddir}/sshd_config" /etc/ssh/sshd_config 79 | 80 | { grep '^sshd:' $dracutsysrootdir/etc/passwd || echo 'sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin'; } >> "$initdir/etc/passwd" 81 | { grep '^sshd:' $dracutsysrootdir/etc/group || echo 'sshd:x:74:'; } >> "$initdir/etc/group" 82 | 83 | # Create privilege separation directory 84 | # /var/empty/sshd -> Fedora, CentOS, RHEL 85 | # /usr/share/empty.sshd -> Fedora >= 34 86 | # /var/emtpy -> Arch, OpenSSH upstream 87 | # /var/lib/empty -> Suse 88 | # /var/chroot/ssh -> Void Linux 89 | local d 90 | for d in /var/empty/sshd /usr/share/empty.sshd /var/empty /var/lib/empty /var/chroot/ssh ; do 91 | if [ -d "$d" ]; then 92 | mkdir -p -m 0755 "$initdir$d" 93 | fi 94 | done 95 | # workaround for Silverblue (in general for ostree based os) 96 | if grep ^OSTREE_VERSION= /etc/os-release > /dev/null; then 97 | mkdir -p -m 0755 "$initdir/var/empty/sshd" 98 | fi 99 | 100 | systemctl -q --root "$initdir" enable sshd 101 | 102 | # Add command to unlock luks volumes to bash history for easier use 103 | echo systemd-tty-ask-password-agent >> "$initdir/root/.bash_history" 104 | chmod 600 "$initdir/root/.bash_history" 105 | 106 | # sshd requires /var/log/lastlog for tracking login information 107 | mkdir -p -m 0755 "$initdir/var/log" 108 | touch "$initdir/var/log/lastlog" 109 | 110 | inst_simple "${moddir}/motd" /etc/motd 111 | inst_simple "${moddir}/profile" /root/.profile 112 | 113 | return 0 114 | } 115 | 116 | -------------------------------------------------------------------------------- /46sshd/motd: -------------------------------------------------------------------------------- 1 | 2 | Welcome to the early boot SSH environment. You may type 3 | 4 | systemd-tty-ask-password-agent 5 | 6 | (or press "arrow up") to unlock your disks. 7 | 8 | This shell will terminate automatically a few seconds after the 9 | unlocking process has succeeded and when the boot proceeds. 10 | 11 | -------------------------------------------------------------------------------- /46sshd/profile: -------------------------------------------------------------------------------- 1 | if [ -n "$SSH_TTY" ]; then 2 | export PS1='initramfs-ssh:${PWD}# ' 3 | fi 4 | 5 | if [ -n "$TERM" ]; then 6 | export TERM=vt220 7 | fi 8 | -------------------------------------------------------------------------------- /46sshd/sshd.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=OpenSSH server daemon 3 | Documentation=man:sshd(8) man:sshd_config(5) 4 | DefaultDependencies=no 5 | Before=cryptsetup.target 6 | 7 | 8 | [Service] 9 | # With `Type=notify` the sshd service is started in a reliable 10 | # and robust way but it requires an sshd with systemd support. 11 | # Fedora/RHEL/CentOS/Debian/Ubuntu provide such an sshd. 12 | # 13 | # On distributions such as Gentoo, sshd doesn't come with 14 | # systemd support, thus, one has to set `Type=simple` there. 15 | Type=notify 16 | 17 | EnvironmentFile=-/etc/crypto-policies/back-ends/opensshserver.config 18 | EnvironmentFile=-/etc/crypto-policies/back-ends/openssh-server.config 19 | EnvironmentFile=-/etc/sysconfig/sshd 20 | EnvironmentFile=-/etc/sysconfig/ssh 21 | EnvironmentFile=-/etc/sysconfig/dracut-sshd 22 | 23 | # Start command requires the `-e` option if and only if `Type=simple` 24 | # is configured, see above. 25 | ExecStart=/usr/sbin/sshd -D $SSHD_OPTS $OPTIONS $CRYPTO_POLICY 26 | 27 | ExecReload=/bin/kill -HUP $MAINPID 28 | KillMode=process 29 | Restart=on-failure 30 | RestartSec=42s 31 | 32 | # Create privilege separation directory /run/sshd for Debian/Ubuntu 33 | RuntimeDirectory=sshd 34 | RuntimeDirectoryMode=0755 35 | 36 | [Install] 37 | WantedBy=sysinit.target 38 | -------------------------------------------------------------------------------- /46sshd/sshd_config: -------------------------------------------------------------------------------- 1 | SyslogFacility AUTHPRIV 2 | PermitRootLogin prohibit-password 3 | AuthorizedKeysFile .ssh/authorized_keys 4 | AuthenticationMethods publickey 5 | UsePAM no 6 | X11Forwarding no 7 | Subsystem sftp internal-sftp 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Copr Build Status](https://copr.fedorainfracloud.org/coprs/gsauthof/dracut-sshd/package/dracut-sshd/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/gsauthof/dracut-sshd/) 2 | [![CI Build Status](https://github.com/gsauthof/dracut-sshd/actions/workflows/e2e-trigger.yml/badge.svg)](https://github.com/gsauthof/dracut-sshd/actions/workflows/e2e-trigger.yml) 3 | 4 | This [Dracut][dracut] module (dracut-sshd) integrates the 5 | [OpenSSH][ossh] sshd into the [initramfs][iramfs]. It allows for 6 | remote unlocking of a fully encrypted root filesystem and remote 7 | access to the Dracut emergency shell (i.e. early userspace). 8 | 9 | It's compatible with systems that use Dracut as initramfs manager 10 | and systemd as init system, such as Fedora, CentOS/RHEL (version 11 | 7 or greater) and SUSE. Gentoo is also to known to work with 12 | dracut-sshd, as long as it's configured with systemd and Dracut. 13 | 14 | 2018, Georg Sauthoff , GPLv3+ 15 | 16 | ## TOC 17 | 18 | - [Example: Open Encrypted Root Filesystem](#example-open-encrypted-root-filesystem) 19 | - [Example: Emergency Shell](#example-emergency-shell) 20 | - [Install](#install) 21 | - [Space Overhead](#space-overhead) 22 | - [Host Keys](#host-keys) 23 | - [Timeout](#timeout) 24 | - [Network](#network) 25 | - [Hardware Alternatives](#hardware-alternatives) 26 | - [FAQ](#faq) 27 | - [Related Work](#related-work) 28 | - [Testing](#testing) 29 | - [Tested Environments](#tested-environments) 30 | - [Packages](#packages) 31 | 32 | ## Example: Open Encrypted Root Filesystem 33 | 34 | After booting a Fedora system with encrypted root filesystem 35 | (i.e. a filesystem on a [LUKS volume to be opened with 36 | cryptsetup][luks]) the [Dracut][dracut] [initramfs][iramfs] 37 | blocks at the password prompt. With dracut-sshd enabled remote 38 | unlocking is then as simple as: 39 | 40 | $ ssh headless.example.org 41 | -sh-4.4# systemd-tty-ask-password-agent 42 | Please enter passphrase for disk luks-123-cafe! ********* 43 | Please enter passphrase for disk luks-124-cafe! ********* 44 | -sh-4.4# Connection to 203.0.113.23 closed by remote host. 45 | Connection to 203.0.113.23 closed. 46 | 47 | That means under normal circumstances the completion of all 48 | password prompts automatically resumes the boot process. 49 | 50 | The command [`systemd-tty-ask-password-agent --list`][pwagent] prints an overview 51 | over all pending password prompts. 52 | 53 | ## Example: Emergency Shell 54 | 55 | The start of the [Dracut][dracut] emergency shell can be 56 | requested via adding `rd.break` to the kernel command line, but 57 | it also happens when Dracut is unable to mount the root 58 | filesystem or other grave issues. In such cases the emergency 59 | shell blocks the boot process. Without remote access the machine 60 | is quite dead then. 61 | 62 | Example session: 63 | 64 | $ ssh headless.example.org 65 | -sh-4.4# export TERM=vt220 66 | -sh-4.4# export SYSTEMD=FRMXK 67 | -sh-4.4# export LC_ALL=C 68 | -sh-4.4# less /run/initramfs/rdsosreport.txt 69 | -sh-4.4# journalctl -e 70 | -sh-4.4# systemctl status 71 | -sh-4.4# systemctl list-jobs 72 | 73 | After fixing potential issues the emergency shell can be terminated to resume the boot: 74 | 75 | switch_root:/root# systemctl stop dracut-emergency.service 76 | switch_root:/root# Connection to 203.0.113.23 closed by remote host. 77 | Connection to 203.0.113.23 closed. 78 | 79 | Alternatively, one can send a signal to the emergency service, e.g. 80 | with `systemctl kill ...` or `systemctl kill --signal=... ...`. 81 | 82 | ## Install 83 | 84 | Copy the `46sshd` subdirectory to the [Dracut][dracut] module directory: 85 | 86 | # cp -ri 46sshd /usr/lib/dracut/modules.d 87 | 88 | Alternatively, you can install the latest stable version from the 89 | [dracut-sshd copr repository][copr]. 90 | 91 | Either way, once present under `/usr/lib/dracut/modules.d` it's 92 | enabled, by default. 93 | 94 | With an sshd that lacks systemd support (e.g. under Gentoo), one 95 | has to adjust the systemd service file: 96 | 97 | # echo 'Skip this sed on Fedora/RHEL/CentOS/Debian/Ubuntu/...!' 98 | # sed -e 's/^Type=notify/Type=simple/' \ 99 | -e 's@^\(ExecStart=/usr/sbin/sshd\) -D@\1 -e -D@' \ 100 | -i \ 101 | /usr/lib/dracut/modules.d/46sshd/sshd.service 102 | 103 | Dracut-sshd includes the first available ssh authorized keys file of the 104 | following list into the initramfs: 105 | 106 | - /root/.ssh/dracut_authorized_keys 107 | - /etc/dracut-sshd/authorized_keys 108 | - /root/.ssh/authorized_keys 109 | 110 | Note that on some distributions such as [Fedora 111 | Silverblue][rpm-ostree] your only option is to create a keys file 112 | under `/etc/dracut-sshd` as `/root` isn't accessible during 113 | `dracut` runtime. 114 | 115 | Of course, our initramfs image needs network support. The simplest 116 | way to achieve this is to include [networkd][networkd]. To install 117 | the networkd dracut module and networkd itself: 118 | 119 | # dnf install -y dracut-network systemd-networkd 120 | 121 | When installing from copr, `dracut-network` is automatically 122 | installed as dependency. 123 | 124 | Create a non-[NetworkManager][nm] network config, e.g. via 125 | [Networkd][networkd]: 126 | 127 | ``` 128 | $ cat /etc/systemd/network/20-wired.network 129 | [Match] 130 | Name=e* 131 | 132 | [Network] 133 | DHCP=ipv4 134 | ``` 135 | 136 | Adjust the `Name=`, if necessary. 137 | 138 | Note that the dracut networkd module doesn't include the system's 139 | network configuration files by default and note that the module 140 | isn't enabled, by default, either. Thus, you have to configure 141 | Dracut for networkd (cf. the [install_items][iitems] and 142 | [add_dracutmodules][addmod] directives). Example: 143 | 144 | ``` 145 | # cat /etc/dracut.conf.d/90-networkd.conf 146 | install_items+=" /etc/systemd/network/20-wired.network " 147 | add_dracutmodules+=" systemd-networkd " 148 | ``` 149 | 150 | Alternatively, early boot network connectivity can be configured 151 | by other means (i.e. kernel parameters, see below). However, 152 | the author of this README strongly recommends to use Networkd 153 | instead of NetworkManager on servers and server-like systems. 154 | 155 | If the above example is sufficient you just need to copy the 156 | example configuration files from the `example/` subdirectory: 157 | 158 | # cp example/20-wired.network /etc/systemd/network 159 | # cp example/90-networkd.conf /etc/dracut.conf.d 160 | 161 | Finally regenerate the initramfs: 162 | 163 | # dracut -f -v 164 | 165 | Note that Ubuntu's dracut defaults to an initramfs filename that 166 | is incompatible with Ubuntu's grub default initrd settings ... m( 167 | Thus, on Ubuntu one has to explicitly specify the initramfs filename like this: 168 | 169 | # dracut -f -v /boot/initrd.img-$(uname -r) 170 | 171 | Verify that this `sshd` module is included. Either via inspecting the verbose 172 | output or via `lsinitrd`, e.g.: 173 | 174 | # lsinitrd | grep 'authorized\|bin/sshd\|network/20' 175 | -rw-r--r-- 1 root root 119 Jul 17 15:08 etc/systemd/network/20-wired.network 176 | -rw------- 1 root root 99 Jul 17 17:04 root/.ssh/authorized_keys 177 | -rwxr-xr-x 1 root root 876328 Jul 17 17:04 usr/sbin/sshd 178 | 179 | Finally, reboot. 180 | 181 | 182 | ## Space Overhead 183 | 184 | The space overhead of the [Dracut][dracut] sshd module is 185 | negligible: 186 | 187 | enabled modules initramfs size 188 | -------------------------------------- 189 | vanilla -network -ifcfg 16 MiB 190 | +systemd-networkd 17 MiB 191 | +systemd-networkd +sshd 19 MiB 192 | +network +ifcfg 21 MiB 193 | +network +ifcfg +sshd 21 MiB 194 | +network +ifcfg +sshd 22 MiB 195 | +systemd-networkd 196 | 197 | (all numbers from a Fedora 28 system, measuring the compressed 198 | initramfs size) 199 | 200 | Technically, the [`systemd-networkd`][networkd] Dracut module is 201 | sufficient for establishing network connectivity. It even 202 | includes the `ip` command. Since the network Dracut module is 203 | included by default (under CentOS 7/Fedora 27/28) via the 204 | [ifcfg][ifcfg] 205 | Dracut module, it may make sense to explicitly exclude it when 206 | building the initramfs on a system where networkd is available, 207 | e.g. via 208 | 209 | dracut -f -v --omit ifcfg 210 | 211 | as this saves a few megabytes. 212 | 213 | Since the [initramfs][iramfs] is actually loaded into a 214 | [tmpfs][tmpfs] that is [freed during switch-root][switchroot] it 215 | doesn't really pay off to safe a few mega-/kilobytes in the 216 | initramfs. A few KiBs could be saved via switching from 217 | [OpenSSH][ossh]'s sshd to something like [Dropbear][dropbear], 218 | but such an alternative sshd server is likely less well audited 219 | for security issues and supports less features (e.g. ssh-ed25519 220 | public key authentication was only [added as late as 221 | 2020][drop25519], and, as of 2021, there are some [interoperability 222 | issues][drop25519b] and [ed25519-sk keys aren't 223 | supported][dropsk]). 224 | 225 | Last but not least, in times where even embedded systems feature 226 | hundreds of megabytes RAM, temporarily occupying a few extra 227 | KiBs/MiBs before switch root has no dramatic impact. 228 | 229 | ## Host Keys 230 | 231 | By default, this module includes the system's 232 | `/etc/ssh/ssh_host_*_key` private host keys into the 233 | [initramfs][iramfs]. Note that this doesn't decrease the security 234 | in comparison with a system whose root filesystem is unencrypted: 235 | 236 | - the generated initramfs image under /boot is only readable by 237 | the root user 238 | - if an attacker is able to access the /boot/initramfs file (e.g. 239 | by booting the machine from a Live stick) then she is also able 240 | to access all host keys on a unencrypted root filesystem 241 | 242 | That said, if `/etc/ssh/dracut_ssh_host_*_key{,.pub}` 243 | files are present then those are included, instead. 244 | 245 | As always, it depends on your threat model, whether it makes 246 | sense to use an extra host key for the initramfs or not. Using an 247 | extra key may complicate the life of an attacker who is able to 248 | read out the initramfs content but is unable to change it and 249 | thus the attacker has to wait for the next SSH connection to the 250 | initramfs before being able to perform a [MITM attack][mitm]. On 251 | the other hand, when the attacker is able to change to initramfs 252 | image then an extra key doesn't provide more security than using 253 | the system's host key as the attacker can intercept the entered 254 | password, anyway. 255 | 256 | If your primary threat model is an attacker who gets access to 257 | decommissioned but still readable hard-disks, then the system's 258 | host key in the initramfs image provides no value to the 259 | attacker given that the root filesystem is fully encrypted (and 260 | that the host key isn't reused in the replacement system). 261 | 262 | ## Timeout 263 | 264 | With recent Fedora versions (e.g. Fedora 28) a cryptsetup 265 | password prompt shouldn't timeout. If it does then it's a 266 | regression (cf. [Bug 868421][bug868421]). Even if it times out 267 | and [Dracut][dracut] drops into the emergency shell then remotely 268 | connecting to it should still work with this module. In such 269 | situations [`systemd-tty-ask-password-agent`][pwagent] should 270 | still work. See also Section 'Example: Emergency Shell' on how 271 | to resume the boot process then. 272 | 273 | A simple way to trigger the timeout is to enter the wrong 274 | password 3 times when unlocking a LUKS volume. Under Fedora 28, 275 | the timeout is then 2 minutes or so long, i.e. the emergency 276 | shell is then started after 2 minutes, by default, even without 277 | explicitly adding `rd.shell` to the kernel command line. One can 278 | recover from such a situation with e.g.: 279 | 280 | # systemctl restart 'systemd-cryptsetup@*' 281 | 282 | Another example for the emergency shell getting started is that 283 | a device that is necessary for mounting the root filesystem 284 | simply isn't attached - or the UUIDs specified on the kernel 285 | command line don't match. After inspecting the situation with 286 | `systemctl status ...`, `journalctl -e`, etc. one can 287 | regenerate some config and restart the appropriate services in a 288 | similar fashion. 289 | 290 | ## Network 291 | 292 | An alternative to the [networkd][networkd] configuration is to 293 | configure network via additional [Dracut command line 294 | parameters][dracut-cmdline]. 295 | 296 | This requires the activation of the network dracut module, e.g.: 297 | 298 | # cat /etc/dracut.conf.d/90-network.conf 299 | add_dracutmodules+=" network " 300 | 301 | On systems without networkd (e.g. CentOS 7/RHEL 8) this is the only way 302 | to enable network connectivity in early userspace. For example, 303 | the following parameters enable DHCP on all network interfaces in 304 | early userspace: 305 | 306 | rd.neednet=1 ip=dhcp 307 | 308 | They need to be appended to `GRUB_CMDLINE_LINUX=` in 309 | `/etc/default/grub` and to be effective the Grub config then 310 | needs to be regenerated: 311 | 312 | # grub2-editenv - unset kernelopts # required on rhel8 313 | # grub2-mkconfig -o /etc/grub2.cfg 314 | # grub2-mkconfig -o /etc/grub2-efi.cfg 315 | # grub2-mkconfig -o /etc/grub2.cfg --update-bls-cmdline # rhel9.5 316 | # grub2-mkconfig -o /etc/grub2-efi.cfg --update-bls-cmdline # rhel9.5 317 | 318 | Note that on distributions like CentOS 7/Fedora 27/28 there is 319 | also the old-school [ifcfg][ifcfg] network scripts system under 320 | `/etc/sysconfig/network-scripts` that can be used instead of 321 | [NetworkManager][nm]. It can be launched via the auto-generated 322 | `network` service that calls the old sysv init.d script. However, 323 | the network Dracut module doesn't include neither this service 324 | nor the network-scripts configuration (it includes some of the 325 | scripts but the Dracut modules auto-generate the configuration 326 | during early userspace boot based on the kernel 327 | command line/detected hardware). With CentOS 7/Fedora 27/28 the 328 | default network configuration (in late userspace) uses 329 | NetworkManager which only uses the `ifcfg-*` files under 330 | `/etc/sysconfig/network-scripts`. 331 | 332 | The `grub2-editenv` call is only necessary on systems (such as 333 | RHEL 8) where the kernel parameters are stored in `/etc/grubenv` 334 | instead of in each menu entry (either in the main `grub2.cfg` or 335 | under `/boot/loader/entries` if the system follows the [boot 336 | loader specification (bls)][bls]). 337 | 338 | 339 | ## Hardware Alternatives 340 | 341 | A Baseboard Management Controller (BMC) or some kind of [remote KVM][kvm] 342 | device can help with early boot issues, however: 343 | 344 | - not all remote machines even have a BMC 345 | - the BMC often is quite tedious to use and buggy 346 | - the BMC often contains low quality proprietary software that is 347 | never updated and likely contains many security issues 348 | - in some hosting environments a KVM must be manually attached 349 | and is charged at an hourly rate. That means you end up paying 350 | the remote hands for attaching the KVM, plus possibly an extra 351 | charge if you need it outside business hours and the hourly rate. 352 | 353 | Thus, as a general rule, one wants to avoid a BMC/KVM as much as 354 | possible. 355 | 356 | ## FAQ 357 | 358 | - How to upgrade the dracut-sshd [Copr][copr] repository? 359 | 360 | A: As of 2024, the Copr integration into Fedora doesn't 361 | follow key rotations of the Copr platform. 362 | Thus, it's necessary to explicitly upgrade to the new Copr key, from time to time, 363 | e.g. by simply removing and adding again the dracut-sshd Copr 364 | repository. 365 | For example, after each Fedora system upgrade. 366 | See also the [Copr upstream bug](https://github.com/fedora-copr/copr/issues/2894) 367 | and [dracut-sshd issues where this problem and workarounds are discussed](https://github.com/gsauthof/dracut-sshd/issues/82#issuecomment-2272302987). 368 | - How to make the early boot sshd listen on a non-standard port? 369 | 370 | A: If you really [want to do that][port] you can provide a 371 | `/etc/sysconfig/dracut-sshd` that defines `SSHD_OPTS` 372 | ([see also][port]). 373 | - Why does sshd hangs during early-boot when running dracut-sshd 374 | inside a virtual machine (VM)? 375 | 376 | A: Most likely the VM guest is short of entropy and thus sshd 377 | blocks during startup (without logging a warning) for an 378 | indefinite amount of time. Possible up to the systemd service 379 | restart timeout. Directing some of the VM host's entropy into 380 | the VM guest fixes this issue ([cf. these comments for 381 | examples of how to do this][entropy]). 382 | - Why do I get `Permission denied (publickey)` although the same 383 | authorized key works after the system is booted? 384 | 385 | A: This can be caused by a root account that is locked with `!` 386 | instead of `*`. In that case it's enough to change the lock 387 | method (or set a password) and regenerate the initramfs. 388 | Background: On some systems Dracut also includes `/etc/shadow` 389 | which is then used by sshd. In early userspace, there is no 390 | PAM, thus sshd uses built-in code for shadow handling. In 391 | contrast to usual PAM configuration (which is used by late 392 | userspace sshd, by default), sshd itself differentiates 393 | between `*` and `!` as invalid password field tokens. Meaning 394 | that only `*` allows public key authentication while `!` blocks 395 | any login ([see also][i30]). 396 | - Can I use dracut-sshd when my root account is locked? 397 | 398 | A: Yes, you can. 399 | However, you have to make sure that your account isn't locked 400 | with a `!` in `/etc/shadow`. If it is locked like that, you 401 | have to lock it differently, e.g. via `usermod -p '*' root` 402 | or simply set a strong password for the root user, followed 403 | by `dracut -f`. 404 | See also the previous question for additional details. 405 | - Does dracut-sshd only work with networkd? 406 | 407 | A: No, it doesn't. 408 | Dracut-sshd is network service agnostic. 409 | It just requires the network being online during early boot. 410 | Depending on the distribution, there might be different 411 | alternatives available for bringing network 412 | interfaces up early, such as Systemd's networkd, legacy network 413 | scripts, NetworkManager etc. 414 | A given distribution and release might support one of those 415 | or many, and default to one of them when the `network` dracut 416 | module is included. 417 | Besides selecting a specific dracut network module, there are 418 | also dracut cmdline parameters for configuring network options 419 | and addresses. 420 | Depending on your concrete network setup and distribution, a 421 | certain network module might be more suitable than another. 422 | In general, it isn't an issue to use one network service during 423 | early boot and another for late boot (e.g. networkd and 424 | NetworkManager). 425 | The same goes for configurations, e.g. perhaps for early boot a 426 | simple DHCP setups makes most sense while in late boot you have a 427 | more complicated network configuration. 428 | - How do I make it work on Ubuntu 20.04? 429 | 430 | A: There are some pitfalls on Ubuntu. Firstly, dracut isn't 431 | installed by default (fix: `apt install dracut-core 432 | dracut-network`). Secondly, dracut isn't a first class citizen 433 | on Ubuntu (i.e. it's only included in the universe repository, 434 | not in the main repository). As a result, the default dracut 435 | initramfs filename doesn't match what Ubuntu uses in its 436 | Grub configuration. Thus, you have to explicitly specify 437 | the right one (i.e. `/boot/initrd.img-$(uname -r)`) in the 438 | `dracut` and `lsinitrd` commands. 439 | - How do I debug dracut-sshd issues in the early boot 440 | environment? 441 | 442 | A: You start by dropping into the dracut emergency shell and 443 | looking at the journal and status of the involved services. 444 | For example, via `systemctl status sshd.service`, `journalctl 445 | -u sshd` etc. You drop into the emergency shell by adding 446 | `rd.break` (and possibly `rd.shell`) to kernel parameter 447 | command-line. Of course, you need some kind of console 448 | access when doing such debugging. Using a virtual machine 449 | usually is sufficient to reproduce issues which simplifies 450 | things. 451 | 452 | ## Related Work 453 | 454 | There is the [unmaintained][cryptssh-unm] (since 2019 or earlier) 455 | [dracut-crypt-ssh][cryptssh] module which aimed to provide SSH 456 | access for remotely unlocking an encrypted LUKS volume. Main 457 | differences to dracut-sshd: 458 | 459 | - uses [Dropbear][dropbear] instead of [OpenSSH][ossh] sshd (cf. the Space 460 | Overhead Section for the implications) 461 | - doesn't use [systemd][systemd] for starting/stopping the Dropbear daemon 462 | - generates a new set of host keys, by default 463 | - listens on a non-standard port for ssh, by default 464 | - arguably more complex than dracut-sshd - certainly more lines 465 | of code and some options 466 | - comes with an unlock command that is superfluous in the 467 | presence of [`systemd-tty-ask-password-agent`][pwagent] - and it's kind of 468 | dangerous to use, e.g. when the password prompt times out the 469 | password is echoed to the console 470 | 471 | In 2017, a [dracut-crypt-ssh pull request][cryptssh-uwe] added 472 | support for optionally using OpenSSH's sshd instead of Dropbear, 473 | without changing the other differences. It was closed without 474 | being merged in 2021. 475 | 476 | There are also some other dracut modules that use Dropbear: 477 | [mk-fg/dracut-crypt-sshd][mkfg] which was marked 478 | deprecated in 2016 in favour of the above dracut-crypt-ssh. It 479 | uses Dropbear and some console hacks instead of 480 | `systemd-tty-ask-password-agent`. 481 | [mdcurtis/dracut-earlyssh][mdcurtis] is a fork 482 | mk-fg/dracut-crypt-sshd. The main difference is that it also 483 | suppports RHEL 6 (which features a quite different version of 484 | dracut). [xenoson/dracut-earlyssh][xenoson] is a fork of 485 | mdcurtis/dracut-earlyssh. It has RHEL 6 support removed and some 486 | questionable helpers removed. It creates a systemd unit file for 487 | Dropbear although it still explicitly starts/stops it via hook 488 | files instead of making use of the systemd dependency features. 489 | 490 | The [ArchWiki dm-crypt page][arch] lists two initramfs hooks for 491 | remote access. Both don't use [Dracut][dracut] nor systemd, 492 | though. Also, they use Dropbear and Tinyssh as ssh daemon. 493 | 494 | Another initramfs (non-dracut) hook script, but targeting Debian 495 | systems, is [UnLUKS][unluks]. Similar to the Arch scripts it 496 | starts the SSH daemon directly from the hook script into the 497 | background, i.e. without any systemd integration. However, in 498 | contrast to the Arch scripts it uses stock OpenSSH sshd. 499 | 500 | [Clevis][clevis], an automatic decryption framework, has some 501 | [LUKS][luks] unlocking and Dracut support. Looking at its documentation, 502 | when it comes to automatic LUKS unlocking, the LUKS passphrase is 503 | stored encrypted in the LUKS header. Clevis then decrypts it 504 | using an external service/hardware (e.g. a [Tang][tang] server 505 | or a [TPM] module). 506 | 507 | Similar to Clevis, [Mandos][mandos] also implements a framework 508 | for unattended LUKS unlocking. Unlike Clevis, it primarily 509 | targets Debian and doesn't support TPM. That means for unlocking 510 | the Mandos client fetches the asymmetrically encrypted LUKS 511 | password from a Mandos server. 512 | 513 | With version 248 (i.e. available since early 2021 or so), 514 | [systemd integrated some automatic LUKS2 volume unlocking 515 | features][systemd248]. Similar to Clevis it supports TPM2 modules. 516 | In addition, it also supports smart cards and FIDO2/hmac-secret 517 | devices. At least some of those FIDO2 devices seem to support 518 | non-interactive HMAC computation and thus allow to auto-unlock 519 | LUKS volumes as long as the enrolled FIDO2 device is connected. 520 | 521 | If your threat model goes beyond what is described in the [Host 522 | Keys](#host-keys) Section, you have to look into [authenticated 523 | boot and disk encryption][authboot]. 524 | 525 | Although enterprise motherboard and server vendors often 526 | integrate unpleasant BMCs (cf. the [Hardware Alternatives 527 | Section](#hardware-alternatives)), a hardware solution for remote 528 | access to early boot doesn't have to be awful. For example, there is 529 | the open and DIY [Pi-KVM][pikvm] project which looks quite 530 | promising. 531 | 532 | Related Fedora ticket: [Bug 524727 - Dracut + encrypted root + networking (2009)][bug524727] 533 | 534 | 535 | ## Testing 536 | 537 | The `test` sub-directory contains an end-to-end test suite that 538 | downloads the latest available Fedora cloud image (from a release 539 | branch), creates a libvirt VM from it (using 540 | [virt-install](https://virt-manager.org/) and 541 | [libvirt](https://libvirt.org/)), encrypts the root filesystem 542 | (via [guestfish](https://libguestfs.org/)), installs current 543 | dracut-sshd, including the sample configuration snippets from the 544 | `example` directory, and verifies that the resulting system can be 545 | remotely unlocked over ssh and thus fully booted. 546 | 547 | Example usage: 548 | 549 | ``` 550 | cd /temp_directory_with_enough_space 551 | tdir=/path_to_dracut_sshd_repo/test 552 | $tdir/get-fedora.sh 41 553 | ls -l f41-latest.x86_64.qcow2 554 | $tdir/e2e.sh 41 555 | echo $? 556 | ``` 557 | 558 | Since the test scripts aren't overly long, they can also be used 559 | as a reference for how to install dracut-sshd, how to use it 560 | for unlocking, and even how to transform a vanilla Fedora system 561 | into an encrypted without having to re-install it from scratch. 562 | 563 | 564 | ## Tested Environments 565 | 566 | - Fedora Silverblue 33 567 | - Fedora 27 to 41 568 | - CentOS 7, 8 569 | - CentOS Stream 9 (by a contributor) 570 | - RHEL 8 beta 1 571 | - Rocky Linux 8.8, 9 (by a contributor) 572 | - Gentoo (by a contributor) 573 | - SUSE (by a contributor) 574 | - openSUSE Leap 15.5 575 | - Arch (by a contributor) 576 | - Ubuntu 20.04 LTS 577 | - Debian 12 (by a contributor) 578 | 579 | 580 | ## Packages 581 | 582 | - [Copr][copr] - for Fedora, EPEL (i.e. RHEL or RHEL clones such 583 | as AlmaLinux or Rocky) 584 | - [openSUSE](https://build.opensuse.org/package/show/openSUSE:Factory/dracut-sshd) 585 | - [Arch AUR](https://aur.archlinux.org/packages/dracut-sshd-git) 586 | 587 | 588 | [arch]: https://wiki.archlinux.org/index.php/Dm-crypt/Specialties#Remote_unlocking_.28hooks:_netconf.2C_dropbear.2C_tinyssh.2C_ppp.29 589 | [bls]: https://systemd.io/BOOT_LOADER_SPECIFICATION 590 | [bug524727]: https://bugzilla.redhat.com/show_bug.cgi?id=524727 591 | [bug868421]: https://bugzilla.redhat.com/show_bug.cgi?id=868421 592 | [clevis]: https://github.com/latchset/clevis 593 | [copr]: https://copr.fedorainfracloud.org/coprs/gsauthof/dracut-sshd/ 594 | [cryptssh]: https://github.com/dracut-crypt-ssh/dracut-crypt-ssh 595 | [cryptssh-uwe]: https://github.com/dracut-crypt-ssh/dracut-crypt-ssh/pull/17 596 | [cryptssh-unm]: https://github.com/dracut-crypt-ssh/dracut-crypt-ssh/issues/43 597 | [dracut]: https://dracut.wiki.kernel.org/index.php/Main_Page 598 | [dracut-cmdline]: https://manpath.be/f32/7/dracut.cmdline 599 | [dropbear]: https://en.wikipedia.org/wiki/Dropbear_(software) 600 | [drop25519]: https://github.com/mkj/dropbear/pull/91 601 | [drop25519b]: https://github.com/mkj/dropbear/issues/136#issuecomment-913134728 602 | [dropsk]: https://github.com/mkj/dropbear/issues/135 603 | [ifcfg]: https://www.centos.org/docs/5/html/Deployment_Guide-en-US/s1-networkscripts-interfaces.html 604 | [iramfs]: https://en.wikipedia.org/wiki/Initial_ramdisk 605 | [kvm]: https://en.wikipedia.org/wiki/KVM_switch#Remote_KVM_devices 606 | [luks]: https://gitlab.com/cryptsetup/cryptsetup 607 | [mitm]: https://en.wikipedia.org/wiki/Man-in-the-middle_attack 608 | [mkfg]: https://github.com/mk-fg/dracut-crypt-sshd 609 | [mdcurtis]: https://github.com/mdcurtis/dracut-earlyssh 610 | [xenoson]: https://github.com/xenoson/dracut-earlyssh 611 | [networkd]: https://wiki.archlinux.org/index.php/systemd-networkd 612 | [nm]: https://wiki.archlinux.org/index.php/NetworkManager 613 | [ossh]: https://en.wikipedia.org/wiki/OpenSSH 614 | [pwagent]: https://manpath.be/f32/1/systemd-tty-ask-password-agent 615 | [systemd]: https://en.wikipedia.org/wiki/Systemd 616 | [systemd248]: http://0pointer.net/blog/unlocking-luks2-volumes-with-tpm2-fido2-pkcs11-security-hardware-on-systemd-248.html 617 | [switchroot]: https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt 618 | [tmpfs]: https://en.wikipedia.org/wiki/Tmpfs 619 | [tpm]: https://en.wikipedia.org/wiki/Trusted_Platform_Module 620 | [addmod]: https://manpath.be/f32/dracut/050-26.git20200316.fc32.x86_64/5/dracut.conf#L74 621 | [port]: https://github.com/gsauthof/dracut-sshd/issues/9#issuecomment-531308602 622 | [entropy]: https://github.com/gsauthof/dracut-sshd/issues/12 623 | [iitems]: https://manpath.be/f32/dracut/050-26.git20200316.fc32.x86_64/5/dracut.conf#L74 624 | [i30]: https://github.com/gsauthof/dracut-sshd/issues/30 625 | [rpm-ostree]: https://discussion.fedoraproject.org/t/using-dracut-sshd-to-unlock-a-luks-encrypted-system/23449/6 626 | [pikvm]: https://github.com/pikvm/pikvm 627 | [authboot]: https://0pointer.net/blog/authenticated-boot-and-disk-encryption-on-linux.html 628 | [tang]: https://github.com/latchset/tang 629 | [mandos]: https://www.recompile.se/mandos 630 | [unluks]: https://github.com/BarbarossaTM/fluffy-unluks 631 | -------------------------------------------------------------------------------- /dracut-sshd.spec: -------------------------------------------------------------------------------- 1 | Name: {{{ git_dir_name }}} 2 | # The git_dir_version macro is quite a mis-match for our use-case 3 | # since using a 3-component version number requires updating 4 | # the 'lead' parameter, anyways 5 | # cf. https://pagure.io/rpkg-util/issue/21#comment-601077 6 | #Version: {{{ git_dir_version }}} 7 | Version: 0.7.0 8 | Release: 1%{?dist} 9 | Summary: Provide SSH access to initramfs early user space 10 | URL: https://github.com/gsauthof/dracut-sshd 11 | License: GPLv3+ 12 | VCS: {{{ git_dir_vcs }}} 13 | Source: {{{ git_dir_pack }}} 14 | BuildArch: noarch 15 | Requires: dracut-network 16 | 17 | %description 18 | This Dracut module integrates the OpenSSH sshd into your 19 | initramfs. It allows for remote unlocking of a fully encrypted 20 | root filesystem and remote access to the Dracut emergency shell 21 | (i.e. early userspace). 22 | 23 | %prep 24 | {{{ git_dir_setup_macro }}} 25 | 26 | %build 27 | # nothing to do here 28 | 29 | %install 30 | mkdir -p %{buildroot}/usr/lib/dracut/modules.d 31 | cp -r 46sshd %{buildroot}/usr/lib/dracut/modules.d/ 32 | 33 | %files 34 | %dir /usr/lib/dracut/modules.d/46sshd 35 | /usr/lib/dracut/modules.d/46sshd/module-setup.sh 36 | /usr/lib/dracut/modules.d/46sshd/sshd.service 37 | /usr/lib/dracut/modules.d/46sshd/motd 38 | /usr/lib/dracut/modules.d/46sshd/profile 39 | %config(noreplace) /usr/lib/dracut/modules.d/46sshd/sshd_config 40 | %doc README.md 41 | %doc example/20-wired.network 42 | %doc example/90-networkd.conf 43 | 44 | %changelog 45 | * Mon Apr 28 2025 Georg Sauthoff - 0.7.0-1 46 | - support openssh 10 47 | 48 | * Sat Aug 08 2024 Georg Sauthoff - 0.6.7-1 49 | - support recent sshd versions 50 | - enable sftp access 51 | 52 | * Sun Jun 18 2023 Georg Sauthoff - 0.6.6-1 53 | - update docs and add directory to files list 54 | 55 | * Sat May 27 2023 Georg Sauthoff - 0.6.5-1 56 | - eliminate tmpfiles and fix Debian/Ubuntu support 57 | 58 | * Sun May 7 2023 Georg Sauthoff - 0.6.4-1 59 | - fix motd 60 | 61 | * Sat May 1 2021 Georg Sauthoff - 0.6.3-1 62 | - fix privilege separation directory for Fedora 34 63 | 64 | * Sun Nov 22 2020 Akos Balla - 0.6.2-2 65 | - support Fedora Silverblue 66 | - add motd/profile files 67 | 68 | * Sat Oct 31 2020 Georg Sauthoff - 0.6.2-1 69 | - check whether key is included 70 | 71 | * Thu May 28 2020 Georg Sauthoff - 0.6.1-2 72 | - add example dracut config 73 | 74 | * Thu May 28 2020 Georg Sauthoff - 0.6.1-1 75 | - eliminate dracut module dependencies 76 | - don't auto-include networkd configurations, anymore 77 | - auto-include sshd executable dependencies 78 | 79 | * Sat Jan 26 2019 Georg Sauthoff - 0.4-1 80 | - initial packaging 81 | -------------------------------------------------------------------------------- /example/20-wired.network: -------------------------------------------------------------------------------- 1 | # Generic networkd configuration 2 | # copy to /etc/systemd/network/20-wired.network 3 | 4 | [Match] 5 | Name=e* 6 | 7 | [Network] 8 | DHCP=ipv4 9 | -------------------------------------------------------------------------------- /example/90-networkd.conf: -------------------------------------------------------------------------------- 1 | # copy to e.g. /etc/dracut.conf.d/90-networkd.conf 2 | 3 | # You may add any other networkd files by appending them 4 | # to install_items 5 | install_items+=" /etc/systemd/network/20-wired.network " 6 | 7 | # the networkd dracut module is only enabled on request, thus: 8 | add_dracutmodules+=" systemd-networkd " 9 | 10 | # When networkd isn't available include the network Dracut module instead: 11 | # add_dracutmodules+=" network " 12 | -------------------------------------------------------------------------------- /test/conf.sh: -------------------------------------------------------------------------------- 1 | 2 | 3 | release=$1 4 | 5 | if [ $# -gt 1 ]; then 6 | distri=$2 7 | else 8 | distri=f 9 | fi 10 | 11 | src="$distri$release"-latest.x86_64.qcow2 12 | tag="$distri$release"-dracut-sshd-test 13 | dst="$tag".qcow2 14 | 15 | key=$PWD/ssh-user 16 | 17 | sshflags="-oIdentityFile=$key -oUserKnownHostsFile=$PWD/known_hosts -oUpdateHostKeys=no -oAddKeysToAgent=no" 18 | ssh="ssh $sshflags" 19 | scp="scp $sshflags" 20 | 21 | function get_addr 22 | { 23 | python -c 'import sys; import libvirt; con = libvirt.open("qemu:///system"); dom = con.lookupByName(sys.argv[1]); print(dom.interfaceAddresses(libvirt.VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE).popitem()[1]["addrs"][-1]["addr"])' "$1" 24 | } 25 | 26 | function sync_shutdown 27 | { 28 | local tag=$1 29 | virsh --connect qemu:///system shutdown "$tag" 30 | for ((i=0; i<3; ++i)); do 31 | if [ "$(virsh --connect qemu:///system domstate "$tag")" = 'shut off' ]; then 32 | return 0 33 | fi 34 | sleep 1 35 | done 36 | echo "$tag not shut down in time ..." >&2 37 | return 1 38 | } 39 | 40 | function sync_poweron 41 | { 42 | local tag=$1 43 | virsh --connect qemu:///system start "$tag" 44 | for ((i=0; i<3; ++i)); do 45 | if [ "$(virsh --connect qemu:///system domstate "$tag")" = running ]; then 46 | return 0 47 | fi 48 | sleep 1 49 | done 50 | echo "$tag not started in time ..." >&2 51 | return 1 52 | } 53 | 54 | function wait4sshd 55 | { 56 | local tag=$1 57 | sleep 3 58 | for ((i=0;i<10;++i)); do 59 | local guest=$(get_addr "$tag") 60 | if [ "$guest" ]; then 61 | echo "$guest $(cat host-key-ed25519.pub)" > known_hosts 62 | if [ "$(socat -u -T2 tcp:"$guest":22,connect-timeout=2,readbytes=5 stdout 2>/dev/null)" = "SSH-2" ] && $ssh root@"$guest" uname > /dev/null; then 63 | echo ' done' 64 | break 65 | fi 66 | fi 67 | echo -n . 68 | sleep 2 69 | done 70 | if [ -z "$guest" ]; then 71 | echo "Couldn't get IP address of guest $tag ..." >&2 72 | return 1 73 | fi 74 | return 0 75 | } 76 | -------------------------------------------------------------------------------- /test/create-vm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eux 4 | 5 | mydir=$(dirname -- "${BASH_SOURCE[0]}") 6 | . "$mydir"/conf.sh 7 | 8 | 9 | if virsh --connect qemu:///system domid "$tag" ; then 10 | if [ "$(virsh --connect qemu:///system domstate "$tag")" = running ]; then 11 | virsh --connect qemu:///system shutdown "$tag" 12 | sync_shutdown "$tag" || true 13 | fi 14 | virsh --connect qemu:///system undefine "$tag" 15 | fi 16 | if virsh --connect qemu:///system domid "$tag" ; then 17 | virsh --connect qemu:///system destroy "$tag" 18 | fi 19 | 20 | rm -f "$dst" 21 | qemu-img create -F qcow2 -b "$src" -f qcow2 "$dst" 10g 22 | 23 | cat < cloud-init.yml 24 | #cloud-config 25 | users: 26 | - name: root 27 | ssh-authorized-keys: 28 | - $(cat ssh-user.pub) 29 | disable_root: false 30 | ssh_deletekeys: true 31 | ssh_genkeytypes: ['ed25519'] 32 | ssh_keys: 33 | ssh_deletekeys: true 34 | ssh_genkeytypes: ['ed25519'] 35 | ed25519_private: | 36 | $(sed 's/^/ /' host-key-ed25519) 37 | ed25519_public: $(cat host-key-ed25519.pub) 38 | EOF 39 | 40 | virt-install --connect qemu:///system \ 41 | --name "$tag" \ 42 | --memory 2048 \ 43 | --network default \ 44 | --cpu host --vcpus 2 \ 45 | --graphics none \ 46 | --autoconsole none \ 47 | --import \ 48 | --disk "$dst",format=qco2,bus=virtio \ 49 | --osinfo fedora-unknown \ 50 | --cloud-init user-data=cloud-init.yml 51 | 52 | 53 | -------------------------------------------------------------------------------- /test/e2e.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eux 4 | 5 | mydir=$(dirname -- "${BASH_SOURCE[0]}") 6 | . "$mydir"/conf.sh 7 | 8 | # XXX download latest stable/rawhide 9 | # "$mydir"/get-fedora.sh "$release" 10 | 11 | if [ ! -e ssh-user.pub -o ! -e ssh-user ]; then 12 | ssh-keygen -t ed25519 -f $PWD/ssh-user -N '' 13 | fi 14 | if [ ! -e host-key-ed25519 ]; then 15 | ssh-keygen -t ed25519 -N '' -C '' -f host-key-ed25519 16 | fi 17 | if [ ! -e pw.log ]; then 18 | goxkcdpwgen -d - > pw.log 19 | fi 20 | 21 | 22 | "$mydir"/create-vm.sh "$@" 23 | 24 | wait4sshd "$tag" 25 | 26 | "$mydir"/install-dracut-sshd.sh "$@" 27 | 28 | sync_shutdown "$tag" 29 | 30 | if [ "$distri" = f ]; then 31 | "$mydir"/encrypt-fedora.sh "$@" 32 | else 33 | "$mydir"/encrypt-rhel.sh "$@" 34 | fi 35 | "$mydir"/update-grub.sh "$@" 36 | 37 | sync_poweron "$tag" 38 | wait4sshd "$tag" 39 | 40 | "$mydir"/unlock.sh "$@" 41 | 42 | wait4sshd "$tag" 43 | 44 | "$mydir"/verify-boot.sh "$@" 45 | -------------------------------------------------------------------------------- /test/encrypt-fedora.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eux 4 | 5 | mydir=$(dirname -- "${BASH_SOURCE[0]}") 6 | . "$mydir"/conf.sh 7 | 8 | 9 | rm -f tmp.img 10 | qemu-img create -f qcow2 tmp.img 10g 11 | 12 | guestfish -x -a "$dst" -a tmp.img --keys-from-stdin <&2 15 | exit 1 16 | fi 17 | 18 | curl -sSf -o AlmaLinux-"$release"-GenericCloud-latest.x86_64.qcow2 https://repo.almalinux.org/almalinux/"$release"/cloud/x86_64/images/AlmaLinux-"$release"-GenericCloud-latest.x86_64.qcow2 19 | 20 | 21 | csum=$(sha256sum AlmaLinux-"$release"-GenericCloud-latest.x86_64.qcow2 | awk '{ print $1}') 22 | 23 | 24 | curl -sSf -o checksum-"$release" https://repo.almalinux.org/almalinux/"$release"/cloud/x86_64/images/CHECKSUM 25 | 26 | name=$(grep -v latest checksum-"$release" | awk '/^'"$csum"'/ { print $2 }' | head -n 1 | tr -d -c 'a-zA-Z0-9._-') 27 | 28 | if [ -z "$name" ]; then 29 | echo "Unexpected target name: $name" >&2 30 | exit 1 31 | fi 32 | 33 | mv AlmaLinux-"$release"-GenericCloud-latest.x86_64.qcow2 "$name" 34 | 35 | ln -sf "$name" alma"$release"-latest.x86_64.qcow2 36 | 37 | echo "$release" > alma-release 38 | -------------------------------------------------------------------------------- /test/get-fedora.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eux 4 | 5 | release= 6 | 7 | if [ $# -gt 0 ]; then 8 | release=$1 9 | fi 10 | 11 | 12 | if [ "$release" = rawhide ]; then 13 | img_url=$(curl -sSf https://openqa.fedoraproject.org/nightlies.html | awk -F '"' ' /^ f-release 61 | -------------------------------------------------------------------------------- /test/install-dracut-sshd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eux 4 | 5 | mydir=$(dirname -- "${BASH_SOURCE[0]}") 6 | . "$mydir"/conf.sh 7 | 8 | 9 | dracut_dir="$mydir"/.. 10 | guest=$(get_addr "$tag") 11 | 12 | 13 | pushd "$dracut_dir" 14 | 15 | $scp -r 46sshd root@"$guest":/usr/lib/dracut/modules.d/ 16 | 17 | 18 | if [ "$distri" = f ]; then 19 | $scp example/20-wired.network root@"$guest":/etc/systemd/network/20-wired.network 20 | $scp example/90-networkd.conf root@"$guest":/etc/dracut.conf.d/90-networkd.conf 21 | 22 | $ssh root@"$guest" <> /etc/systemd/network/20-wired.network 26 | dnf -y install dracut-network 27 | 28 | # not strictly necessary, but keeps the IP-address of the guest stable 29 | systemctl disable NetworkManager 30 | systemctl mask NetworkManager 31 | systemctl enable systemd-networkd 32 | 33 | dracut -f -v 34 | EOF 35 | else # RHEL, Alma, ... Linux distributions that lack networkd 36 | 37 | # NB: RHEL/Alma images already have dracut-network pre-installed 38 | 39 | $ssh root@"$guest" </dev/null ; then 54 | # new scheme since RHEL 9.5 ... 55 | grub2-mkconfig -o /etc/grub2.cfg --update-bls-cmdline 56 | else 57 | grub2-mkconfig -o /etc/grub2.cfg 58 | fi 59 | 60 | dracut -f -v 61 | EOF 62 | fi 63 | -------------------------------------------------------------------------------- /test/unlock.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eux 4 | 5 | mydir=$(dirname -- "${BASH_SOURCE[0]}") 6 | . "$mydir"/conf.sh 7 | 8 | 9 | guest=$(get_addr "$tag") 10 | 11 | n=$($ssh root@"$guest" systemd-tty-ask-password-agent --list | wc -l) 12 | 13 | if [ "$n" -ne 1 ]; then 14 | echo 'Unexpected early boot environment' >&2 15 | exit 1 16 | fi 17 | 18 | set +e 19 | timeout 30 $ssh root@"$guest" 'systemd-tty-ask-password-agent; sleep 60' < pw.log 20 | r=$? 21 | set -e 22 | if [ $r = 124 ]; then 23 | echo 'Unlock timed out' >&2 24 | exit 1 25 | fi 26 | exit 0 27 | -------------------------------------------------------------------------------- /test/update-grub.guestf: -------------------------------------------------------------------------------- 1 | 2 | # cf. update-grub 3 | 4 | run 5 | mount /dev/sda3 / 6 | ls /loader/entries | grep -v rescue | tee entry-name 7 |