boclaes102-eng/threat-intel-platform

GitHub: boclaes102-eng/threat-intel-platform

一个生产就绪的威胁情报后端服务,解决多源 IOC 采集、关联与告警的统一管理问题。

Stars: 0 | Forks: 0

# 威胁情报平台 — 后端 API [![CI](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/12ab8a335e095959.svg)](https://github.com/boclaes102-eng/threat-intel-platform/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/boclaes102-eng/threat-intel-platform/branch/main/graph/badge.svg)](https://codecov.io/gh/boclaes102-eng/threat-intel-platform) 一个生产就绪的后端服务,用于监控注册资产的威胁、摄取 CVE 源数据,并丰富多个来源的 IOC 数据。设计用于与 [Online-Cyber-Dashboard](./Online-Cyber-dashboard) 配对,作为独立的后端服务。 ## 技术栈 | 层级 | 技术 | |---|---| | HTTP API | [Fastify](https://fastify.dev) + TypeScript | | 数据库 | PostgreSQL 16 + [Drizzle ORM](https://orm.drizzle.team)(迁移) | | 缓存 / 队列 | Redis 7 + [BullMQ](https://bullmq.io) | | 认证 | JWT 访问令牌(15 分钟)+ 刷新令牌(30 天)+ API 密钥 | | 可观测性 | Pino 结构化日志 + Prometheus 指标 + Grafana 仪表板 | | 容器 | Docker 多阶段构建 + docker-compose | | CI/CD | GitHub Actions:类型检查 → 安全审计 → 测试 → 覆盖率 → Docker 构建 | | 测试 | Vitest — 单元测试 + 针对真实 Postgres 的集成测试 | | OpenAPI | 自动生成的 Swagger UI,位于 `/docs` | ## 架构 ``` ┌──────────────────────────────────────┐ │ Online-Cyber-Dashboard (Next.js) │ │ X-API-Key: tip_abc123... │ ← long-lived API key, no login flow └─────────────────┬────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Fastify API :3001 │ │ /auth /assets /alerts /vulnerabilities /ioc /docs │ │ X-Request-ID on every request/response (log correlation) │ └─────┬────────────────────────────────────────┬──────────────┘ │ │ ▼ ▼ ┌───────────────────┐ ┌──────────────────────┐ │ PostgreSQL │ │ Redis │ │ ─────────────── │ │ ──────────────── │ │ users │ │ IOC lookup cache │ │ refresh_tokens │ │ BullMQ job queues │ │ api_keys │ │ Rate limit counters │ │ assets │ └──────────────────────┘ │ vulnerabilities │ │ │ asset_vulns │ ┌──────────────┘ │ ioc_records │ ▼ │ alerts │ ┌─────────────────────────────────┐ │ feed_syncs │ │ BullMQ Workers │ └───────────────────┘ │ ───────────────────────────── │ │ cve-feed every 6h (+ retry) │ │ ioc-scan every 1h │ │ asset-scan on-demand │ └─────────────────────────────────┘ ┌───────────────────────────────────────────────────────────┐ │ Observability │ │ Prometheus :9090 ← scrapes /metrics every 15s │ │ Grafana :3002 ← pre-built dashboard (8 panels) │ └───────────────────────────────────────────────────────────┘ ``` ## 数据库架构 九个表,通过两次迁移实现完整的关系完整性: ``` users ──< refresh_tokens (30-day refresh tokens, hashed in DB) │ └─< api_keys (server-to-server keys, SHA-256 hashed) │ └──< assets ──< asset_vulnerabilities >── vulnerabilities │ (NVD CVE data) └──< alerts (nullable FK — alert survives asset deletion) ioc_records (enriched threat intel per indicator, TTL-based) feed_syncs (job run history: duration, records processed, errors) ``` **关键设计决策:** - 刷新令牌和 API 密钥永远不以明文存储 — 刷新令牌使用 SHA-256,密码使用 bcrypt(12 轮) - `asset_vulnerabilities` 是一个标准的连接表,包含 `status` 枚举(`open` → `acknowledged` → `remediated`) - `ioc_records.expires_at` 允许 API 在后台工作器刷新时提供陈旧数据作为降级 - 所有外键都有明确的 `ON DELETE` 动作(CASCADE 或 SET NULL) ## 快速开始 ### 使用 Docker(推荐) ``` cp .env.example .env # 编辑 .env — 至少设置 JWT_SECRET(openssl rand -base64 48) docker compose up ``` | 服务 | URL | 凭证 | |---|---|---| | API | `http://localhost:3001` | — | | Swagger UI | `http://localhost:3001/docs` | — | | Prometheus | `http://localhost:9090` | — | | Grafana | `http://localhost:3002` | admin / admin | ### 本地开发 ``` npm install cp .env.example .env # 仅启动基础架构 docker compose up postgres redis -d npm run db:migrate # run both migrations npm run dev # API on :3001 npm run dev:worker # background workers (separate terminal) ``` ### 有用的脚本 ``` npm run db:generate # regenerate Drizzle migration files from schema npm run db:studio # open Drizzle Studio (visual DB browser) npm run lint # TypeScript type check (no emit) ``` ## 认证与安全 ### 令牌流程(浏览器 / 移动客户端) ``` POST /api/v1/auth/login → { accessToken (JWT, 15 min), refreshToken (opaque, 30 days), expiresIn: 900 } # 访问令牌过期时自动续期: POST /api/v1/auth/refresh { refreshToken: "..." } → { accessToken (new JWT, 15 min), expiresIn: 900 } # 登出时使刷新令牌失效: POST /api/v1/auth/logout { refreshToken: "..." } → { success: true } ``` 刷新令牌是 128 字符的十六进制随机字符串,存储为 SHA-256 哈希。吊销一个令牌不会使其他令牌失效(支持多设备)。 ### API 密钥流程(服务器到服务器) 仪表盘在不进行用户登录流程的情况下调用此 API。请改用 API 密钥: ``` # 一次性设置:创建密钥(需要来自您自己的登录的 JWT) curl -X POST http://localhost:3001/api/v1/auth/api-keys \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"name": "online-cyber-dashboard"}' # → { "data": { "key": "tip_abc123...", "id": "...", "name": "..." } } # 密钥仅显示一次。请安全存储。 ``` 密钥以 `tip_` 为前缀,并存储为 SHA-256 哈希 — 丢失后无法恢复。吊销请使用 `DELETE /api/v1/auth/api-keys/:id`。 ### 请求关联 每个请求都会获得一个 `X-Request-ID`(接受调用方提供的值或自动生成 UUID)。每条日志都包含 `reqId`。每个响应都会回显 `X-Request-ID`,以便您可以在 API 日志和工作器日志中追踪仪表盘的请求。 ## API 参考 除 `/health`、`/metrics`、`/docs` 和 `/api/v1/auth/register|login|refresh` 之外,所有端点都需要以下之一: - `Authorization: Bearer ` - `X-API-Key: tip_` 完整的交互式规范位于 **`http://localhost:3001/docs`**。 ### 认证 | 方法 | 端点 | 描述 | |---|---|---| | `POST` | `/api/v1/auth/register` | 创建账户 → `{ accessToken, refreshToken, user }` | | `POST` | `/api/v1/auth/login` | 登录 → `{ accessToken, refreshToken, expiresIn: 900, user }` | | `POST` | `/api/v1/auth/refresh` | 从刷新令牌获取新访问令牌 | | `POST` | `/api/v1/auth/logout` | 吊销刷新令牌 | | `GET` | `/api/v1/auth/me` | 当前用户资料 | | `GET` | `/api/v1/auth/api-keys` | 列出您的 API 密钥(哈希永不显示) | | `POST` | `/api/v1/auth/api-keys` | 创建 API 密钥 — `{ "name": "..." }` → 仅显示一次密钥 | | `DELETE` | `/api/v1/auth/api-keys/:id` | 吊销 API 密钥 | ### 资产 | 方法 | 端点 | 描述 | |---|---|---| | `GET` | `/api/v1/assets` | 列出资产 — 分页,可按 `type`、`active` 过滤 | | `POST` | `/api/v1/assets` | 注册资产 — 立即排队 IOC 扫描 + CVE 关联 | | `GET` | `/api/v1/assets/:id` | 单个资产 | | `PATCH` | `/api/v1/assets/:id` | 更新 `label`、`tags`、`active` | | `DELETE` | `/api/v1/assets/:id` | 删除资产(级联到告警 + 漏洞关联) | **资产类型:** `ip`、`domain`、`cidr`、`url` ### 告警 | 方法 | 端点 | 描述 | |---|---|---| | `GET` | `/api/v1/alerts` | 列出告警 — 按 `severity`、`type`、`unread`、`assetId` 过滤 | | `POST` | `/api/v1/alerts/:id/read` | 标记单个告警为已读 | | `POST` | `/api/v1/alerts/read-all` | 标记所有告警为已读 | | `DELETE` | `/api/v1/alerts/:id` | 删除告警 | **告警类型:** `vulnerability`、`ioc_match`、`scan_complete`、`feed_update` ### 漏洞 | 方法 | 端点 | 描述 | |---|---|---| | `GET` | `/api/v1/vulnerabilities` | 全局 CVE 列表 — 按 `severity`、`search`(CVE ID)过滤 | | `GET` | `/api/v1/vulnerabilities?assetId=X` | 与特定资产关联的 CVE,包含每个资产的 `status` | | `GET` | `/api/v1/vulnerabilities/:cveId` | 单个 CVE 详情及原始 NVD 数据 | | `PATCH` | `/api/v1/assets/:assetId/vulnerabilities/:cveId` | 更新修复状态 | **漏洞状态:** `open` → `acknowledged` → `remediated` → `false_positive` ### IOC 查询 | 方法 | 端点 | 描述 | |---|---|---| | `GET` | `/api/v1/ioc/:indicator` | 丰富 IP、域名或哈希 — 检查 Redis → 数据库 → 实时 API | | `GET` | `/api/v1/ioc` | 列出所有缓存的 IOC 记录 — 按 `verdict` 过滤 | **判决结果:** `malicious`、`suspicious`、`clean`、`unknown` ### 系统 | 端点 | 描述 | ---|---| | `GET /health` | Postgres + Redis 活跃性检查 — 返回 `{ status, checks, uptime }` | | `GET /metrics` | Prometheus 抓取端点 | | `GET /docs` | Swagger UI — 完整的交互式 OpenAPI 3.0 规范 | ### 分页 所有列表端点使用基于游标的分页(避免在实时数据上出现偏移漂移): ``` GET /api/v1/alerts?limit=20&cursor=2024-01-15T10:00:00.000Z&severity=high&unread=true → { data: [...], nextCursor: "2024-01-14T09:30:00.000Z" // null if no more pages } ``` 将 `nextCursor` 作为 `cursor` 传递以获取下一页。 ## 后台工作器 工作器作为独立进程运行(`npm run dev:worker`),使用三个由 Redis 支持的 BullMQ 队列。 | 队列 | 调度 | 执行内容 | |---|---|---| | `cve-feed` | 每 6 小时 | 分页浏览 NVD API,将 CVE 插入 `vulnerabilities` 表,并在 `feed_syncs` 中记录运行统计。对 NVD 限流期间每页最多重试 4 次,使用指数退避 + 抖动 | | `ioc-scan` | 每小时 | 并行通过 AbuseIPDB、VirusTotal、AlienVault OTX 丰富所有活动的 IP/域名资产。插入/更新 `ioc_records`。在恶意/可疑判决时创建 `ioc_match` 告警。如果配置了 SMTP,则发送邮件 | | `asset-scan` | 在资产创建时 | 将资产值与所有已知 CVE 中的 CPE 字符串进行关联。在 `asset_vulnerabilities` 中建立关联。对关键/高危 CVE 创建 `vulnerability` 告警。如果配置了 SMTP,则发送邮件 | 所有工作器在 Prometheus 中跟踪作业持续时间(`job_duration_seconds`),并为 Grafana 仪表板递增 `jobs_total{status="completed|failed"}`。 ## 可观测性 ### Prometheus + Grafana ``` docker compose up # starts Prometheus + Grafana automatically ``` 在 `http://localhost:3002` 打开 Grafana(admin / admin)。**威胁情报平台** 仪表板会自动加载,包含 8 个面板: | 面板 | 指标 | |---|---| | 每分钟请求数 | `rate(http_requests_total[5m])` | | 错误率 % | 5xx / 总请求数 | | 活跃资产数 | `active_assets_total` 仪表 | | 缓存命中率 | 命中 / (命中 + 未命中) | | 请求延迟 | p50 / p95 / p99 直方图 | | 后台作业速率 | 按队列统计的完成与失败 | | 按路由的请求数 | 按端点细分 | | 开启的告警 | 按严重程度(关键 / 高 / 中 / 低)统计 | ### 结构化日志 每条日志都是结构化 JSON(Pino),包含 `reqId`、`service` 和 `env` 字段。在开发环境中,`pino-pretty` 会格式化输出以提高可读性: ``` 10:24:31 INFO reqId=a3f2... method=POST url=/api/v1/assets status=201 ms=12.4 10:24:31 WARN reqId=a3f2... indicator=1.2.3.4 verdict=malicious score=87 IOC threat detected ``` ## 测试 ``` npm run test:unit # unit tests — no DB or Redis needed (external APIs are mocked) npm run test:integration # integration tests — requires Postgres + Redis running npm run test:coverage # all tests with v8 coverage report → coverage/ ``` 集成测试使用 `fastify.inject()` — 不使用真实 HTTP 端口或网络,但连接**真实的 Postgres 数据库**以测试实际查询行为。测试设置会在每个测试前清空所有表以保证隔离性。 CI 会运行完整测试套件,使用 Postgres 和 Redis 作为服务容器,将结果上传到 Codecov,并在 `npm audit --audit-level=high` 发现问题时报失败。 ## 仪表盘集成 仪表盘将此 API 作为后端服务调用,使用长期有效的 API 密钥 — 无需用户登录流程,后端无需 Clerk 令牌或会话管理。 **一次性设置:** ``` # 1. 在后端注册您自己的账户 curl -X POST http://localhost:3001/api/v1/auth/register \ -H "Content-Type: application/json" \ -d '{"email":"admin@yoursite.com","password":"strongpassword"}' # 2. 为仪表板创建命名的 API 密钥 curl -X POST http://localhost:3001/api/v1/auth/api-keys \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"name":"online-cyber-dashboard"}' # → { "data": { "key": "tip_abc123...", ... } } ``` **在 `Online-Cyber-Dashboard/.env.local` 中:** ``` THREAT_INTEL_API_URL=http://localhost:3001 THREAT_INTEL_API_KEY=tip_abc123... ``` **从 Next.js 路由处理程序调用:** ``` // app/api/threat/assets/route.ts export async function GET() { const res = await fetch(`${process.env.THREAT_INTEL_API_URL}/api/v1/assets`, { headers: { 'X-API-Key': process.env.THREAT_INTEL_API_KEY! }, next: { revalidate: 60 }, }); const { data } = await res.json(); return Response.json({ data }); } // app/api/threat/alerts/route.ts export async function GET(request: Request) { const { searchParams } = new URL(request.url); const url = new URL(`${process.env.THREAT_INTEL_API_URL}/api/v1/alerts`); url.searchParams.set('unread', 'true'); url.searchParams.set('limit', searchParams.get('limit') ?? '20'); const res = await fetch(url.toString(), { headers: { 'X-API-Key': process.env.THREAT_INTEL_API_KEY! }, }); return Response.json(await res.json()); } ``` ## 环境变量 将 `.env.example` 复制为 `.env` 并填写所需值。 ### 必需项 | 变量 | 描述 | |---|---| | `DATABASE_URL` | Postgres 连接字符串 | | `JWT_SECRET` | 至少 32 个字符 — 使用 `openssl rand -base64 48` 生成 | ### 认证 | 变量 | 默认值 | 描述 | |---|---|---| | `ACCESS_TOKEN_EXPIRY` | `15m` | JWT 访问令牌有效期 | | `REFRESH_TOKEN_EXPIRY_DAYS` | `30` | 刷新令牌有效期(天) | ### 基础设施 | 变量 | 默认值 | 描述 | |---|---|---| | `REDIS_URL` | `redis://localhost:6379` | Redis 连接字符串 | | `DB_POOL_MAX` | `20` | Postgres 连接池最大连接数 | | `PORT` | `3001` | HTTP 服务器端口 | | `HOST` | `0.0.0.0` | HTTP 绑定地址 | | `CORS_ORIGIN` | `http://localhost:3000` | 允许的跨域来源(逗号分隔) | | `LOG_LEVEL` | `info` | `trace`、`debug`、`info`、`warn`、`error`、`fatal` | ### 外部 API(全部可选 — 功能降级) | 变量 | 用途 | |---|---| | `ABUSEIPDB_API_KEY` | IP 信誉评分,用于 IOC 丰富 | | `VT_API_KEY` | VirusTotal IP 和域名分析 | | `NVD_API_KEY` | 更高的 NVD 速率限制(50 次/30 秒,替代默认的 5 次/30 秒) | | `OTX_API_KEY` | AlienVault OTX 威胁脉冲查询 | ### 邮件告警(可选) | 变量 | 默认值 | 描述 | |---|---|---| | `SMTP_HOST` | — | SMTP 服务器主机 — 留空以禁用邮件 | | `SMTP_PORT` | `587` | SMTP 端口 | | `SMTP_USER` | — | SMTP 用户名 | | `SMTP_PASS` | — | SMTP 密码 | | `SMTP_FROM` | `alerts@threat-intel.local` | 发件人地址 | ### 可观测性 | 变量 | 默认值 | 描述 | |---|---|---| | `GRAFANA_PASSWORD` | `admin` | Grafana 管理员密码(仅限 docker-compose) | ## 项目结构 ``` src/ ├── api/ │ ├── plugins/ │ │ └── auth.ts # JWT + API key authenticate decorator │ ├── routes/ │ │ ├── auth.ts # register / login / refresh / logout / api-keys │ │ ├── assets.ts # CRUD + pagination │ │ ├── alerts.ts # list / read / delete │ │ ├── vulnerabilities.ts │ │ ├── ioc.ts │ │ └── health.ts # /health + /metrics │ └── server.ts # Fastify setup, CORS, rate limit, swagger, hooks ├── db/ │ ├── schema/ # Drizzle table definitions (9 tables) │ ├── index.ts # postgres.js connection + Drizzle instance │ └── migrate.ts # migration runner ├── lib/ │ ├── env.ts # Zod-validated environment │ ├── logger.ts # Pino structured logger │ ├── mailer.ts # Nodemailer — skips if SMTP_HOST unset │ ├── metrics.ts # Prometheus counters, histograms, gauges │ └── redis.ts # ioredis client + cache helpers ├── services/ │ ├── nvd.ts # NVD CVE API client │ ├── abuseipdb.ts │ ├── virustotal.ts │ ├── otx.ts │ └── ioc-enrichment.ts # fan-out across all sources, Redis cache ├── workers/ │ ├── queues.ts # BullMQ Queue definitions + recurring job setup │ ├── cve-feed-worker.ts # NVD sync with exponential backoff │ ├── ioc-scan-worker.ts # per-asset IOC enrichment + email alerts │ └── asset-scan-worker.ts # CVE correlation + email alerts ├── index.ts # API server entry point └── worker.ts # Worker process entry point drizzle/ ├── 0000_initial_schema.sql └── 0001_refresh_and_api_keys.sql grafana/ ├── dashboards/threat-intel.json └── provisioning/ ├── datasources/prometheus.yml └── dashboards/dashboard.yml .github/workflows/ci.yml # lint → audit → test → coverage → docker build prometheus.yml # scrape config ```
标签:API 密钥, BullMQ, CVE 馈送, Docker, docker-compose, Drizzle ORM, Fastify, GitHub Actions, Grafana 可视化, IOC 情报, JWT 认证, masscan, OpenAPI, Pino 日志, PostgreSQL, Prometheus 监控, Redis, Swagger UI, TypeScript, Vitest, Web API, 后端 API, 在线网络安全仪表盘, 多阶段构建, 威胁情报平台, 威胁监测, 安全插件, 安全防御评估, 搜索引擎查询, 数据丰富, 测试用例, 生产就绪, 自动化攻击, 自动笔记, 自定义请求头, 请求拦截, 资产监控, 集成测试