Skip to content

SPIRE 整体架构源码分析

分析对象:SPIRE v1.15.2(SPIFFE Runtime Environment 官方实现,Go) 版本常量:pkg/common/version/version.go:12(Base = "1.15.2")。 所有 路径:行号,已用一手代码核对。 配套文档:SVID 颁发流程 · 高可用


0. 一句话架构

SPIRE 是 SPIFFE 标准的参考实现,由两个可独立部署的二进制组成,都构建在同一套插件化 catalog 框架之上:

  • spire-server:信任域(trust domain)的信任根与控制面。持有/轮换 CA,维护注册表(registration entries + attested nodes),签发 X509/JWT/WIT SVID,对外暴露 SPIRE Server gRPC API 与 SPIFFE 联邦 bundle HTTPS 端点。
  • spire-agent:每个工作负载节点上的本地守护进程。先向 server 完成节点认证换取自身 SVID,再周期同步"本节点被授权的注册项",本地缓存并轮换工作负载 SVID,通过 SPIFFE Workload API(Unix socket / named pipe)下发给本机工作负载。

一个贯穿全局的设计:对外 API 契约与插件契约都外置到上游模块,本仓库只做实现。 go.mod:go-spiffe/v2(Workload API 标准,:80)、spire-api-sdk(Server v1 API proto,:81)、spire-plugin-sdk(插件 gRPC 契约,:82)、hashicorp/go-plugin(进程外插件,:60)。


1. 顶层组件与启动流程

1.1 极薄的 main + 三段式启动

两个组件的 main.go 几乎对称,只把 CLI 的 Run 包进跨平台 EntryPoint:

go
// cmd/spire-server/main.go:10   (cmd/spire-agent/main.go:11 对称)
func main() {
    os.Exit(entrypoint.NewEntryPoint(new(cli.CLI).Run).Main())
}

run 子命令是唯一启动守护进程的命令,骨架是经典"三段式"(cmd/spire-server/cli/run/run.go:270):

go
c, err := LoadConfig(...)   // ① 加载配置:parseFlags → ParseFile(HCL) → mergeInput → NewServerConfig
s := server.New(*c)         // ② New:仅装配 Config(config.go:176)
err = s.Run(ctx)            // ③ Run:依赖注入编排(见 1.2)

server.New / agent.New 都极简,只包一层 Config。真正的装配在 Run 里。

1.2 server.run —— 顺序化依赖注入 + 并发 TaskRunner(核心)

Server.run(pkg/server/server.go:90)是整个 server 的装配中枢,按严格顺序创建并连线所有子系统,最后把长驻任务交给 TaskRunner 并发跑。装配顺序(server.go:90-299)简化:

telemetry → host services(identityprovider/agentstore)→ loadCatalog(加载插件)
 → bundle cache/pubmanager → validateTrustDomain
 → credBuilder(credtemplate) → credValidator(credvalidator)
 → newCA(ca.ServerCA) → newCAManager(ca/manager) → newCASync(ca/rotator)
 → newSVIDRotator(server 自身 SVID)→ authpolicy 引擎 → newBundleManager(联邦)
 → newEndpointsServer(组装 API 端点)→ host services SetDeps → newRegistrationManager

然后并发运行长驻任务:

go
// pkg/server/server.go:264
tasks := []func(context.Context) error{
    caSync.Run,                    // CA 轮换
    svidRotator.Run,               // server 自身 SVID 轮换
    endpointsServer.ListenAndServe,// API 端点(TCP mTLS + UDS)
    metrics.ListenAndServe,
    bundleManager.Run,             // 联邦 bundle 拉取
    registrationManager.Run,       // 注册项清理
    bundlePublishingManager.Run,   // bundle 发布到 BundlePublisher 插件
    catalog.ReconfigureTask(...),  // 插件动态重配
}
// 可选:配置了 PruneAttestedNodesExpiredFor 时追加 nodeManager.Run

无 DI 框架——全靠 server.go:366-530 的一组 newXxx 工厂手工连线。想理解"谁依赖谁",读这段即可(如 newEndpointsServer 把 catalog、svidRotator、serverCA、caManager、authPolicy、bundleManager 全注入 endpoints.Config)。

1.3 agent.Run —— 多了一段"入网引导"

Agent.Run(pkg/agent/agent.go:63)比 server 多一段认证引导循环:

storage.Open(本地持久化)→ telemetry → catalog.Load(加载插件)
 → 选 NodeAttestor(join token 用内置,否则用插件)
 → 【引导循环 agent.go:190-261】加载 bootstrap bundle → a.attest(节点认证)→ 失败 backoff 重试
 → newManager(SVID 缓存与轮换)→ workload_attestor → newEndpoints(Workload API)
 → 并发:metrics / mgr.Run / storeService.Run / agentEndpoints.ListenAndServe / catalog 重配

a.attest(agent.go:377)是 agent "入网"的关键一跳,落到 node attestor 子系统(详见 SVID 颁发流程 链路一)。


2. Server 侧内部模块(pkg/server/)

签发链贯穿这些模块:API/Rotator → ca.ServerCAcredtemplate.Builder 构模板 → 签名 → credvalidator.Validator 校验

模块职责关键定位
ca/CA 证书/令牌签发(核心)。ServerCA 接口 + CA 实现接口 ca.go:33;CA ca.go:167;签名主干见 SVID 颁发流程 §4
ca/managerCA 密钥**双槽(current/next)**生命周期:prepare/activate/rotate;CA journal 持久化manager.go:258(Prepare)/:327(Activate)/:334(Rotate)
ca/rotator定时器驱动 manager 做 Prepare/Activaterotator.go:85(Run)
endpoints/组装并运行 Server 全部 gRPC API(双监听器,见 §5.2)endpoints.go:249(ListenAndServe)/:305(TCP)/:324(UDS)
catalog/服务端插件目录,类型化访问插件接口 catalog.go:50;Load :142
datastore/唯一持久化抽象 DataStore;实现是内置 SQL接口 datastore.go:14;SQL sqlstore/sqlstore.go:105
credtemplate/集中构造所有 X.509 模板与 JWT/WIT claimsBuilder builder.go:136
credvalidator/签发后(纵深防御)校验产物符合 SPIFFE 约束Validator validator.go:22
authorizedentries/进程内多维索引快照,快速算"某 agent 可见的 entry 集合",避免每次打库Cache cache.go:35;LookupAuthorizedEntries :63
svid/Server 自身 SVID 轮换(用于 TCP mTLS 端点),Observer 发布状态rotator.go:20;observer.go:6
bundle/三子包:client(拉联邦 bundle)/pubmanager(发布本地 bundle 到插件)/datastore(bundle 变更回调)client/manager.go:86;pubmanager/pubmanager.go:26
registration/注册项清理守护(每 5 分钟 prune 过期 entry)manager.go:49(Run)
node/attested node 清理守护(默认 ~1h + 抖动)manager.go:67(Run)
cache/三类读缓存:entrycache(全量快照)/dscache(bundle 1s TTL)/nodecache(节点 5s)entrycache/fullcache.go:91

3. Agent 侧内部模块(pkg/agent/)

装配链:agent.go 顶层 → catalog → node attestor(入网)→ manager(缓存与轮换,内部创建 svid.Rotatorclient)→ endpoints(Workload API)。

模块职责关键定位
manager/agent 中枢:周期同步授权条目+bundle,维护工作负载 SVID 的 LRU 内存缓存,驱动签发/轮换,向 Workload API 提供订阅与查询接口 manager.go:44;Run :230;synchronize sync.go:102
attestor/nodeagent 入网:走 AttestAgent 双向流,经 NodeAttestor 插件应答挑战,换回 agent SVID + bundlenode.go:65(Attest)
attestor/workload对给定 PID 并发调所有 WorkloadAttestor 插件,聚合 selectorsworkload.go:27(接口)/:112(attest)
endpoints/本地(UDS/named pipe)gRPC server:Workload API + Envoy SDS v3 + Healthendpoints.go:45(New)/:110(ListenAndServe)
svid/agent 自身 SVID 轮换,按 Reattestable 决定 reattest 或续签rotator.go:211(rotateSVIDIfNeeded)
client/到 server 的 mTLS gRPC 连接(引用计数复用),四个类型化子客户端接口 client.go:91;dial.go:47
catalog/agent 端插件目录(4 类:KeyManager/NodeAttestor/SVIDStore/WorkloadAttestor)接口 catalog.go:31
storage/agent SVID/bundle/bootstrap 状态 JSON 落盘,重启后恢复接口 storage.go:22;Open :50

4. 插件化 catalog 机制(核心扩展点)

4.1 内置与外部插件完全同构——都走 gRPC

通用框架在 pkg/common/catalog/。分叉点在 loadPlugin(catalog.go:319),判据是配置里是否给了可执行文件路径(HCL 的 plugin_cmd):

go
// pkg/common/catalog/catalog.go:320
if pluginConfig.IsExternal() {
    return loadExternal(...)   // 外部:hashicorp/go-plugin 子进程
}
// 否则匹配内置插件名 → loadBuiltIn(进程内)

关键设计:内置插件也走 gRPC,只是经内存管道回环loadBuiltIn(builtin.go:44)在进程内起 gRPC server,用 newPipeNet() + grpc.WithContextDialer 建进程内连接。于是内置与外部插件对上层是同构的 gRPC 客户端,上层代码对二者无感知。

外部插件走 go-plugin 子进程(external.go:74):用插件类型做握手 cookie,AllowedProtocols: [gRPC],可选 plugin_checksum(SHA256 校验二进制),Linux 下设 Pdeathsig: SIGKILL 防残留。host services 通过 go-plugin broker 反向暴露给子进程。

4.2 全部插件类型清单(v1.15.2)

插件类型数量约束内置实现
NodeAttestorserver + agentserver ZeroOrMore / agent ExactlyOneaws_iid, azure_msi/imds, gcp_iit, http_challenge, join_token, k8s_psat, sshpop, tpm_devid, x509pop
KeyManagerserver + agentExactlyOneserver: aws_kms, gcp_kms, azure_keyvault, hashicorp_vault, disk, memory / agent: disk, memory
UpstreamAuthorityserverMaybeOneaws_secret, aws_pca, gcp_cas, vault, spire, disk, cert_manager, ejbca
WorkloadAttestoragentAtLeastOnedocker, k8s, systemd, unix, windows
SVIDStoreagentZeroOrMoreaws_secretsmanager, gcp_secretmanager
NotifierserverZeroOrMoregcs_bundle, k8s_bundle
BundlePublisherserverZeroOrMoreaws_s3, gcp_cloudstorage, aws_rolesanywhere, k8s_configmap
CredentialComposerserverZeroOrMoreuniqueid
DataStoreserver(特例)恰好 1,仅内置 sqlsql(mysql/postgres/sqlite)

出处:server 类型常量 pkg/server/catalog/catalog.go:39-45;agent pkg/agent/catalog/catalog.go:22-27;各 repo 的 BuiltIns()

两个必须纠正的事实(相对旧版本/常见误解):

  1. DataStore 是刻意的例外:它被 FilterByType 从通用加载中剥离并直连内置 SQL(catalog.go:162),以绕过 gRPC 消息大小限制;外部 DataStore 已废弃——catalog.go:265:"pluggability for the DataStore is deprecated; only the built-in ... plugin is supported"。
  2. NodeResolver 在 v1.15.2 已被完全移除(全仓库 grep 零命中)。旧文档里的这个插件类型已不存在,其职责并入 attestation。

4.3 插件接口三层结构

每类插件:Go facade 接口(<name>.go)+ gRPC facade 适配(v1.go)+ repository(repository.go)。例:

go
// pkg/server/plugin/nodeattestor/nodeattestor.go:10
type NodeAttestor interface {
    catalog.PluginInfo
    Attest(ctx, payload []byte, challengeFn func(ctx, challenge []byte) ([]byte, error)) (*AttestResult, error)
}

v1.gospire-plugin-sdk 生成的 gRPC stub 适配成上面的 Go 接口。本仓库 proto/ 只保留三份内部 proto:common.proto(共享类型)、plugin.proto(Init/Configure)、private/.../journal.proto(CA journal)。


5. 对外 API 面

对外面共三类:①Server 管控面 gRPC;②Server 联邦面 HTTPS;③Agent 面 gRPC。核心结论:v1 API 的 proto 契约全部外置到 spire-api-sdk/go-spiffe,本仓库只实现接口,无 REST 网关

5.1 SPIRE Server API(pkg/server/api/)—— 9 个 v1 gRPC 服务

服务职责关键 RPC
Agent(agent/v1)节点生命周期AttestAgent(流,service.go:294)、RenewAgent(:423)、PostStatus(:491)
SVID(svid/v1)签发/铸造 SVID 及下游 CABatchNewX509SVID(:193)、NewJWTSVID(:377)、BatchNewWITSVID(:424)、NewDownstreamX509CA(:603)、MintX509SVID(:72)
Entry(entry/v1)注册项 CRUD;agent 拉授权项GetAuthorizedEntries
Bundle(bundle/v1)本地+联邦 bundle 管理GetBundle
TrustDomain(trustdomain/v1)联邦关系管理
LocalAuthority(localauthority/v1)本地 CA 生命周期 prepare/activate/rotate/taint/revoke
Debug / Health / Logger调试 / 健康检查 / 运行时日志级别仅 UDS

5.2 传输 vs 授权:双监听器 + 方法级 OPA 策略

endpoints.ListenAndServe 同起两个 gRPC server(endpoints.go:253-254):

  • TCP server(:305):grpc.Creds(TLS) —— mTLS,面向远程 agent、下游 CA、联邦 server、持 admin SVID 的远程管理员。证书源是 server 自身 SVID,信任源是 bundle。
  • UDS server(:324):peertracker 本机 Unix 域套接字,面向本地 admin CLI。Logger/Health/Debug 挂 UDS。

但**"agent 面 vs 管理员面"不由传输决定,而由逐 RPC 方法的 OPA 策略决定**(执行点 api/middleware/authorization.go:18,策略数据 authpolicy/policy_data.json)。实测分档计数:

allow_local     : 53   仅本机 UDS(Debug/Logger/Health + 大量 admin CRUD)
allow_admin     : 46   admin SVID / 本机
allow_agent     :  8   已鉴权 agent(BatchNewX509SVID/NewJWTSVID/GetAuthorizedEntries/RenewAgent/PostStatus…)
allow_downstream:  3   下游 CA(NewDownstreamX509CA/PublishJWTAuthority/PublishWITAuthority)
allow_any       :  2   免鉴权引导入口:Bundle/GetBundle 与 Agent/AttestAgent

allow_any 的两个方法(GetBundleAttestAgent)正是 agent "零信任冷启动" 的入口——未持 SVID 时也能取信任根并发起认证。

5.3 联邦面 HTTPS(pkg/server/endpoints/bundle/)

一个独立 HTTPS 服务(非 gRPC),GET / 返回本信任域 SPIFFE trust bundle(JSON/JWKS),供跨信任域联邦。三种认证:SPIFFEAuth(默认,SPIFFE mTLS)/ ACMEAuth(Web PKI)/ DiskCertManager。详见 高可用 §5。

5.4 Agent 面 gRPC(over UDS)

单一 gRPC server(peertracker 凭据),注册三服务:SPIFFE Workload API(workload/handler.go)、Envoy SDS v3(sdsv3/handler.go,给 Envoy 下发证书)、标准 gRPC Health。工作负载鉴权不用 mTLS,而是 peertracker 拿调用进程属性 + Attestor.Attest 现算 selectors(详见 SVID 颁发流程 链路二)。


6. 整体架构图与数据流

6.1 组件与插件挂载点

┌──────────────────────────── 工作负载节点(Node)────────────────────────────┐
│   Workload ──SPIFFE Workload API(UDS,peertracker+attest)──► spire-agent   │
│   (Envoy) ───Envoy SDS v3────────────────────────────────►                │
│  ┌──────────────────────── spire-agent ───────────────────────────────┐   │
│  │  endpoints(Workload API + SDS)                                      │   │
│  │  manager(LRU SVID 缓存 + 轮换)                                       │   │
│  │     ├ svid.Rotator(agent 自身 SVID)                                 │   │
│  │     └ client(mTLS gRPC → server,引用计数)                          │   │
│  │  storage(SVID/bundle/bootstrap 落盘)                                │   │
│  │  ── 插件挂载点 ──  NodeAttestor* │ WorkloadAttestor* │ KeyManager │ SVIDStore* │
│  └───────────────────────────────┬──────────────────────────────────────┘   │
└──────────────────────────────────┼───────────────────────────────────────────┘
                                    │  gRPC/mTLS(TCP)
        AttestAgent(流)/ SyncAuthorizedEntries / BatchNewX509SVID / RenewAgent

┌──────────────────────────── spire-server ──────────────────────────────────┐
│  endpoints:  TCP(mTLS,面向 agent)  +  UDS(本地 admin CLI)                │
│              └─逐方法 OPA 授权(allow_agent/admin/local/downstream/any)     │
│  API(v1): Agent │ SVID │ Entry │ Bundle │ TrustDomain │ LocalAuthority ... │
│     ├─► ca.ServerCA ── credtemplate(构模板)─► 签名 ─► credvalidator(校验) │
│     │        ▲ 密钥槽由 ca/manager(current/next)+ ca/rotator 轮换          │
│     │        └── KeyManager 插件(签名密钥)/ UpstreamAuthority 插件(上游CA)│
│     ├─► authorizedentries.Cache / cache/*(授权项与节点缓存)                │
│     ├─► catalog ──► NodeAttestor* / Notifier* / CredentialComposer*        │
│     └─► datastore.DataStore ──(直连,非 gRPC)──► 内置 SQL(mysql/pg/sqlite) │
│  联邦面:endpoints/bundle(独立 HTTPS,GET / → trust bundle JSON)           │
│  bundle/client(拉联邦 bundle)   bundle/pubmanager ─► BundlePublisher* 插件 │
└─────────────────────────────────────────────────────────────────────────────┘
   注:带 * 者为可替换插件挂载点;内置插件走进程内内存管道 gRPC,外部插件走 go-plugin 子进程。

6.2 端到端数据流(工作负载申请 SVID)

阶段一/二/三的代码级细节见 SVID 颁发流程


7. 架构要点总结

  1. 两个薄二进制 + 一个厚编排:mainEntryPointclirun 都极薄,真正装配在 server.run(server.go:90)与 agent.Run(agent.go:63)里靠顺序化依赖注入 + TaskRunner 并发完成,无 DI 框架。
  2. 一切皆插件,且内置/外部同构:通用 catalog 让内置插件也走(内存管道)gRPC,外部插件走 go-plugin 子进程,上层对二者无感知;插件契约外置到 spire-plugin-sdk
  3. DataStore 是刻意的例外:为绕过 gRPC 消息大小限制而直连内置 SQL,v1.15.2 已彻底关闭其可插拔性。
  4. 签发链职责清晰:ServerCA 只做编排,模板构造(credtemplate)与产物校验(credvalidator)拆成独立模块,签名密钥来自 KeyManager 插件,信任根可由 UpstreamAuthority 插件链到企业 PKI。
  5. 授权是方法级、数据驱动的:传输层(TCP mTLS / 本地 UDS)只是身份来源,真正权限判定由 OPA 策略按 RPC 方法给出 agent/admin/local/downstream/any 五档。
  6. 性能靠多级缓存:server 侧 authorizedentries.Cache + cache/{entrycache,dscache,nodecache} 把授权计算与节点校验挡在 DB 之前;agent 侧 manager 的 LRU 缓存把工作负载 SVID 挡在 server 之前。

内容基于 SPIFFE/SPIRE 官方开源资料整理与翻译,仅供学习交流;商标与版权归各自所有者。SPIFFE 与 SPIRE 是 CNCF 项目。