用 Dropbear 为 Buildroot ARM64 设备配置密钥登录与端口转发
前几篇已经在 Ubuntu 20.04、Buildroot 2025.02.8 和 QEMU ARM64 环境中完成了服务启动、权限隔离、日志轮转与 TCP 重连实验。接下来解决设备远程维护中的一个基础问题:怎样通过 SSH 安全登录目标系统,并使用端口转发访问目标端可达的 TCP 服务。
本文使用 Buildroot 提供的 Dropbear SSH 服务端。实验包括公钥登录、禁用密码登录、通过 QEMU user-mode 网络将主机本机端口转发到 guest 的 SSH 端口,以及通过 SSH 本地转发访问 Ubuntu 主机上的 TCP 测试服务。
安全边界:本文为了验证方便,以 root 的 SSH 公钥登录 QEMU guest,并只把 QEMU 转发端口绑定到 Ubuntu 的127.0.0.1。不要把此配置不加审查地用于实体设备或暴露到局域网、互联网;实际产品应使用专用非 root 管理账户、持久保存并保护主机密钥,并制定访问控制和密钥轮换方案。
一、理解两层转发
本实验同时出现 QEMU 的hostfwd和 SSH 的-L。它们是两种不同的转发,不要混为一谈:
Ubuntu SSH 客户端 127.0.0.1:2222 | QEMU hostfwd v QEMU guest Dropbear:22 | SSH -L 隧道中的目标连接 v 10.0.2.2:5001(Ubuntu 主机上的 SSH 转发测试服务)- QEMU
hostfwd把 Ubuntu 主机本机的 TCP2222转到 guest 的22,使主机可以连接 Dropbear。 - SSH
-L在 Ubuntu 本机监听18080;连接进入 SSH 隧道后,由 guest 连接10.0.2.2:5001。 -R是反向转发:监听端口建立在 SSH 服务端一侧。本篇解释方向,不把它与 QEMUhostfwd当成同一功能。
两个转发端口都绑定在127.0.0.1,实验只允许同一 Ubuntu 主机访问,不向局域网开放。
二、环境与操作约定
| 项目 | 本文沿用的设置 |
|---|---|
| 开发主机 | Ubuntu 20.04 x86_64 虚拟机 |
| Buildroot | 2025.02.8,~/buildroot-2025.02.8 |
| 输出目录 | ~/buildroot-output-arm64-demo |
| 外部目录 | ~/buildroot-external-demo,标识ARM64_DEMO |
| Rootfs overlay | ~/buildroot-external-demo/board/qemu-arm64-demo/rootfs-overlay |
| 目标系统 | QEMU ARM64,BusyBox init |
| SSH 服务端 | QEMU guest 中的 Dropbear,TCP 22 |
| Ubuntu 转发端口 | 127.0.0.1:2222 |
| SSH 本地转发测试 | 127.0.0.1:18080到10.0.2.2:5001 |
默认命令在Ubuntu 主机执行;标注QEMU 目标端的命令才在 ARM64 系统内执行。请沿用现有 Buildroot 输出目录和外部树,不要重新生成 defconfig 或在 QEMU 正使用镜像时构建。
Buildroot 2025.02.8 的 SysV 启动脚本/etc/init.d/S50dropbear会读取/etc/default/dropbear。它启动服务时会追加-R;缺少 host key 时,Dropbear 会在第一次收到 SSH 连接时按需生成所需密钥。服务端 host key 与 Ubuntu 上用于登录的用户密钥是两套不同密钥:前者用于识别服务器,后者用于证明客户端身份。
三、检查环境并备份
如果 QEMU 正在运行,先在QEMU 目标端执行:
poweroff等待 QEMU 退出,再在Ubuntu 主机执行:
exportDEMO_OUT="$HOME/buildroot-output-arm64-demo"exportDROPBEAR_OVERLAY="$HOME/buildroot-external-demo/board/qemu-arm64-demo/rootfs-overlay"test-f"$DEMO_OUT/.config"printf'config check exit code: %s\n'"$?"test-d"$DROPBEAR_OVERLAY"printf'overlay check exit code: %s\n'"$?"grep-E'^BR2_(INIT_BUSYBOX|PACKAGE_DEBUG_DEMO)='"$DEMO_OUT/.config"文件检查都应返回0,且 BusyBox init 与前文服务仍启用。若此 Buildroot 输出由外部 defconfig 管理,后续还要把 Dropbear 选项同步到实际使用的 defconfig,避免下一次重新生成配置时丢失。
先备份 Buildroot 配置和外部树中本篇会涉及的文件:
exportSSH_BACKUP="$HOME/dropbear-before-ssh-$(date+%Y%m%d-%H%M%S)"mkdir-p"$SSH_BACKUP"cp-p"$DEMO_OUT/.config""$SSH_BACKUP/buildroot.config"cp-a"$DROPBEAR_OVERLAY""$SSH_BACKUP/rootfs-overlay"printf'Backup: %s\n'"$SSH_BACKUP"确认备份命令成功后再继续。若 overlay 已有/root/.ssh/authorized_keys或/etc/default/dropbear,先检查并备份其内容,不要直接覆盖已有密钥或 Dropbear 参数。
四、在 Ubuntu 主机生成登录密钥
在Ubuntu 主机生成一对 Ed25519 用户密钥。私钥保留在 Ubuntu 主机,不要复制到 Rootfs overlay、QEMU 镜像或版本库:
install-d-m700"$HOME/.ssh"exportSSH_KEY="$HOME/.ssh/buildroot-qemu-dropbear-ed25519"if[-e"$SSH_KEY"]||[-e"$SSH_KEY.pub"];thenprintf'Key path already exists; choose a different SSH_KEY.\n'>&2exit1fissh-keygen-ted25519-a64-f"$SSH_KEY"-C"buildroot-qemu-dropbear"按提示设置私钥口令;输入时终端不会回显字符。若选择留空,私钥就没有口令保护,应严格保管文件权限并在实验后妥善删除或归档。若目标路径已有密钥文件,先选择新路径或确认备份,避免覆盖。
本次实际输出确认密钥对已生成(用户名路径已用$HOME简写;省略了用户专属的 fingerprint 和 randomart):
Generating public/private ed25519 key pair. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in $HOME/.ssh/buildroot-qemu-dropbear-ed25519 Your public key has been saved in $HOME/.ssh/buildroot-qemu-dropbear-ed25519.pub该输出确认密钥对已生成;下一步只会把.pub公钥安装到 guest,私钥留在 Ubuntu 主机。生成成功尚不代表 SSH 登录已验证。
将公钥放进 guest 的 rootfs overlay,并设置权限:
install-d-m700"$DROPBEAR_OVERLAY/root/.ssh"if[-e"$DROPBEAR_OVERLAY/root/.ssh/authorized_keys"];thenprintf'authorized_keys already exists; inspect and append the new public key without replacing existing entries.\n'>&2exit1fiinstall-m600"$SSH_KEY.pub"\"$DROPBEAR_OVERLAY/root/.ssh/authorized_keys"install-d-m755"$DROPBEAR_OVERLAY/etc/default"printf'%s\n''DROPBEAR_ARGS="-s"'\>"$DROPBEAR_OVERLAY/etc/default/dropbear"chmod644"$DROPBEAR_OVERLAY/etc/default/dropbear"-s禁用密码登录,公钥认证仍可使用。Rootfs overlay 只包含公钥,不包含$SSH_KEY私钥。若已有authorized_keys,应先备份并把新公钥追加到原文件,而不是覆盖其他管理员密钥。
五、启用 Dropbear 并重建镜像
在Ubuntu 主机运行配置界面:
cd"$HOME/buildroot-2025.02.8"||exit1makeO="$DEMO_OUT"BR2_EXTERNAL="$HOME/buildroot-external-demo"menuconfig在菜单中启用:
Target packages -> Networking applications -> dropbear在 Dropbear 子菜单中,本实验按以下方式选择:
| 菜单项 | 选择 | 原因 |
|---|---|---|
dropbear | 勾选 | 安装 SSH 服务端 |
client programs | 不勾选 | SSH 客户端使用 Ubuntu 主机上的ssh,guest 不需要再安装一份 |
disable reverse DNS lookups | 建议勾选 | 本地 QEMU 实验不依赖反向 DNS,避免登录时等待名称查询 |
optimize for size | 保持默认 | 本实验不以缩小镜像为目标 |
log dropbear access to wtmp/lastlog | 不勾选 | 本实验不需要登录历史数据库 |
enable legacy crypto | 不勾选 | 不启用已淘汰的旧加密算法 |
path to custom localoptions.h definitions file | 留空 | 本实验不需要自定义编译宏 |
保存配置并退出。密码认证不是在此菜单中控制:后面通过/etc/default/dropbear中的DROPBEAR_ARGS="-s"禁用密码登录,同时保留公钥认证。确认服务端选项:
grep'^BR2_PACKAGE_DROPBEAR='"$DEMO_OUT/.config"本轮 Ubuntu 主机实际输出为:
$ grep '^BR2_PACKAGE_DROPBEAR=' "$DEMO_OUT/.config" BR2_PACKAGE_DROPBEAR=y这确认 Dropbear 服务端选项已启用。如配置由外部 defconfig 维护,请把该选项并入实际 defconfig,避免下次重新生成配置时丢失。不要运行make clean、distclean或重置整个配置。
QEMU 必须已退出。重新构建:
(set-eucd"$HOME/buildroot-2025.02.8"makeO="$DEMO_OUT"BR2_EXTERNAL="$HOME/buildroot-external-demo"-j2)build_status=$?printf'Build exit code: %s\n'"$build_status"只有退出码为0才继续启动新镜像。本轮 Ubuntu 主机实际输出节选如下(用户主目录以$HOME代替):
ln -sf rootfs.ext2 "$DEMO_OUT/images/rootfs.ext4" >>> Executing post-image script board/qemu/post-image.sh Build exit code: 0退出码0表示本次 Buildroot 构建及 post-image 脚本成功结束;输出还显示rootfs.ext4是指向rootfs.ext2的符号链接。这只确认镜像构建成功,Dropbear 启动、公钥登录和端口转发仍需按后续步骤在 QEMU 中验证。
六、启动 QEMU 并检查 Dropbear
在Ubuntu 主机启动 QEMU。与前文命令相比,唯一的网络变化是在-netdev user中加入只绑定回环地址的 SSHhostfwd:
exportDEMO_OUT="$HOME/buildroot-output-arm64-demo"/usr/bin/qemu-system-aarch64\-Mvirt\-cpucortex-a53\-smp2\-m1G\-nographic\-kernel"${DEMO_OUT:?请先设置 DEMO_OUT}/images/Image"\-append"rootwait root=/dev/vda console=ttyAMA0"\-drivefile="$DEMO_OUT/images/rootfs.ext2",if=none,format=raw,id=hd0\-devicevirtio-blk-device,drive=hd0\-netdevuser,id=net0,hostfwd=tcp:127.0.0.1:2222-:22\-devicevirtio-net-device,netdev=net0QEMU 启动后,终端被串口控制台占用。以root登录后,以下检查在QEMU 目标端执行:
ps|grep'[d]ropbear'cat/etc/default/dropbearls-ld/root/.sshls-l/root/.ssh/authorized_keysls-l/etc/dropbear本轮 QEMU 目标端实际输出如下:
# ps | grep '[d]ropbear' 122 root /usr/sbin/dropbear -s -R # cat /etc/default/dropbear DROPBEAR_ARGS="-s" # ls -ld /root/.ssh drwxr-xr-x 2 root root 1024 Nov 2 05:25 /root/.ssh # ls -l /root/.ssh/authorized_keys -rw-r--r-- 1 root root 105 Nov 2 05:25 /root/.ssh/authorized_keys # ls -l /etc/dropbear total 0这确认 Dropbear 正在运行,启动参数包含禁用密码登录的-s和按需生成 host key 的-R。初次检查发现.ssh权限为755、authorized_keys为644,因此按下列步骤修正为本实验要求的700/600。首次 SSH 握手前/etc/dropbear为空是-R的预期行为。
修正当前 guest 的公钥权限:
chownroot:root /root/.ssh /root/.ssh/authorized_keyschmod700/root/.sshchmod600/root/.ssh/authorized_keysls-ld/root/.sshls-l/root/.ssh/authorized_keys检查结果应显示/root/.ssh为drwx------,authorized_keys为-rw-------。
本轮修正后的实际输出为:
# chown root:root /root/.ssh /root/.ssh/authorized_keys # chmod 700 /root/.ssh # chmod 600 /root/.ssh/authorized_keys # ls -ld /root/.ssh drwx------ 2 root root 1024 Nov 2 05:25 /root/.ssh # ls -l /root/.ssh/authorized_keys -rw------- 1 root root 105 Nov 2 05:25 /root/.ssh/authorized_keys这确认当前运行的 guest 权限已修正;无需重启 Dropbear。overlay 源文件的权限仍需按下方主机端步骤修正,以便后续重建时保留。
持久化权限:上面的chown/chmod已立即修正当前运行的 guest。为让后续重建镜像也保留权限,可在另一个Ubuntu 主机 shell修正 overlay 源文件;不必为此先关闭 QEMU,但修改 overlay 不会改变当前运行的 guest。DROPBEAR_OVERLAY是主机 shell 的变量,不会传入 guest;不要在 QEMU 的#提示符下执行以下命令。若之后要重建镜像,必须先在 QEMU 目标端执行poweroff并确认 QEMU 退出,再运行 Buildroot 构建:
exportDROPBEAR_OVERLAY="$HOME/buildroot-external-demo/board/qemu-arm64-demo/rootfs-overlay"chmod700"$DROPBEAR_OVERLAY/root/.ssh"chmod600"$DROPBEAR_OVERLAY/root/.ssh/authorized_keys"在QEMU 目标端执行目录检查:
ls-ld/etc/dropbear readlink /etc/dropbear||truels-la/etc/dropbearls-la/var/run/dropbear本轮首次 SSH 连接前的实际输出如下:
# ls -ld /etc/dropbear drwxr-xr-x 2 root root 1024 Nov 2 05:38 /etc/dropbear # readlink /etc/dropbear # ls -la /etc/dropbear total 2 drwxr-xr-x 2 root root 1024 Nov 2 05:38 . drwxr-xr-x 8 root root 1024 Nov 2 05:38 .. # ls -la /var/run/dropbear ls: /var/run/dropbear: No such file or directoryreadlink没有输出,说明当前/etc/dropbear是真实目录;/var/run/dropbear不存在,在可写 rootfs 下符合S50dropbear将该链接替换为实际目录的处理方式。该目录首次握手前为空是预期的,host key 将由-R在首次连接时按需生成。
可写 ext2 镜像会保存生成后的 host key。下面直接通过 Ubuntu 主机发起首次连接,并在核对指纹后决定是否信任该 host key。
七、核对 host key 并测试公钥登录
本节的三个步骤都围绕首次 SSH 登录,按顺序在标明的终端执行。保持 QEMU 运行。
第一步:从 Ubuntu 主机触发首次握手,但先拒绝未知 host key。在 Ubuntu 主机另开一个终端执行:
exportSSH_KEY="$HOME/.ssh/buildroot-qemu-dropbear-ed25519"ssh-i"$SSH_KEY"\-oPreferredAuthentications=publickey\-oPasswordAuthentication=no\-oStrictHostKeyChecking=ask\-p2222root@127.0.0.1首次连接会显示服务端 host key 指纹并询问是否继续。记录指纹,在提示处输入no,不要直接输入yes。客户端中止本次登录是预期结果;首次握手已触发 Dropbear 生成并保存 host key。本次实测输出如下:
The authenticity of host '[127.0.0.1]:2222 ([127.0.0.1]:2222)' can't be established. ECDSA key fingerprint is SHA256:xkEIJasYUsWkwjIgYAEIZ7WuozHkqkwIoZXcSfgxYek. Are you sure you want to continue connecting (yes/no/[fingerprint])? no Host key verification failed.第二步:回到 QEMU 目标端核对指纹。
forhost_keyin/etc/dropbear/*_host_key;do[-f"$host_key"]||continueprintf'%s\n'"$host_key"dropbearkey-y-f"$host_key"|grep'Fingerprint:'done本轮 QEMU 目标端的实际输出如下:
# for host_key in /etc/dropbear/*_host_key; do > [ -f "$host_key" ] || continue > printf '%s\n' "$host_key" > dropbearkey -y -f "$host_key" | grep 'Fingerprint:' > done /etc/dropbear/dropbear_ecdsa_host_key Fingerprint: SHA256:xkEIJasYUsWkwjIgYAEIZ7WuozHkqkwIoZXcSfgxYek该 ECDSA 指纹与 Ubuntu 首次连接提示完全一致,host key 核验通过。若循环没有输出 key 文件,或指纹不一致,则停止登录并检查-R、/etc/dropbear写入状态和 QEMU 镜像,不要关闭 host key 校验来绕过问题。
第三步:指纹一致后再登录。在 Ubuntu 主机重新运行第一步的 SSH 命令;再次提示时确认指纹一致并输入yes。公钥认证成功后应进入 Buildroot guest shell;服务端配置-s,密码认证被禁用。本例虽使用 root 公钥登录以便做教学检查,但仅用于主机回环接口上的 QEMU 实验。真实设备应使用权限受限的专用管理账户,并尽可能禁用 root 远程登录。
可在 guest shell 中确认自己位于目标端并查看服务仍在运行:
uname-midpidof debug_demo本轮 SSH 登录后的 QEMU guest 实际输出如下:
# uname -m aarch64 # id uid=0(root) gid=0(root) groups=0(root),10(wheel) # pidof debug_demo 128输出确认当前 shell 位于 ARM64 guest,登录用户为 root,且debug_demo进程仍在运行。若这些命令是在 SSH 登录后的 shell 中执行,则公钥 SSH 登录已成功;PID128是本轮 guest 中该进程的进程号。
八、验证 SSH 本地端口转发
前文的 Python 服务一次只处理一个连接;debug_demo当前保持着到 TCP5000的连接,因此服务不会接受 SSH 隧道测试的新连接,直到该连接断开。这里单独使用 Ubuntu 主机 TCP5001做 SSH 转发测试,原有5000服务保持不变。
在Ubuntu 主机终端 D启动专用测试服务并保持运行。服务绑定0.0.0.0是为了让 QEMU guest 能访问10.0.2.2:5001,同时也会接受 Ubuntu 虚拟机其他网络接口上的连接;仅在可信、隔离的实验环境中临时运行,不要为此配置额外的宿主机或公网端口映射,测试后按Ctrl+C停止。
python3-u-<<'PY' import socket with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(("0.0.0.0", 5001)) server.listen(4) print("SSH test server listening on 0.0.0.0:5001", flush=True) while True: connection, address = server.accept() with connection: with connection.makefile("rb") as stream: line = stream.readline(257) if line.endswith(b"\n") and len(line) <= 256: response = b"ACK " + line connection.sendall(response) print(f"received={line!r} sent={response!r}", flush=True) elif line: print(f"ignored incomplete or oversized request from {address}", flush=True) PY另开 Ubuntu 主机终端确认服务在监听:
ss-lntp|grep':5001'应能看到0.0.0.0:5001处于LISTEN状态。确认后再启动 SSH 隧道。
在Ubuntu 主机终端 C执行以下命令并保持前台运行:
exportSSH_KEY="$HOME/.ssh/buildroot-qemu-dropbear-ed25519"ssh-N\-oExitOnForwardFailure=yes\-i"$SSH_KEY"\-p2222\-L127.0.0.1:18080:10.0.2.2:5001\root@127.0.0.1-N表示不在 guest 启动远程 shell;该 SSH 会话只维持转发。终端 C 无提示符、没有新输出是正常的。若端口18080已被占用,换一个本地未使用端口,并在下面的测试命令中同步修改。
另开一个Ubuntu 主机终端,用 Bash 的/dev/tcp发送一行并读取 ACK:
exec3<>/dev/tcp/127.0.0.1/18080printf'ssh-forward-check\n'>&3IFS=read-r-t5response<&3printf'response=%s\n'"$response"exec3<&-exec3>&-预期输出为:
response=ACK ssh-forward-check本轮 Ubuntu 主机实测收到:
response=ACK ssh-forward-check这确认路径为 Ubuntu 本机端口18080→ SSH 本地转发 → guest →10.0.2.2:5001→ 专用 Python 测试服务。服务终端同时应记录收到请求并发送 ACK。结束时分别在终端 C 按Ctrl+C关闭 SSH 隧道,并在终端 D 按Ctrl+C停止专用测试服务;这不会停止 Dropbear 或debug-demo。
SSH 的-R与这里的-L方向相反:-R在 Dropbear 所在端建立监听端口,再把连接转回 SSH 客户端所在端。本实验只实际验证-L;不要把-R或 QEMU 的hostfwd误认为已经测试。
九、常见故障排查
| 现象 | 检查方向 |
|---|---|
Connection refused连接127.0.0.1:2222 | 确认 QEMU 的hostfwd将本机127.0.0.1:2222转到 guest22;在 guest 检查 Dropbear 是否启动和监听 |
Permission denied (publickey) | 确认公钥行在 guest 的/root/.ssh/authorized_keys;私钥与.pub配对;检查目录/文件 owner 和权限;确认/etc/default/dropbear中参数为-s而不是禁用公钥的选项 |
REMOTE HOST IDENTIFICATION HAS CHANGED | 不要关闭校验;先在 guest 控制台核对 host key 指纹。确认镜像确实重建后,再清理known_hosts中[127.0.0.1]:2222的旧记录 |
SSH 能登录但-L报绑定失败 | Ubuntu 本机端口18080已占用;改用另一空闲端口,且确认本地绑定为127.0.0.1 |
channel 1: open failed: connect failed: Connection refused或隧道建立但读不到 ACK | -L最后一段目标地址由 guest 连接;确认 Ubuntu 主机专用 Python 测试服务正在0.0.0.0:5001监听、guest 可访问10.0.2.2:5001,且换行协议一致 |
| 局域网其他机器无法连接 | 本文将 QEMU 转发和 SSH 本地转发都绑定在127.0.0.1,这是预期限制;不要直接改为0.0.0.0暴露服务 |
排查时可在 Ubuntu 客户端增加-vvv获取 SSH 连接诊断;诊断输出可能包含用户名、路径、主机 key 指纹和网络信息,公开前应先审查并脱敏。
十、实验边界与发布前记录
本篇演示的是 QEMU 本机回环场景下的 Dropbear 公钥登录和 SSH-L。它不覆盖真实网卡、DHCP、无线网络、VPN、外网防火墙、专用管理账户、密钥撤销/轮换、SSH agent 转发或生产设备的持久化密钥管理。-s禁用密码登录不等于完整安全加固;root 公钥登录仅为本实验便利,不建议直接用于产品设备。
截至本轮,Buildroot 构建、guest 的 Dropbear 启动状态、host key 指纹核验、公钥登录和 SSH 本地端口转发 ACK 均已有实测结果并记录在文中。其余未提供实测结果的内容仍按预期行为描述,不应误认为已在本次实验中验证。
十一、小结
通过在 Buildroot 中启用 Dropbear、把 Ubuntu 用户公钥放入 rootfs overlay,并以-s禁用密码登录,可以在 QEMU ARM64 中建立 SSH 公钥登录。本篇还通过只绑定127.0.0.1的 QEMUhostfwd接入 guest SSH,再用 SSH-L将主机本机端口安全转发到 guest 可达的主机 TCP 服务。QEMU 端口映射、SSH 本地转发与 SSH 反向转发是不同机制;理解各自的监听端和连接发起端,是避免配置错误和意外暴露服务的关键。