OpenSource-For-Freedom/Legion_runner

GitHub: OpenSource-For-Freedom/Legion_runner

基于 Rust 和 eBPF 的开源 GitHub Actions runner 加固工具,通过出站流量监控拦截、文件篡改检测和进程级连接归因来保护 CI 流水线安全。

Stars: 1 | Forks: 0

Legion Runner

Legion Runner

"Not your standard Actions Runner" - Legion

Harden any GitHub Actions runner: monitor and block egress, detect tampering, attribute connections to processes. Open, dependency-free Action, runs on Linux.

GitHub Marketplace Latest release License: MIT

**Legion Runner 是一个用于强化 CI 安全的 GitHub Action。** 它是专有 runner 加固代理的开源替代方案,且该 Action 本身零依赖 —— 纯 Node 内置模块,无外部打包的 npm 包。(其可选的 eBPF 捕获和文件完整性辅助程序是单一的静态 Rust 二进制文件,按需获取。)将其作为任何作业(包括 GitHub 托管的 runner)的第一步加入,它会: - **监控并选择性拦截出站网络流量。** 审计每一个出站连接,或配合 allowlist 进行默认拒绝(`block` 模式)。动态的按域名放行机制可确保轮换的 CDN/云 IP 正常工作。 - **检测文件篡改。** 在作业开始时对凭据/配置文件、`.git` 钩子和检出的源码进行快照,然后标记运行过程中任何被覆盖的内容。 - **将连接归属到进程**(通过 socket 层 eBPF 代理(防绕过)),并在作业摘要中以表格形式打印每一个出站连接。 ``` steps: - uses: OpenSource-For-Freedom/legion_runner@v1 # first step with: egress-policy: block allowed-presets: cargo # curated per-ecosystem allowlists - uses: actions/checkout@v6 - run: ./build.sh ``` ## 为什么选择 Rust? 安全工具自身不应成为薄弱环节。Legion Runner 中执行敏感工作(监视每一个网络连接并对文件进行哈希处理以捕获篡改)的部分是用 Rust 编写的,这种选择带来了真正的安全性: - **设计上的内存安全。** Rust 排除了在 C 语言编写的工具中导致大多数漏洞的 Bug 类型(缓冲区溢出、释放后使用)。一个不会被转化为入侵途径的守护者。 - **单一、小巧、快速的二进制文件。** 无需安装或更新解释器或运行时 —— 它被编译为单个静态二进制文件(其 Rust/aya 依赖已内置、锁定版本,并通过 `cargo-deny` 审计),可直接部署到 runner 上运行,开销极小,适合在每个作业中运行。 - **可审计。** 特权逻辑 —— 即出站和篡改检测路径 —— 位于紧凑的编译代码中,你可以从头到尾阅读,这正是当你向别人挑战要其反编译时的核心意义所在。 ## 作为 GitHub Action 使用 此 Action 可强化任何作业,包括 GitHub 托管的 runner。它监控(并选择性拦截)出站网络流量,然后在作业摘要中将每个出站连接打印为 Markdown 表格。特性包括:**socket 层 eBPF 捕获**(进程归因、防绕过)、**包仓库归因**(标明每个作业实际访问的仓库 —— npm、PyPI、crates.io、apt、Docker 等)、**动态按域名放行**拦截、**自包含的学习后强制执行**(无需外部服务)以及 **文件完整性/篡改检测**。 ``` steps: - uses: OpenSource-For-Freedom/legion_runner@v1 # Legion Runner with: egress-policy: audit # "audit" (monitor only) or "block" (default-deny) allowed-endpoints: | # used in block mode api.nuget.org:443 registry.npmjs.org:443 - uses: actions/checkout@v6 - run: ./build.sh ``` 作业结束时你会看到以下内容(当 eBPF 代理处于活动状态时,会出现 **Process** 列): **具名目标及实际访问的包仓库。** 供应链攻击隐藏在你的构建所通信的仓库中,因此仅靠简单 IP 的表格是不够的 —— 你需要看到作业访问了*哪些*仓库。**📦 访问的包仓库**汇总表将具名目标分类到其所属生态系统(npm、PyPI、crates.io、apt、Docker、Go 等),并显示访问该目标的进程,从而使恶意或意外的仓库一目了然。在托管 runner 上获取真实名称是最困难的部分:`getaddrinfo`(curl/pip/npm/cargo)通过 systemd-resolved 进行解析,后者会忽略单纯的 `resolv.conf` 重写 —— 因此 Legion 通过其捕获转发器路由这些查询(而漏网之鱼的裸 IP 会获得 CDN/提供商提示)。**Diagnostics** 行报告哪条解析路径生效以及捕获了多少查询 —— 仅有计数和枚举值,绝不包含解析器 IP、路径或主机名 —— 这样即使返回的是裸 IP 的运行结果,也能在不泄露基础设施细节的情况下进行排查。 ### 输入项 | 输入项 | 默认值 | 描述 | |-------|---------|-------------| | `egress-policy` | `audit` | `audit`(绝不破坏构建)或 `block`(默认拒绝的 allowlist)。 | | `allowed-endpoints` | `` | 在 block 模式下允许的 `host` / `host:port` 条目。 | | `allowed-presets` | `` | 在 block 模式下允许的精选生态系统 allowlist(npm、pip、cargo、apt、docker 等)。 | | `allow-github` | `true` | 始终允许 GitHub + Actions 端点。 | | `dns-capture` | `true` | 通过本地日志记录器路由解析器,将连接映射到作业解析的**确切域名**(比反向 DNS 更准确)。在无权限时回退到反向 DNS。 | | `ebpf` | `auto` | `auto` 使用 Rust/aya eBPF 代理进行 socket 层捕获 + 进程归因(使用本地二进制文件,否则验证并下载最新 release 资产);`off` 禁用它。回退到 `/proc` 采样器。 | | `policy-file` | `.legion/egress-allowed.txt` | 提交的 allowlist(先学习后强制执行)。 | | `learn` | `false` | 在 audit 模式下,将观察到的目标写入 `policy-file`。 | | `learned-baseline` | `true` | 在 block 模式下,同样允许学习到 Actions 缓存中的目标。设置为 `false` 则仅强制执行显式 allowlist。 | | `file-integrity` | `auto` | 检测作业期间的文件篡改(Rust `legionr-fim` 代理):凭据/配置文件、`.git` 配置 + 钩子以及检出的源码。`auto` 或 `off`。 | | `fim-extra-paths` | `` | 额外监视篡改的文件(每行一个/逗号分隔)。 | | `job-summary` | `true` | 将连接表写入作业摘要。设置为 `false` 可保持监控/强制执行激活状态,但隐藏该表 —— 当一个工作流中的多个作业各自进行加固,而你只想输出一次摘要时非常有用。 | | `disable-sudo` | `false` | 在设置完成后撤销 runner 用户的 sudo 权限。 | | `disable-telemetry` | `false` | 禁止生命周期事件(完全保持本地运行)。 | | `legion-link` | `` | 将出站事件流式传输到 Legion 桌面端点。 | 该 Action 核心是纯 Node + 内置模块(无外部打包的 `node_modules`)。捕获功能优雅降级:**eBPF 代理**(socket 层、进程归因)回退到 **`/proc/net` 采样器**,因此它可在任何地方运行。eBPF 代理([`agent/`](agent/))是一个独立的 Rust/aya crate,附加到每个 release 中并按需获取。下载的二进制文件在运行前会针对已发布的 `.sha256` 校验和进行验证。 ### 强制执行默认拒绝(自包含,无需安装) 强制执行程序内置在 Action 中。使用者只需使用 `uses:`。无需提交文件、无需额外工作流、无需容器。有两种驱动方式: **A. 内联 allowlist(显式)。** 列出你的作业所需的域名并切换到 `block`。其他一切请求都会被拒绝。 ``` - uses: OpenSource-For-Freedom/legion_runner@v1 with: egress-policy: block allowed-presets: cargo # or list hosts in allowed-endpoints ``` **B. 自动学习,然后强制执行(零配置)。** 在 `audit` 模式下运行一次。该 Action 会将你的作业访问过的目标记录到 **GitHub Actions 缓存**中(由 Action 自身携带)。切换到 `block`,它就会强制执行学习到的基线。无需提交文件,无需单独的工作流。 ``` with: egress-policy: ${{ vars.LEGION_EGRESS || 'audit' }} # audit learns, block enforces ``` 设置仓库变量 `LEGION_EGRESS=block` 以进行全系统范围的强制执行,改回 `audit` 则可重新学习。 在开启 `dns-capture`(默认)的 block 模式下,强制执行是**基于域名**的:当 allowlist 中的域名解析时,防火墙会在建立连接之前为其当前的 IP 开放访问。轮换 IP 的 CDN/云端点(`*.crates.io`、apt 镜像)无需固定地址即可继续工作,而其他所有请求都会被丢弃。Allowlist 条目也匹配子域名(`github.com` 允许 `api.github.com`)。被拒绝的尝试会显示在作业摘要中(从防火墙日志解析),而不是被静默丢弃。 ### 文件完整性监控(篡改检测) 出站控制可以阻止受损的步骤进行外部通信,但恶意的 Action 或依赖项也可能**篡改文件**:覆盖检出的构建脚本、植入 `git` 钩子,或重写 `~/.npmrc` / `~/.ssh` 以窃取凭据(`tj-actions` 类攻击)。启用 `file-integrity: auto`(默认)后,Rust `legionr-fim` 代理会在作业开始时对高价值的篡改目标进行快照,并在作业结束时对其进行比对: - **敏感内容:** 凭据/配置文件(`~/.ssh/*`、`~/.npmrc`、`~/.netrc`、`~/.docker/config.json`、`~/.aws/*`、`~/.gitconfig` 等),以及仓库的 `.git/config` 和 `.git/hooks`。此处的任何更改都是高危信号。 - **源码:** 作业开始时已存在于工作区的文件。更改意味着检出的文件在运行过程中被覆盖或删除(当 Action 在检出后运行或重新运行时激活)。 任何发生变化的文件都会显示在作业摘要中: 系统仅存储或比较文件**哈希**(sha256),绝不涉及内容,因此对私钥或 `.npmrc` 进行快照不会记录任何机密信息。该引擎是一个编译好的 Rust 二进制文件([`crates/legionr-fim`](crates/legionr-fim)),附加到每个 release 中并按需获取;如果无法获取该二进制文件,则会静默跳过。 ## 临时自托管 Runner 上述 Action 可强化*任何* runner。本仓库还附带了一个完整的**临时、一次性自托管 runner** 平台:一个 Rust 控制平面(`legionr`),用于生成即时的 runner 凭据并监督其生命周期;一个 Bash + systemd 骨干网,用于锁定主机;以及一个可选的 **Legion link**,用于将每个 runner 的生命周期以心跳形式发送到 [Legion](https://github.com/OpenSource-For-Freedom/legion) 桌面仪表板。它是一个可选的独立产品;你不需要它即可使用该 Action。 ### 为什么选择临时 + 一次性 长期存活的自托管 runner 是一个软目标:一个恶意作业就可以植入后门、毒化缓存或窃取下一个作业的机密。Legion Runner 彻底消除了持久化攻击面: - **JIT 凭据。** GitHub 发布绑定到单一 runner 身份的即时配置。它无法重新注册。 - **单一作业,然后销毁。** runner 在完成单个作业后退出;systemd 随即重启它,并配置一个全新的 runner。单个单元就此成为一个持续自我更新的池。 - **工作区擦除。** 每次销毁时都会清空 `_work`。 - **纵深防御。** 符合 `systemd-analyze security` 标准的单元、内核 sysctl 加固、默认拒绝的出站 allowlist,以及每个作业可选的无根容器沙箱。 ### 架构 ``` +------------------------- Linux host -------------------------+ | | GitHub <--+ legionr (Rust control plane) | API | provision -> mint JIT cred -> run ONE job -> wipe -> loop | | | | | | | hardened systemd unit | Legion link | | v (non-root, seccomp, no-new-privs) v (lifecycle) | | official actions/runner Legion desktop dashboard | | (optionally inside a rootless Podman/Docker sandbox) | +--------------------------------------------------------------+ ``` | 组件 | Crate / 文件 | 角色 | |-----------|--------------|------| | 控制平面 | `crates/legionr-cli` (`legionr`) | 配置 · 运行 · 加固 · 配对 · 状态 · 诊断 | | 核心引擎 | `crates/legionr-core` | GitHub JIT API · 生命周期 · 加固生成器 · Legion link | | 骨干网 | `scripts/install.sh`, `scripts/harden.sh` | 服务用户 · runner 获取 · systemd · sysctl · nftables | | 单元 | `systemd/legionr@.service` | 经过加固的、一次性服务模板 | ### 快速开始 ``` # 1. 安装(创建 legionr 用户,获取官方 runner,构建 legionr) sudo ./scripts/install.sh # 2. 将 runner 指向 repo 或 org(token 绝不触碰磁盘) export LEGIONR_TOKEN= sudo -u legionr -E legionr provision OpenSource-For-Freedom/legion_runner \ --config /etc/legion-runner/default.json \ --container podman \ --link http://127.0.0.1:3000 # 3. 加固主机(systemd unit + sysctl + default-deny egress 防火墙) sudo ./scripts/harden.sh # 4. 启动它。一个自我更新的 single-use runner 池。 sudo systemctl enable --now legionr@default journalctl -u legionr@default -f ``` 然后从任何工作流中调用它: ``` jobs: build: runs-on: [self-hosted, linux, legion, ephemeral] ``` ### CLI | 命令 | 功能 | |---------|--------------| | `legionr provision ` | 写入加固的 runner 配置(验证 token,磁盘上无密钥)。 | | `legionr run [--once]` | 配置、运行一个作业、销毁、循环(或运行一次,供 systemd 使用)。 | | `legionr harden [--install]` | 输出(或安装)systemd 单元、sysctl 插入配置和 nftables 规则集。 | | `legionr pair [--link URL]` | 测试或重定向 Legion 桌面连接。 | | `legionr status` | 显示配置 + GitHub/Legion 连接情况。 | | `legionr doctor` | 正式上线前的预检。 | ### 加固一览 - **身份标识** 专用的非 root `legionr` 用户;直接拒绝 `root`。 - **systemd 沙盒:** `NoNewPrivileges`、空的 `CapabilityBoundingSet`、`ProtectSystem=strict`、`ProtectKernel*`、`SystemCallFilter=@system-service` 减去 `@privileged @resources @obsolete`、`RestrictAddressFamilies`、`TasksMax`/`MemoryMax`/`CPUQuota`。 - **内核:** `ptrace_scope=2`、`kptr_restrict=2`、`unprivileged_bpf_disabled`、`kexec_load_disabled`、网络防欺骗。 - **网络:** nftables **默认拒绝**出站;仅 DNS + GitHub 端点 allowlist(以及操作员的 `--allow` 主机)可离开本机。 - **单作业沙盒(可选):** 无根 Podman/Docker,配合 `--cap-drop=ALL`、`--read-only`、`--security-opt no-new-privileges`,以及 PID/内存限制。 通过 `systemd-analyze security legionr@default` 验证该单元的暴露面。 ### 环境要求 - 带有 systemd 的 Linux(以及用于出站防火墙的 `nftables`)。 - [Rust](https://rustup.rs) 1.78+ 以构建 `legionr`。 - `curl`、`tar`;如果需要单作业沙盒,可选 `podman` 或 `docker`。 - 具有在目标范围管理 runner 权限的 GitHub PAT(或 app token)。 ## 构建与测试 ``` make build # debug build make test # workspace tests make release # optimized binary make lint # fmt --check + clippy -D warnings ``` ## 许可证 MIT © Tim Burns。参见 [LICENSE](LICENSE)。
标签:DevSecOps, DNS 反向解析, GitHub Actions, IP 地址批量处理, MITM代理, Rust, 上游代理, 可视化界面, 网络信息收集, 网络流量审计, 自动笔记, 自定义脚本