72602-minipc → ecs-99

SSH 反向隧道:72602-minipc → ecs-99(双入口)

本文档是 72602-minipc 的当前参考方案;执行前应核对 live unit、ECS 安全组、 SSH banner 和监控状态,目标是避免单端口掉线导致完全失联。

  • 主入口:10021
  • 备入口:10022
  • 两个端口由两个独立 service 维护

一、架构

已登记且获安全组授权的来源客户端        ecs-99 (47.110.67.161)                     72602-minipc (192.168.0.25)
ssh -p 10021 aaron@47.110.67.161  ->   0.0.0.0:10021 (sshd) --SSH reverse-->      localhost:22
ssh -p 10022 aaron@47.110.67.161  ->   0.0.0.0:10022 (sshd) --SSH reverse-->      localhost:22
ECS HAProxy :25/:465/:587/:993 -> 127.0.0.1:10225/:10465/:10587/:10993 (sshd)
                                      --SSH reverse--> minipc hostPort :25/:465/:587/:993

说明:反向隧道必须由 72602-minipc 主动发起。0.0.0.0 是 ECS 上 sshd 的 reverse-bind,并不等于对任意公网来源开放;安全组来源限制、SSH key 认证、已建立的 SSH child/session、banner 和监控恢复都必须分别验证。

二、上线前检查

2.1 在 72602-minipc 检查基础条件

# 1) 本机 SSH 服务
sudo systemctl is-active ssh

# 2) autossh 是否安装
autossh -V

# 3) 本机到 ECS 网络与认证
ssh -o ConnectTimeout=5 root@47.110.67.161 hostname
# 期望输出: ecs-99

2.2 在 ECS 检查前置配置

/etc/ssh/sshd_config 至少包含:

GatewayPorts clientspecified

重载:

sudo systemctl reload sshd

安全组仅应按当前 72602 出口地址来源受限地放行 10021/tcp10022/tcp。 不要为 ZJLAB 的 10023/tcp10024/tcp 添加公网规则;那两条 listener 属于 ECS loopback-only 的 ProxyJump 路径。

同时确认 ECS 本机防火墙(UFW)放行这两个端口:

sudo ufw status numbered
# 应包含 10021/tcp 和 10022/tcp 的 ALLOW 规则;公网来源边界由云安全组控制

三、创建双 service(72602-minipc 上执行)

下面步骤全部在 72602-minipc 上执行。

3.1 统一 SSH 客户端配置(可选但推荐)

编辑 ~/.ssh/config

Host ecs-99
    HostName 47.110.67.161
    User root
    ServerAliveInterval 60
    ServerAliveCountMax 3
    ExitOnForwardFailure yes
    TCPKeepAlive yes
    ConnectTimeout 10

3.2 创建 systemd 用户服务目录

mkdir -p ~/.config/systemd/user

3.3 新建 service(10021 主)

文件:~/.config/systemd/user/reverse-tunnel-ecs-10021.service

[Unit]
Description=Reverse SSH tunnel to ecs-99 (port 10021 -> local SSH)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
Environment="AUTOSSH_GATETIME=0"
Environment="AUTOSSH_POLL=60"
Environment="AUTOSSH_FIRST_POLL=30"
ExecStart=/usr/bin/autossh -M 0 -N -R 0.0.0.0:10021:localhost:22 ecs-99
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=default.target

3.4 10022 备入口(含 Mailu)

文件:~/.config/systemd/user/reverse-tunnel-ecs-10022.service

[Unit]
Description=Reverse SSH tunnel to ecs-99 (port 10022 -> local SSH)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
Environment="AUTOSSH_GATETIME=0"
Environment="AUTOSSH_POLL=60"
Environment="AUTOSSH_FIRST_POLL=30"
ExecStart=/usr/bin/autossh -M 0 -N \
   -R 0.0.0.0:10022:localhost:22 \
  -R 127.0.0.1:10225:127.0.0.1:25 \
  -R 127.0.0.1:10465:127.0.0.1:465 \
  -R 127.0.0.1:10587:127.0.0.1:587 \
  -R 127.0.0.1:10993:127.0.0.1:993 \
  ecs-99
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=default.target

说明:10022 只承载备用 SSH 和 Mailu 四个 hostPort。Web 80/443 已迁移 到 HAProxy + WireGuard,不得重新加入该 service,除非按 WireGuard 回滚步骤 临时恢复旧路径。ECS 上的四个 Mailu入口必须是 loopback-only;公网绑定由 HAProxy 完成。

3.5 启用并启动

export XDG_RUNTIME_DIR=/run/user/$(id -u)

systemctl --user daemon-reload
systemctl --user enable --now reverse-tunnel-ecs-10021.service
systemctl --user enable --now reverse-tunnel-ecs-10022.service

systemctl --user status reverse-tunnel-ecs-10021.service --no-pager
systemctl --user status reverse-tunnel-ecs-10022.service --no-pager

3.6 防登出失效(强烈建议)

sudo loginctl enable-linger aaron
loginctl show-user aaron | grep Linger
# 期望: Linger=yes

四、连通性验证

4.1 在 ECS 上看监听

ssh root@47.110.67.161 "ss -tlnp | grep -E '10021|10022'"

期望看到(监听归属示例;地址、来源限制和健康状态需按 2026-08-13 之后的 live 状态复核):

  • 0.0.0.0:10021
  • 0.0.0.0:10022
  • 127.0.0.1:10225, 127.0.0.1:10465, 127.0.0.1:10587, 127.0.0.1:10993 (sshd)
  • 0.0.0.0:80, 0.0.0.0:443 (HAProxy)
  • 0.0.0.0:51820/udp (WireGuard)

4.2 在外网验证

nc -zv 47.110.67.161 10021
nc -zv 47.110.67.161 10022

ssh -p 10021 aaron@47.110.67.161
ssh -p 10022 aaron@47.110.67.161

10021/10022 外网超时,但 ECS 上已监听,优先检查:

  • 云安全组来源网段是否覆盖当前出口 IP
  • ECS UFW 是否放行对应端口

4.3 Mailu 入口验证

在 ECS 上确认公网 Web/Mail 端口归 HAProxy;10021/10022 SSH listener 和 10225/10465/10587/10993 Mailu loopback backend 归 sshd:

sudo systemctl is-active haproxy
haproxy -c -f /etc/haproxy/haproxy.cfg
sudo ss -ltnp

从不发送邮件数据的测试客户端验证 banner、TLS 和 STARTTLS。relay 检查最多 发送 EHLOMAIL FROMRCPT TOQUIT,不要发送 DATA,不要认证。 同时检查 ArgoCD 与 front rollout:

argocd app get ops-docs --refresh --insecure --grpc-web
argocd app get mailu --refresh --insecure --grpc-web
kubectl -n mailu rollout status deployment/mailu-front
kubectl -n mailu get endpoints mailu-front mailu-front-ext

若 Dovecot 报 Client not trusted,先检查 Mailu realIpFrom 与 hostPort/CNI 实际传输源是否一致。修正必须提交到 Git 并等待 ArgoCD 自动同步,不能手动 patch、apply、sync 或重启 Mailu。

五、故障恢复(按顺序)

场景 A:ECS 本机 ssh localhost -p 10022 失败

这说明 ECS 上没有监听 10022,问题几乎总在源机器(72602-minipc)侧。

在 72602-minipc 执行:

export XDG_RUNTIME_DIR=/run/user/$(id -u)

# 1) 看 service
systemctl --user status reverse-tunnel-ecs-10021.service --no-pager
systemctl --user status reverse-tunnel-ecs-10022.service --no-pager

# 2) 一次只处理一条;先处理 backup,验证 listener/banner/监控恢复后再决定是否处理 primary
TUNNEL_SERVICE=reverse-tunnel-ecs-10022.service
systemctl --user restart "$TUNNEL_SERVICE"

# 3) 看日志
journalctl --user -u "$TUNNEL_SERVICE" --since "10 min ago" --no-pager

# 4) 验证到 ECS 的基础连通
ssh -o ConnectTimeout=5 root@47.110.67.161 echo ok

场景 B:外网超时但 ECS 本机可通

问题在安全组或 ECS 防火墙,不在隧道本身。

ssh root@47.110.67.161 "ss -tlnp | grep -E '10021|10022'"
ssh root@47.110.67.161 "iptables -L INPUT -n | grep -E '10021|10022' || true"

场景 C:重启后隧道没起来

export XDG_RUNTIME_DIR=/run/user/$(id -u)
systemctl --user is-enabled reverse-tunnel-ecs-10021.service
systemctl --user is-enabled reverse-tunnel-ecs-10022.service
loginctl show-user aaron | grep Linger

六、常用运维命令

export XDG_RUNTIME_DIR=/run/user/$(id -u)

# 重载;重启时一次只选择一个入口,验证 listener/banner/监控后再处理另一个
systemctl --user daemon-reload
TUNNEL_SERVICE=reverse-tunnel-ecs-10021.service
systemctl --user restart "$TUNNEL_SERVICE"

# 停止(仅在明确授权的维护窗口内;一次只停止一个入口)
TUNNEL_SERVICE=reverse-tunnel-ecs-10021.service
systemctl --user stop "$TUNNEL_SERVICE"

# 日志实时跟踪
journalctl --user -u "$TUNNEL_SERVICE" -f

七、和现网监控的关系(72602;2026-08-13 审计快照,动态状态需 live verify)

ECS 上原有巡检继续只监控 72602-minipc 的两个公开入口;其端口列表、脚本、 unit、timer 和状态文件保持不变。

当 72602-minipc 双入口上线后,只需确认:

  • 安全组已放行 10021/10022
  • /etc/tunnel-healthcheck-ports.conf 包含 1002110022
  • /etc/tunnel-healthcheck.env 企业应用参数可用(DINGTALK_CLIENT_ID/SECRET/AGENT_ID/USER_IDS

按该审计快照记录的 72602 线上实现:

  • 告警通道为钉钉企业应用 API(非 webhook)
  • 告警消息为 Markdown 格式(标题:🚨 ECS Tunnel Alert
  • 连续失败 3 次才发送告警(防抖)

ZJLAB 的两个私有 loopback listener 不加入上述端口列表,而由独立的 check-only 巡检按 primarybackup 标签检查。该巡检要求每个标签恰有一个 loopback-only listener、唯一 owner 为 sshd、两个 owner 相互独立、短超时 SSH banner 正常且 owner 签名稳定;连续失败 3 次才告警。日志和告警不包含 endpoint、端口、账户、内网拓扑或签名值。巡检不会 restart/kill 隧道,也不会 修改 sshd、防火墙、安全组、DNS 或 endpoint。真实配置和恢复步骤只保存在 私有 SOPS inventory 与 root-only host 配置中。

八、Mailu 代理回滚

按以下顺序回滚,不恢复旧 HAProxy 配置,也不卸载 HAProxy:

# ECS
sudo systemctl stop haproxy

# 72602-minipc(使用实际保存的备份路径)
cp /home/aaron/Ops/ops-private/backups/reverse-tunnel-ecs-10022.service.<UTC>.before-haproxy \
  ~/.config/systemd/user/reverse-tunnel-ecs-10022.service
export XDG_RUNTIME_DIR=/run/user/$(id -u)
systemctl --user daemon-reload
systemctl --user restart reverse-tunnel-ecs-10022.service

若还原 ECS 的 GatewayPorts 修正,恢复对应的 /var/backups/sshd_config.<UTC>.before-haproxy,执行 sshd -t 后 reload sshd,再按上面步骤恢复并重启 10022。10021 不在本次回滚范围内。