为 AI 编程代理正确配置堡垒主机访问
AI 编程代理的堡垒主机访问需要强制路由、受控凭据、SSH 命令证据和可靠的目标日志记录。

堡垒主机可以为 AI 编程代理进入私有基础设施提供更安全的路径,但前提是你把它当作访问边界的一部分。仅仅在 SSH 配置文件中出现一个跳板主机,并不等于有了控制。代理可能绕过它、借用凭据、建立隧道,或者只留下能够证明“某个东西连接过跳板主机”的审计记录。
对于自主执行的任务,更可靠的设计要严格得多:代理只能到达一个受控入口,入口只能连接已知目标,凭据不能进入代理的上下文,记录还必须区分代理请求的主机与实际接受连接的主机。这个区别听起来有些吹毛求疵,直到第一次事故复盘。那时它决定了你手里的是证据,还是一个令人安心的故事。
堡垒主机会改变路径,不会改变代理的权限
堡垒主机访问设计可以限制网络可达性,但不能减少代理到达特权账户后能做的事情。团队常常因为两者都涉及 SSH 而混淆它们。它们是不同的控制,应当分别失效。
当私有目标只接受来自堡垒主机或其子网的管理流量时,跳板主机很有帮助。它为你提供了一个位置,可以施加出站限制、收集连接证据,并在调查期间切断访问。它还避免让每台工作站、构建运行器和代理沙箱都需要直接访问敏感网络。
但它不能回答自主客户端更关心的四个问题:
- 代理能否通过另一条路径直接访问目标?
- 目标账户的权限是否超出了当前任务的需要?
- 代理能否建立未经审核的隧道?
- 事后能否确认最终机器和远程命令?
如果第一个问题的答案是“能”,ProxyJump 就只是便利设置,而不是强制措施。如果第二个问题的答案是“能”,堡垒主机只是把权限过大的凭据进入网络的位置挪了过去。
我见过团队精心维护跳板主机,却为了“故障排查”允许 CI 子网拥有广泛的 SSH 出站权限。运行在该子网中的代理不需要漏洞利用,也不需要什么高明技巧,它可以直接访问最终地址。架构失败的原因是网络允许绕过,而不是 SSH 表现异常。
把各层职责说清楚。代理环境只能连接入口主机。入口主机只能连接指定的目标网络和端口。最终主机应当只为狭窄用途授权账户。每一层都应生成记录,回答不同的问题。
ProxyJump 不会暴露最终命令
SSH 跳板主机通常无法检查代理在最终主机上执行的命令。这正是团队承诺“堡垒主机可以记录全部命令”时最容易忽略的一点。
OpenSSH 在 ssh_config(5) 中说明,ProxyJump 会先连接跳板主机,然后建立到最终目标的转发。在常见的 OpenSSH 使用方式中,客户端实际上请求跳板主机与目标建立 TCP 连接,随后通过这条字节流运行端到端 SSH 会话。最终的 SSH 传输仍然在客户端与目标之间加密。
堡垒主机通常可以知道它连接到了 10.42.8.19:22。它可能记录堡垒主机上使用的账户、来源、时间,以及转发请求的目标。但它不会自动知道加密会话后来执行的是 systemctl restart api、cat /etc/shadow,还是一个交互式 shell。
这会产生三种经常被混为一谈的观察结果:
- 请求目标是代理提供的内容,例如
prod-api-01。 - 连接目标是代理实际打开的地址和端口。
- 执行目标是接受最终 SSH 身份验证的服务器身份和账户。
它们可能不同。DNS 变更后,别名可能解析到不同地址。遭入侵或过期的 known-host 条目会让连接变得可疑。代理规则可能把一个友好的别名指向意外地址。只记录 prod-api-01,无法证明哪台服务器执行了命令。
同样的区分也适用于命令。客户端网关可以在传输前记录请求的命令。最终主机上的包装器可以记录 OpenSSH 传递给 exec 请求的命令。堡垒主机可以记录 TCP 目标。应当通过会话标识和时间窗口关联这些观察结果,不要假装某一层能看到全部内容。
如果你需要一个能够检查并授权每条远程命令的统一位置,那么普通的端到端 SSH 加跳板主机并不是合适的基础设施。可以使用专用远程执行服务、受限命令端点,或只接受预定操作的目标侧包装器。这样的选择会牺牲灵活性,但也能避免声称你并没有的可见性。
网络规则必须让获批路径不可绕过
只有当周围网络让其他路径不可用时,受控入口才真正有效。先解决可达性,再讨论审批对话框或日志格式。
代理运行环境只需要向堡垒主机的地址和端口发起出站 SSH。它不应拥有通往生产子网、公开管理接口或第二个未纳入设计的跳板主机的宽泛路由。DNS 在这里同样重要。如果代理可以解析并连接原始目标地址,仅靠友好的主机名约定无法保护你。
堡垒主机需要一份简短的出站允许列表。如果它只管理两个私有网段中 TCP 22 端口上的主机,就只允许这些网段和端口。不要给它任意出站访问,然后把它称为堡垒主机。入口主机一旦可以访问每个服务、数据库和公共端点,在代理账户被滥用时就会变成通用中继。
最终目标只能接受来自堡垒路径的 SSH 管理流量。云安全组、主机防火墙和网络 ACL 都能提供帮助。只有在能够明确维护责任时,才叠加多个控制。无人维护的规则不能算防御。
用拒绝场景测试路径,而不只是测试成功命令。从运行代理的同一用户、容器或虚拟机中执行这些检查:
nc -vz bastion.internal.example 22
nc -vz 10.42.8.19 22
ssh -o ConnectTimeout=5 prod-api-01 true
第一个连接应当成功。直接连接最终主机应当失败。SSH 命令只能因为配置的路径使用堡垒主机而成功。如果环境中没有 nc,就使用环境通常提供的 TCP 测试命令。重点是测试网络路径,不要让 SSH 配置掩盖直接路由。
防火墙变化、新增子网或代理运行器变化后,都要重复测试。这是一个成本很低、收益很高的小测试,能发现 SSH 配置看起来正确但网络仍允许直连的常见问题。
代理需要可撤销的身份
不要给代理一个共享的管理员密钥,然后希望堡垒主机能让这种做法变得合理。被复制的私钥会形成一个影响时间很长的授权决定。
为每个代理进程或每次运行分配可以归因和撤销的身份。可以使用短期 SSH 证书、为限定任务注册的临时密钥,或由操作网关持有并代表代理执行 SSH 的凭据。具体选择取决于你的环境,但代理不应把可重复使用的秘密写入会话记录、工作区、shell 历史或工具输出。
如果你已经运行 SSH 证书颁发机构,SSH 证书会很有帮助。证书可以包含较短的有效期、目标账户的主体,以及源地址限制等关键选项。目标主机可以信任 CA,而不用维护不断增长的单独公钥列表,因此签发和过期更容易检查。
证书不会让宽权限账户变得狭窄。给 root 的证书在过期前仍然是 root 凭据。证书也不能证明命令意图。应把它们看作签发和生命周期管理机制。
网关会更明显地改变暴露模式。Sallyport 可以通过 sp-ssh 辅助程序执行 SSH,同时让 SSH 密钥留在加密保险库中,不进入代理进程。这能保护私钥不出现在代理记录里,但团队仍应测试预期的跳板路径、目标身份和命令证据如何被记录。
审批应绑定到可识别的进程,而不是代理在聊天窗口中写下的句子。进程可以经过签名、记录启动时间、绑定本地用户,并作为运行中的会话撤销。自然语言说明可以作为上下文,但不是访问控制边界。
避免所有自动化任务共享一个凭据。当目标侧日志显示 deploy 时,你还需要另一条记录来识别发起调用的代理运行。如果每次运行都使用相同密钥和账户,事故复盘就会变成考古工作。
同时记录执行声明、路径事实和结果
代理 SSH 审计记录应明确说明每个字段的含义,避免审计系统声称它无法支持的内容。
在请求操作时,记录代理运行身份、请求的主机别名、请求的远程账户、命令文本或结构化操作,以及调用方是否请求 TTY 或转发。这是执行声明,说明调用方试图做什么。
在路径边界记录入口主机身份、解析后的目标地址、目标端口和连接结果。这是路径事实。如果代理打开了连接,它可以说明连接打开到了哪里,但不能从加密字节中真实推断最终命令。
在最终主机记录目标主机的稳定身份、已认证账户、身份验证指纹或证书序列号、服务器收到 exec 请求时的命令、退出状态,以及相关本地服务日志。这是执行证据。最终主机是普通 SSH 端点中唯一能在不破坏 SSH 端到端设计的情况下看到远程命令的位置。
使用能让缺失证据显现出来的事件结构:
{
"run_id": "run_7c31",
"requested": {
"host": "prod-api-01",
"user": "deploy",
"command": "sudo systemctl restart api",
"tty": false
},
"route": {
"bastion": "bastion.internal.example",
"destination_ip": "10.42.8.19",
"destination_port": 22
},
"final_host": {
"host_fingerprint": "SHA256:example",
"authenticated_user": "deploy",
"command_observed": true,
"exit_status": 0
}
}
示例特意把 requested.command 与最终主机上的观察结果分开。如果会话是交互式的,就将 command_observed 设为 false,并说明原因。没有解释的空字段会让人误以为工具捕获了命令。
对于普通命令执行,sshd_config(5) 记录了 ForceCommand,它可以为匹配的账户或 match 块强制指定命令。目标侧包装器可以读取 exec 请求中的 SSH_ORIGINAL_COMMAND,验证允许的操作,并在调用获批程序前写入审计记录。它需要谨慎处理 shell。不要通过 eval 传递不受信任的命令字符串,也不要假设交互式 shell 一定存在 SSH_ORIGINAL_COMMAND。
哈希链和只追加存储有助于发现审计事件后续被修改,但它们无法修复薄弱的事件内容。一条保存得完美、却只写着“SSH 已连接”的记录,仍然是薄弱证据。
让 SSH 配置可检查
为人类保留主机别名,但在把配置交给代理前,先检查 OpenSSH 实际会做什么。ssh -G 会输出 OpenSSH 处理匹配的 Host 条目后生成的有效客户端配置。
下面的示例将一个命名目标通过指定堡垒主机进行路由,并关闭一些经常引发意外的选项:
Host agent-bastion
HostName bastion.internal.example
User agent-gateway
IdentityFile ~/.ssh/agent_gateway
IdentitiesOnly yes
Host prod-api-01
HostName 10.42.8.19
User deploy
ProxyJump agent-bastion
StrictHostKeyChecking yes
UserKnownHostsFile ~/.ssh/agent_known_hosts
ForwardAgent no
RequestTTY no
然后检查展开后的设置:
ssh -G prod-api-01 | grep -E '^(hostname|user|proxyjump|forwardagent|requesttty) '
输出应当具有类似这样的结构:
user deploy
hostname 10.42.8.19
requesttty no
forwardagent no
proxyjump agent-bastion
这能证明客户端计划如何运行,但不能证明网络阻止了直接 SSH、目标提供了预期主机密钥,或堡垒主机限制了出站访问。这些都要单独测试。
不要让代理自由提供任意 ssh 参数。如果执行层只是透传 shell 字符串,代理可以用命令行选项覆盖配置、指定原始 IP 地址、替换 ProxyJump,或添加转发选项。尽可能把目标、账户和允许的 SSH 选项放进结构化操作输入中。如果接受原始命令,就应把它视为代码,像对待来自不受信任来源的 shell 脚本一样谨慎。
主机密钥验证尤其需要注意,因为代理会自动重试,常常把错误当成任务障碍。通过受控流程预先加载可信主机指纹。保持严格检查。不要教代理删除 known-host 条目,或接受已变化的密钥来让部署继续。主机密钥变化时,必须由操作员确认变化原因。
转发和 shell 会绕过审核
除非特定任务确实需要,否则应为自主代理禁用转发和交互式 shell。这两项功能都会把受限的远程命令变成更宽泛的访问通道。
本地转发允许客户端打开一个本地端口,通过 SSH 访问内部服务。远程转发允许远端端点暴露一条返回客户端侧的路径。动态转发会创建 SOCKS 代理。代理转发会让另一台主机获得凭据。每一项对人工管理员都可能合理,但每一项也都会削弱“堡垒主机是唯一受控路径”的说法。
OpenSSH 提供的服务器侧限制应当配置在最终目标或堡垒账户上,而不只是代理的客户端配置中。sshd_config(5) 记录了 DisableForwarding,authorized_keys 则支持 no-port-forwarding、no-agent-forwarding、no-X11-forwarding 和 no-pty 等选项。使用你的 OpenSSH 版本支持的控制,然后通过真实连接尝试验证它们。
受限账户可以使用类似这样的 authorized key 条目:
no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... agent-run
这行设置会限制该公钥的功能,但不会限制账户可以执行的命令。如果命令范围很重要,还要配合受限账户、ForceCommand 包装器或特定服务接口。
对于交互式 shell,规则应当直接明确:默认不要给自主代理使用。shell 接受一连串命令、管道、重定向、后台进程和复制的凭据。初始操作请求中的命令字段不会描述完整会话。强制包装器可能阻止 shell,但必须进行测试,因为 shell 行为会随账户配置和 SSH 子系统请求而变化。
有些维护任务确实需要隧道或 shell。应把它们作为明确的例外路径,要求指定操作员批准并设置短期有效期。把例外隐藏在通用代理凭据中,临时访问就会变成永久访问。
目标限制优于命令字符串过滤
简单的 shell 命令前缀允许列表容易绕过,也难以维护。它很受欢迎,是因为看起来很精确:允许 systemctl restart api,拒绝其他内容。但 shell 语法会让这种信心变得脆弱。
假设过滤器接受以 systemctl restart api 开头的文本。调用方可能追加 shell 操作符、使用不同的可执行文件路径、通过环境变量触发行为,或利用解析方式不一致的包装器。即使解析器写得很谨慎,也无法预知一个会调用其他程序的命令的全部影响。
当任务形态稳定时,应使用结构化操作。部署运行器可以接受应用名称和发布标识。维护端点可以从固定列表中接受服务名称。受限包装器可以把少量操作名映射到固定的 argv 数组。在每种情况下,都尽量完全避免 shell。
如果必须支持自由格式命令,就要承认你实际授予的是该账户的远程 shell 权限。记录它,为凭据设置时限,限制目标环境,并要求更高等级的单独审批。不要把字符串过滤器包装成命令授权。
目标账户还应当有操作系统级权限边界。能够运行不受限制 sudo 的 deploy 账户,本质上就是一个多打几个字的管理员账户。只授予自动化所需的服务操作、文件和目录。把 sudo 规则当作代码审查,包括每个命令路径和参数模式。
实际测试是:一个被要求重启服务的代理,是否能够读取生产机密、建立反向隧道、修改 SSH 授权或更改审计收集器。如果可以,即使每次连接都使用了正确的堡垒主机,账户范围仍然是错误的。
事故响应从停止实时路径开始
当代理表现可疑时,在重建其意图前先撤销实时访问。如果有操作边界,就在那里停止其活动会话;阻止来源访问堡垒主机,并阻止相关凭据再次进行身份验证。在有人轮换日志文件或重启保存证据的主机前,先保存日志。
然后从分开的记录建立时间线。先看代理请求的操作,再将其与堡垒主机的连接目标和时间匹配,最后与最终主机的身份验证和命令证据匹配。检查是否出现 TTY、转发请求或备用目标。差异不一定意味着恶意行为,但它会告诉你应该调查哪个控制点。
不要为了“让代理修复问题”而永久授予更多权限。这是常见的恐慌式做法。如果代理已经偏离预期,扩大其账户权限或开放直接路径,只会移除你需要的证据,并制造另一场事故。
在生产环境真正需要之前,先进行一次小规模演练。在无害的 SSH 命令执行期间撤销测试代理,确认新的请求会失败,并验证记录显示了请求的主机、实际路径、目标身份和退出结果。如果这次演练还需要在群聊中拼凑发生了什么,你的访问设计仍然过于模糊。
当堡垒主机能够强制执行路径、支持快速撤销,并为审计记录贡献精确事实时,它才真正有存在价值。先配置路径,让凭据远离代理,并在最终主机上收集可见的命令证据。除此之外,它只是一个带有安全色彩名称的 SSH 便利功能。
常见问题
堡垒主机本身能让 AI 代理变得安全吗?
不能。堡垒主机只是把某台主机放进网络路径。如果代理仍能直接访问目标地址、选择其他代理,或持有可重复使用的私钥,它就能绕过预期路径。
跳板主机能看到代理执行的每条命令吗?
通常不能。使用 ProxyJump 时,堡垒主机转发 SSH 传输,最终主机会看到实际的 SSH 会话。堡垒主机通常无法读取加密会话中的远程命令。
代理 SSH 访问的审计记录应包含哪些内容?
记录请求的别名、解析后的主机和端口、使用的堡垒主机、最终主机指纹、远程账户、可获取时的确切命令、退出状态,以及按照明确保留策略保存的会话记录或输出摘要。不要把别名当作已到达某台机器的证据。
如何阻止代理绕过 ProxyJump?
把网络控制作为强制执行点。只允许代理环境向堡垒主机发起出站 SSH,然后只允许堡垒主机向获批的目标地址和端口发起出站连接。
AI 编程代理应该持有 SSH 私钥吗?
代理不应持有长期有效的私钥。把凭据保存在网关中,或在批准后签发短期证书,然后将目标账户限制在完成任务所需的最小操作范围内。
SSH 证书比静态密钥更适合代理吗?
SSH 证书可以缩小影响范围并让签发过程可追溯,但它们本身不会检查命令,也不会阻止转发。你仍然需要账户限制、网络边界,以及在命令可见的位置收集执行证据。
应为自主代理禁用哪些 SSH 功能?
把端口转发、代理转发、X11 转发和交互式 shell 视为独立能力。默认关闭它们,只有在实际任务需要时,才添加经过严格测试的例外。
为什么代理应避免使用交互式 SSH shell?
交互式 shell 会给代理一个开放式会话,也会大幅削弱命令级审计。任务允许时,优先使用不分配 TTY 的单条明确命令、受限包装器或远程 API。
在哪里捕获远程命令最合适?
最终主机上的 ForceCommand 包装器可以接收普通 exec 请求中的 SSH_ORIGINAL_COMMAND,客户端操作网关则可以在传输开始前记录请求的操作。单独一条记录都不能证明所有实际影响,因此还要与目标主机的本地日志和结果进行关联。
如果代理发起了可疑的 SSH 连接,应该怎么办?
先撤销活动会话,移除或停止签发其凭据,并在进行大范围配置更改前保存相关审计记录。然后比较请求的目标、实际连接目标和目标主机的本地证据,找出控制失效的位置。