Server API
概述
SPIRE Server 提供 gRPC API 用于管理身份和配置。API 分为以下服务:
Agent API: Agent 证明和管理
Entry API: 注册条目管理
Bundle API: 信任包管理
SVID API: SVID 颁发
TrustDomain API: 联邦管理
认证和授权
认证方式
Unix Socket: 本地管理
mTLS: Agent 和远程管理
Admin Token: 可选的管理令牌
授权
SPIRE 使用基于策略的授权(OPA Rego):
package spire
default allow = false
# 允许管理员执行所有操作
allow {
input.caller.admin == true
}
# 允许 Agent 更新自己的条目
allow {
input.method == "entry.Entry/BatchUpdateEntry"
input.caller.downstream == true
}
Entry API
创建条目
import (
"context"
entryv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/entry/v1"
"github.com/spiffe/spire-api-sdk/proto/spire/api/types"
)
func createEntry(client entryv1.EntryClient) error {
_, err := client.BatchCreateEntry(context.Background(), &entryv1.BatchCreateEntryRequest{
Entries: []*types.Entry{
{
SpiffeId: &types.SPIFFEID{
TrustDomain: "example.org",
Path: "/myworkload",
},
ParentId: &types.SPIFFEID{
TrustDomain: "example.org",
Path: "/myagent",
},
Selectors: []*types.Selector{
{Type: "unix", Value: "uid:1000"},
},
X509SvidTtl: 3600,
},
},
})
return err
}
列出条目
func listEntries(client entryv1.EntryClient) ([]*types.Entry, error) {
resp, err := client.ListEntries(context.Background(), &entryv1.ListEntriesRequest{
Filter: &entryv1.ListEntriesRequest_Filter{
ByParentId: &types.SPIFFEID{
TrustDomain: "example.org",
Path: "/myagent",
},
},
})
if err != nil {
return nil, err
}
return resp.Entries, nil
}
更新条目
func updateEntry(client entryv1.EntryClient, entryID string) error {
_, err := client.BatchUpdateEntry(context.Background(), &entryv1.BatchUpdateEntryRequest{
Entries: []*types.Entry{
{
Id: entryID,
Selectors: []*types.Selector{
{Type: "unix", Value: "uid:2000"},
},
},
},
})
return err
}
删除条目
func deleteEntry(client entryv1.EntryClient, entryID string) error {
_, err := client.BatchDeleteEntry(context.Background(), &entryv1.BatchDeleteEntryRequest{
Ids: []string{entryID},
})
return err
}
Agent API
列出 Agent
import agentv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/agent/v1"
func listAgents(client agentv1.AgentClient) ([]*types.Agent, error) {
resp, err := client.ListAgents(context.Background(), &agentv1.ListAgentsRequest{})
if err != nil {
return nil, err
}
return resp.Agents, nil
}
创建 Join Token
func createJoinToken(client agentv1.AgentClient) (string, error) {
resp, err := client.CreateJoinToken(context.Background(), &agentv1.CreateJoinTokenRequest{
Ttl: 600, // 10 分钟
AgentId: &types.SPIFFEID{
TrustDomain: "example.org",
Path: "/myagent",
},
})
if err != nil {
return "", err
}
return resp.Value, nil
}
驱逐 Agent
func evictAgent(client agentv1.AgentClient, spiffeID *types.SPIFFEID) error {
_, err := client.DeleteAgent(context.Background(), &agentv1.DeleteAgentRequest{
Id: spiffeID,
})
return err
}
Bundle API
获取本地 Bundle
import bundlev1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/bundle/v1"
func getBundle(client bundlev1.BundleClient) (*types.Bundle, error) {
return client.GetBundle(context.Background(), &bundlev1.GetBundleRequest{})
}
设置联邦 Bundle
func setFederatedBundle(client bundlev1.BundleClient, bundle *types.Bundle) error {
_, err := client.BatchSetFederatedBundle(context.Background(), &bundlev1.BatchSetFederatedBundleRequest{
Bundle: []*types.Bundle{bundle},
})
return err
}
SVID API
颁发 X.509-SVID
import svidv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/svid/v1"
func mintX509SVID(client svidv1.SVIDClient) (*types.X509SVID, error) {
resp, err := client.MintX509SVID(context.Background(), &svidv1.MintX509SVIDRequest{
Csr: csrBytes, // PKCS#10 CSR
Ttl: 3600,
})
if err != nil {
return nil, err
}
return resp.Svid, nil
}
颁发 JWT-SVID
func mintJWTSVID(client svidv1.SVIDClient) (string, error) {
resp, err := client.MintJWTSVID(context.Background(), &svidv1.MintJWTSVIDRequest{
Id: &types.SPIFFEID{
TrustDomain: "example.org",
Path: "/myworkload",
},
Audience: []string{"https://api.example.org"},
Ttl: 300,
})
if err != nil {
return "", err
}
return resp.Svid.Token, nil
}
连接示例
通过 Unix Socket
import (
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func connectViaSocket(socketPath string) (*grpc.ClientConn, error) {
return grpc.Dial(
socketPath,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
return net.Dial("unix", addr)
}),
)
}
通过 mTLS
import (
"crypto/tls"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
func connectViaMTLS(address string, tlsConfig *tls.Config) (*grpc.ClientConn, error) {
return grpc.Dial(
address,
grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)),
)
}
完整客户端示例
package main
import (
"context"
"fmt"
"log"
"net"
entryv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/entry/v1"
"github.com/spiffe/spire-api-sdk/proto/spire/api/types"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
// 连接 Server
conn, err := grpc.Dial(
"/tmp/spire-server/private/api.sock",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
return net.Dial("unix", addr)
}),
)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// 创建客户端
client := entryv1.NewEntryClient(conn)
// 列出条目
resp, err := client.ListEntries(context.Background(), &entryv1.ListEntriesRequest{})
if err != nil {
log.Fatal(err)
}
for _, entry := range resp.Entries {
fmt.Printf("Entry: %s -> %s\n", entry.ParentId, entry.SpiffeId)
}
}
错误处理
API 使用 gRPC 状态码:
状态码 |
描述 |
|---|---|
|
成功 |
|
无效参数 |
|
资源不存在 |
|
资源已存在 |
|
权限不足 |
|
内部错误 |
import "google.golang.org/grpc/status"
resp, err := client.GetEntry(ctx, req)
if err != nil {
st, ok := status.FromError(err)
if ok {
switch st.Code() {
case codes.NotFound:
log.Println("条目不存在")
case codes.PermissionDenied:
log.Println("权限不足")
default:
log.Printf("错误: %v", st.Message())
}
}
}
参考资源
spire-api-sdk: 官方 Go SDK
Proto 定义: API 协议定义