rupivbluegreen/omni-sag
GitHub: rupivbluegreen/omni-sag
一款开源的 Linux 特权访问网关,通过 AD+MFA 认证、策略授权、防篡改凭证链和会话录制,为企业提供可审计的 SSH/SFTP 堡垒机能力。
Stars: 0 | Forks: 0

### 留存凭证的 SSH 堡垒机
*你服务器的保镖。每一次登录都被核查,每一个 shell/隧道/传输都被授权,每一个字节都
有据可查——附带你可以在网关关闭时离线验证的防篡改凭证链。*
[](https://github.com/rupivbluegreen/omni-sag/actions/workflows/ci.yml)
[](LICENSE)
[](go.mod)
[](docs/audit/fail-closed-matrix.md)
[](#-how-it-was-built)
[](CONTRIBUTING.md)
**[🌐 官网](https://rupivbluegreen.github.io/omni-sag/) · [🚀 快速开始](#-60-seconds-to-whoa) · [🧾 审计包](docs/audit/) · [🗺️ 路线图](#️-roadmap)**
## 👋 这到底是什么?
让你的团队指向 **Omni-SAG**,而不是直接指向你的服务器。它会针对
Active Directory(结合 MFA)对他们进行身份验证,精确决定他们可以访问什么——
真实的 shell、SFTP,或者仅仅是一个转发的端口——并以一种任何人都无法在事后
悄悄篡改的方式记录下*一切*。
默认的回答永远是**拒绝**;访问权限是你按连接争取来的。
它是**开源的**,公开构建,献给所有认为特权访问应该附带凭证的人。
不需要企业销售电话。 🙂
```
$ ssh -L 5432:db1.lab.local:5432 alice@gateway # alice ∈ dba
✔ ALLOWED · role=dba · tunnel open · evidence: tunnel_decision allow=true
$ ssh -L 5432:db1.lab.local:5432 bob@gateway # bob ∉ dba
✘ administratively prohibited · evidence: tunnel_decision allow=false
$ omni-verify -bundle ./evidence -pubkey $KEY
PASS — evidence bundle is intact and authentic.
# 翻转一个字节…
FAIL — TAMPER: record hash mismatch. loudly.
```
那不是样图——而是一次真实的录制运行:**[▶ 观看实时演示](https://rupivbluegreen.github.io/omni-sag/#start)**
(相同的实验室,相同的两个用户,相同的篡改行为——原始的 [asciicast](docs/assets/demo.cast) 也在 repo 中)。
## ✨ 功能
确切的功能,极少的废话——下面的每一行都有代码支撑,而不是路线图 PPT 上的空话。
**🔐 AD + MFA** — 针对 Active Directory 进行 LDAPS 绑定,然后使用 RADIUS MS-CHAPv2 作为第二因素
(从不使用 PAP;交互式 OTP 在 SSH 密码路径上会失败闭合,因为它无法进行提示)。
```
mfa:
enabled: true
radius: { server: "radius.corp.local:1812", secret: "...", nas_identifier: "omni-sag" }
```
**🖥️ 目标上的真实 shell 与 SFTP** — SSH 用户名中的 `user%host` 用于选择真实的
目标;网关会开启真正的第二段 SSH 连接,并将实际的 PTY shell 或 SFTP session 代理
给它。绝不是替代品。
```
$ ssh 'alice%db1.lab.local'@gateway -p 2222
$ sftp 'alice%db1.lab.local'@gateway -P 2222
```
匹配的策略规则必须解析为确切的一个主机和一个端口(模糊匹配会
失败闭合,而不是“随便选一个”)。
**🚇 端口转发** — `-L` 隧道按 host:port 受策略控制;`-D` 动态 SOCKS 转发
沿用相同的 `direct-tcpip` 通道,`-J` ProxyJump 也可以使用(跳转
只是另一次 `direct-tcpip` 打开)。
```
$ ssh -L 5432:db1.lab.local:5432 alice@gateway
$ ssh -D 1080 alice@gateway
$ ssh -J alice@gateway alice@db1.lab.local
```
`scp` 对于任何当前的 OpenSSH 客户端都能开箱即用——它在底层
默认使用 SFTP 协议,由上述相同的真实 shell/SFTP 路径提供服务。传统的基于
exec 的协议(`scp -O`)也被支持,仅限单文件(不支持 `-r`),但属于选择启用
——需设置 `enable_scp: true` 才能开启(因为它增加了 exec-channel 攻击面,
所以默认保持关闭)。
不支持:`-R` 远程/反向转发,或 X11 转发。
**🎛️ 功能熔断开关** — 在网关处禁用整类访问权限,独立于
策略(三个中至少必须保持开启一个)。`enable_scp` 的逻辑相反——属于
选择启用,默认关闭。
```
disable_ssh: true # no interactive shells
disable_tunnel: true # no -L/-D forwarding
disable_sftp: true # no SFTP subsystem (also blocks default-protocol scp)
enable_scp: true # opt-in: serve legacy scp -O (off by default)
```
**🧭 策略** — 绑定 AD 组的角色;每条规则都会设置允许的 host+ports 以及录制
姿态和凭证姿态。`host` 也可以是 CIDR 范围(例如 `10.0.0.0/8`),而不是
每行一个主机名——直接匹配字面 IP,或者如果它解析出的每个地址都
落在该范围内,则匹配解析后的主机名(部分/拆分匹配会失败闭合)。
```
policy:
roles:
- name: dba
groups: ["dba"]
allow:
- host: "db1.lab.local"
ports: [5432]
record: none # none | metadata-only | full (full refuses -L)
credential: passthrough # passthrough | prompt | inject | deny
require_approval: false # four-eyes-gate this -L tunnel
- host: "10.0.0.0/8" # whole subnet, one rule
ports: [22]
```
**🎯 单一拨号器 + SSRF 防护** — 每个 `-L`/`-D` 目标 socket 都经过授权,然后
通过同一条代码路径打开;*解析后的* IP(不仅是主机名)会针对
loopback、link-local(包括 `169.254.169.254` 云元数据 IP)、unspecified 以及 multicast/
broadcast 进行检查——从而填补了 DNS-rebinding TOCTOU 漏洞。RFC1918/CGNAT 是
故意允许的;触达内部主机正是堡垒机的核心意义。
**🔑 CyberArk 注入** — `credential: inject` 通过
mTLS 实时从 CyberArk CCP 获取目标凭证;用户永远看不到它。CCP 不可达 = 拒绝,绝不
静默降级为提示/透传。
```
cyberark:
base_url: "https://ccp.lab.local/AIMWebService"
client_cert: "cyberark-client.crt"
client_key: "cyberark-client.key"
ca_cert: "cyberark-ca.crt"
app_id: "omni-sag"
safe: "targets"
```
**🦠 SFTP 内容检查** — 上传流经 ICAP(AV/DLP)。*每一次*上传——无论干净
与否——都会在 Object-Locked 隔离桶中获得一份永久副本;被拦截的、无法扫描的或
“已修改”的结果会被彻底拒绝。
```
inspection:
enabled: true
icap: { endpoint: "icap.lab.local:1344", service: "avscan" }
```
**👀 双人审批 (Four-eyes)** — `require_approval` 会阻塞 `-L` 隧道,直到
第二个人类批准它;另外,每次受检的 SFTP 上传都会在结束时阻塞,直到
第二个人类将其从隔离区放行——基于组划分,因此放行者必须与上传者
共享一个 AD 组。无论哪种方式,都绝不能是你自己。
```
$ omnisag-ctl approvals
$ omnisag-ctl approve
```
**📥 拉取-释放** — 已批准的 SFTP 上传永远不会自动推送到目标;
同一个上传者会在批准窗口期(默认 6h)内从网关的 `/releases` 目录自行
取回。
**🎥 会话录制** — asciicast v2,流式传输到存储中,因此长会话永远不会
完整滞留在内存中。
```
recording:
local_dir: "recordings"
```
`record: full` 目标会录制其 shell;到这些目标的 `-L` 转发会被拒绝(
转发的字节无法被有意义地录制)。
**🧾 防篡改凭证** — 每一个决策(auth、MFA、隧道、凭证、审批、
检查、录制)都是哈希链式、Merkle 检查点化且经过 Ed25519 签名的。
```
$ omni-verify -bundle ./evidence -pubkey $KEY -head $HEAD
PASS — evidence bundle is intact and authentic.
```
**🛰️ 控制平面** — 位于自己监听器上的 HTTP API(mTLS 或 bearer tokens),一个 CLI,以及一个
Bubble Tea TUI。关闭 API,SSH 依然继续服务——它是真正的带外
(out-of-band) 运行。
```
$ omnisag-ctl sessions
$ omnisag-ctl sessions kill
$ omnisag-ctl trace alice dba db1.lab.local 5432
$ omnisag-ctl tui
```
**🪵 `-debug`** — 将 auth/MFA 失败和每一个凭证决策镜像到 stdout。仅限
开发环境;它会削弱防枚举姿态。
```
$ omni-sag -config config.yaml -debug
```
**📦 打包** — UBI9 非 root 镜像,Helm chart(restricted-v2 pod security),
位于独立监听器上的 Prometheus 指标,SIGTERM 上的优雅排水,以及
FIPS 就绪模式(`off` | `warn` | `enforce`)。
## 🚀 60 秒到“哇哦”
启动一个一次性的实验室(Samba AD、MinIO、FreeRADIUS),然后是两个 SSH 用户,其中
只有 DBA 能获得隧道——凭证日志中会记录这两次尝试。
```
git clone https://github.com/rupivbluegreen/omni-sag
cd omni-sag
make lab-up # samba-AD + MinIO + FreeRADIUS
make lab-seed # create alice (dba) and bob (not)
make binaries # build omni-sag, omnisag-ctl, omni-verify
./bin/omni-sag_linux_amd64 -config deploy/compose/config.example.yaml
# ssh -L 5432:db1.lab.local:5432 alice@gw → 允许
# ssh -L 5432:db1.lab.local:5432 bob@gw → 被管理性禁止
# ./bin/omni-verify_linux_amd64 -bundle ./evidence → PASS(篡改一个字节 → FAIL)
```
要求:**Go 1.25+**、**Docker**(用于实验室)以及一个 SSH 客户端。使用 `make lab-down` 清理。
## 🧠 工作原理
一个**模块化单体**,具有 CI 强制的 package 边界——易于理解,且免去了
分布式系统的沉重代价。
```
client ──ssh──▶ [ session ] ──▶ authn + ratelimit ──▶ policy ──┬──▶ [ dialer ] ──▶ target (-L/-D)
│ └──▶ 2nd SSH leg ──▶ target (shell/SFTP)
▼
[ evidence ] ── hash-chain · Merkle · Ed25519 · WORM ──▶ omni-verify (offline)
─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ out-of-band control plane ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
operator ──mTLS──▶ [ api ] ──▶ sessions · policy · approvals · supervision
▲ (the data path never imports this)
omnisag-ctl / TUI
```
**承载核心的常量**(每一项均由 CI 或测试强制执行):
1. **失败闭合 (Fail closed)** — 任何依赖故障都会导致拒绝。由 [29 行失败闭合矩阵](docs/audit/fail-closed-matrix.md) 证明。
2. **无静默凭证降级** — `inject` 失败即拒绝;绝不回退到提示/透传。
3. **凭证非可选且防篡改** — 并且 emit 错误会浮现,绝不会被吞没。
4. **凭证永不成为 Go 字符串** — 仅限 `[]byte`,并清零 ([ADR-0001](docs/decisions/0001-mlock-free-credential-handling.md))。
5. **双人原则 (Four-eyes)** — 审批者 ≠ 请求者,服务端强制执行,重启后依然持久有效。
6. **控制平面带外运行** — 关闭 API,SSH 继续服务。(这是经过测试的,而不是一句口号。)
## 🏗️ 如何构建
薄切片,GA 及以后,每个都可以独立演示——没有五项半成品分支的那种
草率感。
每个切片在开始下一个之前都经过了**对抗性审查关卡**,其中一些
审查捕获了真实的 bug。这才是关键所在。
- **6** 个真实的 bug 被审查/加固发现(一个 ICAP OOM,一个策略评估契约失误,一个
检查失败开启,一个 SFTP 检查缺口绕过……)——全部修复,全部进行回归锁定。
- 在称之为 v1 之前进行了 **12 名审计员的扫描**(经过对抗性验证);real-target 代理以及
组范围审批 + 拉取-释放随后作为经过审查的切片落地。
- 完整故事 + 切片图请查看[网站](https://rupivbluegreen.github.io/omni-sag/)和
[`docs/audit/`](docs/audit/)。
## 🗺️ 路线图
- **v1(当前):** SSH + SFTP,真实 shell/SFTP 目标代理,AD+MFA,拨号器 authz,凭证链,
录制,内容检查 + 隔离,CyberArk 注入,双人原则(会话隧道 +
组范围的隔离-释放)与拉取-下载,按功能切换开关,API + CLI + TUI +
打包。 ✅
- **v1.x:** SSH 证书颁发机构,Kerberos/GSSAPI,用于 API 的真实 OIDC (JWKS) 验证器
(目前是静态 token 替代品),CRD 支持的策略/审批源(需要活动集群),通过
每个监听器的 FIPS TLS 配置路由。
- **v2:** RDP(原生 mstsc,然后是带录制的浏览器 RDP)。
- **绝不:** 共享进程的多租户,OSS 中的商业化机制,从像素中进行语义命令
重构。
## 📜 许可证
[MIT](LICENSE) — 随意折腾。
*由过量的咖啡和对默认允许 (default-allow) 的极度不信任所打造。* ☕
标签:EVTX分析, Go语言, SSH网关, 内存分配, 堡垒机, 日志审计, 特权访问管理, 程序破解, 自定义请求头, 请求拦截, 身份认证与访问控制, 运维审计