# Workload API ## 概述 Workload API 是 SPIFFE 规范定义的标准 API,工作负载通过它获取身份凭证。SPIRE Agent 实现了这个 API。 ## API 端点 Agent 通过 Unix Domain Socket 暴露 Workload API: - **默认路径**: `/tmp/spire-agent/public/api.sock` - **协议**: gRPC ## gRPC 服务定义 ```protobuf service SpiffeWorkloadAPI { // X.509 相关 rpc FetchX509SVID(X509SVIDRequest) returns (stream X509SVIDResponse); rpc FetchX509Bundles(X509BundlesRequest) returns (stream X509BundlesResponse); // JWT 相关 rpc FetchJWTSVID(JWTSVIDRequest) returns (JWTSVIDResponse); rpc FetchJWTBundles(JWTBundlesRequest) returns (stream JWTBundlesResponse); rpc ValidateJWTSVID(ValidateJWTSVIDRequest) returns (ValidateJWTSVIDResponse); } ``` ## X.509 操作 ### FetchX509SVID 获取 X.509-SVID,这是一个流式 RPC,会持续推送更新。 **请求**: ```protobuf message X509SVIDRequest {} ``` **响应**: ```protobuf message X509SVIDResponse { repeated X509SVID svids = 1; repeated X509Bundle federated_bundles = 2; } message X509SVID { string spiffe_id = 1; bytes x509_svid = 2; // DER 编码的证书链 bytes x509_svid_key = 3; // PKCS#8 编码的私钥 bytes bundle = 4; // DER 编码的信任包 } ``` **Go 示例**: ```go package main import ( "context" "log" "github.com/spiffe/go-spiffe/v2/workloadapi" ) func main() { ctx := context.Background() // 创建 X.509 源 source, err := workloadapi.NewX509Source(ctx, workloadapi.WithClientOptions( workloadapi.WithAddr("unix:///tmp/spire-agent/public/api.sock"), ), ) if err != nil { log.Fatal(err) } defer source.Close() // 获取 SVID svid, err := source.GetX509SVID() if err != nil { log.Fatal(err) } log.Printf("SPIFFE ID: %s", svid.ID) log.Printf("证书数量: %d", len(svid.Certificates)) log.Printf("过期时间: %v", svid.Certificates[0].NotAfter) } ``` ### FetchX509Bundles 仅获取信任包,不包含 SVID。 **Go 示例**: ```go func fetchBundles() { ctx := context.Background() source, err := workloadapi.NewBundleSource(ctx) if err != nil { log.Fatal(err) } defer source.Close() bundle, err := source.GetBundleForTrustDomain(spiffeid.RequireTrustDomainFromString("example.org")) if err != nil { log.Fatal(err) } log.Printf("信任包包含 %d 个 CA 证书", len(bundle.X509Authorities())) } ``` ## JWT 操作 ### FetchJWTSVID 获取 JWT-SVID。 **请求**: ```protobuf message JWTSVIDRequest { repeated string audience = 1; // 必填:目标受众 string spiffe_id = 2; // 可选:请求特定 SPIFFE ID } ``` **响应**: ```protobuf message JWTSVIDResponse { repeated JWTSVID svids = 1; } message JWTSVID { string spiffe_id = 1; string svid = 2; // JWT token } ``` **Go 示例**: ```go func fetchJWTSVID() { ctx := context.Background() client, err := workloadapi.New(ctx, workloadapi.WithAddr("unix:///tmp/spire-agent/public/api.sock"), ) if err != nil { log.Fatal(err) } defer client.Close() svid, err := client.FetchJWTSVID(ctx, jwtsvid.Params{ Audience: "https://api.example.org", }) if err != nil { log.Fatal(err) } log.Printf("JWT Token: %s", svid.Marshal()) log.Printf("SPIFFE ID: %s", svid.ID) log.Printf("过期时间: %v", svid.Expiry) } ``` ### FetchJWTBundles 获取 JWT 验证密钥。 ### ValidateJWTSVID 验证 JWT-SVID。 **请求**: ```protobuf message ValidateJWTSVIDRequest { string audience = 1; // 期望的受众 string svid = 2; // JWT token } ``` **响应**: ```protobuf message ValidateJWTSVIDResponse { string spiffe_id = 1; // 验证成功返回 SPIFFE ID google.protobuf.Struct claims = 2; // JWT 声明 } ``` **Go 示例**: ```go func validateJWT(token string) { ctx := context.Background() source, err := workloadapi.NewJWTSource(ctx) if err != nil { log.Fatal(err) } defer source.Close() svid, err := jwtsvid.ParseAndValidate(token, source, []string{"https://api.example.org"}) if err != nil { log.Printf("验证失败: %v", err) return } log.Printf("SPIFFE ID: %s", svid.ID) log.Printf("声明: %v", svid.Claims) } ``` ## 监听更新 ### X.509 更新监听 ```go func watchX509Updates() { ctx := context.Background() watcher := &x509Watcher{} err := workloadapi.WatchX509Context(ctx, watcher, workloadapi.WithClientOptions( workloadapi.WithAddr("unix:///tmp/spire-agent/public/api.sock"), ), ) if err != nil { log.Fatal(err) } } type x509Watcher struct{} func (w *x509Watcher) OnX509ContextUpdate(ctx *workloadapi.X509Context) { for _, svid := range ctx.SVIDs { log.Printf("收到 SVID 更新: %s (过期: %v)", svid.ID, svid.Certificates[0].NotAfter) } } func (w *x509Watcher) OnX509ContextWatchError(err error) { log.Printf("监听错误: %v", err) } ``` ### JWT Bundle 更新监听 ```go func watchJWTBundles() { ctx := context.Background() watcher := &jwtWatcher{} err := workloadapi.WatchJWTBundles(ctx, watcher) if err != nil { log.Fatal(err) } } type jwtWatcher struct{} func (w *jwtWatcher) OnJWTBundlesUpdate(bundles *jwtbundle.Set) { log.Printf("收到 JWT Bundle 更新") } func (w *jwtWatcher) OnJWTBundlesWatchError(err error) { log.Printf("监听错误: %v", err) } ``` ## TLS 配置 ### mTLS 服务端 ```go func startMTLSServer() { ctx := context.Background() source, err := workloadapi.NewX509Source(ctx) if err != nil { log.Fatal(err) } defer source.Close() // 创建 TLS 配置 tlsConfig := tlsconfig.MTLSServerConfig( source, source, tlsconfig.AuthorizeAny(), // 或使用更严格的授权 ) listener, err := tls.Listen("tcp", ":8443", tlsConfig) if err != nil { log.Fatal(err) } // 处理连接... } ``` ### mTLS 客户端 ```go func createMTLSClient() *http.Client { ctx := context.Background() source, err := workloadapi.NewX509Source(ctx) if err != nil { log.Fatal(err) } // 只允许特定 SPIFFE ID authorized := tlsconfig.AuthorizeID( spiffeid.RequireFromString("spiffe://example.org/server"), ) tlsConfig := tlsconfig.MTLSClientConfig(source, source, authorized) return &http.Client{ Transport: &http.Transport{ TLSClientConfig: tlsConfig, }, } } ``` ## 授权策略 ### 允许任意 SPIFFE ID ```go tlsconfig.AuthorizeAny() ``` ### 允许特定 SPIFFE ID ```go tlsconfig.AuthorizeID(spiffeid.RequireFromString("spiffe://example.org/server")) ``` ### 允许同一信任域 ```go tlsconfig.AuthorizeMemberOf(spiffeid.RequireTrustDomainFromString("example.org")) ``` ### 自定义授权 ```go tlsconfig.Authorize(func(id spiffeid.ID) error { if strings.HasPrefix(id.Path(), "/web/") { return nil } return errors.New("unauthorized") }) ``` ## 错误处理 ```go svid, err := source.GetX509SVID() if err != nil { switch { case errors.Is(err, context.Canceled): log.Println("操作被取消") case errors.Is(err, context.DeadlineExceeded): log.Println("操作超时") default: log.Printf("获取 SVID 失败: %v", err) } return } ``` ## 最佳实践 :::{admonition} 建议 :class: tip 1. **重用 X509Source**: 创建一次,多次使用 2. **监听更新**: 使用 Watch 而非轮询 3. **优雅关闭**: 确保调用 `Close()` 4. **处理错误**: 实现重试逻辑 5. **验证对端**: 不要使用 `AuthorizeAny()` 在生产环境 :::