seebom-labs/BOMHort
GitHub: seebom-labs/BOMHort
BOMHort 是一个独立的 Kubernetes 原生平台,用于集中可视化和管理大规模软件物料清单,帮助团队进行漏洞追踪与许可证合规治理。
Stars: 21 | Forks: 5
Kubernetes 原生软件物料清单 (SBOM) 可视化与治理平台
接入 1000 多个 SPDX 和 CycloneDX SBOM,通过 OSV 扫描漏洞,执行许可证合规性检查,并应用 VEX 声明 —— 所有这些都由 ClickHouse 分析提供支持,并在快速的 Angular 仪表盘中进行可视化。
**BOMHort**(前身为 SeeBOM)是换了新名字的同一个项目。了解更多:[为什么我们将 SeeBOM 更名为 BOMHort](docs/content/docs/getting-started/rename.md)。
快速开始 ·
架构 ·
路线图 ·
贡献 ·
AI 政策
## 快速开始
### 前置条件
| 工具 | 最低版本 |
|------|----------------|
| Docker + Docker Compose | v2.20+ |
| Go | 1.24+(仅用于本地开发) |
| Node.js | 22+(仅用于本地开发) |
### 选项 A:通过 Docker Compose 部署全栈(推荐)
```
# 1. 克隆仓库
git clone https://github.com/seebom-labs/BOMHort.git && cd BOMHort
# 2. 将你的 SBOM 文件放在 sboms/ 目录中
# 支持 SPDX JSON、CycloneDX JSON 和 in-toto attestation envelopes(自动检测)
# (包含示例:sboms/_example.spdx.json、sboms/_example.cdx.json)
# 3. 启动一切
make dev
# 或者不使用 make:
docker compose up --build -d
```
这将启动:
- **ClickHouse**,端口为 `localhost:9000` (TCP) / `localhost:8123` (HTTP)
- **API Gateway**,端口为 `localhost:8080`
- **Ingestion Watcher**(运行一次,扫描 `sboms/` 中的新文件)
- **Parsing Worker**(处理排队的 SBOM/VEX 文件)
- **Angular UI**,端口为 `localhost:8090`
在浏览器中打开 **http://localhost:8090**。
### 配置 (`.env`)
将 `.env.example` 复制到 `.env` 并进行调整:
```
cp .env.example .env
```
| 变量 | 默认值 | 描述 |
|----------|---------|-------------|
| `SBOM_SOURCE_DIR` | `./sboms` | SBOM 文件的路径(可以指向外部代码库检出) |
| `SBOM_LIMIT` | `0` | 每次 watcher 运行时排队的最大 SBOM 数量。`0` = 无限制。本地开发时建议使用 `50`–`200`。 |
| `WORKER_REPLICAS` | `1` | 并行解析 worker 容器的数量 |
| `WORKER_BATCH_SIZE` | `50` | 每个 worker 每次轮询周期领取的作业数 |
| `SKIP_OSV` | `false` | 跳过 OSV 漏洞 API 调用。设置为 `true` 可进行快速的初始批量加载(仅限许可证),随后再设置为 `false` 重新运行。 |
| `SKIP_GITHUB_RESOLVE` | `false` | 对于具有 `NOASSERTION`/空许可证的包,跳过 GitHub 许可证解析。 |
| `GITHUB_TOKEN` | *(空)* | 用于许可证解析的 GitHub 个人访问 token。可将速率限制从 60 提高到 5000 req/h。不需要任何作用域。 |
| `CLUSTER_NAME` | *(空)* | 用于多集群部署的集群标识符。所有接入的数据都会使用此值进行标记。留空 = 单实例模式。 |
| `AUTH_ENABLED` | `false` | 启用 API 身份验证中间件。当设置为 `false`(默认)时,所有 API endpoint 均不需要身份验证。 |
| `SERVICE_TOKEN` | *(空)* | 用于上游代理/网关集成的共享密钥。可通过 `Authorization: Bearer
` 或 `X-Service-Token: ` 传入。 |
| `API_KEYS` | *(空)* | 用于直接 API 消费者(CI/CD、脚本)的逗号分隔 API 密钥列表。可通过 `X-API-Key: ` 传入。 |
| `CUSTOM_THEME` | (示例文件) | UI 自定义 CSS 主题文件的路径。参见“自定义主题”部分。 |
| `UI_CONFIG` | `./ui/public/ui-config.json` | 包含 UI 文本覆盖(品牌名称、仪表盘文本、免责声明)的 JSON 文件的路径。参见“站点配置”部分。 |
| `S3_BUCKETS` | *(空)* | S3 存储桶配置的 JSON 数组(支持按存储桶覆盖 `cluster`)。参见“S3 接入”部分。 |
| `S3_BUCKET` | *(空)* | 单个 S3 存储桶名称(比 `S3_BUCKETS` 更简单的替代方案)。 |
| `S3_ENDPOINT` | `s3.amazonaws.com` | S3 endpoint URL。 |
| `S3_REGION` | `us-east-1` | AWS 区域。 |
| `S3_ACCESS_KEY` | *(空)* | 共享的 S3 访问密钥(应用于所有存储桶)。对于公共存储桶请留空。 |
| `S3_SECRET_KEY` | *(空)* | 共享的 S3 密钥。 |
**更改 `.env` 后:**
```
# 应用新值(保留 ClickHouse 数据):
docker compose up -d --force-recreate
# 或者完全重置(清除 ClickHouse 数据):
make dev-reset
```
### 配置文件
有两个 JSON 配置文件用于控制许可证治理。编辑它们并重启受影响的服务。
| 文件 | 挂载于 | 用途 |
|------|-----------|---------|
| `sboms/license-policy.json` | API Gateway, Workers | 定义哪些 SPDX ID 是 **permissive** 的,哪些是 **copyleft** 的。未列出的均视为 `unknown`。 |
| `sboms/license-exceptions.json` | API Gateway, Workers | 豁免特定的许可证(全面豁免)或包+许可证组合的违规上报。[CNCF 格式](https://github.com/cncf/foundation/blob/main/license-exceptions/exceptions.json)。 |
### 自定义主题 (CSS)
整个 UI 配色方案通过 CSS 自定义属性定义,并且**无需重新构建** Angular 即可覆盖。
**本地(Docker Compose):** 创建一个 CSS 文件并在 `.env` 中设置 `CUSTOM_THEME`:
```
# .env
CUSTOM_THEME=./my-theme.css
```
```
/* my-theme.css */
:root {
--accent: #e94560;
--nav-bg: #1a1a2e;
--nav-brand: #e94560;
--severity-critical: #ff4444;
--license-permissive: #22c55e;
}
```
```
docker compose up -d --force-recreate ui
```
**Kubernetes:** 在 Helm values 中启用 theme ConfigMap:
```
ui:
customTheme:
enabled: true
```
然后编辑 ConfigMap:
```
kubectl create configmap seebom-custom-theme \
--from-file=custom-theme.css=./my-theme.css \
--dry-run=client -o yaml | kubectl apply -f -
kubectl rollout restart deployment seebom-ui
```
有关所有可用变量,请参见 `ui/src/assets/custom-theme.example.css`。
### 站点配置(文本和品牌)
所有 UI 文本内容(品牌名称、页面标题、仪表盘描述、免责声明等)均可通过 `ui-config.json` 文件进行自定义,**无需重新构建** Angular。
**本地(Docker Compose):** 直接编辑默认文件或指向你自己的文件:
```
# 选项 1:编辑内置默认值
vim ui/public/ui-config.json
# 选项 2:通过 .env 使用自定义文件
UI_CONFIG=./my-ui-config.json
docker compose up -d --force-recreate ui
```
**`ui-config.json` 示例:**
```
{
"brandName": "My SBOM Platform",
"pageTitle": "My SBOM Platform",
"dashboard": {
"title": "Overview",
"subtitle": "Software Supply Chain Governance",
"description": "Welcome to our internal SBOM governance platform.",
"disclaimer": "Internal use only. Data sourced from OSV and GitHub."
},
"footer": {
"enabled": true,
"text": "© 2026 My Company"
}
}
```
所有字段均为可选 —— 任何缺失的 key 都会回退到内置的 BOMHort 默认值。`description` 和 `disclaimer` 支持 HTML。
**Kubernetes:** 在 Helm values 中启用站点配置:
```
ui:
siteConfig:
enabled: true
content:
brandName: "My SBOM Platform"
pageTitle: "My SBOM Platform"
dashboard:
title: "Overview"
subtitle: "Software Supply Chain Governance"
description: "Welcome to our SBOM platform."
disclaimer: "Internal use only."
```
更改将在 pod 重启后生效(`kubectl rollout restart deployment seebom-ui`)。无需重新构建。
### S3 接入(默认)
直接从兼容 S3 的存储桶(AWS S3、MinIO、GCS)接入 SBOM。这是默认且推荐的接入方法 —— 无需数据卷、PVC 或 git-sync。
**单个存储桶:**
```
# .env
S3_BUCKET=cncf-subproject-sboms
S3_ENDPOINT=s3.amazonaws.com
S3_REGION=us-east-1
```
**多个存储桶(JSON 数组):**
```
# .env
S3_BUCKETS='[{"name":"cncf-subproject-sboms","endpoint":"s3.amazonaws.com","region":"us-east-1"},{"name":"cncf-project-sboms","region":"us-east-1"}]'
```
**带凭证的私有存储桶:**
```
# .env(所有 buckets 的共享凭证)
S3_ACCESS_KEY=AKIA...
S3_SECRET_KEY=...
S3_BUCKETS='[{"name":"my-private-bucket"}]'
# 或者在 JSON 中为每个 bucket 指定凭证:
S3_BUCKETS='[{"name":"my-bucket","accessKey":"AKIA...","secretKey":"..."}]'
```
**多集群:按存储桶分配集群:**
```
# .env — 每个 bucket 映射到不同的 cluster
CLUSTER_NAME=default
S3_BUCKETS='[
{"name":"prod-eu-sboms", "cluster":"prod-eu", "region":"eu-west-1"},
{"name":"prod-us-sboms", "cluster":"prod-us", "region":"us-east-1"},
{"name":"staging-sboms", "cluster":"staging"}
]'
```
没有 `cluster` 字段的存储桶将继承全局的 `CLUSTER_NAME`。如果两者都未设置,则数据不带标签(单实例模式)。
**工作原理:**
- Ingestion Watcher 从每个存储桶流式传输 `ListObjects`(分页处理,不在内存中保存完整列表)
- 根据扩展名对文件进行分类:`*.spdx.json` / `*_spdx.json` → SBOM,`*.openvex.json` / `*.vex.json` → VEX
- 通过流式传输对象计算 SHA256 哈希值(不加载到内存中)
- 作业以 500 个为一批进行排队,以便高效插入 ClickHouse
- Parsing Worker 通过 `s3://bucket/key` URI 按需获取 S3 对象
- 本地文件系统接入(来自 `SBOM_SOURCE_DIR`)仍可与 S3 并行工作
```
# 编辑配置文件后:
docker compose up -d --force-recreate api-gateway parsing-worker
```
有关 Kubernetes 部署说明,请参见 [docs/DEPLOYMENT_GUIDE.md](docs/DEPLOYMENT_GUIDE.md)。
### 选项 B:本地 Kubernetes (Kind)
将全栈部署到本地 [Kind](https://kind.sigs.k8s.io/) 集群,包括 ClickHouse Operator、CNCF SBOM 接入和 Angular UI:
```
# 1. 复制 secrets 模板并填入你的值
cp examples/kind/secrets.env.example local/secrets.env
vi local/secrets.env
# 2. 部署
make kind-up
# UI: http://localhost:8090 API: http://localhost:8080/healthz
```
有关 Kind 和生产级 Kubernetes 部署配置,请参见 [`examples/`](examples/)。
### 选项 C:本地开发(热重载)
当你想快速迭代代码时,请使用此方法:
```
# 1. 仅启动 ClickHouse
make ch-only
# 2. 运行 migrations(仅首次)
make ch-migrate
# 3. 在不同的终端中:
# 终端 1:API Gateway
make api
# 终端 2:运行 Ingestion Watcher(一次)
make ingest
# 终端 3:启动 Parsing Worker
make worker
# 终端 4:Angular 开发服务器(热重载,代理到 API)
make ui-dev
```
打开 **http://localhost:4200** —— Angular 会将 `/api/*` 代理到 `localhost:8080`。
## 架构
```
sboms/*.spdx.json + *.openvex.json
│
▼
┌─────────────────────────┐
│ Ingestion Watcher │ CronJob: scans files, deduplicates by SHA256,
│ (Go binary) │ enqueues jobs into ClickHouse queue
└────────────┬────────────┘
│
▼
┌─────────────────────────┐
│ Parsing Workers (N) │ Stateless: claims jobs, parses SPDX/VEX
│ (Go binary) │ (supports plain SPDX + in-toto attestation
│ │ envelopes), resolves unknown licenses via
│ │ GitHub API (50+ well-known Go module mappings),
│ │ batch-INSERTs resolved data into ClickHouse,
│ │ then queries OSV for vulns and checks license
│ │ compliance
└────────────┬────────────┘
│
▼
┌─────────────────────────┐
│ CVE Refresher │ CronJob (daily): checks all PURLs for new CVEs
│ (Go binary) │ without re-scanning all SBOMs
└────────────┬────────────┘
│
▼
┌─────────────────────────┐
│ ClickHouse │ 11 tables: sboms, sbom_packages, vulnerabilities,
│ │ license_compliance, vex_statements, ingestion_queue,
│ │ dashboard_stats_mv, cve_refresh_log, github_license_cache,
│ │ github_repo_metadata
└────────────┬────────────┘
│
▼
┌─────────────────────────┐
│ API Gateway │ 19 REST endpoints, stateless
│ (Go binary) │
└────────────┬────────────┘
│
▼
┌─────────────────────────┐
│ Angular UI │ 10 lazy-loaded pages, virtual scrolling,
│ │ OnPush change detection, dark mode,
│ │ CSS custom properties theming
└─────────────────────────┘
```
### 解析流水线
Parsing Worker 按照严格排序的流水线处理每个 SBOM:
1. **解析** —— 自动检测格式并解码:
- **SPDX JSON**(带有 `spdxVersion` 字段的普通文档)
- **In-toto attestation 信封**(封装在 `predicate` 字段中的 SPDX,常见于 Syft/BuildKit)
- **CycloneDX JSON**(通过 `bomFormat: "CycloneDX"` 检测,支持 1.0–1.7 版本)
- 可选:设置 `USE_PROTOBOM=true` 将所有解析工作委托给 [protobom](https://github.com/protobom/protobom),以实现最大程度的格式覆盖
2. **解析许可证** —— 对于具有 `NOASSERTION`/空许可证的包,使用三种解析策略查询 GitHub API:
- 直接从 PURL 中提取 `github.com/{owner}/{repo}`
- **知名的 Go 模块映射**(50 多个条目:`golang.org/x/*` → `golang/*`, `gopkg.in/*`, `go.uber.org/*`, `k8s.io/*`, `dario.cat/mergo` 等)
- **回退**到专用的 GitHub `/repos/{owner}/{repo}/license` endpoint
- 对 GitHub 错误检测许可证的代码库进行**静态覆盖**(例如 `opencontainers/go-digest`、`shopspring/decimal`)
3. **插入** —— 将 SBOM 元数据和包(及其解析出的许可证)批量 INSERT 到 ClickHouse 中
4. **扫描漏洞** —— 对所有 PURL 进行 OSV 批量查询
5. **检查许可证合规性** —— 根据策略对许可证进行分类并应用例外情况
有关完整的蓝图,请参见 [docs/ARCHITECTURE_PLAN.md](docs/ARCHITECTURE_PLAN.md)。
有关 Kubernetes 部署,请参见 [docs/DEPLOYMENT_GUIDE.md](docs/DEPLOYMENT_GUIDE.md)。
有关构建和发布容器镜像,请参见 [docs/RELEASE.md](docs/RELEASE.md)。
有关编写和运行测试,请参见 [docs/TESTING.md](docs/TESTING.md)。
有关完整的 endpoint 文档,请参见 [API 参考](https://docs.bomhort.dev/docs/api-reference/)。
## Makefile 命令
| 命令 | 描述 |
|---------|-------------|
| **Docker Compose** | |
| `make dev` | 通过 Docker Compose 启动全栈 |
| `make dev-down` | 停止所有容器 |
| `make dev-restart` | 使用新的 `.env` 值重启(保留数据) |
| `make dev-logs` | 跟踪所有容器日志 |
| `make dev-reset` | 销毁数据卷并重新启动 |
| `make dev-status` | 显示容器状态和接入进度 |
| `make re-ingest` | 重新触发 Ingestion Watcher(扫描新文件) |
| `make re-scan` | 清除所有数据并重新处理所有内容(例如在启用 OSV 之后) |
| `make cve-refresh` | 检查所有已知 PURL 的新 CVE(无需重新扫描 SBOM) |
| `make migrate` | 运行所有待处理的数据库迁移 |
| **Kind(本地 Kubernetes)** | |
| `make kind-up` | 创建 Kind 集群并通过 Helm 部署 BOMHort |
| `make kind-down` | 销毁 Kind 集群(删除所有内容) |
| `make kind-stop` | 停止 Kind 集群而不丢失数据(保留数据卷) |
|make kind-start` | 恢复已停止的 Kind 集群(所有 pod 和数据完好无损) |
| `make kind-status` | 显示 Kind 集群和 pod 的状态 |
| `make kind-build` | 构建所有容器镜像并将其加载到 Kind 中 |
| `make kind-deploy` | 构建镜像、Helm 升级并重启 pod |
| `make kind-reingest` | 重新接入所有 SBOM(清空数据、重新排队、不重新下载) |
| **ClickHouse** | |
| `make ch-only` | 仅启动 ClickHouse(用于本地开发) |
| `make ch-migrate` | 针对 ClickHouse 运行 SQL 迁移 |
| `make ch-shell` | 打开 ClickHouse CLI |
| **本地开发** | |
| `make api` | 在本地运行 API Gateway |
| `make ingest` | 在本地运行 Ingestion Watcher |
| `make worker` | 在本地运行 Parsing Worker |
| `make ui-dev` | 启动带有 API 代理的 Angular 开发服务器 |
| `make backend-build` | 构建所有 Go 二进制文件 |
| `make backend-test` | 运行所有 Go 测试 |
| `make backend-vet` | 运行 go vet + go fmt |
| `make ui-build` | 构建生产环境的 Angular |
| **镜像** | |
| `make images` | 在本地构建所有 5 个容器镜像 (TAG=dev) |
| `make images-push` | 构建并将所有镜像推送到 GHCR |
## API Endpoint
| 方法 | Endpoint | 描述 |
|--------|----------|-------------|
| GET | `/healthz` | 健康检查 |
| GET | `/api/v1/stats/dashboard` | 仪表盘统计(VEX 有效/抑制计数) |
| GET | `/api/v1/stats/dependencies?limit=N` | 所有项目中的前 N 个依赖项 |
| GET | `/api/v1/stats/version-skew?page=&page_size=&search=` | 各项目间版本不一致的包 |
| GET | `/api/v1/sboms?page=&page_size=&search=` | 分页的 SBOM 列表(可搜索) |
| GET | `/api/v1/sboms/{id}/detail` | 包含严重程度细分的 SBOM 详情 |
| GET | `/api/v1/sboms/{id}/vulnerabilities` | 特定 SBOM 的漏洞 |
| GET | `/api/v1/sboms/{id}/licenses` | 特定 SBOM 的许可证细分 |
| GET | `/api/v1/sboms/{id}/dependencies` | 依赖树 |
| GET | `/api/v1/vulnerabilities?page=&vex_filter=` | 分页的漏洞(可选:`vex_filter=effective`) |
| GET | `/api/v1/vulnerabilities/{id}/affected-projects` | 受某个 CVE 影响的所有项目 |
| GET | `/api/v1/licenses/compliance` | 全局许可证合规性概览 |
| GET | `/api/v1/projects/license-compliance` | 存在许可证违规的项目(已根据例外情况过滤) |
| GET | `/api/v1/license-exceptions` | 活动的许可证例外情况(只读,来自配置文件) |
| GET | `/api/v1/license-policy` | 活动的许可证分类策略(permissive/copyleft 列表) |
| GET | `/api/v1/vex/statements?page=&page_size=` | 分页的 VEX 声明 |
| GET | `/api/v1/packages/archived` | 使用已归档(不再维护)的 GitHub 代码库的包 |
| GET | `/api/v1/packages/search?q=&page=&page_size=` | 跨所有 SBOM 的包名称搜索 |
| GET | `/api/v1/packages/detail?name=&page=&page_size=` | 使用特定包的所有项目(分页) |
有关包含请求/响应示例的完整 API 文档,请参见 [API 参考](https://docs.bomhort.dev/docs/api-reference/)。
## 添加你的 SBOM
1. 将 `.spdx.json` 文件放在 `sboms/` 目录中(或在 `.env` 中设置 `SBOM_SOURCE_DIR`)
2. 将 `.openvex.json` 或 `.vex.json` 文件放在同一目录中
3. 重新触发接入(见下文)
4. Parsing Worker 将自动处理新文件
Ingestion Watcher 会根据 SHA256 哈希值进行去重 —— 它将跳过已经处理过的文件。
### 重新触发接入
```
# 再次运行 watcher(扫描新文件,完成后退出):
docker compose up ingestion-watcher
# 如果你更改了 SBOM_LIMIT 或 SBOM_SOURCE_DIR,请强制重新创建:
docker compose up --force-recreate ingestion-watcher
# 要从头重新 ingestion 所有内容(清除所有数据):
make dev-reset
```
## 许可证策略
默认情况下,BOMHort 执行 [CNCF 允许的第三方许可证策略](https://github.com/cncf/foundation/blob/main/policies-guidance/allowed-third-party-license-policy.md):
- **Permissive(允许):** Apache-2.0, MIT, MIT-0, 0BSD, BSD-2-Clause, BSD-3-Clause, ISC, PSF-2.0, Python-2.0, PostgreSQL, UPL-1.0, X11, Zlib, OpenSSL 以及其他几个(共 18 个)
- **Copyleft(标记):** GPL, LGPL, AGPL, MPL-2.0, EPL, EUPL, CPAL 及其他(共 21 个)
- **未知:** 任何不在上述列表中的许可证都会被标记以供审查
### CNCF 例外列表
[CNCF 许可证例外情况](https://github.com/cncf/foundation/blob/main/license-exceptions/exceptions.json) 会自动下载并应用。受 CNCF 管理委员会例外条款保护的包将被标记为豁免,而非不合规。
带有 `"project": "All CNCF Projects"` 的例外情况将被视为全面例外(应用于每个 SBOM)。
### 自定义策略
通过 Helm values 覆盖默认策略:
```
licensePolicy:
custom: |
{
"permissive": ["Apache-2.0", "MIT"],
"copyleft": ["GPL-3.0-only", "AGPL-3.0-only"]
}
```
或直接编辑 ConfigMap:
```
kubectl edit configmap seebom-license-policy -n seebom
```
## 技术栈
| 层级 | 技术 |
|-------|-----------|
| 后端 | Go 1.25, net/http (stdlib) |
| 数据库 | ClickHouse (MergeTree family) |
| 前端 | Angular 19, CDK Virtual Scrolling |
| 漏洞扫描 | OSV.dev API |
| VEX | OpenVEX Spec v0.2.0 |
| 部署 | Helm 3, Docker Compose |
## 贡献
我们欢迎你的贡献!请查看[贡献指南](CONTRIBUTING.md)了解如何开始。
- 📖 [完整文档](https://docs.bomhort.dev/)
- 🗺️ [路线图](https://docs.bomhort.dev/docs/roadmap/)
- 🪜 [贡献者阶梯](https://docs.bomhort.dev/docs/development/contributor-ladder/)
- 🤖 [AI 使用政策](https://docs.bomhort.dev/docs/development/ai-policy/)
- 🛡️ [安全政策](SECURITY.md)
## 许可证
[Apache License 2.0](LICENSE)
## 徽章
[](https://scorecard.dev/viewer/?uri=github.com/seebom-labs/BOMHort)标签:Angular, ClickHouse, EVTX分析, Go, Grype, Kubernetes原生, Ruby工具, 可视化看板, 合规治理, 子域名突变, 日志审计, 请求拦截, 软件物料清单(SBOM)