从内核到云原生:首席架构师深度剖析堡垒机运维审计系统

本文面向具备一定经验的工程师与架构师,旨在深入剖析堡垒机(Bastion Host)运维审计系统的核心设计原理与工程实践。我们将跳出“功能介绍”的层面,从操作系统内核的 PTY/TTY 机制、网络代理模型,到分布式环境下的高可用与弹性伸缩,系统性地拆解一个现代堡垒机从零到一的架构考量、技术权衡与演进路径,为你提供一个可落地、高标准的参照系。

现象与问题背景

在企业发展的早期阶段,服务器运维普遍处于一种“蛮荒”状态。工程师通过 SSH 直连生产服务器,通常使用共享的 root 账户或个人账户。这种模式虽然便捷,但随着团队规模和业务复杂度的增长,其固有的安全风险与管理难题会集中爆发:

  • 身份无法鉴别: 共享 root 密码意味着操作日志中的 `root` 用户无法追溯到具体的自然人。一旦出现生产事故或恶意操作,事后审计将成为一句空话。
  • 权限失控: 工程师一旦登录,通常拥有服务器的全部权限。误操作(如 `rm -rf /`)或越权操作的风险极高,缺乏有效的权限控制与收敛机制。
  • 访问入口分散: 运维入口散落在成百上千台服务器上,当员工离职时,清理其散布各处的公钥或账户成为一项繁琐且容易遗漏的工作,留下巨大的安全隐患。
  • 审计缺失: 缺乏对操作过程的记录。管理员无法得知运维人员在服务器上执行了哪些具体指令,也无法复盘整个操作过程,这在金融、医疗等强合规行业是不可接受的。

堡垒机(或称 Jumpserver)作为解决上述问题的标准方案应运而生。它的核心思想是构建一个统一的运维入口,将所有运维人员和目标服务器进行隔离。任何对后台服务器的访问都必须经过这个“堡垒”,从而实现了“认证、授权、审计”的闭环管理。它不再仅仅是一个跳板,而是一个集中的安全管控与审计平台。

关键原理拆解

要构建一个工业级的堡垒机,我们必须回归到底层的计算机科学原理。堡垒机的核心功能——会话拦截与审计——并非魔法,而是对操作系统和网络协议栈已有机制的精妙运用。

学术之声:从计算机安全三大基石(AAA)谈起

一个完备的访问控制系统,都建立在 AAA 模型 之上,堡垒机正是该模型的典型工程实现:

  • Authentication (认证): “你是谁?”。系统需要确认操作者的真实身份。简单的用户名密码、SSH 密钥对,到更安全的双因素认证(MFA),都是认证环节的手段。堡垒机必须成为唯一的认证入口,并最好与企业统一的身份提供商(IdP,如 LDAP, AD, OAuth2)集成。
  • Authorization (授权): “你能做什么?”。在确认身份后,系统需要根据预设的策略,决定该用户能够访问哪些资源(服务器、数据库),以及在这些资源上允许执行何种级别的操作。这就是最小权限原则(Principle of Least Privilege, PoLP)的体现。
  • Accounting/Auditing (审计): “你做了什么?”。这是堡垒机区别于简单跳板机的核心价值。系统必须完整、准确地记录用户的所有操作行为,包括登录时间、源 IP、执行的每一条命令、会话的完整录像,以备合规审查和故障排查。

深入内核:TTY/PTY – 会话劫持的基石

堡垒机如何能在不侵入目标服务器的情况下,记录下用户的全部键盘输入和屏幕输出?答案在于对伪终端(Pseudo-Terminal, PTY)的利用。

在经典的UNIX体系中,用户与命令行程序的交互是通过终端(Terminal)设备完成的。当我们通过 SSH 登录一台远程服务器时,SSH 服务端进程(`sshd`)会为这个会话创建一个伪终端。这个 PTY 设备表现为一个设备对(device pair):一个 master 端和一个 slave 端(例如 `/dev/ptymx` 和 `/dev/pts/1`)。

  • Slave 端: 它看起来就像一个真实的物理终端,用户的 Shell(如 bash)和所有在此 Shell 中启动的程序(如 `top`, `vim`)都会将它作为自己的标准输入、输出和错误流。
  • Master 端: `sshd` 进程持有 master 端。它负责将从网络(用户SSH客户端)收到的数据写入 master 端,这些数据会立刻出现在 slave 端的输入中,被 Shell 读取;同时,`sshd` 也从 master 端读取数据(即 Shell 和其子进程的全部输出),然后将这些数据通过网络发回给用户的 SSH 客户端。

堡垒机巧妙地将自己插入到了这个数据流的中间。当用户通过堡垒机连接目标服务器时,流程变为:

User’s SSH Client <=> Bastion Host <=> Target Server’s sshd

在堡垒机内部,它为用户的连接创建了一个 PTY 设备对。它的一端(master)连接着用户的 SSH 客户端,另一端(slave)则启动了一个新的 SSH 客户端进程,这个进程再去连接真正的目标服务器。如此一来,用户与目标服务器之间的所有 I/O 流量都必须流经堡垒机的这个用户态进程。这个进程就像一个“中间人”,可以轻而易举地将双向数据流完整地复制一份,存入日志文件,从而实现了“无侵入”的会话录制。

系统架构总览

一个现代化的、高可用的堡垒机系统通常采用微服务化的分布式架构。我们可以将其划分为以下几个核心组件,它们协同工作,共同构成一个完整的运维审计平台。

(以下为架构图的文字描述)

系统的逻辑架构图可以分为四个主要层次:接入层、应用核心层、数据存储层和被管资源层。

  • 接入层: 位于最前端,直接面向用户。通常由一个 L4/L7 负载均衡器(如 Nginx, F5, 或者云厂商的 SLB)构成。它负责分发两种类型的流量:HTTP/HTTPS 流量到 Web 控制台集群,以及 SSH/RDP 流量到核心的代理网关(Jumpserver)集群。
  • 应用核心层: 这是系统的大脑,包含多个无状态或半状态的服务。
    • Web Console: 提供给管理员和用户的前端界面,用于用户管理、资产注册、权限策略配置、审计日志查看和会话录像回放。它是一个标准的 Web 应用,可以水平扩展。
    • Core API Service: 后端核心服务,为 Web Console 和其他组件提供 RESTful API。它负责处理所有的业务逻辑,如用户认证、权限校验、资产管理等。同样可以水平扩展。
    • Jumpserver/Gateway Service: 这是真正处理运维协议流量的代理服务,是系统的性能关键点。每个实例都是一个独立的 SSH/RDP 代理,负责建立、维持和拦截用户会话。这个组件必须设计为可水平扩展的,以应对大量的并发连接。
    • Session Recorder Service: 一个异步处理服务,负责将会话日志从临时存储转换为持久化的、可回放的格式,并存储到最终的存储系统中。
  • 数据存储层: 负责持久化系统的所有数据。
    • 关系型数据库 (MySQL/PostgreSQL): 存储结构化数据,如用户信息、资产信息、权限策略、操作日志索引等。需要配置主从复制或集群以实现高可用。
    • 分布式缓存 (Redis): 存储会话信息、用户 Token 等热数据,加速 API 响应。
    • 对象存储/分布式文件系统 (S3/Ceph/HDFS): 存储海量的会话录像文件。这类非结构化数据体积庞大,需要专门的存储系统。
    • 消息队列 (Kafka/Pulsar): 用于 Jumpserver 网关与 Session Recorder 服务之间的解耦。网关将原始的会话 I/O 流作为消息发送到队列中,录制服务作为消费者异步处理。
  • 被管资源层: 即企业内部需要通过堡垒机进行访问和管理的各种服务器、网络设备、数据库等。网络策略上,应配置防火墙规则,只允许来自 Jumpserver/Gateway 集群的 IP 地址访问这些资源,从而强制所有运维流量收敛到堡垒机。

核心模块设计与实现

在这里,我们转入极客工程师的视角,深入探讨几个关键模块的实现细节和代码片段。

SSH 代理与会话劫持实现

这是堡垒机技术含量最高的部分。如前所述,核心是利用 PTY。使用 Go 语言可以非常优雅地实现这一点,因为它强大的并发能力和丰富的标准库/第三方库支持。


package main

import (
	"io"
	"log"
	"os"
	"os/exec"

	"github.com/creack/pty"
	"golang.org/x/term"
)

// 这个函数演示了堡垒机劫持会话的核心逻辑
func forwardSSHSession(targetUser, targetHost string) {
	// 1. 创建一个到目标服务器的SSH命令
	// 在生产环境中,这里的参数会动态生成,并且会处理密钥认证
	cmd := exec.Command("ssh", "-p", "22", targetUser+"@"+targetHost)

	// 2. 为这个命令创建一个PTY
	// 这是魔法发生的地方。pty.Start会返回一个os.File对象,
	// 它代表了PTY的master端。cmd的stdin/stdout/stderr
	// 会被自动连接到PTY的slave端。
	ptmx, err := pty.Start(cmd)
	if err != nil {
		log.Fatalf("failed to start pty: %v", err)
	}
	defer ptmx.Close()

	// 3. 将当前程序的终端设置为原始模式,以便透传所有按键,
	// 如Ctrl+C等特殊字符。
	oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
	if err != nil {
		log.Fatalf("failed to set raw mode: %v", err)
	}
	defer term.Restore(int(os.Stdin.Fd()), oldState)

	// 4. 创建一个会话日志文件
	logFile, err := os.Create("session.log")
	if err != nil {
		log.Fatalf("failed to create log file: %v", err)
	}
	defer logFile.Close()

	// 5. 核心:设置 I/O 复制
	// 创建一个 MultiWriter,它会将所有写入操作同时复制到
	// 用户的标准输出 (os.Stdout) 和日志文件 (logFile)。
	// 这样就实现了屏幕输出的录制。
	mw := io.MultiWriter(os.Stdout, logFile)

	// 使用两个goroutine来双向复制数据
	// 从 PTY master 端读取(目标服务器的输出),写入 MultiWriter
	go func() {
		io.Copy(mw, ptmx)
	}()
	// 从用户标准输入读取,写入 PTY master 端(发送给目标服务器)
	io.Copy(ptmx, os.Stdin)
    
    // 等待命令执行完毕
    cmd.Wait()
}

func main() {
    // 简化示例,实际应用中会从认证信息中获取这些参数
	forwardSSHSession("root", "192.168.1.100")
}

上述代码虽然简化,但已完整展示了核心机制。一个生产级的 Jumpserver 服务会在此基础上:

  • 将 `os.Stdin` 和 `os.Stdout` 替换为代表用户网络连接的 `net.Conn` 对象。
  • `io.Copy` 会在一个更复杂的事件循环中处理,以管理成千上万的并发会话。
  • 写入 `logFile` 的操作会被替换为写入 Kafka 消息队列,以实现异步处理和削峰填谷。
  • 会增加对窗口大小变化(`SIGWINCH` 信号)的处理,确保 `less`, `vim` 等全屏应用正常工作。

操作指令审计与实时阻断

既然所有 I/O 流都经过了我们的代理进程,那么对指令进行审计和阻断就变得可行。我们可以在从用户输入流向 PTY 的 `io.Copy` 过程中加入一个“钩子”。

这个钩子的实现通常是一个带缓冲的 Reader。它按行读取用户输入,当检测到换行符(表示用户按下了 Enter)时,就将整行命令提取出来,与预设的规则库进行匹配。规则库可以基于正则表达式,例如:

  • 高危命令(黑名单): `^rm -rf /`, `^mkfs`, `^reboot` 等。
  • 敏感文件访问: `cat /etc/passwd`, `vi /etc/shadow` 等。

匹配到黑名单命令后,系统可以选择两种策略:

  1. 实时阻断: 直接丢弃该行输入,不写入 PTY,并向用户返回一条权限拒绝的提示。这种方式最安全,但对用户体验有一定影响,且可能被绕过(例如通过 `echo “rm -rf /” | bash`)。
  2. 事后告警: 允许命令执行,但立即通过消息系统(如钉钉、Slack)向安全管理员发送高危操作告警。这种方式平衡了可用性和安全性。

会话录屏与回放

录屏的本质是忠实记录 PTY 的输出流,这个流不仅包含可见字符,还包含了大量的 ANSI 转义序列,用于控制光标位置、文本颜色等。`asciinema` 是一个流行的开源工具,它定义了一种高效的录屏文件格式(JSON-based),非常适合作为我们录屏功能的参考。

一个录屏文件本质上是一个事件流,每个事件是一个元组 `[timestamp, event_type, data]`,例如 `[1.2345, “o”, “hello world”]` 表示在 1.2345 秒时输出了 “hello world”。
在 Web 端回放时,我们使用 `xterm.js` 这样的前端终端模拟器库。后端提供一个 WebSocket API,当用户请求回放某个会话时,服务端就按录屏文件中的时间戳,流式地将数据块(data)发送给前端的 `xterm.js` 实例,`xterm.js` 负责解释 ANSI 序列并渲染出与当时完全一致的终端画面。

性能优化与高可用设计

堡垒机作为所有运维流量的必经之路,其性能和可用性至关重要。一个节点的宕机可能导致所有运维工作中断。

高可用性(HA)

高可用设计的核心是消除单点故障(SPOF)。

  • 网关层(Jumpserver Service): 部署多个无状态的网关实例,前端使用 L4 负载均衡器(如 NLB, LVS)进行流量分发。使用 L4 而非 L7 的原因是 SSH 是长连接协议,L4 负载均衡器基于 TCP/IP 头部信息进行转发,效率更高,且能更好地处理长连接。健康检查可以基于一个简单的 TCP 端口探测。
  • 核心服务层(Core API & Web): 这些是标准的 Web 服务,可以部署多个实例,通过 L7 负载均衡器分发流量。它们应该是无状态的,所有状态信息都存放在数据库或缓存中。
  • 数据层: 数据库采用主从热备或集群模式(如 MySQL Galera Cluster, PostgreSQL with Patroni)。Redis 使用哨兵(Sentinel)或集群(Cluster)模式。会话录像文件存储在 S3 或 Ceph 这样本身就具备高可用性的存储系统中。

一个棘手的问题:会话中断与恢复

当一个网关实例宕机时,通过该实例的所有 SSH 会话会立即中断。这对用户来说体验不佳。实现会话的无缝迁移是一个巨大的挑战,因为它要求在网关节点之间同步 TCP 连接状态和 PTY 状态,工程复杂度极高,收效甚微。在实践中,绝大多数堡垒机系统接受的方案是:会话中断,用户重连。负载均衡器会自动将新的连接请求路由到健康的节点上,从而快速恢复服务。这是一种务实且成本效益最高的权衡。

性能优化

性能瓶颈主要集中在 Jumpserver/Gateway Service,因为它处理着大量的并发 I/O。

  • 异步 I/O 模型: 必须采用非阻塞 I/O 模型。Go 语言的 Goroutine + Channel 模型天生适合这种高并发 I/O 密集型场景。一个网关实例可以轻松处理数千甚至上万个并发 SSH 连接。
  • 日志处理解耦: 会话 I/O 的读写与日志的持久化必须解耦。如前所述,网关节点不应直接写磁盘或对象存储,这会引入 I/O 阻塞。正确的做法是,将原始的 I/O 流数据块打包成消息,快速推送到高吞吐的消息队列(如 Kafka)中。下游独立的、可水平扩展的 Session Recorder 服务集群负责消费这些消息,进行解析、转换和持久化。这套架构不仅提升了网关的性能和稳定性,还提供了极好的弹性,当日志量激增时,只需增加 Recorder 服务的消费者数量即可。
  • 资源隔离: 可以通过容器化(Docker/Kubernetes)部署,为每个服务组件设置明确的 CPU 和内存限制,防止某个组件(如失控的日志处理任务)耗尽整个节点的资源,影响核心的代理功能。

架构演进与落地路径

构建这样一个复杂的系统不可能一蹴而就。一个务实的演进路径至关重要。

第一阶段:核心功能 MVP (最小可行产品)

此阶段的目标是快速解决最痛的问题:统一入口和基本审计。可以基于 Jumpserver、Teleport 等优秀的开源项目进行二次开发或直接部署。

  • 部署单节点的堡垒机系统。
  • 集成企业统一的 LDAP/AD 用户认证。
  • 配置核心资产,实现基于 SSH 密钥或密码的代理登录。
  • 实现基本的会话录制和事后审计功能。
  • – 强制推行:通过网络策略,关闭服务器对外的 22 端口,只允许堡垒机访问,迫使所有运维人员切换到新流程。

第二阶段:高可用与可扩展性建设

当系统稳定运行并被团队接受后,就需要解决其单点故障和性能瓶颈问题。

  • 将 Jumpserver/Gateway 服务拆分为独立的、可水平扩展的集群,并引入 L4 负载均衡。
  • 为数据库和缓存系统配置高可用方案。
  • 引入消息队列,对会话日志处理进行异步化改造。
  • 将会话录像存储从本地磁盘迁移到对象存储。

第三阶段:高级功能与生态集成

在系统具备了坚实的基础架构后,可以开始构建更高级的安全功能和自动化能力。

  • 开发并上线命令的实时阻断功能。
  • 扩展支持的协议,如 RDP(Windows)、数据库协议(MySQL, PostgreSQL)、Kubernetes API。
  • 提供标准化的 API,允许与企业的 CMDB、SIEM(安全信息和事件管理)平台、自动化运维平台(如 Ansible)进行集成。
  • 开发细粒度的 RBAC(基于角色的访问控制),例如,允许用户 A 登录服务器 X,但只能执行 `sudo /usr/bin/docker ps` 等有限的几个命令。

通过这样分阶段的演进,我们可以在控制风险和投入的前提下,逐步构建起一个既满足当前需求,又具备未来扩展能力的强大、可靠的运维安全中枢。

延伸阅读与相关资源

  • 想系统性规划股票、期货、外汇或数字币等多资产的交易系统建设,可以参考我们的
    交易系统整体解决方案
  • 如果你正在评估撮合引擎、风控系统、清结算、账户体系等模块的落地方式,可以浏览
    产品与服务
    中关于交易系统搭建与定制开发的介绍。
  • 需要针对现有架构做评估、重构或从零规划,可以通过
    联系我们
    和架构顾问沟通细节,获取定制化的技术方案建议。
滚动至顶部