feat(image): remote LUKS unlock via a Tor onion service in the initramfs
Encrypted image setups can now bake a Tor onion service into the initramfs so the dropbear unlock shell stays reachable behind NAT or a dynamic IP. When the user opts in, configure_encryption installs tor + busybox, drops the mkinitcpio hooks (ordered `netconf tor dropbear encryptssh`), generates the v3 onion keys offline in the image chroot, and prints the stable .onion address. Unlock with `torsocks ssh root@<onion-address>`. The runtime hook syncs the clock via NTP first (RTC-less boards boot at 1970, which Tor's consensus checks reject) and starts the onion service pointing at dropbear on 127.0.0.1:22. Hardening baked in from an adversarial review of the shipped path: - cmdline.txt boot path (RPi4-class firmware boot) now sets the same ip=::::<host>:eth0:dhcp net.ifnames=0 params as the boot.txt path, so the initramfs actually gets a network and the onion can publish. - the initramfs bakes in libnss_dns.so.2 so the NTP hostname resolves. - the hook extracts DHCP DNS with sed instead of sourcing the lease files, which would run attacker-controlled DHCP option strings as root pre-boot. - NTP is attempted unconditionally (bounded), not gated on DHCP-provided DNS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
47
lim/configuration/initcpio/tor_hook
Normal file
47
lim/configuration/initcpio/tor_hook
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/ash
|
||||
# mkinitcpio runtime hook: start Tor so the dropbear unlock shell is
|
||||
# reachable as an onion service while encryptssh waits for the passphrase.
|
||||
# Installed to /etc/initcpio/hooks/tor by linux-image-manager.
|
||||
|
||||
# netconf's ipconfig drops its DHCP lease data (incl. DNS) into
|
||||
# /tmp/net-*.conf; busybox's resolver only reads /etc/resolv.conf.
|
||||
_tor_write_resolv_conf() {
|
||||
[ -s /etc/resolv.conf ] && return 0
|
||||
local conf dns
|
||||
for conf in /tmp/net-*.conf; do
|
||||
[ -f "$conf" ] || continue
|
||||
# Extract ONLY the DNS fields with sed; never source these files —
|
||||
# they hold attacker-controllable DHCP option strings (hostname,
|
||||
# domain, rootpath), and sourcing would run them as root pre-boot.
|
||||
for dns in $(sed -n 's/^IPV4DNS[01]=//p' "$conf"); do
|
||||
[ -n "$dns" ] && [ "$dns" != "0.0.0.0" ] \
|
||||
&& echo "nameserver $dns" >> /etc/resolv.conf
|
||||
done
|
||||
done
|
||||
[ -s /etc/resolv.conf ]
|
||||
}
|
||||
|
||||
run_hook() {
|
||||
# Tor rejects consensus documents when the clock is far off; boards
|
||||
# without an RTC boot in 1970, so sync before starting Tor. Bounded:
|
||||
# a failed sync must never block the boot.
|
||||
msg "tor: syncing clock via NTP..."
|
||||
# Best-effort DNS; a tor_ntp IP literal needs none, so never gate on it.
|
||||
_tor_write_resolv_conf || msg "tor: no DNS from DHCP (fine if tor_ntp is an IP)"
|
||||
/usr/local/bin/busybox timeout 30 \
|
||||
/usr/local/bin/busybox ntpd -n -q -p "${tor_ntp:-pool.ntp.org}" \
|
||||
|| msg "tor: NTP sync failed, keeping current clock"
|
||||
|
||||
msg "tor: starting onion service for remote unlock..."
|
||||
mkdir -p /var/lib/tor
|
||||
chmod 0700 /var/lib/tor /etc/tor/onion
|
||||
chmod 0600 /etc/tor/onion/hs_ed25519_secret_key
|
||||
tor -f /etc/tor/torrc --RunAsDaemon 1 --Log "notice file /tmp/tor.log" \
|
||||
|| msg "tor: failed to start, unlock stays reachable via direct IP"
|
||||
}
|
||||
|
||||
run_cleanuphook() {
|
||||
# Nothing from the initramfs may keep running after the pivot.
|
||||
/usr/local/bin/busybox killall tor 2>/dev/null
|
||||
return 0
|
||||
}
|
||||
37
lim/configuration/initcpio/tor_install
Normal file
37
lim/configuration/initcpio/tor_install
Normal file
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
# mkinitcpio install hook: Tor onion service for remote LUKS unlock.
|
||||
# Installed to /etc/initcpio/install/tor by linux-image-manager.
|
||||
|
||||
build() {
|
||||
add_binary /usr/bin/tor
|
||||
# Full busybox for the ntpd applet: boards without an RTC (e.g. most
|
||||
# Raspberry Pis) wake up in 1970, which Tor's consensus checks reject.
|
||||
# A different target path keeps mkinitcpio's own busybox untouched.
|
||||
add_binary /usr/bin/busybox /usr/local/bin/busybox
|
||||
# glibc resolves the NTP server hostname via getaddrinfo(), which dlopen()s
|
||||
# these NSS modules at runtime — add_binary only follows NEEDED libs, so
|
||||
# without them DNS silently fails, ntpd never syncs, and the clock stays
|
||||
# at 1970. glibc's built-in default (no nsswitch.conf) is "dns ... files".
|
||||
add_binary /usr/lib/libnss_dns.so.2
|
||||
add_binary /usr/lib/libnss_files.so.2
|
||||
add_file /etc/tor/initramfs-torrc /etc/tor/torrc
|
||||
add_file /etc/tor/initramfs-onion/hostname /etc/tor/onion/hostname
|
||||
add_file /etc/tor/initramfs-onion/hs_ed25519_public_key /etc/tor/onion/hs_ed25519_public_key
|
||||
add_file /etc/tor/initramfs-onion/hs_ed25519_secret_key /etc/tor/onion/hs_ed25519_secret_key
|
||||
add_runscript
|
||||
}
|
||||
|
||||
help() {
|
||||
cat <<HELPEOF
|
||||
Starts a Tor onion service in early userspace so the dropbear unlock
|
||||
shell stays reachable under the .onion address baked into the image,
|
||||
even behind NAT or a dynamic IP.
|
||||
|
||||
Place it between netconf and dropbear:
|
||||
HOOKS=(... netconf tor dropbear encryptssh ...)
|
||||
|
||||
Optional kernel parameter:
|
||||
tor_ntp=<server> NTP server used to set the clock before Tor starts
|
||||
(default: pool.ntp.org)
|
||||
HELPEOF
|
||||
}
|
||||
7
lim/configuration/initcpio/torrc
Normal file
7
lim/configuration/initcpio/torrc
Normal file
@@ -0,0 +1,7 @@
|
||||
# Tor configuration for the initramfs onion unlock service.
|
||||
# Baked into the initramfs as /etc/tor/torrc by the "tor" mkinitcpio hook;
|
||||
# the onion keys come from /etc/tor/initramfs-onion on the system.
|
||||
DataDirectory /var/lib/tor
|
||||
HiddenServiceDir /etc/tor/onion
|
||||
HiddenServicePort 22 127.0.0.1:22
|
||||
SocksPort 0
|
||||
Reference in New Issue
Block a user