nolabs-ai/runseal
GitHub: nolabs-ai/runseal
Runseal 通过内核级沙箱、虚拟凭证替换和 L7 网络过滤,为 GitHub Actions 提供密封执行环境,防御 CI/CD 环境中的软件供应链攻击。
Stars: 57 | Forks: 6
Runseal 的构建旨在解决通常由 GitHub Actions 漏洞 exploit 引发的软件供应链攻击问题。
为了应对针对 GitHub Actions 的供应链攻击日益增加的趋势,我们开发了 runseal。在这些攻击中,攻击者通常会获取对 repository secrets 的访问权限,并利用它们来窃取数据或部署恶意代码。通过使用 [nono 的](https://github.com/always-further/nono)内核级强制沙箱机制,runseal 可以保护敏感文件、secrets/tokens,并过滤来自不受信任或恶意代码的网络访问,同时仍然通过灵活的策略系统允许必要的软件工程操作。
由带给您 [sigstore](https://sigstore.dev) 和 [nono](https://nono.sh) 的同一团队打造。
## Runseal 的功能
- 使用会话作用域的虚拟凭证替换工作流中的真实 secrets,这些凭证即使泄露也毫无用处
- 通过 nono 使用 Landlock 的内核级强制沙箱机制,保护敏感文件和 secrets 免受 CI 中不受信任代码的窃取
- L7 网络过滤,通过 HTTP 方法和路径锁定网络访问
- 默认拦截网络,除非策略明确允许特定的主机或凭证路由
- 在沙箱外部捕获加密审计记录,涵盖所有网络请求、凭证注入和文件系统访问
## 快速开始
```
name: Publish
on:
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: always-further/runseal@v0.3.3
with:
run: npm publish
policy: |
fs:
read: ["."]
write: []
network:
mode: filtered
access:
npm:
secret: NPM_TOKEN
url: https://registry.npmjs.org
allow:
- PUT /**
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
```
在此示例中,`npm publish` 可以读取 repository,但不能写入未在 `fs.write` 中列出的路径,网络访问仅限于 'registry.npmjs.org' 而无法访问其他主机。同时,'NPM_TOKEN' 会从 GitHub Secrets 中获取,并在原位注入一个虚拟凭证。如果该工作流被攻破,攻击者试图窃取 token 或发布恶意 package,此攻击将被沙箱拦截,并且真实的 token 将保持安全,位于 'npm publish '工作流之外。
## 策略格式
Runseal 策略是通过 `policy` 输入传递的 YAML。
```
fs:
read:
- "."
- "$HOME/.cache/my-tool"
write:
- "./dist"
network:
mode: filtered
allow:
- api.github.com
access:
deploy:
secret: DEPLOY_TOKEN
url: https://api.example.com
allow:
- POST /v1/deployments
- GET /v1/deployments/*
```
### 文件系统访问
`fs.read` 列出了命令可以读取的路径。`fs.write` 列出了命令可以写入的路径。
请尽量缩小这些路径的范围。例如,部署步骤通常只需读取 `./dist` 和一个配置文件,并且可能完全不需要写入权限。
```
fs:
read: ["./dist", "./fly.toml"]
write: []
```
### 网络访问
Runseal 要求设置 `network.mode: blocked` 或 `network.mode: filtered`。
仅在命令必须访问的未经身份验证的主机上添加 `network.allow`。访问授权中使用的主机会自动添加到生成的 `nono` 配置文件中。
```
network:
mode: filtered
allow:
- api.github.com
```
### 访问授权
`access` 下的每个 key 都是一个命名授权。`secret` 是包含真实 secret 的环境变量,`url` 是服务的基础 URL,而 `allow` 列出了可以注入该 secret 的 HTTP 路由。Runseal 会在日志中屏蔽该 secret,将其写入一个私有文件,从子进程环境中将其移除,并配置 `nono` 通过本地代理将其注入。
```
access:
fly:
secret: FLY_API_TOKEN
url: https://api.machines.dev
allow:
- POST /v1/apps/*/machines
```
沙箱化的命令会接收到一个用于兼容 SDK 的虚拟凭证。真实的 secret 保留在沙箱之外,仅当主机和 endpoint 策略匹配时才由代理插入。
### HTTPS Endpoint 过滤
`allow` 通过 HTTP 方法和路径限制访问。匹配是基于允许列表(allow-list)的。
```
allow:
- POST /v1/apps/*/releases
- GET /v1/apps/*/status
```
Runseal 依赖 `nono` 的 TLS 拦截来实现此功能。`nono` 代理会创建一个临时的信任包,并将标准的 CA 环境变量注入到沙箱化的进程中,因此常见的 HTTPS 客户端可以通过代理进行连接,同时仍然允许执行 L7 策略。
## 常用方案
### 在无网络环境下运行测试
```
- uses: always-further/runseal@v0.3.2
with:
run: npm test
policy: |
fs:
read: [".", "./node_modules"]
write: ["./coverage"]
network:
mode: blocked
```
### 在拥有 Package Registry 访问权限的情况下构建
```
- uses: always-further/runseal@v0.3.2
with:
run: npm ci
policy: |
fs:
read: ["."]
write: ["./node_modules"]
network:
mode: filtered
allow:
- registry.npmjs.org
```
### 使用受保护的 Token 进行部署
```
- uses: always-further/runseal@v0.3.2
with:
run: ./scripts/deploy.sh
policy: |
fs:
read: ["./dist", "./deploy.yaml"]
write: []
network:
mode: filtered
access:
deploy:
secret: DEPLOY_TOKEN
url: https://deploy.example.com
allow:
- POST /v1/releases
- GET /v1/releases/*
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
```
## 输入
| Input | Required | Default | Description |
| --- | --- | --- | --- |
| `run` | 是 | 无 | 在沙箱内执行的命令。 |
| `policy` | 否 | 空 | Runseal 策略 YAML。建议在新工作流中使用此项。 |
| `fs-read` | 否 | 空 | 未设置 `policy` 时,以逗号分隔的读取路径。 |
| `fs-write` | 否 | 空 | 未设置 `policy` 时,以逗号分隔的写入路径。 |
| `network` | 否 | `blocked` | 未设置 `policy` 时的网络策略:`blocked` 或以逗号分隔的域名。 |
| `runseal-version` | 否 | `0.3.1` | 要安装的 Runseal 发布版本。接受 `v0.1.0` 或 `0.1.0`。 |
| `nono-version` | 否 | `0.62.0` | 要安装的 nono 发布版本。接受 `v0.1.0` 或 `0.1.0`。 |
| `verify-attestations` | 否 | `true` | 验证已下载发布资产的 GitHub artifact attestations。 |
| `audit` | 否 | `false` | 设置为 `artifact` 或 `true` 以将 nono 审计证据作为 GitHub Actions artifact 上传。 |
## 审计证据
Runseal 可以为沙箱化的命令导出 nono 审计会话:
```
- uses: always-further/runseal@v0.3.2
with:
run: npm rebuild
audit: artifact
policy: |
fs:
read: [".", "./node_modules"]
write: ["./node_modules"]
network:
mode: blocked
```
启用后,Runseal 会在命令完成后捕获新的 nono 审计会话,并上传一个包含以下内容的 `runseal-audit` artifact:
- `summary.md`
- 每个检测到的 nono 审计会话对应的一个 JSON 文件
审计导出会在 Runseal 返回沙箱化命令的退出状态之前运行,因此即使是失败或被拒绝的命令也可以生成审计证据。
## 要求
- Linux x86_64 GitHub-hosted runner
- runner 上必须提供 `gh` CLI 以进行 attestation 验证
- Runseal 和 `nono` 均需发布发布资产
发布资产应使用以下命名方案:
- `runseal-v-x86_64-unknown-linux-gnu.tar.gz`
- `nono-v-x86_64-unknown-linux-gnu.tar.gz`
- `SHA256SUMS`
## 开发
```
make ci
```
`make ci` 运行 `make lint` 和 `make test` —— 与 [CI 工作流](.github/workflows/ci.yml)中的 Rust 检查相同。
```
make lint # clippy + fmt check
make test # unit tests only
make fmt # format code
make audit # cargo audit (run make audit-install first)
```
标签:DevSecOps, GitHub Actions, StruQ, 上游代理, 可视化界面, 文档安全, 沙箱, 自动笔记, 通知系统