SPIRE 系列之二:架构深度解析

Posted on 五 24 4月 2026 in Journal

SPIRE 系列之二:架构深度解析

SPIRE 系列第 2 篇 — 第一篇回答“为什么需要 Workload Identity”,这一篇回答“SPIRE 到底怎样把身份发给工作负载”。别急着背名词,先看清楚数据和信任是怎么流动的。

系列导航: - 01:从 Workload Identity 到 Zero Trust - 02:SPIRE 架构深度解析 - 03:安全性分析与加固清单 - 04:实战 Lab:用零信任身份替代数据库密码分发

1. 全局视角:SPIRE 在零信任中的位置

架构文章最怕画一堆框,最后读者还是不知道请求从哪儿来、身份到哪儿去。咱们先定一个锚点:在零信任架构中,身份是一切访问控制的基础。

SPIRE(SPIFFE Runtime Environment)是 SPIFFE 标准的生产级实现,负责为分布式系统中的每个工作负载自动签发、轮换和验证身份凭证。

┌─────────────────────────────────────────────────────────────┐
│                    零信任身份层                                │
│                                                              │
│   传统模型                        SPIRE 模型                  │
│   ─────────                      ──────────                  │
│   IP + 防火墙 → 信任网络位置       SVID → 信任加密身份          │
│   密码/Token → 静态凭证           X.509/JWT → 自动轮换         │
│   人工配置 → 运维负担              自动证明 → 内核级认证         │
└─────────────────────────────────────────────────────────────┘

2. 核心架构:两层模型

SPIRE 采用经典的 Server-Agent 两层架构,这是理解整个系统的关键:

                    ┌─────────────────────┐
                    │    SPIRE Server     │
                    │  (控制平面,集中式)    │
                    │                     │
                    │  ┌───────────────┐  │
                    │  │  CA (签发证书)  │  │
                    │  ├───────────────┤  │
                    │  │ Registration  │  │
                    │  │   Entries     │  │
                    │  ├───────────────┤  │
                    │  │  Datastore    │  │
                    │  │ (SQLite/PG)   │  │
                    │  └───────────────┘  │
                    └──────┬──────┬───────┘
                     gRPC  │      │  gRPC
                 ┌─────────┘      └──────────┐
                 ▼                            ▼
        ┌─────────────────┐         ┌─────────────────┐
        │   SPIRE Agent   │         │   SPIRE Agent   │
        │   (Node-1)      │         │   (Node-2)      │
        │                 │         │                 │
        │ ┌─────────────┐ │         │ ┌─────────────┐ │
        │ │ Workload API│ │         │ │ Workload API│ │
        │ │  (UDS)      │ │         │ │  (UDS)      │ │
        │ └──────┬──────┘ │         │ └──────┬──────┘ │
        └────────┼────────┘         └────────┼────────┘
                 │                           │
        ┌────────┼────────┐         ┌────────┼────────┐
        │  App-A │  App-B │         │  App-C │  App-D │
        └────────┴────────┘         └────────┴────────┘

2.1 为什么是两层而不是一层?

设计选择 优势 代价
Server 集中管理 统一的注册表、统一的 CA、便于审计 单点需要做 HA
Agent 节点常驻 本地 UDS 通信零网络开销、内核级 attestation 每个节点多一个进程
两层分离 Server 被攻破不影响已发证书;Agent 被攻破只影响单节点 架构复杂度增加

如果只用一层(所有工作负载直连 Server),那么: - Server 需要直接观察每个工作负载的内核属性——跨网络做不到 - 每次 SVID 请求都走网络——延迟和带宽不可接受 - Server 成为超级热点——扩展性差

3. SPIRE Server 深度剖析

Server 是 SPIRE 的“大脑”。它不直接服务业务请求,却决定谁能拿到什么身份,承担三大职责:

3.1 CA(Certificate Authority)— 身份签发中心

Server CA 的工作流程:

  Agent 请求签发 SVID
       │
       ▼
  ┌─────────────┐
  │ 验证 Agent   │ ← Agent 已通过 Node Attestation
  │ 节点身份     │
  └──────┬──────┘
         │
         ▼
  ┌─────────────┐
  │ 查询注册表   │ ← 该节点上有哪些工作负载注册?
  │ (Entries)   │
  └──────┬──────┘
         │
         ▼
  ┌─────────────┐
  │ 签发 SVID   │ ← X.509-SVID 或 JWT-SVID
  │ (短期证书)   │    默认 TTL: 1 小时
  └──────┬──────┘
         │
         ▼
  返回给 Agent → Agent 缓存并分发给工作负载

关键设计决策

  • 短期证书:默认 TTL 1 小时,最短可设 5 分钟。证书泄露的影响窗口极小
  • 自动轮换:Agent 在证书过期前自动请求新证书,应用无感知
  • 上游 CA 集成:Server 可以对接外部 CA(如 Vault、AWS PCA),自己只做中间 CA
# server.conf 中的 CA 配置示例
server {
    trust_domain = "example.org"
    default_x509_svid_ttl = "1h"
    default_jwt_svid_ttl = "5m"
    ca_ttl = "24h"              # Server CA 证书自身的有效期
    ca_key_type = "ec-p256"     # 推荐使用椭圆曲线
}

3.2 Registration Entries — 身份注册表

注册表是 SPIRE 的"通讯录",记录了"哪个工作负载应该获得什么身份":

Entry 的核心字段:

┌──────────────────────────────────────────────────────┐
│  Entry ID:    abcd-1234                               │
│  SPIFFE ID:   spiffe://example.org/service/payments   │
│  Parent ID:   spiffe://example.org/agent/node-1       │
│  Selectors:   [k8s:sa:payments-sa, k8s:ns:prod]      │
│  DNS Names:   [payments.prod.svc]                     │
│  TTL:         300s                                    │
│  Downstream:  false                                   │
│  Admin:       false                                   │
│  Hint:        "payments service in production"        │
└──────────────────────────────────────────────────────┘

Selector 组合逻辑

单条 Entry 内的多个 Selector → AND 逻辑(全部满足才匹配)
多条 Entry 匹配同一工作负载 → 工作负载获得多个 SPIFFE ID

示例:
  Entry A: selectors = [k8s:sa:payments-sa, k8s:ns:prod]
  → 必须同时是 payments-sa 账户 AND 在 prod 命名空间

  Entry B: selectors = [k8s:sa:payments-sa, k8s:ns:staging]
  → 同一个 SA 在 staging 获得不同的 SPIFFE ID

3.3 Datastore — 持久化存储

Server 需要持久化存储注册表、CA 证书和节点信息:

存储后端 适用场景 特点
SQLite 开发/测试/小规模 零依赖,单文件,不支持 HA
PostgreSQL 生产环境 支持 HA,多 Server 共享
MySQL 生产环境 同上
# 生产环境推荐 PostgreSQL
plugins {
    DataStore "sql" {
        plugin_data {
            database_type = "postgres"
            connection_string = "dbname=spire host=pg.internal sslmode=verify-full"
        }
    }
}

3.4 Server HA(高可用)

生产环境 Server HA 部署:

  ┌─────────┐   ┌─────────┐   ┌─────────┐
  │ Server-1│   │ Server-2│   │ Server-3│
  │ (active)│   │ (active)│   │ (active)│
  └────┬────┘   └────┬────┘   └────┬────┘
       │              │              │
       └──────────────┼──────────────┘
                      │
              ┌───────┴───────┐
              │  PostgreSQL   │
              │  (共享存储)    │
              └───────────────┘
  • 所有 Server 实例都是 active-active,无主从之分
  • 共享同一个数据库,注册表自动同步
  • Agent 可以配置多个 Server 地址,自动 failover

4. SPIRE Agent 深度剖析

Agent 是 SPIRE 的“手脚”,部署在每个节点上,离工作负载最近。身份能不能安全、快速地送到进程手里,主要看它。

4.1 Node Attestation — Agent 如何证明自己

Agent 首次启动时,必须向 Server 证明"我是一个合法的节点":

Node Attestation 流程:

  Agent 启动
     │
     ▼
  收集节点证据(取决于 attestor 插件)
     │
     ├─ AWS: EC2 Instance Identity Document (IID)
     ├─ GCP: GCE Instance Identity Token (IIT)
     ├─ Azure: MSI Token
     ├─ K8s: Projected Service Account Token (PSAT)
     └─ 通用: Join Token (一次性令牌)
     │
     ▼
  发送证据给 Server
     │
     ▼
  Server 验证证据(调用云厂商 API 或验证签名)
     │
     ▼
  验证通过 → 签发节点级 SVID → Agent 获得身份

各 Attestor 对比

Attestor 安全强度 自动化程度 适用环境
join_token ⭐⭐ 手动 开发/测试
aws_iid ⭐⭐⭐⭐ 全自动 AWS EC2
gcp_iit ⭐⭐⭐⭐ 全自动 GCP GCE
azure_msi ⭐⭐⭐⭐ 全自动 Azure VM
k8s_psat ⭐⭐⭐⭐⭐ 全自动 Kubernetes
k8s_sat ⭐⭐⭐ 全自动 旧版 K8s
x509pop ⭐⭐⭐⭐ 半自动 裸金属/自有 PKI

4.2 Workload Attestation — 识别本地工作负载

当工作负载通过 UDS 连接 Agent 时,Agent 需要确认"你是谁":

Workload Attestation 流程:

  工作负载连接 Agent 的 Unix Domain Socket
       │
       ▼
  Agent 通过 SO_PEERCRED 获取对端 PID
       │
       ▼
  ┌─────────────────────────────────────┐
  │  Workload Attestor 插件收集信息:     │
  │                                     │
  │  unix:  PID → UID, GID, 路径, 哈希   │
  │  k8s:   PID → Pod Name, SA, Labels  │
  │  docker: PID → Container ID, Labels │
  │  systemd: PID → Unit Name           │
  └──────────────────┬──────────────────┘
                     │
                     ▼
  将收集到的 Selectors 与注册表匹配
       │
       ├─ 匹配成功 → 返回对应的 SVID
       └─ 匹配失败 → 拒绝,返回 PermissionDenied

关键安全属性: - SO_PEERCRED 是 Linux 内核机制,无法被用户空间伪造 - UDS 文件权限可以进一步限制哪些用户能连接 - 多个 Attestor 可以组合使用(unix + k8s 同时生效)

4.3 Workload API — 应用获取身份的接口

Workload API 是应用与 SPIRE 交互的唯一接口,通过 Unix Domain Socket 暴露:

Workload API (基于 gRPC):

  ┌─────────────────────────────────────────┐
  │  FetchX509SVID()                        │
  │  → 返回 X.509 证书 + 私钥 + Trust Bundle│
  │  → 流式接口,证书轮换时自动推送新证书     │
  │                                         │
  │  FetchJWTSVID(audience)                 │
  │  → 返回 JWT Token (含 aud 声明)          │
  │  → 每次调用生成新 Token                  │
  │                                         │
  │  FetchX509Bundles()                     │
  │  → 返回信任域的 CA 证书束               │
  │  → 用于验证对端的 X.509-SVID            │
  │                                         │
  │  ValidateJWTSVID(token, audience)       │
  │  → 验证 JWT Token 的签名和有效性         │
  └─────────────────────────────────────────┘

X.509-SVID vs JWT-SVID 选择

维度 X.509-SVID JWT-SVID
用途 mTLS 双向认证 API 调用认证
生命周期 长(默认 1h),流式轮换 短(默认 5min),按需获取
传输方式 TLS 握手自动完成 HTTP Header 携带
中间人风险 TLS 通道保护 需防重放(audience 绑定)
典型场景 服务间 gRPC/HTTP 通信 跨信任域、API Gateway

4.4 SVID 缓存与轮换机制

Agent 内部维护一个 SVID 缓存,确保高性能和高可用:

Agent SVID 缓存机制:

  ┌──────────────────────────────────────┐
  │          Agent 内存缓存               │
  │                                      │
  │  SPIFFE ID → {X.509 cert, key, TTL} │
  │                                      │
  │  轮换策略:                            │
  │  ├─ 在 TTL 的 50% 时开始尝试轮换      │
  │  ├─ 轮换成功 → 通过流式 API 推送新证书 │
  │  ├─ 轮换失败 → 重试,直到旧证书过期    │
  │  └─ Server 不可达 → 使用缓存证书续命   │
  └──────────────────────────────────────┘

  关键韧性设计:
  - Agent 重启 → 从 Server 重新获取(秒级恢复)
  - Server 宕机 → Agent 缓存的证书仍然有效直到过期
  - 网络分区 → 已签发的证书不受影响,新签发暂停

5. 数据流全景:一次完整的身份获取

把所有组件串起来,看一次完整的 SVID 获取流程:

时间线:

T0: 管理员注册 Entry
    spire-server entry create \
      -spiffeID spiffe://example.org/service/web \
      -parentID spiffe://example.org/agent/node-1 \
      -selector k8s:sa:web-sa

T1: Agent 启动,完成 Node Attestation
    Agent → Server: "我是 node-1,这是我的 k8s PSAT 证据"
    Server → Agent: "验证通过,这是你的节点 SVID"

T2: Web 服务启动,连接 Workload API
    Web Pod → Agent UDS: FetchX509SVID()

T3: Agent 执行 Workload Attestation
    Agent: SO_PEERCRED → PID 12345
    Agent: PID 12345 → k8s pod "web-abc" → SA "web-sa" → ns "prod"

T4: Agent 匹配注册表
    Agent: selectors [k8s:sa:web-sa] 匹配 Entry → SPIFFE ID 确定

T5: Agent 向 Server 请求签发(如果缓存中没有)
    Agent → Server: "请为 spiffe://example.org/service/web 签发 X.509-SVID"
    Server → Agent: {cert, key, bundle, TTL=1h}

T6: Agent 返回 SVID 给工作负载
    Agent → Web Pod: {cert, key, bundle}
    Web Pod 用此证书建立 mTLS 连接

T7: 45 分钟后(TTL 50%),Agent 自动轮换
    Agent → Server: 请求新证书
    Agent → Web Pod: 流式推送新证书(应用无感知)

6. 部署模式对比

SPIRE 在不同环境下有不同的部署模式:

6.1 Kubernetes 部署

K8s 推荐部署方式:

  Server: Deployment (或 StatefulSet) + PVC
  Agent:  DaemonSet (每个 Node 一个)

  ┌──────────────────────────────────────────┐
  │  K8s Cluster                              │
  │                                           │
  │  ┌─────────┐  Deployment (replicas: 3)    │
  │  │ Server  │  + PostgreSQL                │
  │  └────┬────┘                              │
  │       │                                   │
  │  ┌────┴────┐  DaemonSet                   │
  │  │  Agent  │  hostPath: /run/spire/socket │
  │  │ (每节点) │                              │
  │  └────┬────┘                              │
  │       │ UDS (通过 hostPath 或 CSI 共享)    │
  │  ┌────┴────┐                              │
  │  │  Pods   │  volumeMount: /run/spire     │
  │  └─────────┘                              │
  └──────────────────────────────────────────┘

6.2 Sidecar 模式 vs DaemonSet 模式

维度 DaemonSet (推荐) Sidecar
Agent 数量 每节点 1 个 每 Pod 1 个
资源开销 低(节点级共享) 高(每 Pod 额外 ~30MB)
隔离性 中(共享 Agent) 高(独立 Agent)
运维复杂度 高(Agent 配置分散)
适用场景 大多数场景 多租户强隔离需求

关于 DaemonSet 与 Sidecar 的资源取舍,可以参考本系列第 1 篇的资源成本章节。核心结论很简单:大多数生产集群优先 DaemonSet,强隔离或多租户场景再考虑 Sidecar。

6.3 跨信任域联邦

当多个组织或集群需要互相信任时,SPIRE 支持 Federation

Federation(联邦):

  Trust Domain A                    Trust Domain B
  example.org                       partner.com
  ┌─────────────┐                  ┌─────────────┐
  │  Server A   │ ◄── Bundle ───► │  Server B   │
  │             │    Exchange      │             │
  └──────┬──────┘                  └──────┬──────┘
         │                                │
    ┌────┴────┐                      ┌────┴────┐
    │ Agent A │                      │ Agent B │
    └────┬────┘                      └────┬────┘
         │                                │
    ┌────┴────┐    mTLS (跨域)       ┌────┴────┐
    │  App-A  │ ◄─────────────────► │  App-B  │
    └─────────┘                      └─────────┘

  App-A 的证书: spiffe://example.org/service/a
  App-B 的证书: spiffe://partner.com/service/b
  双方通过交换 Trust Bundle 实现跨域信任

7. 插件体系:SPIRE 的可扩展性

SPIRE 的架构高度插件化,几乎每个功能点都可以替换:

Server 插件:
  ├─ NodeAttestor:     join_token, aws_iid, gcp_iit, azure_msi, k8s_psat
  ├─ DataStore:        sql (sqlite/pg/mysql)
  ├─ KeyManager:       disk, memory, aws_kms, gcp_kms
  ├─ UpstreamAuthority: disk, vault, aws_pca, gcp_cas, spire
  └─ Notifier:         k8s_bundle (自动更新 ConfigMap)

Agent 插件:
  ├─ NodeAttestor:     join_token, aws_iid, gcp_iit, azure_msi, k8s_psat
  ├─ WorkloadAttestor: unix, k8s, docker, systemd
  ├─ KeyManager:       disk, memory
  └─ SVIDStore:        aws_secretmanager, gcp_secretmanager

8. 小结:架构设计的取舍

设计决策 选择 理由
Server-Agent 两层 ✅ 采用 安全隔离 + 本地高性能
短期证书 ✅ 采用 缩小泄露影响窗口
UDS 通信 ✅ 采用 内核级安全,零网络开销
插件化架构 ✅ 采用 适配多云、多平台
无 Agent 模式 ❌ 不支持 牺牲便利性换取安全性
长期证书 ❌ 不推荐 泄露风险大

SPIRE 的架构本质上是在 安全性、性能、运维复杂度 之间做取舍。Server 负责规则和签发,Agent 负责贴近现场,Workload API 则把身份交给应用。

一句话,SPIRE 不是“多装一个证书服务”,而是把身份签发这件事从应用代码里拿出来,放到一套可审计、可轮换、可扩展的运行时系统里。理解了这个取舍,后面的安全加固才有落点。


上一篇:SPIRE 系列之一:从 Workload Identity 到 Zero Trust — 先建立身份模型和落地路线。

下一篇:SPIRE 系列之三:安全性分析与加固清单 — 分析 SPIRE 的信任链、攻击面与防御策略。