跳转到正文
ThinkWatch
开始输入以搜索文档。
中文
77 立即开始

ThinkWatch 安全文档#

本文档描述了 ThinkWatch 的安全架构、机制和加固实践。


1. 认证#

ThinkWatch 支持三种认证机制。网关接受其中任意一种;控制台管理 API 需要 JWT。

1.1 JWT 令牌#

ThinkWatch 在认证成功后签发两个 JWT 令牌:

令牌默认有效期用途
访问令牌900 秒(15 分钟)用于 API 授权的短期凭证
刷新令牌604800 秒(7 天)用于获取新的访问令牌/刷新令牌对

两个 TTL 值均可通过管理员 Web UI(jwt_access_ttl_secondsjwt_refresh_ttl_seconds 设置)进行配置,无需重启服务器。

签名算法: 使用共享密钥(JWT_SECRET 环境变量)的 HS256。计划在未来版本中迁移到使用非对称密钥对的 RS256。

密钥要求:

  • JWT_SECRET 必须至少 32 个字符长。
  • 启动时,ThinkWatch 执行熵检查,拒绝明显弱的密钥(例如全部相同字符、常见模式)。
  • 使用以下命令生成强密钥:openssl rand -hex 32

时钟偏差容忍: 在验证令牌过期(exp)和生效时间(nbf)声明时,应用 30 秒的宽限,以适应分布式服务之间的轻微时钟差异。

令牌声明:

{
  "sub": "user-uuid",
  "email": "user@example.com",
  "role": "admin",
  "exp": 1711930500,
  "iat": 1711929600,
  "iss": "thinkwatch"
}

刷新流程: 客户端将刷新令牌发送到 POST /api/auth/refresh。服务器验证令牌,签发新的访问/刷新令牌对,并使旧的刷新令牌失效。系统会检测刷新令牌的重用,一旦发现将使该用户的所有令牌失效(带重放检测的轮换机制)。

1.2 API 密钥#

API 密钥为网关的编程访问提供长期认证。

属性详情
格式tw- 为前缀(如 tw-sk-a1b2c3d4...
存储SHA-256 哈希值存储在 PostgreSQL 中
验证中间件对传入的密钥进行哈希后查找(过滤 deleted_at IS NULL
范围限制可选的 allowed_models 限制
速率限制可选的按密钥 RPM 限制,通过 Redis 实施
过期创建时可设置可选的 TTL

原始密钥值仅在创建时返回一次。之后无法再检索,因为仅存储了哈希值。

生命周期管理:

API 密钥现在支持完整的生命周期管理,包含以下能力:

  • 轮换: 可通过 POST /api/keys/{id}/rotate 轮换密钥。生成新密钥并返回;旧密钥进入可配置的宽限期,在此期间新旧密钥均被接受。grace_period_ends_at 时间戳指示旧密钥何时停止工作。
  • 宽限期: 轮换后,API 密钥认证中间件在 grace_period_ends_at 到期前同时接受新旧密钥哈希,允许客户端无中断地完成过渡。
  • 不活跃超时: 密钥可配置 inactivity_timeout_days 值。如果密钥在该期间内未被使用,后台生命周期任务将自动禁用该密钥。
  • 自动禁用: 超过不活跃超时的密钥会被软禁用(非删除),允许管理员在需要时重新启用。
  • 过期监控: 使用 GET /api/keys/expiring?days=N 列出即将过期的密钥。
  • 团队验证: API 密钥创建时会验证团队成员身份 — 用户只能在其所属团队内创建密钥。

1.3 OIDC / SSO#

ThinkWatch 支持 OpenID Connect,用于与企业身份提供商(Entra ID、Okta、Keycloak、Auth0 等)进行单点登录。

流程:授权码模式

  1. 用户浏览器被导向 GET /api/auth/sso/authorize
  2. 服务器生成加密随机的 statenonce,将两者存储在 Redis 中(TTL 为 10 分钟),然后将浏览器重定向到 OIDC 提供商的授权端点。
  3. 认证后,身份提供商重定向回 GET /api/auth/sso/callback?code=...&state=...
  4. 服务器验证 state 与 Redis 中的值(CSRF 防护),交换授权码获取令牌,验证 ID 令牌签名和 nonce,并配置或更新本地用户记录。
  5. ThinkWatch JWT 令牌返回给客户端。

CSRF 防护: state 参数是一个一次性随机值,存储在 Redis 中。在回调时被消费(删除),防止重放攻击。

1.4 网关双重认证#

网关中间件按以下顺序尝试认证:

  1. 如果 Authorization 请求头包含以 tw- 开头的令牌,则将其作为 API 密钥验证。
  2. 否则,将其作为 JWT Bearer 令牌验证。

这允许编程客户端(API 密钥)和基于浏览器/控制台的用户(JWT)无缝访问网关。


2. 授权(RBAC)#

ThinkWatch 实现了基于角色的访问控制,包含五个系统角色。

2.1 角色层级#

角色描述
admin完全系统访问权限。可以管理提供商、用户、MCP 服务器、设置,以及查看审计日志。
operator可以管理提供商和 MCP 服务器。不能管理用户或系统设置。
user标准用户。可以创建/管理自己的 API 密钥、查看分析数据和使用网关。
viewer只读访问。可以查看分析数据和模型,但不能创建密钥或修改任何内容。
service机器对机器身份。仅通过 API 密钥访问网关;无控制台访问权限。

2.2 访问控制矩阵#

资源adminoperatoruserviewerservice
Gateway(聊天、模型)
Gateway(MCP)
自有 API 密钥(CRUD)
分析(只读)
提供商(CRUD)
MCP 服务器(CRUD)
用户(CRUD)
审计日志
系统设置

2.3 中间件强制执行#

  • require_auth —— 从 Authorization 请求头中提取并验证 JWT。对未认证的请求返回 401 拒绝。
  • require_admin —— 在 require_auth 之后链式执行。检查 JWT 的 role 声明是否为 admin。对非管理员用户返回 403 拒绝。
  • API 密钥中间件 —— 网关专用。验证 API 密钥哈希,检查过期状态,强制执行 allowed_models,并通过 Redis 应用速率限制。

2.4 MCP 工具级访问控制#

MCP 工具可以在 API 密钥或用户级别进行限制。当 API 密钥设置了 allowed_models 时,MCP 工具调用也会受到管控:网关在将请求代理到上游 MCP 服务器之前,会检查调用方身份是否有权调用该特定工具。


3. 加密#

3.1 静态加密#

提供商 API 密钥

提供商 API 密钥(上游 AI 服务如 OpenAI、Anthropic、Google、Azure OpenAI 和 AWS Bedrock 的凭证)在存储前进行加密:

  • 算法:AES-256-GCM
  • Nonce:12 字节,密码学随机生成,每次加密操作独立生成
  • 密钥:从 ENCRYPTION_KEY 环境变量派生(32 字节十六进制字符串)
  • 存储格式:nonce || ciphertext || tag(base64 编码)

AWS Bedrock 凭证

AWS Bedrock 凭证(以 ACCESS_KEY_ID:SECRET_ACCESS_KEY 格式存储)使用相同的 AES-256-GCM 方案进行静态加密。在请求时,凭证会被解密并通过官方 aws-sigv4 Rust crate 用于 AWS SigV4 请求签名。签名过程在内存中执行,凭证不会以明文形式写入磁盘。

MCP 服务器认证密钥

上游 MCP 服务器的认证凭证使用与提供商 API 密钥相同的 AES-256-GCM 方案。

3.2 密码哈希#

用户密码使用 Argon2id 进行哈希,参数如下:

参数
算法Argon2id
内存开销19 MiB
时间开销2 次迭代
并行度1
盐值16 字节,随机
输出哈希32 字节

Argon2id 是 OWASP 指南推荐的算法,同时提供对 GPU 攻击和侧信道攻击的抵抗能力。

密码复杂度#

所有密码设置操作(注册、初始化向导、密码修改、管理员创建用户)均强制要求:

  • 最少 8 个字符
  • 至少一个大写字母 (A-Z)
  • 至少一个小写字母 (a-z)
  • 至少一个数字 (0-9)

3.3 API 密钥哈希#

API 密钥在存储前使用 SHA-256 进行哈希。这是单向操作;无法从哈希值推导出原始密钥。原始密钥仅在创建时返回给用户一次。

3.4 传输加密#

ThinkWatch 本身不终止 TLS。TLS 终止应由反向代理(Nginx、Caddy、Traefik、云负载均衡器)处理。详见第 6 节的加固清单。


4. 网络安全#

4.1 双端口架构#

ThinkWatch 将关注点分离到两个端口:

端口服务器暴露范围
3000Gateway用于应用/客户端访问
3001Console用于管理员/内部访问

这种分离允许网络级隔离:网关端口可以暴露给应用流量,而控制台端口限制在 VPN 或内部网络中。

4.2 CORS#

跨域资源共享通过 CORS_ORIGINS 环境变量配置:

  • 接受逗号分隔的允许来源列表。
  • 允许凭证(Access-Control-Allow-Credentials: true)。
  • 仅将指定来源反映在 Access-Control-Allow-Origin 中。
  • 在开发环境中,通常允许 http://localhost:5173(Vite 开发服务器)。
  • 在生产环境中,限制为实际的控制台域名。

会话 IP 绑定#

用户登录时,签名密钥与客户端 IP 地址绑定。后续请求中,签名验证中间件会检查请求 IP 是否与登录时存储的 IP 匹配。如果 IP 不同,请求将被拒绝(401 Unauthorized)。

此机制防止被窃取的签名密钥在不同网络中被使用。

4.3 安全响应头#

所有响应中设置以下请求头:

请求头用途
X-Content-Type-Optionsnosniff防止 MIME 类型嗅探
X-Frame-OptionsDENY通过 iframe 防止点击劫持

内容安全策略(仅控制台端口):

控制台端口(3001)包含 Content-Security-Policy 响应头,以缓解 XSS 和数据注入攻击。该策略将脚本来源、样式来源和连接端点限制为已知来源。

4.4 请求超时#

服务器超时时间原因
Gateway120 秒LLM 补全(尤其是流式传输)可能较慢
Console30 秒管理操作应快速完成

4.5 容器安全#

生产 Docker 镜像使用 distroless 基础镜像:

  • 无 shell(/bin/sh/bin/bash
  • 无包管理器
  • 无不必要的系统工具
  • 最小攻击面:仅包含编译后的 Rust 二进制文件及其运行时依赖

5. 审计跟踪#

5.1 记录内容#

每个与安全相关的操作都会生成审计条目:

类别记录的操作
认证登录成功、登录失败、注册、令牌刷新
API 密钥创建、撤销、使用(速率限制命中)
提供商创建、更新、删除
MCP 服务器创建、删除、工具发现
用户创建、角色变更、删除
设置任何配置变更

5.2 审计条目 Schema#

每条审计日志条目包含:

{
  "id": "uuid",
  "timestamp": "2026-03-28T09:15:00.000Z",
  "user_id": "uuid",
  "user_email": "admin@example.com",
  "action": "provider.create",
  "resource_type": "provider",
  "resource_id": "uuid",
  "details": {},
  "ip_address": "10.0.1.50",
  "user_agent": "Mozilla/5.0..."
}

5.3 ClickHouse 集成#

审计条目被插入到 ClickHouse 中,用于 SQL 查询和分析:

  • 数据库:可通过 CLICKHOUSE_DB 配置(默认:think_watch
  • 条目异步发送,以避免阻塞请求处理。
  • 控制台在 /api/audit/logs 提供搜索 UI,支持时间范围过滤和 SQL 查询。

5.4 日志转发 / SIEM 集成#

对于企业环境,ThinkWatch 可以通过管理员 Web UI(管理 > 日志转发器)将审计日志转发到外部系统:

  • 支持的传输方式: UDP Syslog、TCP Syslog (RFC 5424)、Kafka、HTTP Webhook
  • 配置: 通过数据库动态管理——无需重启
  • 格式: Syslog 传输使用 RFC 5424 结构化数据:
<14>1 2026-03-28T09:15:00.000Z thinkwatch - - - [thinkwatch@0 action="provider.create" user="admin@example.com" resource_type="provider" resource_id="uuid"] Provider created: openai-prod

这允许与 SIEM 平台集成,如 Splunk、Elastic SIEM、Microsoft Sentinel 等。


6. 启动验证#

ThinkWatch 在启动服务器前验证所有密钥和依赖。如果任何关键要求未满足,进程将以清晰的错误消息退出,而不是在降级状态下运行。

启动时验证的内容:

  • JWT_SECRET 存在、至少 32 个字符,且通过熵检查
  • ENCRYPTION_KEY 存在且为有效的 64 字符十六进制字符串
  • PostgreSQL 可达并响应测试查询
  • Redis 可达并响应 PING
  • OIDC 变量全部设置或全部未设置(不接受部分配置)
  • ClickHouse 连通性(如已配置;记录警告但不阻止启动)

7. 初始化端点安全#

POST /api/setup/initialize 端点允许在无需认证的情况下创建首个管理员用户。为防止滥用:

  • 速率限制: 该端点限制为每个 IP 地址每分钟 5 次请求。
  • 二次检查: 在创建管理员用户前,端点会执行数据库查询以验证没有管理员用户存在。这防止了两个并发请求都创建管理员账户的竞态条件。
  • 使用后禁用: 系统初始化后,该端点对所有后续调用返回 400 Bad Request,无论速率限制状态如何。

8. 软删除与数据保留#

ThinkWatch 对关键资源使用软删除:

资源行为
用户设置 deleted_at;撤销所有会话;用户无法登录
提供商设置 deleted_at;提供商的模型对新请求不可用
API 密钥设置 deleted_at;认证中间件立即拒绝该密钥

数据保留策略:

  • 软删除的记录在可配置的期间内保留(默认:30 天,由 data_retention_days 设置控制)。
  • 后台任务定期清除超过保留期的记录。
  • 通过 API 进行的账户删除始终是软删除操作:用户的会话被撤销,并标记 deleted_at。记录仅在保留期到期后才被永久移除。

9. 加固清单#

在准备 ThinkWatch 的生产部署时,请使用此清单。

密钥与密码学#

  • JWT_SECRET 设置为密码学随机值(最少 32 个字符,建议 64 个十六进制字符 / 256 位):
    openssl rand -hex 32
  • ENCRYPTION_KEY 设置为随机的 32 字节十六进制字符串:
    openssl rand -hex 32
  • 定期轮换 JWT_SECRET(注意:轮换会使所有活跃令牌失效)
  • 将所有密钥存储在专门的密钥管理器中(Vault、AWS Secrets Manager、K8s Secrets),而非明文 .env 文件
  • 验证启动验证无警告通过(检查日志中的熵检查结果)

网络#

  • CORS_ORIGINS 设置为实际的控制台域名(如 https://console.example.com
  • 将控制台(端口 3001)部署在 VPN 或企业防火墙后面;不要暴露给公共互联网
  • 在反向代理(Nginx、Caddy、Traefik 或云负载均衡器)上启用 TLS 终止
  • 在反向代理上配置 HSTS 响应头
  • 如果可能,将网关(端口 3000)访问限制为已知的 CIDR 范围

认证#

  • 配置 OIDC 以与企业身份提供商进行 SSO
  • 在生产环境中禁用基于密码的注册(使用管理员配置的账户或 SSO)
  • 如果启用了密码认证,强制执行强密码策略
  • 在登录端点上设置速率限制以缓解暴力攻击

数据库与基础设施#

  • 配置 PostgreSQL 要求 TLS(在 DATABASE_URL 中使用 sslmode=require
  • 启用 Redis 认证(requirepass 指令)
  • 如果可用,使用 Redis TLS(rediss:// 协议)
  • 将 PostgreSQL 访问限制为仅 ThinkWatch 服务账户
  • 仅从特权 CI/CD 流水线运行数据库迁移,而非在运行时从应用程序执行

API 密钥生命周期#

  • 为 API 密钥设置适当的 inactivity_timeout_days 以自动禁用未使用的密钥
  • 建立密钥轮换计划,使用 POST /api/keys/{id}/rotate 实现零停机轮换
  • 通过 GET /api/keys/expiring?days=30 定期审查即将过期的密钥
  • 配置 data_retention_days 以符合数据保留策略

审计与监控#

  • 为应用日志设置日志轮转
  • 验证 ClickHouse 审计表正在被填充
  • 如适用,通过管理 > 日志转发器配置日志转发到您的 SIEM
  • auth.login_failed 激增设置告警(可能的暴力攻击)
  • 使用基础设施监控系统监控 /api/health/health/live/health/ready 端点
  • 配置 Prometheus 抓取网关端口(3000)的 /metrics 端点
  • 为 API 密钥不活跃和过期事件设置告警

容器与运行时#

  • 使用 distroless 生产镜像(无 shell、无包管理器)
  • 以非 root 用户运行容器
  • 尽可能设置只读文件系统
  • 应用资源限制(CPU、内存)以防止失控进程
  • 在 CI/CD 中扫描容器镜像的 CVE

RBAC#

  • 审查所有用户角色,确保最小权限分配
  • 定期审计管理员账户
  • 对机器对机器的集成使用 service 角色
  • 在 API 密钥上限制 allowed_models,仅包含每个使用者需要的模型