4、多租户签名链路需求:签名请求生命周期、租户上下文传递、签名密钥管理

在多租户签名链路中,每个租户的签名请求必须经过完整的生命周期管理,同时确保租户上下文在 Istio 服务网格中透明传递,签名密钥的存储与访问必须严格隔离。本章从三个核心维度展开:签名请求的生命周期阶段、租户上下文的传递机制、签名密钥的分级管理策略。

4.1 签名请求生命周期

一个典型的签名请求从客户端发起至签名结果返回,经历以下五个阶段:

阶段 触发条件 关键动作 超时控制
1. 请求组装 客户端 SDK 调用 构造签名原文、附加租户 ID、请求 ID 100ms
2. 路由与认证 Istio Ingress Gateway 接收 JWT 校验、租户路由、速率限制 200ms
3. 密钥获取 签名服务接收请求 从 KMS/HSM 获取租户密钥 500ms
4. 签名计算 密钥就绪 执行 RSA/ECDSA 签名运算 300ms
5. 响应回传 签名完成 封装签名值、更新审计日志 100ms
性能基线: 单次签名请求端到端延迟应控制在 1.2s 以内,其中密钥获取阶段最容易成为瓶颈,建议使用本地缓存 + 异步刷新策略。

下图展示了签名请求在 Istio 网格内的完整生命周期流转:

签名请求生命周期流转图 ① 请求组装 客户端 SDK ② 路由与认证 Ingress Gateway ③ 密钥获取 KMS / HSM ④ 签名计算 签名服务 ⑤ 响应回传 审计日志 ⏱ 端到端超时:1.2s 密钥获取阶段(③)超时 500ms,建议启用本地缓存 租户上下文通过 Istio Envoy 请求头(x-tenant-id)透传 从阶段①组装到阶段⑤回传,全程携带租户标识 生命周期关键指标 • 请求成功率 ≥ 99.99% • 超时重试次数 ≤ 2 • 审计日志覆盖率 100% • 密钥获取缓存命中率 ≥ 95% • 签名计算使用硬件加速模块

4.2 租户上下文传递

租户上下文是签名链路中区分不同租户的核心标识。在 Istio 网格中,上下文通过 Envoy 请求头传递,并经过以下三层传递路径:

  1. 入口层: Ingress Gateway 从 JWT 中提取 x-tenant-id,注入到请求头。
  2. 网格层: Sidecar Envoy 通过 EnvoyFilter 确保请求头不被篡改,并附加租户路由标签。
  3. 服务层: 签名服务从请求头读取租户 ID,用于密钥查找和审计日志记录。
上下文传递契约:
# 请求头规范
x-tenant-id: tenant-12345
x-request-id: req-abc-20250301
x-signature-algorithm: RS256

# Istio EnvoyFilter 示例(防止租户头被篡改)
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: tenant-header-protection
spec:
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_INBOUND
    patch:
      operation: INSERT_BEFORE
      value:
        name: envoy.filters.http.header_mutation
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.http.header_mutation.v3.HeaderMutation
          mutations:
            - remove: "x-tenant-id"  # 禁止下游覆盖
            - append: "x-tenant-id"  # 仅允许上游注入

下表对比了三种常见的租户上下文传递方式:

传递方式 安全性 性能开销 适用场景
HTTP Header 中等(需 EnvoyFilter 加固) 低(无额外编解码) 标准签名链路
gRPC Metadata 高(内置 TLS 加密) 低 高性能签名服务
JWT Claim 高(数字签名验证) 中(需 JWT 解析) 跨信任域签名

4.3 签名密钥管理

签名密钥是多租户签名链路中最敏感的资源。密钥管理采用三级隔离策略:

  • L1 - 密钥存储隔离: 每个租户拥有独立的密钥存储空间(KMS Key Ring 或 HSM Slot),密钥 ID 格式为 projects/{project}/locations/{region}/keyRings/{tenant_id}/cryptoKeys/sign-key。
  • L2 - 密钥访问控制: 签名服务使用租户专属的 Service Account 访问 KMS,通过 IAM 条件绑定限制仅能操作本租户密钥。
  • L3 - 密钥缓存隔离: 本地缓存使用租户 ID 作为前缀,缓存过期时间按租户独立配置,避免缓存污染。
密钥管理避坑: 禁止在签名服务中缓存明文密钥。应使用 KMS 的 sign 接口直接进行签名运算,密钥材料始终不离开硬件安全边界。缓存仅存储密钥引用(Key URI)和访问令牌,过期时间不超过 5 分钟。

下图展示了密钥获取与缓存隔离的架构:

签名密钥管理架构图 签名服务 接收签名请求 提取租户 ID 本地缓存 Key URI + TTL: 5min KMS / HSM 密钥材料不离开 硬件安全边界 缓存未命中 → 回退 KMS 三级密钥隔离策略 L1: 独立 Key Ring / HSM Slot L2: 租户专属 Service Account + IAM 条件绑定 L3: 缓存 Key 前缀 = tenant_id,过期时间独立配置 密钥获取性能目标 缓存命中率 ≥ 95% | 缓存获取延迟 ≤ 5ms | KMS 回退延迟 ≤ 200ms | 密钥轮换不影响在线签名

4.4 密钥轮换与版本管理

签名密钥需要定期轮换以降低泄露风险。密钥版本管理遵循以下原则:

  • 主版本(Primary): 当前用于签名计算的密钥版本,所有新签名请求使用主版本。
  • 旧版本(Previous): 保留用于验证历史签名,不再用于新签名。
  • 轮换策略: 每 90 天自动轮换,轮换期间新旧版本并行存在 7 天,确保签名验证不中断。
密钥轮换最佳实践: 在 Istio 网格中,密钥轮换不应导致签名请求失败。建议在签名服务中实现 双读双写 策略:轮换期间同时持有新旧两个密钥引用,新签名使用新密钥,旧签名验证使用旧密钥。轮换完成后,旧密钥标记为过期并删除。

密钥版本切换的代码示例:

// 密钥版本管理器伪代码
type KeyVersionManager struct {
    primary   string // 主版本 Key URI
    previous  string // 旧版本 Key URI(仅用于验证)
    mu        sync.RWMutex
}

func (m *KeyVersionManager) GetSigningKey(ctx context.Context, tenantID string) (string, error) {
    m.mu.RLock()
    defer m.mu.RUnlock()
    // 始终返回主版本用于签名
    return m.primary, nil
}

func (m *KeyVersionManager) RotateKey(ctx context.Context, tenantID string) error {
    m.mu.Lock()
    defer m.mu.Unlock()
    // 1. 将当前主版本降级为 previous
    m.previous = m.primary
    // 2. 创建新密钥版本并设为主版本
    newKeyURI, err := kmsClient.CreateCryptoKeyVersion(ctx, m.primary)
    if err != nil {
        return err
    }
    m.primary = newKeyURI
    // 3. 7 天后清理 previous
    go func() {
        time.Sleep(7 * 24 * time.Hour)
        m.mu.Lock()
        defer m.mu.Unlock()
        m.previous = ""
    }()
    return nil
}

4.5 审计与合规

多租户签名链路必须满足审计合规要求。每个签名请求的完整生命周期需要记录以下信息:

审计字段 来源 示例值
租户 ID 请求头 x-tenant-id tenant-12345
请求 ID 请求头 x-request-id req-abc-20250301
签名算法 请求头 x-signature-algorithm RS256
密钥版本 KMS 返回 projects/p/keyRings/k/cryptoKeys/sign-key/cryptoKeyVersions/3
签名时间 签名服务本地时间 2025-03-01T12:00:00Z
签名结果 签名运算输出 success / failure
审计日志安全要求: 审计日志中不得包含签名原文或签名值明文。签名值应存储为哈希摘要(SHA-256),便于后续验证但不泄露原始签名。审计日志存储建议使用不可变存储(如对象存储 + WORM 策略),保留周期不少于 3 年。