yakohhhh/portcullis

GitHub: yakohhhh/portcullis

Portcullis 是一款本地运行的 docker-compose 安全审计工具,通过静态分析服务暴露面和配置缺陷,为自托管基础设施生成带 A-F 评级的优先级修复报告。

Stars: 2 | Forks: 2

# Portcullis [![CI](https://static.pigsec.cn/wp-content/uploads/repos/cas/ad/ad5834178f7599af9fdda11629d49cae07f2997beec49821b2920eff5bfd50e7.svg)](https://github.com/yakohhhh/portcullis/actions/workflows/ci.yml) [![CodeQL](https://static.pigsec.cn/wp-content/uploads/repos/cas/53/539e9a6bf48ad24469a4363bff3aa68124154549e26592783d3d8577f2acbbfc.svg)](https://github.com/yakohhhh/portcullis/actions/workflows/codeql.yml) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/yakohhhh/portcullis/badge)](https://scorecard.dev/viewer/?uri=github.com/yakohhhh/portcullis) [![License: PolyForm Noncommercial](https://img.shields.io/badge/license-PolyForm%20Noncommercial-blue.svg)](LICENSE) [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](pyproject.toml) [![Status: alpha](https://img.shields.io/badge/status-alpha-orange.svg)](#roadmap) Portcullis 是一款专为自托管基础设施设计的安全审计工具。它会静态分析您的 docker-compose 文件,查明每个服务实际暴露的情况,执行“走火”(自残式风险)检查,并生成一份带有 A-F 评分的优先级报告。100% 本地运行:不会有任何数据离开您的设备。 ## 为什么需要它 家庭实验室(Homelab)通常会在 Traefik 或 Caddy 背后运行数十个 compose 服务,且没有任何安全团队对其进行审查。这种故障模式是残酷且悄无声息的:只要一个被发布的数据库端口、一个被挂载的 Docker socket、或者一个局域网可访问服务上的默认密码——攻击者就能控制宿主机,而不仅仅是容器。 现有的扫描工具在这里起不到多大作用。它们逐个文件地进行扫描,专为云和 Kubernetes 资产环境构建,并且会用数百个无关紧要的发现将那唯一一个关键发现淹没。Portcullis 将 compose 堆栈作为分析单元,并提出一个自托管者真正关心的问题:*什么可以访问此服务,如果它被攻破会发生什么?* ## 它的功能 - **暴露引擎** - 通过交叉比对三个信号:发布的端口 × 反向代理路由 × `internal: true` 网络,将每个服务分类为 INTERNAL / HOST / LAN / INTERNET。路由信息从 compose 标签(Traefik、caddy-docker-proxy、nginx-proxy)**以及**反向代理文件配置中读取——包括 Traefik(`traefik.yml`/`.toml`、`command:` 标志、动态文件提供程序、`exposedByDefault`、入口点绑定地址)、纯 Caddyfile(站点地址和 `reverse_proxy` 上游,包括代码片段导入)、原始 nginx `server` 块以及 Nginx Proxy Manager(其 `database.sqlite` 或生成的配置)。 - **应用知识库** - YAML 条目将容器镜像映射到应用的实际属性(类别、敏感度、建议的暴露级别),从而将以不同方式对待暴露密码管理器和暴露博客的行为。 - **“走火”规则** - 12 项 compose 级别的检查 (PC-001..PC-012),专门针对那些实际上会轻易将家庭实验室拱手送人的配置错误。每个发现都会解释查出了什么问题、为什么这很重要,以及如何修复它。 - **A-F 评级** - 一个简单且文档完善的评分系统(从 100 分开始,根据发现的严重程度进行扣分),您可以随时跟踪或将其作为 CI 的拦截标准。 - **可选的 Trivy 委托** - 当安装了 `trivy` 二进制文件时,镜像的 CVE 将被合并到报告中,并聚合为每个镜像一条发现。 ### 检查规则 | ID | 检查项 | 严重程度 | | --- | --- | --- | | PC-001 | Docker socket 挂载到容器中 | CRITICAL | | PC-002 | 容器在特权模式下运行 | CRITICAL | | PC-003 | 容器使用宿主网络 | HIGH | | PC-004 | 授予了危险的 Linux capability (`cap_add`) | HIGH 或 MEDIUM(取决于具体的 capability) | | PC-005 | 镜像没有标签或使用 `latest` | LOW | | PC-006 | 容器明确以 root 身份运行 | LOW | | PC-007 | 容器共享宿主的 PID namespace | HIGH | | PC-008 | 环境变量中存在弱密码、默认密码或空密码 | CRITICAL 或 HIGH(取决于暴露程度) | | PC-009 | 敏感应用的暴露程度超出了推荐水平 | CRITICAL 或 HIGH(取决于敏感度) | | PC-010 | 数据库端口发布在宿主上 | HIGH | | PC-011 | 经过代理的服务同时也直接发布了端口(绕过代理) | MEDIUM | | PC-012 | 在堆栈使用 Docker `secrets:` 的情况下,通过 `environment:` 传递机密信息 | LOW | 完整详情请参阅 [docs/checks.md](docs/checks.md)。 ## 暴露程度是如何分类的 - **INTERNAL** - 只能由同一个 Docker 网络上的其他容器访问。 - **HOST** - 已发布,但绑定到环回地址:仅可从宿主机访问。 - **LAN** - 在所有接口上发布:可从本地网络访问。 - **INTERNET** - 由反向代理进行路由,这通常是互联网的入口点。 诚实的说明:静态分析无法得知您的路由器是否转发了某个端口,因此在所有接口上发布的服务会被归类为 LAN——这是最安全、无可辩驳的答案。如果该端口*确实*被转发,那它就是面向互联网的,并且相关发现只会变得更加紧急。详细信息和局限性请参阅 [docs/exposure-model.md](docs/exposure-model.md)。 ## 快速开始 ``` # from PyPI(发布后) pipx install portcullis # 来自 git 的最新版本 pipx install git+https://github.com/yakohhhh/portcullis # 或者使用 pip,从源码 git clone https://github.com/yakohhhh/portcullis cd portcullis && pip install . ``` 然后将其指向一个 compose 文件或目录树(例如您的 homelab Git 仓库): ``` portcullis scan . # 或者是交互式 HTML 报告(可过滤的发现结果、暴露图表、分数趋势): portcullis report . -o report.html # open report.html in a browser portcullis report . --serve # or serve it on http://127.0.0.1:8765 # 可选地,确认实际响应的内容(可选,仅为 stack 声明的端口): portcullis probe . # local check of 127.0.0.1 portcullis probe . --host my-server.example.com # from your own external vantage point ``` 需要 Python 3.10+。可在 Linux(主要目标平台)、macOS 和 Windows 上运行——测试套件在 CI 中涵盖了这三个平台。 | 选项 | 默认值 | 描述 | | --- | --- | --- | | `PATH` | `.` | 一个 compose 文件,或一个递归遍历的目录。 | | `--format terminal\|markdown\|html\|json` | `terminal` | 报告格式。 | | `-o, --output FILE` | stdout | 将 markdown/html/json 报告写入文件。 | | `--min-severity LEVEL` | `info` | 隐藏低于 `info`/`low`/`medium`/`high`/`critical` 的发现。 | | `--fail-on LEVEL` | `never` | 如果任何发现处于或高于 LEVEL 级别,则返回退出代码 1(用于 CI 拦截)。 | | `--trivy` / `--no-trivy` | auto | 强制开启或禁用 Trivy(默认:找到该二进制文件时使用)。 | | `--rules DIR` | - | 加载社区规则包目录(可重复使用)。详见 [docs/rule-packs.md](docs/rule-packs.md)。 | | `--suggest-patches [FILE]` | - | 将机械化的修复方案输出为统一差异文件(默认为 `portcullis.patch`)。请自行审查并执行 `git apply`——Portcullis 绝不会修改您的文件。 | ### 输出示例 这是一个包含三个服务的堆栈的实际输出——vaultwarden 由 Traefik 代理但同时也发布了一个端口,postgres 绑定到环回地址并使用了默认密码,以及拥有 Docker socket 的 watchtower: ``` ╭────────────────────────────────────────────────────────────────╮ │ Portcullis - security report for /home/user/homelab │ │ Grade: C (score 60/100, 3 services, 4 findings) │ ╰────────────────────────────────────────────────────────────────╯ Service exposure ┏━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓ ┃ Service ┃ Image ┃ Exposure ┃ ┡━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩ │ db │ postgres:16 │ HOST │ │ vaultwarden │ vaultwarden/server:latest │ INTERNET │ │ watchtower │ containrrr/watchtower:1.7.1 │ INTERNAL │ └─────────────┴─────────────────────────────┴──────────┘ ╭────────── CRITICAL Docker socket mounted into 'watchtower' ──────────╮ │ The container 'watchtower' mounts /var/run/docker.sock. Whoever │ │ controls this socket controls the Docker daemon. │ │ │ │ Why it matters: Any code execution inside this container (a │ │ vulnerability in the app is enough) can start a privileged container │ │ and take over the whole host - data, other services, everything. │ │ │ │ Fix: Remove the mount if the app does not truly need it. If it does │ │ (reverse proxy auto-discovery, dashboards, updaters), put a socket │ │ proxy in front (e.g. tecnativa/docker-socket-proxy) and grant only │ │ the API sections the app requires. │ ╰──────────────────────────────────────── PC-001 · exposure: INTERNAL ───╯ … followed by the 3 remaining findings in the same format: HIGH PC-008 (default POSTGRES_PASSWORD), MEDIUM PC-011 (vaultwarden bypasses the proxy via port 8081), LOW PC-005 (mutable `latest` tag). ``` 将其用作 CI 拦截标准,或生成可共享的报告: ``` portcullis scan . --format markdown -o report.md --fail-on high portcullis scan . --format html -o report.html # self-contained, no external requests ``` ## GitHub Action 在每次推送代码和拉取请求时运行 Portcullis。该操作会将报告写入作业摘要,可选择将其作为评论发布在 PR 上,并在严重程度超过阈值时使作业失败。 ``` # .github/workflows/portcullis.yml name: Portcullis on: [push, pull_request] permissions: contents: read pull-requests: write # only needed for comment-on-pr jobs: scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: yakohhhh/portcullis@v1 with: path: . # a compose file or a directory tree fail-on: high # critical | high | medium | low | info | never comment-on-pr: true ``` | 输入项 | 默认值 | 描述 | | --- | --- | --- | | `path` | `.` | 要扫描的 compose 文件或目录。 | | `fail-on` | `high` | 在达到或超过此严重程度时使作业失败(设为 `never` 则永不失败)。 | | `min-severity` | `info` | 在报告中隐藏低于此严重程度的发现。 | | `trivy` | `false` | 在可用时同时运行 Trivy。 | | `comment-on-pr` | `false` | 将报告作为评论发布在 PR 中。 | 输出项:`grade` (A-F) 和 `score` (0-100)。 ## Trivy 集成 Portcullis 有意不去重新实现 [Trivy](https://github.com/aquasecurity/trivy) 已经做得很好的功能。分工如下: | Trivy 的工作 | Portcullis 的工作 | | --- | --- | | 镜像 CVE 扫描 | 将 compose 堆栈作为首要目标 | | 文件中提交的机密信息 | 暴露引擎(端口 × 代理 × 网络) | | Dockerfile 分析 | 应用知识库 | | | Compose “走火”规则 (PC-001..PC-012) | | | 具有教育意义、按优先级排序并带有评分的报告 | 当 `trivy` 处于您的 PATH 中时,Portcullis 会运行其三个相关的扫描程序,并将结果作为常规发现合并进来: - `trivy image` - 已知的镜像漏洞 (CVE),每个镜像生成一条发现; - `trivy fs --scanners secret` - 提交到目录树中的机密信息,每个文件生成一条发现; - `trivy config` - Dockerfile 配置错误,每个 Dockerfile 生成一条发现。 结果会进行聚合(按镜像 / 文件 / Dockerfile,绝不会每次出现都生成一条发现),从而保持报告的可读性,并且已被 Portcullis 自身的 PC-008 规则标记过的机密信息不会被重复报告。如果在没有安装 Trivy 的情况下,其他所有功能的工作方式完全相同:功能降级,但绝不出错。 ## 隐私 Portcullis 是 100% 本地运行的。它读取您的配置文件,打印报告,然后就结束了:没有网络调用,没有遥测,不需要账号。(可选的 Trivy 集成是一个独立的二进制文件,具有其自身的行为,完全可以通过 `--no-trivy` 选择退出。) ## 路线图 - **M1 - 核心扫描**(已完成,v0.1):compose 发现与解析、暴露引擎、规则 PC-001..PC-011、应用知识库、A-F 评级、终端与 markdown 报告、Trivy 合并、`--fail-on` CI 拦截。 - **M2 - 反向代理配置文件**(已完成):Traefik(静态 `traefik.yml`/`.toml`,`command:` 标志,动态文件提供程序)和 Caddyfile 解析,以及扩展到 90 多个应用的知识库。 - **M3 - 报告与丰富化**(已完成):独立的 HTML 报告 (`--format html`) 和更丰富的 Trivy 合并(镜像 CVE、提交的机密、Dockerfile 配置错误)。 - **M4 - 工业化**(已完成):机器可读的 JSON 输出 (`--format json`)、GitHub Action、macOS/Windows CI,以及通过受信任发布方式自动发布到 PyPI(详见 [RELEASING.md](RELEASING.md))。 - **v2**(已完成):nginx 和 Nginx Proxy Manager 路由、社区规则包 (`--rules`)、补丁建议 (`--suggest-patches`)、交互式 Web 报告 (`portcullis report`),以及可选的实时可达性探测 (`portcullis probe`),用于确认哪些发布的端口实际上会响应——无论是在本地测试,还是从您自己的外部观察点进行测试。 ## 贡献 请参阅 [CONTRIBUTING.md](CONTRIBUTING.md)。最简单且影响最大的贡献方式是提供**知识库条目**:只需一个用于描述您所熟知的一款自托管应用的小型 YAML 文件——完全不需要掌握 Python。 ## 许可证 [PolyForm Noncommercial 1.0.0](LICENSE) - **对个人免费**(家庭实验室、业余项目、研究、学习)**且对非商业组织免费**(慈善机构、学校、公共机构)。**公司及任何其他商业用途需要购买付费的商业许可证**:详见 [COMMERCIAL.md](COMMERCIAL.md)。 准确地说:源代码是公开的,并且欢迎贡献,但这是一种*源码可见*的许可证,而不是 OSI 批准的开源许可证。在许可证切换之前发布的版本仍然保持 MIT 许可证(参见 git 历史记录)。
标签:DevSecOps, Docker Compose, Python, 上游代理, 云安全监控, 无后门, 本地部署, 聊天机器人, 请求拦截, 逆向工具, 静态分析