Skip to content

virsh console 失联后 send-key 盲打恢复 sshd 实战

KVM 虚拟机 SSH 不通、virsh console 串口也无回显的绝境下, 如何通过 virsh send-key 向图形控制台盲打命令 + nginx Web 目录读回结果, 完成"无回显诊断",最终修复 SELinux 标签丢失导致的 sshd 启动失败。

背景

  • 虚拟机:rocky-repo(Rocky Linux 8.7,hostname repo122156,IP 192.168.122.156
  • 角色:内网仓库服务器,跑 Harbor(docker-compose,30000 端口)、nginx 文件源(80 端口)、Gitea(3000 端口)
  • 故障诉求:需要 SSH 登录该 VM 做变更,但发现 22 端口连接被拒

故障现象

bash
ping 192.168.122.156        # 通,VM 活着
nc -vz 192.168.122.156 22   # refused,sshd 未监听
nc -vz 192.168.122.156 80   # open,nginx 正常
nc -vz 192.168.122.156 30000 # open,Harbor 正常

VM 业务一切正常,唯独 SSH 进不去。

第一阶段:virsh console 登录诊断

宿主机上通过串口控制台登录(pexpect 脚本化处理 login 交互):

python
child = pexpect.spawn('virsh console rocky-repo', encoding='utf-8')
# 发送回车唤醒 → 匹配 login: → 输入用户 → 匹配 Password: xxx 输入密码 → 匹配 # 提示符

进入系统后发现 sshd 处于启动失败-自动重启死循环

text
● sshd.service - OpenSSH server daemon
   Active: activating (auto-restart) (Result: exit-code)
 Main PID: 695141 (code=exited, status=1/FAILURE)

/usr/sbin/sshd -t 配置检查却返回 0(root 下手动能读配置),线索指向权限之外的原因。

第二阶段:console 失联

排查中途 pexpect 子进程被强杀,遗留 libvirt 控制台残留会话,随后 console 彻底失联:

text
$ virsh console rocky-repo
error: operation failed: Active console session exists for this domain

$ virsh console rocky-repo --force
Connected to domain 'rocky-repo'
(连接成功但串口完全无回显,发送任何字符均无响应)

即使 virsh reboot 重启 VM 后,ttyS0 依然沉默。此时面临绝境:

通道状态
SSH 22拒绝(sshd 损坏)
virsh console (ttyS0)无回显
qemu-guest-agent未安装,guest-ping 无响应
离线读磁盘 (virt-cat)qcow2 属 libvirt-qemu:kvm 且 0600,无 sudo 权限读不了

第三阶段:send-key 盲打 + HTTP 读回

突破口:VM 有 SPICE 图形控制台,tty1 上有 gettyvirsh send-key 注入的是虚拟键盘事件,直达 tty1。

思路:

  1. send-key 盲打登录 tty1(看不到输出,只能信)
  2. 盲打命令把诊断输出写入 nginx 的 Web 根目录/u02/repo/rke2/,80 端口有 autoindex)
  3. 从宿主机 curl http://192.168.122.156/xxx.txt 读回结果 —— 用 HTTP 当"显示器"

字符 → Linux 键码映射(Python 实现)

python
base = {'a':'KEY_A', ..., '0':'KEY_0', '-':'KEY_MINUS', '/':'KEY_SLASH',
        '.':'KEY_DOT', ';':'KEY_SEMICOLON', ' ':'KEY_SPACE', ...}
shifted = {'_':'KEY_MINUS', '$':'KEY_4', '&':'KEY_7', '*':'KEY_8',
           '>':'KEY_DOT', '{':'KEY_LEFTBRACE', '|':'KEY_BACKSLASH', ...}

def press(keys):
    subprocess.run(['virsh', 'send-key', 'rocky-repo'] + keys)
    time.sleep(0.08)

def type_text(s):
    for ch in s:
        if ch in base:      press([base[ch]])
        elif ch in shifted: press(['KEY_LEFTSHIFT', shifted[ch]])
        elif ch.isupper():  press(['KEY_LEFTSHIFT', base[ch.lower()]])

大写和符号通过 KEY_LEFTSHIFT 组合键实现(send-key 多键码同按时前者为修饰键)。

盲打登录并验证

bash
# 依次注入:回车唤醒 → root 回车 → 密码 回车
# 然后打一条带回显标记的命令验证 shell 可用:
echo ttyok-$(date +%s) > /u02/repo/rke2/ttymark.txt

# 宿主机上读回:
curl http://192.168.122.156/ttymark.txt
# ttyok-1786624097   ← 盲打成功!

盲打采集诊断信息

bash
F=/u02/repo/rke2/sshdiag.txt
{ date; /usr/sbin/sshd -t; echo RC=$?; journalctl -u sshd -n 40 --no-pager;
  ls -la /etc/ssh/ssh_host_*; grep -vE '^#|^$' /etc/ssh/sshd_config; } > $F 2>&1

读回日志,真相大白:

text
sshd[4405]: /etc/ssh/sshd_config: Permission denied

根因:SELinux 标签丢失

继续盲打采集 SELinux 信息:

bash
{ getenforce; ls -Z /etc/ssh/sshd_config;
  ausearch -m avc -ts recent | tail -5; } > /u02/repo/rke2/sel.txt 2>&1

读回结果:

text
Enforcing
system_u:object_r:unlabeled_t:s0 /etc/ssh/sshd_config

根因/etc/ssh/sshd_config 的 SELinux 类型是 unlabeled_t(正常应为 etc_t)。 系统处于 Enforcing 模式,sshd 运行在受限域 sshd_t 中,无权读取 unlabeled_t 文件, 启动即失败,systemd 每 42 秒拉起一次,无限循环。

这也解释了为什么 sshd -t 正常:root 在 unconfined_t 域下手动执行不受限制。

修复(继续盲打)

bash
F=/u02/repo/rke2/fix.txt
restorecon -Rv /etc/ssh > $F 2>&1
systemctl restart sshd
sleep 2
{ systemctl is-active sshd; ss -tlnp | grep -w :22; } >> $F 2>&1

读回:

text
Relabeled /etc/ssh/sshd_config from system_u:object_r:unlabeled_t:s0 to system_u:object_r:etc_t:s0
active
LISTEN 0 128 [::]:22  users:(("sshd",pid=5958))

宿主机验证:

bash
ssh root@192.168.122.156 'hostname'
# repo122156  ← SSH 完全恢复

最后清理 Web 目录下的临时诊断文件(ttymark.txt / sshdiag.txt / sel.txt / fix.txt)。

总结

排障要点回顾

  1. sshd 报 Permission denied 读配置,但 root 手动 sshd -t 正常 → 高度怀疑 SELinux, 第一时间看 ls -Zausearch -m avcunlabeled_t 是典型信号(常见于文件系统异常、 chattr/immutable 操作、跨文件系统移动文件后未 restorecon)。
  2. virsh consoleActive console session exists → 有残留会话,用 --force 强占; pexpect 自动化时务必 sendcontrol(']') 正常退出,强杀子进程会留坑。
  3. 串口失联不等于 VM 失联virsh send-key 走虚拟键盘到图形 tty1, 配合 VM 内任意一个 HTTP 服务做"输出读回通道",即可实现完全盲打运维。
  4. 修复 SELinux 标签问题优先用 restorecon(按策略恢复应有标签), 而不是 chcon(手写标签,策略变更后可能回退);怀疑大面积丢失时用 fixfiles relabel