Brumbelow/layerleak
GitHub: Brumbelow/layerleak
一款基于 OCI 镜像分层结构的容器镜像机密扫描器,无需 Docker daemon 即可直接扫描公共 Registry 上镜像层、配置元数据及已删除层残留中的敏感信息泄露。
Stars: 38 | Forks: 1
# layerleak,OCI 镜像机密扫描器
[](https://go.dev/)
请查阅 [CONTRIBUTING.md](./CONTRIBUTING.md) 了解贡献指南。
- OCI 镜像机密扫描器,适用于任何符合 OCI 标准的公共 Registry (Docker Hub、GHCR、Quay、GCR、MCR、Amazon ECR Public、自托管)。它会分析镜像层、配置元数据以及镜像历史,然后根据 manifest 摘要存储去重后的发现结果。
- 传统的机密扫描器通常将容器镜像视为一个扁平的 blob,或者依赖于本地的 Docker daemon。本项目则是基于 OCI 镜像内部结构设计的。
## 文档页面
- https://brumbelow.github.io/layerleak/docs
已发布的站点是由 `.github/workflows/pages.yml` 从 `main` 分支上的 `web/` 目录构建的。文档源码和模拟浏览器演示均位于该目录下。
## 当前功能特性:
- 支持来自任何符合 OCI 标准的公共 Registry 的镜像 (Docker Hub、GHCR、Quay、GCR、MCR、Amazon ECR Public、自托管)
- 只读扫描
- 无需机密验证
- 无需依赖 Docker daemon
- 感知 Manifest 与镜像层的扫描
- 扫描最终的文件系统以及已删除层的残留数据
- 扫描镜像配置元数据、环境变量、标签和历史记录
- 根据机密指纹对发现结果进行去重,并折叠每个 manifest 中重复的相同上下文片段
## 安装说明
前置条件:
- Go 1.25.7+
使用 Go 安装:
```
go install github.com/brumbelow/layerleak@latest
layerleak --help
```
规范的安装目标是模块根目录。
如需显式固定某个发布版本:
```
go install github.com/brumbelow/layerleak@v1.0.0
```
将 `v1.0.0` 替换为你想要的、已发布的 `v1.x.y` 标签。
确保你的 `GOBIN` 或 `GOPATH/bin` 目录已添加到 `PATH` 中。
从源码构建:
```
git clone https://github.com/brumbelow/layerleak.git
cd layerleak
go build -o layerleak .
./layerleak --help
```
使用容器镜像运行 API:
```
docker pull ghcr.io/brumbelow/layerleak:latest
docker run --rm \
-p 8080:8080 \
-e LAYERLEAK_DATABASE_URL='postgres://:@:5432/layerleak?sslmode=disable' \
ghcr.io/brumbelow/layerleak:latest
```
该容器镜像默认运行 API,并设置 `LAYERLEAK_API_ADDR=0.0.0.0:8080`。
可选的环境配置:
```
cp .env.example .env
```
结果和数据库配置:
```
export LAYERLEAK_FINDINGS_DIR=findings
export LAYERLEAK_API_ADDR=127.0.0.1:8080
export LAYERLEAK_PERSIST_RAW_SECRETS=0
export LAYERLEAK_TAG_PAGE_SIZE=100
export LAYERLEAK_HTTP_TIMEOUT=30s
export LAYERLEAK_MAX_FILE_BYTES=1048576
export LAYERLEAK_MAX_LAYER_BYTES=536870912
export LAYERLEAK_MAX_LAYER_ENTRIES=50000
export LAYERLEAK_MAX_MANIFEST_BYTES=0
export LAYERLEAK_MAX_CONFIG_BYTES=0
export LAYERLEAK_MAX_TAG_RESPONSE_BYTES=8388608
export LAYERLEAK_MAX_REPOSITORY_TAGS=0
export LAYERLEAK_MAX_REPOSITORY_TARGETS=0
export LAYERLEAK_REGISTRY_REQUEST_ATTEMPTS=2
export LAYERLEAK_DATABASE_URL=postgres://postgres:postgres@localhost:5432/layerleak?sslmode=disable
```
如果未设置 `LAYERLEAK_FINDINGS_DIR`,layerleak 会将 JSON 结果文件写入包含 `go.mod` 的最近父目录(通常是仓库根目录)下的 `findings/` 文件夹中。如果未找到仓库根目录,则会回退到当前工作目录。
保存的结果文件默认只包含检测结果且经过脱敏处理。
仅当你明确希望将原始的检出值和原始的上下文片段写入磁盘和 Postgres 时,才需设置 `LAYERLEAK_PERSIST_RAW_SECRETS=1`。
`LAYERLEAK_TAG_PAGE_SIZE` 用于控制全仓库扫描时的 registry 标签列表分页。
`LAYERLEAK_MAX_LAYER_BYTES` 默认为 `536870912`(即 512 MiB)的单层解压流数据,而 `LAYERLEAK_MAX_LAYER_ENTRIES` 默认为每层 `50000` 个 tar 条目。
`LAYERLEAK_MAX_TAG_RESPONSE_BYTES` 默认为每个 registry 标签列表响应页 `8388608`(即 8 MiB)。
`LAYERLEAK_REGISTRY_BASE_URL` 和 `LAYERLEAK_REGISTRY_AUTH_URL` 是可选的覆盖配置。在正常使用中请保持未设置状态 —— layerleak 会从每个镜像引用中推导出 registry 基础 URL,并通过 registry 的 `WWW-Authenticate` 质询发现 auth realm。仅在需要强制通过代理或备用 endpoint 进行扫描时才设置它们。
当 `LAYERLEAK_MAX_LAYER_BYTES`、`LAYERLEAK_MAX_LAYER_ENTRIES`、`LAYERLEAK_MAX_MANIFEST_BYTES`、`LAYERLEAK_MAX_CONFIG_BYTES`、`LAYERLEAK_MAX_TAG_RESPONSE_BYTES`、`LAYERLEAK_MAX_REPOSITORY_TAGS` 和 `LAYERLEAK_MAX_REPOSITORY_TARGETS` 被设置为 `0` 时,这些限制将被禁用。
如果启用,当超出这些限制时,扫描将因明确的错误而失败,而不是静默截断工作。
`LAYERLEAK_REGISTRY_REQUEST_ATTEMPTS` 控制 registry 请求重试次数,默认为 `2`。
`LAYERLEAK_HTTP_TIMEOUT` 是应用于每次 registry 调用(manifest 获取、blob 下载、标签列表页、auth token 请求)的 HTTP 客户端的单次请求超时时间。接受任何 Go 的 duration 值(例如 `30s`、`2m`、`1h`);默认为 `30s`。
`LAYERLEAK_MAX_FILE_BYTES` 是 layerleak 为层内每个文件缓冲的最大解压字节数;超过此大小的文件将被归类为超大文件并跳过。默认为 `1048576`(1 MiB),且必须大于零。
`LAYERLEAK_API_ADDR` 控制 API 服务器的绑定地址,在本地二进制文件中默认为 `127.0.0.1:8080`。
容器镜像将此覆盖为 `0.0.0.0:8080`。
如果设置了 `LAYERLEAK_DATABASE_URL`,扫描器还会将扫描结果写入 Postgres,如果 Postgres 不可用或保存失败,则命令执行将报错退出。
结果行为:
- 可操作的发现结果会保留在 `findings` 中,并驱动扫描返回非零退出状态码。
- 疑似测试/示例/演示用途的占位符会作为被抑制的示例结果单独发出,不计入 `total_findings`。
- 结果记录包含 `disposition`、`disposition_reason` 和 `line_number`,以便于进行分类排查和误报审查。
- 如果超出了配置的运行限制,layerleak 仍然会写入并呈现失败前生成的部分结果,然后以状态码 `1` 退出,因为扫描未完成。
## Postgres 持久化
Layerleak 在 `migrations/` 目录下附带版本化的 SQL 迁移脚本。
迁移操作设计为需要手动执行。扫描器不会自动创建或升级 schema。
Layerleak 要求 PostgreSQL 服务器版本 `>= 16.13`,以支持基于 DB 的 API 和扫描器持久化。
请使用 `psql` 按顺序应用迁移:
```
psql "$LAYERLEAK_DATABASE_URL" -f migrations/0001_initial.up.sql
psql "$LAYERLEAK_DATABASE_URL" -f migrations/0002_finding_occurrence_metadata.up.sql
psql "$LAYERLEAK_DATABASE_URL" -f migrations/0003_scan_runs.up.sql
```
或者使用容器辅助命令应用迁移:
```
docker run --rm \
-e LAYERLEAK_DATABASE_URL="$LAYERLEAK_DATABASE_URL" \
ghcr.io/brumbelow/layerleak:latest \
layerleak-migrate-up
```
当迁移已经应用时,`layerleak-migrate-up` 可以安全地重复运行。
如果它检测到部分迁移状态,将以非零状态码退出并提示需要人工干预。
该辅助命令还会强制要求服务器版本 `>= 16.13`,并验证内置的 `postgresql-client-16`
使用的是 Ubuntu PGDG `24.04` 打包版本(`.pgdg24.04+`),且版本号 `>= 16.13-1.pgdg24.04+1`。
按相反顺序回滚迁移:
```
psql "$LAYERLEAK_DATABASE_URL" -f migrations/0003_scan_runs.down.sql
psql "$LAYERLEAK_DATABASE_URL" -f migrations/0002_finding_occurrence_metadata.down.sql
psql "$LAYERLEAK_DATABASE_URL" -f migrations/0001_initial.down.sql
```
运行默认值:
- 预期迁移将保持为增量方式。
- Schema 使用 `first_seen_at` 和 `last_seen_at` 保留当前去重后的状态,并在 `scan_runs` 中存储只追加的扫描历史。
- 被当前扫描触及的标签,其映射关系会被刷新。
- 发现结果通过 `(manifest_digest, fingerprint)` 进行规范去重,并且重复的相同上下文片段会在持久化之前被折叠合并。
- 扫描历史存储的是公共结果 JSON 的脱敏快照,不包含原始值或原始片段。
机密安全提示:
- Postgres 持久化默认存储脱敏后的预览数据。
- 如果设置了 `LAYERLEAK_PERSIST_RAW_SECRETS=1`,Postgres 也会存储原始的检出值和原始片段。
- `scan_runs.result_json` 快照保持脱敏状态。
- 请为 layerleak 使用专用的数据库或 schema。
- 对于最安全的清除途径,请直接删除专用的数据库或 schema,而不是试图精确删除单行数据。
## 如何开始
显示 CLI 帮助信息:
```
layerleak --help
layerleak scan --help
```

针对任何支持 registry 上的公共 OCI 镜像运行扫描:
```
./layerleak scan ubuntu
./layerleak scan library/nginx:latest --format json
./layerleak scan alpine:latest --platform linux/amd64
./layerleak scan mongo
./layerleak scan ghcr.io/homebrew/core/hello:latest
./layerleak scan quay.io/prometheus/busybox:latest
./layerleak scan gcr.io/distroless/static:nonroot
./layerleak scan public.ecr.aws/docker/library/alpine:3.20
./layerleak scan mcr.microsoft.com/hello-world:latest
```

每次扫描都会将 JSON 结果文件写入结果输出目录。
如果未设置 `LAYERLEAK_FINDINGS_DIR`,默认输出目录为包含 `go.mod` 的最近父目录(通常是仓库根目录)下的 `findings/`,当找不到仓库根目录时,则回退到当前工作目录。
这些保存的结果文件包含带有 `redacted_value`、已脱敏的 `context_snippet`、确切来源位置、分类元数据以及每个发现的行号的记录。
如果设置了 `LAYERLEAK_PERSIST_RAW_SECRETS=1`,保存的结果文件还将包含原始的 `value` 和 `raw_context_snippet`。
如果启用了 Postgres 持久化,除非设置 `LAYERLEAK_PERSIST_RAW_SECRETS=1`,否则原始的 `findings.value` 和 `finding_occurrences.raw_snippet` 将保持为空。
对于多架构镜像,layerleak 会跳过诸如 `application/vnd.in-toto+json` 的证明和来源 manifest,而不是将它们计入失败的平台扫描中。
如果你传递了一个裸仓库名称(如 `mongo`),layerleak 将枚举该仓库中所有的公共标签,将每个标签解析为摘要,对重复的摘要进行分组,然后扫描不同的目标。如果你只想要单个镜像,请传递显式的标签或摘要,例如 `mongo:latest` 或 `mongo@sha256:...`。
命令语法:
```
layerleak [command]
layerleak scan [flags]
```
## HTTP API
Layerleak 还在 `cmd/api` 下提供了一个轻量级的 JSON API。
该 API 由 Postgres 支持并需要配置 `LAYERLEAK_DATABASE_URL`;它不从磁盘上的结果文件中提供数据。
使用以下命令启动:
```
go run ./cmd/api
```
或运行 API 容器:
```
docker run --rm \
-p 8080:8080 \
-e LAYERLEAK_DATABASE_URL='postgres://:@:5432/layerleak?sslmode=disable' \
ghcr.io/brumbelow/layerleak:latest
```
当前的 endpoint:
- `POST /api/v1/scans`
- `GET /api/v1/scans/{id}`
- `GET /api/v1/repositories`
- `GET /api/v1/repositories/{repository}/scans`
- `GET /api/v1/repositories/{repository}/findings`
- `GET /api/v1/findings/{id}`
`POST /api/v1/scans` 保持同步,并且只要启用了 Postgres 持久化,它现在就会返回 `scan_run_id`。
API 扫描响应复用了与 CLI JSON 输出相同的脱敏结果 schema。
`GET /api/v1/scans/{id}` 返回持久化的运行元数据以及存储的脱敏结果快照。
仓库和发现结果的 endpoint 也保持脱敏:它们返回 `redacted_value` 和已脱敏的 `context_snippet`,绝不会从 Postgres 返回原始的机密值或原始片段。
`GET /api/v1/repositories/{repository}/scans` 和 `GET /api/v1/repositories/{repository}/findings` 接受一个可选的 `registry` 查询参数(例如 `?registry=ghcr.io`)。省略时,出于向后兼容性考虑,registry 默认为 `docker.io`。可使用此参数获取 GHCR、Quay、GCR、MCR、Amazon ECR Public 或任何自托管 registry 上的仓库扫描结果。
该 API 不包含身份验证。
对于组织部署,请将其保留在私有网络中,并在其前面配置你自己的 authn/authz 网关或反向代理策略。
## Docker Compose 部署 (Dockge / Komodo)
本仓库在 `docker-compose.yml` 中附带了一个包含 `db`、`migrate` 和 `api` 服务的 Compose 堆栈。
`db` 服务基线被固定为 `postgres:16.13-alpine`。
如果你使用不同的 Postgres 镜像,请保持服务器版本为 `16.13` 或更新版本。
设置部署变量(在 shell 中 export,或将其放在与 `docker-compose.yml` 相邻的 `.env` 文件中):
```
export LAYERLEAK_IMAGE=ghcr.io/brumbelow/layerleak:latest
export LAYERLEAK_DB_NAME=layerleak
export LAYERLEAK_DB_USER=layerleak
export LAYERLEAK_DB_PASSWORD=replace-me
export LAYERLEAK_API_PORT=8080
```
在启动 API 之前运行一次迁移:
```
docker compose --profile manual run --rm migrate
```
启动 API 服务:
```
docker compose up -d api
```
在 Dockge 或 Komodo 中,导入相同的 Compose 文件,并在启用长期运行的 `api` 服务之前运行一次 `migrate` 服务。
标签:API安全, DevSecOps, Docker Hub, ECR Public, EVTX分析, GCR, GHCR, Go语言, JSON输出, OCI镜像扫描, Secret Scanner, StruQ, Web截图, 上游代理, 元数据扫描, 只读扫描, 安全助手, 容器安全, 密钥泄露检测, 开源安全工具, 日志审计, 测试用例, 版权保护, 程序破解, 误配置预防, 请求拦截, 逆向工程平台, 镜像层分析