aTrust 内网 SSH 跳板部署记录

免责声明:

这篇文章只是一次技术记录。原则上来讲,把 aTrust 登录后的访问能力转接到一台云服务器或跳板机的做法削弱了系统原本对用户身份、终端环境、访问路径和审计链路的控制。换句话说,可能违背组织部署 aTrust 这类零信任系统的本意。

如果你想在公司、学校、实验室或任何受管网络里使用类似方案,请务必先获得网络管理员、安全团队或系统所有者的明确授权

前言

众所周知,Sangfor(深信服)aTrust 的客户端实现使用体验极差,主要问题包括且不限于:

  • 安装时会修改大量系统文件

  • 设置或修改与其功能无关的系统选项

  • 严重的卸载残留

  • 无法方便地与本地的代理共存

但是同时,aTrust 又被大多数企业和机构作为内网访问认证手段。于是在https://github.com/docker-easyconnect/docker-easyconnect项目的基础上,笔者尝试通过跳板机的方式隔离部署 aTrust。

整体框架

在 服务器上运行以 docker 的形式部署 aTrust GUI 客户端,通过登录 aTrust 后获得访问内网的能力,再让本机或其他用户通过使用服务器作为跳板机连接内网 SSH 服务器:

本机 -> SSH 到 服务器 -> 服务器 本地 SOCKS5 127.0.0.1:1080 -> 容器内部的 aTrust -> 内网 

最终使用方式:

ssh -o 'ProxyCommand=ssh -T -q Username@JumpServer /usr/bin/nc -x 127.0.0.1:1080 -X 5 %h %p' Username@Target

环境准备

跳板机的服务器需要具备:

  • SSH 访问

  • Docker

  • /dev/net/tun

检查命令:

command -v docker; test -e /dev/net/tun && echo TUN_OK || echo TUN_MISSING'

容器部署

使用 hagb/docker-atrust 容器内带有 VNC/noVNC。因此不需要在服务器上部署图形界面。

VNC,允许用户通过网络远程访问和控制另一台计算机的桌面环境,适合需要 GUI 输入账号、密码、验证码、MFA 的场景。

容器启动命令:

docker run -d \
  --name atrust \
  --restart unless-stopped \
  --device /dev/net/tun \
  --cap-add NET_ADMIN \
  --sysctl net.ipv4.conf.default.route_localnet=1 \
  -e PASSWORD="noVNCpassword" \
  -e URLWIN=1 \
  -e USE_NOVNC=1 \
  -e VNC_SIZE=1280x800 \
  -v /root/.atrust-data:/root \
  -p 127.0.0.1:5901:5901 \
  -p 127.0.0.1:8080:8080 \
  -p 127.0.0.1:1080:1080 \
  -p 127.0.0.1:8888:8888 \
  -p 127.0.0.1:54631:54631 \
  hagb/docker-atrust:latest

在此处需要设置 "noVNCpassword",即后面浏览器登陆时的密码

端口设置

  • 5901:VNC

  • 8080:noVNC Web GUI

  • 1080:SOCKS5 代理

  • 8888:HTTP 代理

  • 54631:aTrust 相关本地回调端口

出于安全考虑,所有端口应当绑定 127.0.0.1,不暴露到公网。

GUI 登录 aTrust

本机打开 SSH 隧道:

ssh -L 8080:127.0.0.1:8080 \
    -L 5901:127.0.0.1:5901 \
    -L 1080:127.0.0.1:1080 \
    -L 8888:127.0.0.1:8888 \
    Server

浏览器访http://127.0.0.1:8080后输入之前设置的 noVNC 密码:

进入 GUI 后在 aTrust 界面输入账号、密码、验证码 /MFA。登录完成后,跳板机本机的代理可用:

SOCKS5: 127.0.0.1:1080
HTTP:   127.0.0.1:8888

部署状态检查

容器状态:

docker ps -a --filter name=atrust --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"

端口监听:

ss -ltnp | grep -E ":(5901|8080|1080|8888|54631"

noVNC 检查:

curl -I --connect-timeout 5 http://127.0.0.1:8080

HTTP 代理检查:

curl -I -x http://127.0.0.1:8888 --connect-timeout 5 http://example.com/

SOCKS5 到内网 SSH 检查:

nc -z -v -w 10 -x 127.0.0.1:1080 -X 5 target.mechine.ip 22; echo socks_nc_exit=$?

本机连接内网 SSH

确认设置正常之后可以尝试连接内网 ssh

ssh -o 'ProxyCommand=ssh -T -q Username@JumpServer /usr/bin/nc -x 127.0.0.1:1080 -X 5 %h %p' Username@Target'

注意:

1. -T 避免 ProxyCommand 这层分配 TTY。

2. -q 避免 ProxyCommand 这层输出杂信息污染 SSH 协议。

3. /usr/bin/nc 使用绝对路径,减少 PATH 差异问题。

4. %h %p 会被外层 ssh 替换成目标内网地址和端口。

下一步

  • ssh -J只有在服务器本机可以直接访问目标机器时才成立。考虑后续在服务器上做透明路由或 iptables 转发

  • 考虑实现一下自动登陆(目前主要的问题是验证码🤔)

Btw...

  • 推荐把 ProxyCommand 写入本机 ~/.ssh/config

Host intranet-example

    HostName 10.0.0.XX
    User your_username
    ProxyCommand ssh -T -q user@bastion-host /usr/bin/nc -x 127.0.0.1:1080 -X 5 %h %p
  • 配置 VSCode 的 remote 的时候遇到了一些奇怪的端口转发问题,倒腾一会之后发现在 ssh config 里面添加这几行配置能解决问题

ServerAliveInterval 30
ServerAliveCountMax 3
TCPKeepAlive yes
RequestTTY no
ControlMaster no