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 的信任链、攻击面与防御策略。