ruzickap/container-image-scans
GitHub: ruzickap/container-image-scans
一个容器镜像 CVE 对比扫描平台,通过 Trivy 和 Grype 双引擎分析多种基础镜像变体的安全状况,结果存储于 Supabase 并通过 Next.js 仪表板可视化展示历史趋势。
Stars: 0 | Forks: 0
# 容器镜像扫描
使用
[trivy](https://trivy.dev/) 和 [grype](https://github.com/anchore/grype)
扫描器对比容器镜像间的 CVE 发现结果。结果存储在 [Supabase](https://supabase.com/) 中,
并在部署于 GitHub Pages 的 Next.js 仪表板中进行可视化。
## 目录
- [架构](#architecture)
- [容器镜像](#container-images)
- [前置条件](#prerequisites)
- [Supabase 设置](#supabase-setup)
- [GitHub Secrets](#github-secrets)
- [本地开发](#local-development)
- [GitHub Actions](#github-actions)
- [项目结构](#project-structure)
## 架构
```
flowchart TD
GHA["GitHub Actions\n(nightly cron)"]
TRIVY["trivy"]
GRYPE["grype"]
DB["Supabase (PostgreSQL)"]
WEB["Next.js Dashboard (GitHub Pages)"]
GHA --> TRIVY
GHA --> GRYPE
TRIVY -- "SARIF + metadata" --> DB
GRYPE -- "SARIF + metadata" --> DB
DB -- "anon key (read)" --> WEB
```
## 容器镜像
镜像按组进行组织:
| Group | Image |
|--------|--------------------------------------------------------------|
| python | `docker.io/python:alpine` |
| python | `gcr.io/distroless/python3-debian12:latest` |
| python | `cgr.dev/chainguard/python:latest` |
| python | `registry.access.redhat.com/ubi10/python-312-minimal:latest` |
| node | `docker.io/node:24-alpine` |
| node | `gcr.io/distroless/nodejs24-debian12:latest` |
| node | `cgr.dev/chainguard/node:latest` |
| node | `registry.access.redhat.com/ubi10/nodejs-24-minimal:latest` |
| php | `docker.io/php:alpine` |
| php | `cgr.dev/chainguard/php:latest` |
| php | `registry.access.redhat.com/ubi10/php-83:latest` |
| nginx | `docker.io/nginx:alpine` |
| nginx | `cgr.dev/chainguard/nginx:latest` |
| nginx | `registry.access.redhat.com/ubi10/nginx-126:latest` |
| ruby | `docker.io/ruby:alpine` |
| ruby | `cgr.dev/chainguard/ruby:latest` |
| ruby | `registry.access.redhat.com/ubi10/ruby-33:latest` |
| base | `docker.io/alpine:latest` |
| base | `docker.io/debian:stable-slim` |
| base | `docker.io/ubuntu:latest` |
| base | `gcr.io/distroless/static:latest` |
| base | `registry.access.redhat.com/ubi10-micro:latest` |
要添加更多镜像,请向 `container_images` 表中插入行
并指定相应的 `group_id`。夜间扫描会自动获取所有镜像。
## 前置条件
- [mise](https://mise.jdx.dev/)
(`curl https://mise.run | sh`)
-- 通过 `.mise.toml` 管理 Node.js, Supabase CLI, sops, yq 和 lint 工具
- [sops](https://github.com/getsops/sops) +
[age](https://github.com/FiloSottile/age) -- 用于加密
`.env.yaml` 中的环境变量(由 mise 自动安装)
- 一个 [Supabase](https://supabase.com/) 项目(免费层即可)
- [Docker](https://www.docker.com/)(用于在本地运行扫描)
克隆仓库后,运行 `mise install` 以安装
`.mise.toml` 中定义的版本所需的所有工具。
## Supabase 设置
### 1. 创建 Supabase 项目
在 [supabase.com](https://supabase.com/) 注册并创建一个新
项目。记录下 `Settings > API` 中的 **Project URL** 和 **API keys**。
### 2. 应用数据库迁移
运行 `db:push` mise 任务,它会将 CLI 链接到你的项目并
推送 `supabase/migrations/` 中所有待处理的迁移:
```
export SUPABASE_ACCESS_TOKEN="your-access-token"
export SUPABASE_PROJECT_REF="your-project-ref"
export SUPABASE_DB_PASSWORD="your-db-password"
mise run db:push
```
上面的三个变量也可以加密存储在 `.env.yaml` 中
(参见 GitHub Secrets 部分中的 `MISE_SOPS_AGE_KEY` secret)。
或者,通过 Supabase Dashboard 中的 **SQL Editor** 手动应用 schema:
1. 在 Supabase Dashboard 中打开你的项目
2. 前往 **SQL Editor**
3. 粘贴 `supabase/migrations/20250301000000_initial_schema.sql` 的内容
4. 点击 **Run**
### 3. 验证表是否存在
在 Supabase Dashboard 中前往 **Table Editor** 并确认已创建这些
表:
- `image_groups`(已填充 `python`, `node`, `php`, `nginx`,
`ruby`, 和 `base`)
- `container_images`(已填充所有 22 个镜像)
- `scans`(空表,由夜间扫描填充)
- `cves`(空表,由夜间扫描填充)
### 4. 添加新镜像
```
-- First add the group if it does not exist
INSERT INTO image_groups (name)
VALUES ('your-group')
ON CONFLICT (name) DO NOTHING;
-- Then add the image
INSERT INTO container_images (image, group_id)
VALUES (
'docker.io/your-image:tag',
(SELECT id FROM image_groups WHERE name = 'your-group')
);
```
## GitHub Secrets
在 `Settings > Secrets and variables >
Actions` 中配置这些 secrets:
| Secret | Description |
|----------------------------------|----------------------------------------------------|
| `SUPABASE_URL` | Supabase 项目 URL (`https://xxx.supabase.co`) |
| `SUPABASE_SERVICE_ROLE_KEY` | Supabase **service_role** key (写入权限) |
| `NEXT_PUBLIC_SUPABASE_URL` | 同 `SUPABASE_URL`(构建时使用) |
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Supabase **anon** key (只读,公开) |
| `MISE_SOPS_AGE_KEY` | 用于通过 sops 解密 `.env.yaml` 的 age 密钥 |
| `MY_RENOVATE_GITHUB_APP_ID` | Renovate 的 GitHub App ID |
| `MY_RENOVATE_GITHUB_PRIVATE_KEY` | Renovate 的 GitHub App 私钥 |
| `MY_SLACK_BOT_TOKEN` | 用于 PR 通知的 Slack bot token |
| `MY_SLACK_CHANNEL_ID` | 用于 PR 通知的 Slack 频道 ID |
`service_role` key 由扫描脚本用于写入数据。
`anon` key 嵌入在静态 Web 应用中用于只读访问
(因为 RLS 策略将其限制为仅 SELECT,所以是安全的)。
`apply-schema` workflow 使用 `MISE_SOPS_AGE_KEY` 解密
`.env.yaml`(使用 sops + age 加密),其中包含
`SUPABASE_ACCESS_TOKEN`, `SUPABASE_PROJECT_REF`, 和
`SUPABASE_DB_PASSWORD`。
## 本地开发
### Web 应用
```
mise install # install Node.js, Supabase CLI, sops, yq
mise run web:dev
```
或手动:
```
cd web || exit
cp .env.example .env.local
# 使用 Supabase credentials 编辑 .env.local
npm install
npm run dev
```
打开 [http://localhost:3000](http://localhost:3000)。
### 在本地运行扫描
扫描所有镜像并打印 CVE 摘要(无需 Supabase):
```
mise run scan
```
若要将结果上传到 Supabase,请使用 `scan:upload` 任务:
```
export SUPABASE_URL="https://xxx.supabase.co"
export SUPABASE_SERVICE_ROLE_KEY="your-service-role-key"
mise run scan:upload
```
### 推送数据库迁移
```
mise run db:push
```
这需要环境中存在 `SUPABASE_ACCESS_TOKEN`, `SUPABASE_PROJECT_REF`, 和
`SUPABASE_DB_PASSWORD`(或从 `.env.yaml` 解密)。
### 可用的 mise 任务
| Task | Description |
|---------------|------------------------------------------------|
| `web:install` | 安装 Web 应用依赖 |
| `web:dev` | 启动 Next.js 开发服务器 |
| `web:build` | 将静态站点导出构建到 `web/out/` |
| `scan` | 扫描所有容器镜像,打印 CVE 摘要 |
| `scan:upload` | 扫描所有镜像并将结果上传到 Supabase |
| `db:push` | 链接 Supabase 项目并推送迁移 |
| `build` | 构建 Web 应用(`web:build` 的别名) |
镜像列表定义在仓库根目录的 `images.yml` 中。
## GitHub Actions
### 核心 workflows
#### 夜间扫描 (`.github/workflows/nightly-scans.yml`)
- **Schedule**: 每天 00:00 UTC
- **Runner**: `ubuntu-24.04-arm`
- 安装 trivy 和 grype
- 拉取每个容器镜像,使用两个扫描器进行扫描
- 将 SARIF 输出和提取的 CVE 上传到 Supabase
- 每条扫描记录存储:镜像摘要、扫描器版本、
grype DB 状态、CVE 数量、完整 SARIF 和时间戳
#### 部署 Web (`.github/workflows/deploy-web.yml`)
- **Trigger**: 推送到 `main` 且涉及 `web/**`,或手动触发
- 构建 Next.js 静态站点
- 部署到 GitHub Pages
#### 应用 Schema (`.github/workflows/apply-schema.yml`)
- **Trigger**: 推送到 `main` 且涉及 `supabase/migrations/**`,
或手动触发
- 通过 sops 解密 `.env.yaml`(使用 `MISE_SOPS_AGE_KEY`)
- 链接到 Supabase 项目并运行 `supabase db push`
### CI / 质量 workflows
| Workflow | File | Trigger |
|-------------------|-----------------------------|-----------------------------|
| MegaLinter | `mega-linter.yml` | 推送到非 main 分支 |
| CodeQL | `codeql.yml` | 推送到 `main`, PRs, 每周 |
| OSSF Scorecards | `scorecards.yml` | 推送到 `main`, 每周 |
| Commit Check | `commit-check.yml` | 指向 main 的 PRs |
| Semantic PR Title | `semantic-pull-request.yml` | PRs (opened, edited, sync) |
### 自动化 workflows
| Workflow | File | Trigger |
|-----------------------|-----------------------------|---------------------------------|
| Release Please | `release-please.yml` | 推送到 `main` |
| Renovate | `renovate.yml` | 推送到 `main`, 每周 |
| Stale Issues/PRs | `stale.yml` | 每天 09:09 UTC |
| PR Size Labeler | `pr-size-labeler.yml` | Pull requests |
| PR Slack Notification | `pr-slack-notification.yml` | PR events (open, review, close) |
## 项目结构
```
container-image-scans/
+-- .github/
| +-- workflows/
| +-- apply-schema.yml # Push DB migrations via Supabase CLI
| +-- codeql.yml # CodeQL security analysis
| +-- commit-check.yml # Commit message validation
| +-- deploy-web.yml # Deploy dashboard to GH Pages
| +-- mega-linter.yml # Comprehensive linting + security
| +-- nightly-scans.yml # Nightly trivy + grype scans
| +-- pr-size-labeler.yml # Auto-label PRs by size
| +-- pr-slack-notification.yml # Slack notifications for PRs
| +-- release-please.yml # Automated releases
| +-- renovate.yml # Dependency updates
| +-- scorecards.yml # OSSF Scorecard analysis
| +-- semantic-pull-request.yml # PR title validation
| +-- stale.yml # Close stale issues/PRs
+-- scripts/
| +-- apply-schema.sh # Link Supabase + push migrations
| +-- scan-and-upload.sh # Scan images & upload to Supabase
+-- supabase/
| +-- migrations/
| | +-- 20250301000000_initial_schema.sql # Tables, RLS, seed data
| +-- config.toml # Supabase CLI configuration
| +-- schema.sql # Reference schema (seed + DDL)
+-- web/ # Next.js 15 dashboard application
| +-- src/
| | +-- app/
| | | +-- globals.css # Global styles
| | | +-- layout.tsx # Root layout
| | | +-- page.tsx # Main page
| | +-- components/
| | | +-- CveDetailTab.tsx # CVE detail table with filters
| | | +-- CveTooltip.tsx # Hover tooltip with markdown
| | | +-- HistoryCharts.tsx # History line charts
| | +-- lib/
| | +-- supabase.ts # Supabase client + data fetchers
| +-- next.config.js
| +-- package.json
| +-- tsconfig.json
+-- .checkov.yml # Checkov skip-check config
+-- .env.yaml # Encrypted env vars (sops + age)
+-- .gitignore
+-- .mega-linter.yml # MegaLinter configuration
+-- .mise.toml # mise tool versions + tasks
+-- .pre-commit-config.yaml # pre-commit hooks
+-- .rumdl.toml # rumdl markdown linter config
+-- AGENTS.md # AI agent coding guidelines
+-- images.yml # Container images to scan
+-- LICENSE
+-- lychee.toml # Link checker configuration
+-- README.md
+-- SECURITY.md # Security policy
```
标签:Angular, CI/CD安全, CVE分析, DevSecOps, GitHub Actions, Grype, Llama, Nginx安全, Node.js安全, SARIF, Supabase, Web截图, 上游代理, 仪表盘, 安全可视化, 容器安全, 开源安全工具, 提示词注入, 活动识别, 测试用例, 自动笔记, 请求拦截, 逆向工程平台, 镜像合规, 镜像扫描