seebom-labs/BOMHort

GitHub: seebom-labs/BOMHort

BOMHort 是一个独立的 Kubernetes 原生平台,用于集中可视化和管理大规模软件物料清单,帮助团队进行漏洞追踪与许可证合规治理。

Stars: 21 | Forks: 5

BOMHort

Kubernetes 原生软件物料清单 (SBOM) 可视化与治理平台

CI OpenSSF Scorecard OpenSSF Best Practices

接入 1000 多个 SPDX 和 CycloneDX SBOM,通过 OSV 扫描漏洞,执行许可证合规性检查,并应用 VEX 声明 —— 所有这些都由 ClickHouse 分析提供支持,并在快速的 Angular 仪表盘中进行可视化。 **BOMHort**(前身为 SeeBOM)是换了新名字的同一个项目。了解更多:[为什么我们将 SeeBOM 更名为 BOMHort](docs/content/docs/getting-started/rename.md)。

快速开始 · 架构 · 路线图 · 贡献 · AI 政策

BOMHort Dashboard

## 快速开始 ### 前置条件 | 工具 | 最低版本 | |------|----------------| | 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) ## 徽章 [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/seebom-labs/BOMHort/badge)](https://scorecard.dev/viewer/?uri=github.com/seebom-labs/BOMHort)
标签:Angular, ClickHouse, EVTX分析, Go, Grype, Kubernetes原生, Ruby工具, 可视化看板, 合规治理, 子域名突变, 日志审计, 请求拦截, 软件物料清单(SBOM)