本文面向中高级工程师,旨在深度剖析堡垒机(Bastion Host)这一关键基础设施的架构设计与技术实现。我们将从运维审计的原始需求出发,下探到底层 PTY/TTY 的工作原理,剖析核心代理模块的代码实现,并探讨在高并发、高可用场景下的架构权衡与演进路径。最终,我们将视野拓展至云原生时代,审视堡垒机如何向零信任模型下的安全访问网关(Secure Access Gateway)演变。
现象与问题背景
在企业发展的初期,服务器规模较小,研发人员通常通过 SSH 或 RDP 直接连接生产服务器进行操作。这种看似便捷的方式,在团队和业务规模扩大后,会迅速演变成一场安全与管理的灾难。我们通常会面临以下几个典型问题:
- 凭证失控(Credential Sprawl):服务器的 SSH 密钥、密码等敏感凭证散落在各个工程师的本地电脑上。当员工离职或转岗时,权限回收成为一项极其繁琐且容易遗漏的工作,留下巨大的安全隐患。
- 权限泛滥(Privilege Escalation):为了图方便,常常会给开发人员分配过高的权限,甚至直接给予 root 账户的访问权限。这违反了最小权限原则(Principle of Least Privilege),一旦发生误操作或账号被盗,其破坏性是毁灭性的。
- 审计黑洞(Audit Black Hole):当线上出现故障或安全事件时,溯源追责变得异常困难。我们无法准确回答“谁(Who)、在什么时间(When)、从哪里(Where)、对哪个资产(Which)、做了什么(What)”这一审计核心问题。服务器的 `.bash_history` 记录可以被轻易篡改或删除,完全不可信。
- 管理效率低下:成百上千台服务器的账号和权限管理依赖手工脚本或Excel表格,这不仅效率低下,而且极易出错。新员工入职、权限变更等日常操作都成为运维团队的沉重负担。
为了解决这些问题,一个集中式的访问控制和审计平台——堡垒机,应运而生。它作为所有运维流量的唯一入口,成为了内部网络安全的第一道防线。
关键原理拆解
作为一名架构师,我们不能只停留在“它能解决问题”的层面,而必须理解其背后的计算机科学原理。堡垒机的核心功能,本质上是代理模式(Proxy Pattern)和中间人攻击(Man-in-the-Middle, MITM)技术的一种善意应用。
学术派视角:
- 网络咽喉要道(Choke Point):在网络拓扑中,堡垒机是一个强制性的流量经过点。所有对内网服务器的访问请求,都必须首先经过堡垒机进行认证和授权。这创建了一个天然的审计和控制点,符合网络安全纵深防御的基本思想。
- AAA 框架:认证、授权、审计(Authentication, Authorization, Accounting):这是一个经典的安全模型。堡垒机完美地实践了该模型:
- 认证(Authentication):确认用户是谁。堡垒机作为统一认证入口,可以对接 LDAP、RADIUS、OAuth2/OIDC 等多种身份提供商(IdP),实现单点登录(SSO)。
- 授权(Authorization):确认用户能做什么。用户登录后,堡垒机根据预设的策略,决定该用户可以访问哪些主机、使用哪些账号(如 webadmin、dba 等)。
- 审计(Accounting/Auditing):记录用户做了什么。这是堡垒机最核心的价值之一,通过会话录制、命令抓取等方式,记录所有操作以备事后审查。
- 伪终端 PTY/TTY 机制:要理解 SSH 的会话录制,必须深入到操作系统的终端子系统。当用户通过 SSH 客户端连接到堡垒机时,堡垒机的 SSH 服务端(sshd)会为其分配一个伪终端(Pseudo-Terminal, PTY)。一个 PTY 设备对由一个主设备(master)和一个从设备(slave)组成。
- 用户在客户端的输入,通过 SSH 加密通道,被写入 PTY 的主设备。
- 操作系统内核将数据转发到 PTY 的从设备,这个从设备对于上层应用(如 bash shell)来说,看起来就像一个真实的物理终端(TTY)。
- Shell 执行命令产生的输出,被写入从设备,内核再将其转发回主设备,最终通过 SSH 通道返回给用户客户端。
堡垒机的代理程序正是工作在 PTY 的主从设备之间。它启动一个进程,一端连接来自用户的 SSH 连接(Client PTY Master),另一端作为客户端去连接目标服务器(Server PTY Master),然后像一个水管工一样,在两个水管之间转发数据流,并在这个过程中,将所有流经的数据(用户的输入和服务器的输出)完整地记录下来。这便是会话录制(Session Recording)的底层原理。
系统架构总览
一个生产级的堡垒机系统,不是单一的程序,而是一个复杂的分布式系统。我们可以将其核心组件抽象为以下几个部分(以主流开源堡垒机 Jumpserver 为例):
- Core (Web UI & API):系统的“大脑”,通常由 Python/Django 或 Java/Spring Boot 构建。它提供了一个 Web 界面供管理员和用户使用,负责用户管理、资产管理、权限策略、审计日志查询等所有核心业务逻辑,并通过 RESTful API 对外提供服务。
- Koko (SSH/Telnet 代理服务):负责处理 SSH 和 Telnet 协议的代理。当用户通过 SSH 客户端连接堡垒机时,实际连接的是 Koko 服务。Koko 负责处理与用户的 PTY 交互,并根据 Core 组件的授权信息,建立到后端目标服务器的连接,同时进行会话录制。它通常使用 Go 或 Rust 等高性能语言编写,以应对高并发连接。
- Guacamole (RDP/VNC 代理服务):负责处理图形化协议的代理。它是一个 Apache 的顶级项目,提供了一个“无客户端”的远程桌面网关。它将 RDP、VNC 等协议的渲染指令流,实时转换为 HTML5 Canvas 指令流,通过 WebSocket 推送给浏览器,从而实现用户在浏览器中直接操作远程 Windows 或 Linux 桌面。
- 数据库(Database):存储所有元数据,如用户、资产、权限、会话记录索引等。通常使用 MySQL 或 PostgreSQL。
- 分布式对象存储(Object Storage):用于存放大量的会话录像文件。直接存储在堡垒机本地磁盘是不可扩展的。使用 Ceph、MinIO 或云厂商的 S3/OSS 是更合理的选择。
– 消息队列(Message Queue): 如 RabbitMQ 或 Kafka,用于组件间的异步通信,例如 Core 通知 Koko 权限变更、Koko 发送会话结束事件等。
整个工作流程是:用户通过浏览器或 SSH 客户端访问堡垒机。如果是 Web 访问,流量首先到达 Core 组件;如果是 SSH 访问,流量到达 Koko 组件。组件会向 Core 查询认证和授权信息。授权通过后,Koko/Guacamole 会建立到后端目标资产的连接,并开始代理和录制会话。会话结束后,录像文件上传到对象存储,元数据写入数据库。管理员可以通过 Core 的 Web 界面进行审计回放。
核心模块设计与实现
现在,让我们切换到极客工程师的视角,深入几个关键模块的实现细节和坑点。
SSH 核心代理与会话录制
这部分是堡垒机的灵魂。用 Go 语言实现一个简化的 SSH 代理录制逻辑,能非常清晰地展示其工作模式。假设我们已经完成了与客户端和目标服务器的 SSH 握手并建立了连接。
// sessionContext 包含客户端连接、目标服务器连接、录制文件句柄等
func proxyAndRecord(ctx *sessionContext) {
// 创建一个 io.Writer,它会同时写入两个地方:
// 1. 目标服务器的 stdin
// 2. 我们的录制文件 (用于记录用户输入)
serverInWriter := io.MultiWriter(ctx.serverConn.Stdin, ctx.logFile)
// 创建另一个 io.Writer,它会同时写入:
// 1. 客户端的 stdout
// 2. 我们的录制文件 (用于记录服务器输出)
clientOutWriter := io.MultiWriter(ctx.clientConn.Stdout, ctx.logFile)
// 使用两个 goroutine 来双向拷贝数据流
// 这是整个代理的核心,非常直接,但非常强大
// Goroutine 1: 从客户端读取,写入到目标服务器和日志
// io.Copy 会阻塞直到 EOF 或发生错误
go func() {
_, _ = io.Copy(serverInWriter, ctx.clientConn.Stdin)
// 一旦拷贝结束(比如客户端断开),通知另一端
ctx.clientConn.Close()
ctx.serverConn.Close()
}()
// Goroutine 2: 从目标服务器读取,写入到客户端和日志
go func() {
_, _ = io.Copy(clientOutWriter, ctx.serverConn.Stdout)
// 服务器端连接断开(比如用户执行了 exit)
ctx.serverConn.Close()
ctx.clientConn.Close()
}()
// 等待会话结束信号...
}
工程坑点:
- `io.Copy` 的陷阱:`io.Copy` 是阻塞的。当一端断开连接时,`io.Copy` 会返回 `EOF` 错误,goroutine 退出。你必须确保一个 goroutine 的退出能正确地关闭两个连接,从而让另一个 goroutine 也能正常退出,避免资源泄露。
- 窗口大小变更(Window Size Change):交互式 Shell 需要知道终端的尺寸(比如 80×24)来正确渲染界面(例如 `vim` 或 `top`)。客户端改变窗口大小时,会通过 SSH 发送一个 “window-change” 请求。你的代理必须能捕获这个请求,并将其转发给后端服务器,否则终端显示会错乱。
- 录制格式:直接把输入输出流写入一个文本文件是最简单的,但回放效果很差。更优的方案是采用结构化格式,如 Asciinema 的 JSON 格式,它记录了每一帧输出的时间戳和内容:`[timestamp, “output”, “data”]`。这使得我们可以实现精确的、可快进/快退的录屏回放。
命令审计与实时告警
仅仅录屏是不够的,我们还需要对用户执行的命令进行结构化解析和实时告警,比如当有人执行 `rm -rf /` 时立即阻断并报警。
实现思路:在上述 `io.Copy` 的 `serverInWriter` 中,我们不能直接写入目标服务器。而是要加一个中间的 buffer。当从客户端读到数据时,先写入 buffer。然后启动一个解析器协程,尝试从 buffer 中解析出完整的命令。这比听起来要难得多:
- 用户可能一个字符一个字符地输入,也可能一次性粘贴一大段。
- 用户会使用退格键、方向键、`Ctrl+C` 等控制字符。
一个健壮的实现需要一个简易的 shell 解析器,能够处理这些编辑行为,并准确地识别出回车键(`\r`)代表的命令提交。一旦识别出命令,就可以将其与预设的告警规则库进行正则匹配。如果匹配成功,可以执行阻断(即不再将该命令写入 `serverConn.Stdin`)并触发告警。
极客吐槽:这块功能非常容易做得“看起来能用,但实际全是 bug”。比如,用户用 `echo “cm0gLXJmIC8K” | base64 -d | sh` 这样的方式绕过简单的关键词检测。真正的实时命令阻断,需要在性能和准确性之间做极大的权衡,很多时候,高精度的“事后审计”比不完善的“实时阻断”更有价值。
性能优化与高可用设计
堡垒机作为所有运维流量的入口,其性能和可用性至关重要。一旦它宕机,所有人都无法进行运维工作,后果不堪设想。
性能瓶颈
- 并发连接数:每个 SSH 会话都需要消耗文件描述符、内存和 CPU。当有成百上千个并发会话时,单机性能会成为瓶颈。解决方案是采用 Go 这种原生支持高并发的语言,并依赖内核的 `epoll/kqueue` I/O 多路复用模型。
- SSL/TLS 卸载:对于 Web 访问和 RDP/VNC 的 WebSocket 流量,TLS 加解密会消耗大量 CPU。可以在堡垒机集群前部署专用的负载均衡器(如 F5、Nginx)来进行 TLS 卸载。
- 录屏文件 I/O:大量的会话录制会产生巨大的 I/O 压力。将录屏文件直接写入对象存储,而不是本地磁盘,可以有效分摊 I/O 负载,并解决存储容量问题。
高可用架构
- Active-Standby 模式:最简单的 HA 方案。两台堡垒机,一台主(Active),一台备(Standby)。通过 Keepalived 或类似工具维护一个虚拟 IP (VIP)。当主节点心跳超时,VIP 会自动漂移到备节点。数据同步是这里的关键,需要实时同步数据库和会话录像文件(如果存储在本地的话)。
- Active-Active 集群模式:更可靠的方案。部署多台堡垒机节点,前端通过 L4 负载均衡器(如 LVS)分发 TCP 请求。这里最大的挑战是会话状态的一致性。如果一个用户的 SSH 连接在多个 TCP 包中被负载均衡器分发到了不同的堡垒机节点,会话就会中断。因此,需要使用基于源 IP 的会话保持(Session Affinity/Sticky Session)。此外,所有节点必须共享同一套后端存储(数据库、对象存储),且授权等状态信息需要通过分布式缓存(如 Redis)来同步。
架构演进与落地路径
一个复杂的堡垒机系统不是一蹴而就的,其演进路径通常遵循以下阶段:
- 阶段一:单机跳板机 (Jump Host)
- 形态:一台加固的 Linux 服务器,通过 `sshd_config` 的 `ForceCommand` 选项,强制所有登录用户进入一个自定义的脚本。该脚本提供一个菜单让用户选择目标主机,并使用 `script` 命令进行简单的会话文本记录。
- 优点:实现简单,快速解决有无问题。
- 缺点:功能简陋,管理不便,无 Web UI,无图形化支持,单点故障。
- 阶段二:一体化堡垒机 (All-in-One Bastion)
- 形态:引入开源堡垒机项目(如 Jumpserver),将 Core、Koko、数据库、Redis 等所有组件部署在单台或两台(主备)服务器上。提供 Web UI,支持 RDP/VNC,有完整的 AAA 体系。
- 优点:功能完善,满足绝大多数中小型企业的安全审计需求。
- 缺点:随着并发会话增多,单机性能瓶颈凸显;运维和管理集中在少数机器上,仍有风险。
- 阶段三:分布式、高可用集群
- 形态:将核心组件拆分并独立部署。Koko/Guacamole 节点组成无状态集群,前端用 LB 负载。数据库、对象存储使用云服务或自建高可用集群。实现真正的横向扩展(Scale Out)。
- 优点:高可用、高性能,可支撑大规模并发访问。
- 缺点:架构复杂,运维成本高。
- 阶段四:云原生安全访问网关 (Cloud-Native Secure Access Gateway)
- 形态:这是堡垒机在零信任(Zero Trust)理念下的演进。它不再仅仅是服务器的入口,而是所有内部资源(数据库、Kubernetes API、内部 Web 应用)的统一访问代理。代表项目如 Teleport、HashiCorp Boundary。
- 特点:
- 身份驱动:访问控制完全基于用户的身份(通过 OIDC/SAML 与 IdP 集成),而不是其网络位置。
- 协议感知:能够深度解析 MySQL、PostgreSQL、MongoDB、`kubectl` 等协议,实现对数据库查询、K8s 操作的精细化审计。
- 短时凭证:用户访问资源时,动态签发短周期的证书或 Token,取代长期有效的静态密码/密钥,极大提升安全性。
- 服务网格集成:与 Istio、Linkerd 等服务网格结合,为东西向流量(服务间调用)提供统一的认证和授权。
从简单的跳板机,到功能丰富的堡垒机,再到拥抱云原生和零信任理念的安全访问网关,这条演进之路反映了IT基础设施和安全理念的深刻变革。作为架构师,我们需要理解每个阶段背后的技术驱动力和架构权衡,从而为组织在不同发展时期选择最合适的技术方案。
延伸阅读与相关资源
-
想系统性规划股票、期货、外汇或数字币等多资产的交易系统建设,可以参考我们的
交易系统整体解决方案。 -
如果你正在评估撮合引擎、风控系统、清结算、账户体系等模块的落地方式,可以浏览
产品与服务
中关于交易系统搭建与定制开发的介绍。 -
需要针对现有架构做评估、重构或从零规划,可以通过
联系我们
和架构顾问沟通细节,获取定制化的技术方案建议。