Files
sentinel/PRD.md

17 KiB
Raw Blame History

产品需求文档 (PRD)

Key-IP Sentinel大模型 API 密钥 IP 防泄漏网关

版本v1.1 | 状态:待开发 | 作者:算力平台管理员


一、产品背景与目标

1.1 背景

企业内部算力平台(基于 New API + vLLM 自部署)面向几百至上千名内部研发人员开放使用。用户注册账号后可自助申请 API Key但现有机制无法防止用户将 API Key 分享给外部人员或在未授权设备上使用,存在算力被盗刷的风险。

1.2 解决方案定位

在调用方与 New API 之间部署一个独立的轻量级反向代理服务 Key-IP Sentinel,实现"首次使用即绑定First-Use-Bind"机制——API Key 在第一次被调用时,系统自动将该 Key 与发起调用的客户端 IP 绑定;此后该 Key 只能从绑定的 IP 发起请求,其他 IP 全部拦截。整个过程对用户无感知,无需管理员手动介入。

1.3 产品目标

  • 彻底杜绝 API Key 被分享到外部或其他未授权设备上使用。
  • 系统自动完成 IP 绑定,管理员只需处理少数"换 IP"的运维操作。
  • 提供清晰的管理后台,支持查看绑定状态、拦截日志、手动运维。

二、整体架构

2.1 流量链路

调用方 (Client)
      │
      │ HTTP (80)
      ▼
┌─────────────────────────────────────────┐
│                 Nginx                    │
│  职责:路径路由 /                        │
│        静态文件 / 内网鉴权 / 粗粒度限流  │
└────────────────┬────────────────────────┘
                 │ HTTP 内网转发
                 ▼
┌─────────────────────────────────────────┐
│          Key-IP Sentinel App            │
│  职责Token提取 / IP绑定校验 /         │
│        代理转发 / 管理 API / 告警        │
└───────┬─────────────────┬───────────────┘
        │                 │ 异步写
   ┌────▼────┐      ┌─────▼──────┐
   │  Redis  │      │ PostgreSQL │
   │ (热缓存) │      │ (持久化)   │
   └─────────┘      └────────────┘
        │
        ▼
┌─────────────────────────────────────────┐
│              New API (UDPI)             │
│  职责:用户鉴权 / 额度管理 / 计费 / 路由 │
└───────────────┬─────────────────────────┘
                │
         ┌──────▼──────┐
         │  vLLM 节点   │
         └─────────────┘

2.2 技术选型

模块 技术方案 说明
反向代理 & 业务后端 Go (Gin)Python (FastAPI) 优先推荐 Go高并发下内存占用极低SSE 透传天然支持
缓存层 Redis 7+ Token-IP 绑定热数据TTL 7 天
持久化层 PostgreSQL 15+ 绑定记录与审计日志,使用 inet/cidr 原生类型做 IP 范围匹配
前端管理 UI Vue3 + Element Plus 纯静态 SPA打包后由 Nginx 直接托管
外层网关 Nginx 路径隔离、静态文件、limit_req_zone 限流
部署方式 Docker Compose 共 4 个容器nginx / sentinel-app / redis / postgres

三、核心业务流程

3.1 请求拦截与动态绑定(网关核心逻辑)

收到请求
    │
    ├─ 无 Authorization Header ─→ 直接透传给 New API由其拒绝
    │
    ├─ 提取 Tokensk-xxx
    ├─ 提取真实 IP只信任 Nginx 写入的 X-Real-IP见安全说明 §6.1
    ├─ 对 Token 做 HMAC-SHA256 哈希 → token_hash
    │
    ├─ 查 Redis: sentinel:token:{token_hash}
    │       │
    │    命中 ──→ 比对 IP支持 CIDR 匹配)
    │       │         ├─ 匹配 → 刷新 last_used_at异步写 PG→ 放行
    │       │         └─ 不匹配 → 写拦截日志 → 检查告警阈值 → 返回 403
    │       │
    │    未命中 → 查 PG: SELECT * FROM token_bindings WHERE token_hash=?
    │               │
    │            有记录 → 回写 Redis → 比对 IP同上
    │               │
    │            无记录 → 【首次绑定】
    │                         ├─ INSERT INTO token_bindings (token_hash, bound_ip, status=Active)
    │                         ├─ 写入 RedisTTL 7天
    │                         └─ 放行请求

3.2 SSE 流式输出透传要求

  • 网关必须支持 逐 chunk 实时转发,不得缓冲整个响应再转发。
  • 响应 Header 中的 Content-Type: text/event-streamTransfer-Encoding: chunked 必须透传。
  • 连接超时时间应设为不低于 600 秒(大模型长上下文推理可能耗时较长)。

3.3 管理员运维场景

场景 触发条件 管理员操作 系统行为
用户换电脑/换 IP 用户反馈 Key 无法使用 在后台搜索该 Token点击【解绑】 清除 PG 记录 + 清除 Redis 缓存,用户下次调用重新触发首次绑定
用户转岗,需绑定新 IP 用户提交申请 在后台点击【编辑 IP】填入新 IP 或网段 更新 PG + 使 Redis 缓存失效(强制下次从 PG 重新加载)
发现 Key 被泄露 拦截告警推送 点击【封禁】 将该 Token 状态置为 BannedRedis 同步更新,后续所有请求直接被 Sentinel 拒绝
用户离职 HR 通知 点击【封禁】 同上

四、功能需求

4.1 Nginx 层职责

nginx.conf 中需要实现以下配置:

# 1. 代理路径:/ 全部转发给 sentinel-app:7000
# 2. 管理后台访问限制
location /admin/ {
    allow 10.0.0.0/8;        # 内网 IP 段
    allow 192.168.0.0/16;
    deny all;
    proxy_pass http://sentinel-app:7000;
}
# 3. 静态文件(前端 UI
location /admin/ui/ {
    root /etc/nginx/html;
    try_files $uri $uri/ /admin/ui/index.html;
}
# 4. 基础限流
limit_req_zone $binary_remote_addr zone=api:10m rate=60r/m;
# 5. 强制写入真实 IP防客户端伪造
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;  # 覆盖,不信任客户端传入的值

4.2 Sentinel App 反向代理模块

  • 受信 IP Header:只读取 X-Real-IPNginx 写入的),忽略请求中原始的 X-Forwarded-For
  • Token 提取:从 Authorization: Bearer {token} Header 中提取。
  • 连接池:与下游 New API 保持 HTTP Keep-Alive 长连接,连接池最大连接数可通过配置项设置,默认 512。
  • 异步落库:放行后,last_used_at 更新通过内部消息通道异步批量写入 PG每 5 秒 flush 一次),避免阻塞代理主协程。
  • 降级策略
    • Redis 不可用时:降级查询 PG同时触发内部限流最大 QPS 降为正常值的 50%),并写入系统告警日志。
    • PG 也不可用时:行为由环境变量 SENTINEL_FAILSAFE_MODE 决定:open(放行所有,保业务)或 closed(拒绝所有,保安全),生产环境默认 closed

4.3 Web 管理后台Admin UI

页面一数据大盘Dashboard

  • 卡片指标:
    • 今日总请求数
    • 今日放行数 / 拦截数
    • 当前已绑定 Token 数量
    • 当前封禁 Token 数量
  • 折线图:最近 7 天每日放行 vs 拦截数量趋势
  • 列表:最近 10 条拦截记录(实时刷新)

页面二绑定管理Bindings

表格字段:

字段 说明
ID 数据库主键
Token脱敏 展示格式 sk-ab**...**xy(前 4 后 4
绑定 IP / 网段 支持展示 CIDR192.168.1.0/24
状态 Active / Banned
首次绑定时间
最近调用时间
操作 解绑 / 编辑 IP / 封禁 / 解封

筛选功能:按 Token 尾号搜索、按 IP 搜索、按状态筛选。

页面三拦截日志Intercept Logs

表格字段:

字段 说明
时间 拦截发生的精确时间
Token脱敏 被拦截的 Token
绑定 IP 该 Token 注册的合法 IP
尝试 IP 发起非法请求的 IP
是否已告警 是否触发过告警推送

支持按时间范围、Token、尝试 IP 筛选,支持导出 CSV。

页面四系统设置Settings

  • 告警阈值配置N 分钟内同一 Token 被拦截 M 次触发告警默认5 分钟内 5 次)。
  • 告警方式Webhook URL调用方自定义POST JSON 格式)。
  • 自动归档策略last_used_at 超过 N 天的记录自动归档(默认 90 天)。
  • FAILSAFE_MODE 开关显示与切换。

4.4 Admin REST API

所有 /admin/api/* 接口需要携带 JWT Token通过 /admin/api/login 获取),登录凭证从环境变量读取。

Method 路径 说明
POST /admin/api/login 管理员登录,返回 JWT
GET /admin/api/dashboard 大盘统计数据
GET /admin/api/bindings 获取绑定列表(支持分页 & 筛选)
POST /admin/api/bindings/unbind 解除绑定(清除 PG + Redis
PUT /admin/api/bindings/ip 手动修改绑定 IP
POST /admin/api/bindings/ban 封禁 Token
POST /admin/api/bindings/unban 解封 Token
GET /admin/api/logs 获取拦截日志(分页 & 筛选)
GET /admin/api/logs/export 导出日志 CSV
GET /admin/api/settings 获取当前系统配置
PUT /admin/api/settings 更新系统配置
GET /health 健康检查(无需鉴权,供 Nginx/Docker 使用)

五、数据结构设计

5.1 token_bindings(主表)

CREATE TABLE token_bindings (
    id              BIGSERIAL       PRIMARY KEY,
    token_hash      VARCHAR(64)     NOT NULL UNIQUE,  -- HMAC-SHA256 哈希值
    token_display   VARCHAR(20)     NOT NULL,          -- 脱敏展示用,如 sk-ab****xy
    bound_ip        CIDR            NOT NULL,          -- 使用 PG 原生 CIDR 类型,支持网段
    status          SMALLINT        NOT NULL DEFAULT 1, -- 1=Active, 2=Banned
    first_used_at   TIMESTAMPTZ     NOT NULL DEFAULT NOW(),
    last_used_at    TIMESTAMPTZ     NOT NULL DEFAULT NOW(),
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_token_bindings_token_hash ON token_bindings(token_hash);
CREATE INDEX idx_token_bindings_bound_ip ON token_bindings USING GIST (bound_ip inet_ops);

IP 范围匹配查询(使用 PG << 操作符,性能极高):

-- 检查请求 IP 是否在绑定的网段内
SELECT status FROM token_bindings
WHERE token_hash = $1
  AND $2::inet << bound_ip   -- $2 为请求方真实 IP
LIMIT 1;

5.2 intercept_logs(拦截审计日志表)

CREATE TABLE intercept_logs (
    id              BIGSERIAL       PRIMARY KEY,
    token_hash      VARCHAR(64)     NOT NULL,
    token_display   VARCHAR(20)     NOT NULL,
    bound_ip        CIDR            NOT NULL,
    attempt_ip      INET            NOT NULL,
    alerted         BOOLEAN         NOT NULL DEFAULT FALSE,
    intercepted_at  TIMESTAMPTZ     NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_intercept_logs_token_hash ON intercept_logs(token_hash);
CREATE INDEX idx_intercept_logs_intercepted_at ON intercept_logs(intercepted_at DESC);

5.3 Redis 数据结构

绑定记录缓存

  • Keysentinel:binding:{token_hash}
  • ValueJSON
    {
      "bound_ip": "192.168.1.0/24",
      "status": 1
    }
    
  • TTL604800 秒7 天),每次命中时刷新

拦截计数器(用于告警)

  • Keysentinel:alert:{token_hash}
  • ValueInteger拦截次数计数
  • TTL:由告警配置的时间窗口决定(默认 300 秒/5 分钟)
  • 当计数达到阈值时App 触发告警并重置计数器

六、安全规范

6.1 IP 防伪造(最高优先级)

  • Nginx 必须使用 proxy_set_header X-Real-IP $remote_addr; 强制覆盖,$remote_addr 是 TCP 层观察到的连接 IP客户端无法伪造
  • Sentinel App 内部必须明确配置受信上游 IP 列表(即 Nginx 容器的内网 IP172.18.0.2),只有来自受信上游的 X-Real-IP 才被采信,否则直接用 TCP 连接的原始 IP。
  • 禁止在任何情况下直接信任客户端传入的 X-Forwarded-For Header。

6.2 Token 存储安全

  • 禁止明文存储 API Key 的完整内容。
  • 存储时使用 HMAC-SHA256(密钥从环境变量 SENTINEL_HMAC_SECRET 读取32 字节随机字符串)对 Token 进行哈希。
  • token_display 字段仅存储供人识别的脱敏格式(sk-ab****xy),无法被逆推出原始 Key。

6.3 Admin 接口安全

  • 管理员登录接口需要防暴力破解:连续 5 次失败则锁定 IP 15 分钟。
  • JWT Token 有效期设为 8 小时,支持手动吊销(退出登录清除服务端 Session 记录)。
  • 所有管理接口的调用需要记录操作日志(操作人 IP + 操作内容)。

七、环境变量配置清单

AI 开发时应将所有配置项做成环境变量,以下为完整列表:

环境变量名 必填 说明 示例值
DOWNSTREAM_URL 下游 New API 的地址 http://new-api:3000
REDIS_ADDR Redis 连接地址 redis:6379
REDIS_PASSWORD Redis 密码
PG_DSN PostgreSQL 连接串 postgres://user:pass@postgres:5432/sentinel
SENTINEL_HMAC_SECRET Token 哈希的 HMAC 密钥32字节 随机生成
ADMIN_PASSWORD 管理员登录密码
ADMIN_JWT_SECRET JWT 签名密钥 随机生成
TRUSTED_PROXY_IPS 受信上游代理 IPNginx 的内网 IP 172.18.0.2
SENTINEL_FAILSAFE_MODE 全链路故障时行为:open/closed closed
APP_PORT App 监听端口 7000
ALERT_WEBHOOK_URL 告警推送 Webhook 地址
ALERT_THRESHOLD_COUNT 触发告警的拦截次数阈值 5
ALERT_THRESHOLD_SECONDS 告警计数时间窗口(秒) 300
ARCHIVE_DAYS 自动归档的不活跃天数 90

八、Docker Compose 部署结构

version: '3.8'
services:

  nginx:
    image: nginx:alpine
    container_name: sentinel-nginx
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./frontend/dist:/etc/nginx/html/admin/ui:ro
    depends_on:
      - sentinel-app
    networks:
      - sentinel-net

  sentinel-app:
    image: key-ip-sentinel:latest  # 本地 build
    container_name: sentinel-app
    build: .
    restart: always
    # 不暴露端口到宿主机,只在内网被 Nginx 访问
    environment:
      - DOWNSTREAM_URL=http://new-api:3000  # 通过 external network 访问 New API
      - REDIS_ADDR=redis:6379
      - PG_DSN=postgres://sentinel:password@postgres:5432/sentinel
      - SENTINEL_HMAC_SECRET=${SENTINEL_HMAC_SECRET}
      - ADMIN_PASSWORD=${ADMIN_PASSWORD}
      - ADMIN_JWT_SECRET=${ADMIN_JWT_SECRET}
      - TRUSTED_PROXY_IPS=172.18.0.0/16
      - SENTINEL_FAILSAFE_MODE=closed
    depends_on:
      - redis
      - postgres
    networks:
      - sentinel-net
      - llm-shared-net  # 与 New API 的共享网络external

  redis:
    image: redis:7-alpine
    container_name: sentinel-redis
    restart: always
    command: redis-server --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis_data:/data
    networks:
      - sentinel-net

  postgres:
    image: postgres:15
    container_name: sentinel-postgres
    restart: always
    environment:
      POSTGRES_USER: sentinel
      POSTGRES_PASSWORD: ${PG_PASSWORD}
      POSTGRES_DB: sentinel
    volumes:
      - pg_data:/var/lib/postgresql/data
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro  # 初始化建表 SQL
    networks:
      - sentinel-net

volumes:
  redis_data:
  pg_data:

networks:
  sentinel-net:
    driver: bridge
  llm-shared-net:
    external: true  # 与 New API 共享的已存在网络

九、非功能需求

指标 要求
代理延迟增加p99 Redis 命中路径增加延迟 ≤ 5ms
并发能力 单实例支持 ≥ 1000 并发连接Go 实现)
SSE 透传 流式输出无缓冲实时转发,延迟增加 ≤ 10ms
可扩展性 App 本体无状态,可横向扩展多实例,负载由 Nginx upstream 均衡
日志格式 结构化 JSON 日志,兼容 ELK / Loki 采集
健康检查 GET /health 需 200ms 内响应 {"status":"ok"}