跳到正文

10 - 安全体系:五层防护与全链路审计 ​

一句话:安全按五层叠加且互为兜底,网关剥头换身份、实例用 Runtime Token 覆盖含静态资源的全部路径、NetworkPolicy 默认拒绝跨实例与内网访问、Vault 按租户派生密钥并配 Sealed Secrets 与 WASM 沙箱、审计靠 trace_id 串联;代价是每层只在配置不失配时有效,出站从全网 443 收紧为域名白名单后每加一个外部依赖都要改策略,Skill 进沙箱后也只能经受限 host 接口取数。

一、安全模型总览 ​

数字分身产品的安全体系分为五层,每层独立生效、互为兜底:

二、各层详细说明 ​

2.1 第一层:统一网关 ​

职责:企业身份认证 + 流量入口管控

机制说明
SSO 认证OIDC/JWKS 验签,提取 user_id。客户端只带 Bearer,网关换发内部身份
Header 清洗剥离客户端伪造的 x-qwenpaw-runtime-token、x-sso-user 等 header
RT 注入ext_authz 服务从 Vault 缓存取 RT 注入请求头(详见第 03 章)
角色拦截employee 角色不可达管理端点(/api/providers、/api/admin 等)
限流Redis 滑动窗口 per-user 限流,按订阅档位分级

安全保证:员工不接触实例 RT,不存在"平台伪装登录"的可能。

2.2 第二层:Runtime 边界 ​

职责:实例侧最后一道门,保护全部路径

QwenPaw 实例内置 RuntimeBoundaryMiddleware,对所有 HTTP 和 WebSocket 请求校验 x-qwenpaw-runtime-token header:

校验逻辑:
  请求 header 中的 x-qwenpaw-runtime-token
  vs
  环境变量 QWENPAW_RUNTIME_INTERNAL_TOKEN(实例启动时注入)
  
  使用 hmac.compare_digest 做时序安全比较(防 timing attack)
  
  匹配 → 放行
  不匹配 / 缺失 → 401(HTTP)/ 4401(WebSocket)

保护范围:全部路径,包括 /api/healthz、/api/console/chat、/api/auth/*、首页、静态资源。没有任何路径绕过 RT 校验。

四通道验证矩阵:

场景结果
无 RT header401
错误的 RT401
仅 Bearer token(无 RT)401
正确的 RT + Bearer token200

2.3 第三层:网络隔离 ​

职责:K8s 网络层面的访问控制

yaml
# NetworkPolicy:实例间默认拒绝
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: instance-isolation
  namespace: qwenpaw-instances
spec:
  podSelector:
    matchLabels:
      app: qwenpaw
  policyTypes:
    - Ingress
    - Egress
  ingress:
    # 仅允许网关和控制面访问实例端口
    - from:
        - namespaceSelector:
            matchLabels:
              name: qwenpaw-gateway
        - namespaceSelector:
            matchLabels:
              name: qwenpaw-control
      ports:
        - port: 8088
          protocol: TCP
    # 允许 Prometheus 采集指标
    - from:
        - namespaceSelector:
            matchLabels:
              name: qwenpaw-infra
      ports:
        - port: 8088
          protocol: TCP
  
  egress:
    # 允许访问模型 API
    - to:
        - ipBlock:
            cidr: 0.0.0.0/0
            except:
              - 10.0.0.0/8      # 禁止访问内网(除白名单)
              - 172.16.0.0/12
              - 192.168.0.0/16
      ports:
        - port: 443
    # 允许访问 Nacos(服务发现)
    - to:
        - namespaceSelector:
            matchLabels:
              name: qwenpaw-infra
      ports:
        - port: 8848
    # 允许访问 MinIO(备份/产物)
    - to:
        - namespaceSelector:
            matchLabels:
              name: qwenpaw-infra
      ports:
        - port: 9000
    # 允许 DNS 查询
    - to:
        - namespaceSelector: {}
      ports:
        - port: 53
          protocol: UDP

关键约束:

  • 实例间完全隔离:A 员工的实例无法访问 B 员工的实例
  • 实例出站白名单控制:只能访问模型 API(443)、Nacos、MinIO,不能访问其他内网服务
  • 仅网关、控制面、监控可访问实例端口

2.4 第四层:密钥治理 ​

职责:全生命周期密钥管理

密钥类型生成存储使用轮换吊销
RT Tokentoken_urlsafe(48)Vault(per-tenant Fernet 加密)+ Redis 缓存网关注入 header90 天自动轮换即时生效
模型 API Key管理员配置Vault + Sealed Secrets → secrets 卷实例进程读取按 Provider 策略控制面替换
MCP 凭证管理员配置Vault → 控制面注入 headerMCP Client 连接90 天自动轮换即时生效
IM 凭证管理员配置Vault → 聚合器运行时渠道聚合器按平台策略即时生效

Per-Tenant 密钥派生(P0 修复) ​

P0 修复:原方案所有租户使用同一个 Fernet 主密钥,一旦泄露全部租户凭证暴露。改为 per-tenant 密钥派生:每个租户使用独立的加密密钥,由主密钥 + tenant_id 通过 HKDF 派生。

python
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
from cryptography.fernet import Fernet
import base64

MASTER_KEY = os.environ["VAULT_MASTER_KEY"].encode()  # 32 字节主密钥

def derive_tenant_key(tenant_id: str) -> bytes:
    """从主密钥派生租户专属密钥"""
    hkdf = HKDF(
        algorithm=hashes.SHA256(),
        length=32,
        salt=b"qwenpaw-vault-v1",
        info=tenant_id.encode(),
    )
    derived_key = base64.urlsafe_b64encode(hkdf.derive(MASTER_KEY))
    return derived_key

def encrypt_credential(tenant_id: str, plaintext: str) -> str:
    """使用租户专属密钥加密"""
    key = derive_tenant_key(tenant_id)
    f = Fernet(key)
    return f.encrypt(plaintext.encode()).decode()

def decrypt_credential(tenant_id: str, ciphertext: str) -> str:
    """使用租户专属密钥解密"""
    key = derive_tenant_key(tenant_id)
    f = Fernet(key)
    return f.decrypt(ciphertext.encode()).decode()

安全效果:即使攻击者获取了某个租户的加密密钥,也无法解密其他租户的凭证。主密钥泄露时,只需轮换主密钥并重新派生所有租户密钥。

Sealed Secrets 保护 K8s Secret(P0 修复) ​

P0 修复:原方案 K8s Secret 默认 base64 编码,有 kubectl 读权限的运维人员可直接获取明文。改为 Sealed Secrets:Secret 在客户端使用集群公钥加密,只有集群内的 Sealed Secrets Controller 持有私钥可解密。运维人员即使有 K8s 读权限,也只能看到密文。

K8s RBAC 配套策略:

角色权限说明
运维人员pods/log 只读,secrets 无权限可排查问题但无法获取密钥
开发人员pods/log 只读,secrets 无权限同上
Sealed Secrets Controllersealedsecrets 读写,secrets 读写唯一可解密的角色
控制面 ServiceAccountsecrets 指定 namespace 只读仅可读取控制面自身 namespace 的 Secret

禁止操作:

  • 禁止 kubectl exec 进入业务 Pod(通过 OPA/Gatekeeper 策略拦截)
  • 禁止 kubectl port-forward 到业务 Pod(通过 NetworkPolicy 拦截)
  • 禁止 kubectl get secret 读取实例 namespace 的 Secret(通过 RBAC 拦截)

Skill 代码沙箱执行(P0 修复) ​

P0 修复:原方案 Skill 代码在实例进程内无沙箱执行,恶意 Skill 可读取 secrets 卷、外传数据。改为 WASM 运行时沙箱:Skill 代码编译为 WASM 模块,在隔离的沙箱中执行,无法访问宿主文件系统和网络。

WASM 沙箱安全策略:

能力原方案(无沙箱)新方案(WASM 沙箱)
文件系统访问可读写整个实例文件系统不可访问,仅通过 host functions 传入/传出数据
网络访问可发起任意 HTTP 请求不可访问,需通过 host function 代理(受白名单控制)
进程/线程可 fork 进程不可,单线程 WASM 执行
内存共享实例进程内存独立线性内存,不可越界
执行时间无限制可设置 gas 上限,超时自动终止

Skill 分发流程变更:

  1. Skill 包上传到企业市场时,自动编译 Python 代码为 WASM 模块
  2. 编译时进行静态安全分析(检测危险模式:文件访问、网络请求、系统调用)
  3. 管理员审核通过后,WASM 模块分发到实例
  4. 实例内 WASM Runtime 加载并执行,沙箱隔离

实例出站策略收紧(P0 修复):

P0 修复:原方案实例出站允许 0.0.0.0/0:443,恶意 Skill 可外传数据。改为白名单模式:仅允许访问已知域名。

yaml
# NetworkPolicy egress 规则(收紧后)
spec:
  egress:
    - to:
        # 仅允许访问模型 API 域名
        - ipBlock:
            cidr: 100.64.0.0/16   # 模型 API 内网端点
        # 仅允许访问企业 MCP Server
        - ipBlock:
            cidr: 10.0.100.0/24   # 企业 MCP Server 网段
        # 仅允许访问基础设施
        - namespaceSelector:
            matchLabels:
              name: qwenpaw-infra
          ports:
            - port: 8848   # Nacos
            - port: 9000   # MinIO
      ports:
        - protocol: TCP
          port: 443
        - protocol: TCP
          port: 8848
        - protocol: TCP
          port: 9000

密钥安全原则(更新):

原则实现
明文不落库Vault 使用 per-tenant Fernet 加密(HKDF 派生),数据库只存密文
明文不进备份include_secrets=false,备份不含任何密钥
明文不进日志secrets 卷不挂载到日志采集路径
明文不见员工员工在任何界面都看不到密钥/凭证明文
每实例独立每个实例的 RT 独立随机生成,绝不共用
租户隔离每租户独立加密密钥(HKDF 派生),跨租户不可解密
K8s Secret 加密Sealed Secrets 加密存储,运维人员只能看到密文
Skill 沙箱隔离WASM 运行时执行,无法访问文件系统和网络
出站白名单实例仅允许访问模型 API + 企业 MCP + 基础设施,禁止 0.0.0.0/0

2.5 第五层:全链路审计 ​

职责:所有操作可追溯

审计事件格式:

json
{
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "timestamp": "2026-10-25T10:30:00.123Z",
  "event_type": "tool_call",
  "source": "instance",
  "user_id": "zhangsan",
  "instance_id": "inst_abc123",
  "session_id": "user:zhangsan:default",
  "task_id": "task_abc123",
  "tool_name": "file_write",
  "tool_params": {"path": "/data/report.md", "size": 2048},
  "result": "success",
  "tokens_used": 350,
  "latency_ms": 120
}

三、数据安全原则 ​

3.1 数据分类 ​

分类数据保护级别备份策略
机密RT Token、模型 API Key、MCP 凭证、IM 凭证Vault 加密 + K8s Secret不入备份
内部会话记录、记忆数据、执行产物实例 PVC + NetworkPolicy逻辑备份(加密传输)
公开Skill 元信息、健康状态无特殊保护无需备份

3.2 数据生命周期安全 ​

阶段安全措施
创建RT 随机生成 → Vault 加密存储 → K8s Secret 注入 → 明文不落盘
运行PVC 按 user_id 隔离 → NetworkPolicy 实例间拒绝 → 审计全量留痕
暂存备份不含密钥 → 加密传输到 MinIO → PVC 保留(NetworkPolicy 仍生效)
恢复Vault 取 RT → K8s Secret 重建 → 备份恢复(不含密钥)
离职RT 即时吊销 → 数据交接归档 → 归档到期清理 → 审计记录永久保留

3.3 租户隔离审计 ​

审计项检查方式频率
PVC 归属labels 中 owner_user_id 与实际用户一致每日
NetworkPolicy 生效实例间网络不可达每周
Vault scope 隔离跨租户解密尝试失败每月
备份不含密钥解压备份检查无敏感文件每次备份
三处绑定一致K8s labels = Nacos metadata = registry每 5 分钟

四、安全事件响应 ​

4.1 告警规则 ​

事件检测方式告警级别响应
RT 暴力猜测连续 5 次 401(同一来源)紧急封禁来源 IP + 通知安全团队
密钥泄露审计异常调用模式(异地/异时段)紧急即时吊销 + 轮换 + 溯源
越权访问员工角色访问管理端点高记录审计 + 通知管理员
异常流量单实例 QPS 突增 10 倍中限流降级 + 人工复核
非工作时间操作凌晨 0-6 点大量调用中告警 + 人工复核

4.2 应急响应流程 ​

把 AI 工程讲到既懂也会用