再谈 SPIFFE - 最底下的乌龟
Posted on Thu 05 June 2025 in Journal
Abstract | Journal on 2025-06-05 |
---|---|
Authors | Walter Fan |
Category | learning note |
Status | v1.0 |
Updated | 2025-06-05 |
License | CC-BY-NC-ND 4.0 |
- SPIFFE:最底下的乌龟
- 一、什么是 SPIFFE?
- 二、SPIFFE 提供了什么?
- 三、Workload Identity 的问题
- 四、示例:通过 SPIFFE 实现 Workload 认证
- 五、SPIFFE 的优势
- 六、“最底下的乌龟”意味着什么?
- 七、结语
- 八、在 Kubernetes 中安装 SPIRE:从 0 到跑起来
- 九、进阶:SPIFFE 认证 Demo(使用 mTLS)
- 十、为非容器化环境使用 SPIRE
- 1. 整体结构回顾(非 K8s 环境)
- 2. 安装 SPIRE 的步骤(非 K8s 环境)
- Step 1:下载 SPIRE 二进制
- Step 2:配置 SPIRE Server(在服务器节点)
- Step 3:启动 SPIRE Server
- Step 4:配置 SPIRE Agent(在 client 节点)
- Step 5:创建 join token(在 server 上)
- Step 6:注册一个 workload identity
- Step 7:workload 获取身份(调用 SPIFFE Workload API)
- 示例:使用 SPIFFE 身份建立 mTLS 通信(Go)
- 总结:裸机环境使用 SPIRE 的建议
当然可以,以下是一篇以《SPIFFE:最底下的乌龟》为题的博客文章草稿,涵盖了 SPIFFE 的核心理念、实现方式,并通过实际示例展示其在 workload 身份识别和认证中的应用。
SPIFFE:最底下的乌龟
——云原生身份的基石
“这个世界是建立在什么之上的?” “在一只乌龟上。” “那只乌龟站在什么上?” “再下一只乌龟。” “那再下一只呢?” “一直是乌龟,一直往下。”
在软件系统的安全架构中,我们总是在问:“你的信任从哪儿来?” 当我们深入探索云原生安全模型,最终会发现——身份是最底下的乌龟。而在云原生时代,SPIFFE(Secure Production Identity Framework For Everyone),正是那只承载现代 workload 身份的“最底层的乌龟”。
一、什么是 SPIFFE?
SPIFFE 是一个开源标准,用于为工作负载(workloads)定义身份,并提供一种机制来分发、验证和使用这些身份。它脱胎于 Google 的 Borg 系统中用于服务认证的内部机制(如 Loas),并在 CNCF 旗下孵化,旨在为零信任架构提供标准化支持。
SPIFFE 的核心输出是一种类似于 X.509 证书的身份文凭:SVID(SPIFFE Verifiable Identity Document)。每个 SVID 都代表一个 workload 的身份。
一个 SPIFFE ID 通常长这样:
spiffe://example.org/ns/default/sa/frontend
它表示一个 workload 属于某个信任域(trust domain),并且具有命名空间和服务账户等属性。
二、SPIFFE 提供了什么?
- 统一的身份格式(SPIFFE ID)
- 基于证书的强身份认证(X.509 SVID 或 JWT SVID)
- 动态身份分发机制(通过 SPIRE 或其他实现)
- 无需人为干预或共享密钥的工作负载认证能力
- 与 mTLS、Envoy、Istio、Kubernetes 等系统的深度集成
SPIFFE 不是 CA,也不是服务网格,而是一种身份抽象和分发标准。最常见的实现是 SPIRE,它负责在不同的基础设施中为 workload 分配和管理 SVID。
三、Workload Identity 的问题
在没有 SPIFFE 之前,我们常见的 workload 认证方式包括:
- API Token(很容易被泄露或复制)
- 静态配置的用户名/密码
- 在配置文件中写死 TLS 证书(过期、管理困难)
- IP 白名单(云环境下完全不可靠)
这些方式都存在根本性缺陷,不符合现代零信任模型:身份不唯一、不安全、不可验证、难以管理。
而 SPIFFE 则带来了一种自动化、可验证、可撤销的方式来表达和使用工作负载身份。
四、示例:通过 SPIFFE 实现 Workload 认证
示例场景 1:Service A 和 Service B 之间 mTLS 通信
1.1 架构
Service A SPIRE Agent SPIRE Server SPIRE Agent Service B
| | | | |
| --> Get SVID -------->| | | |
| |-----------------------> | | |
| | <----- X.509 SVID ----- | | |
| | | <----------------------- | <--- Get SVID ------|
| |
| -------------------------- mTLS Handshake (X.509 SPIFFE ID) ----------------------------------> |
1.2 配置步骤(基于 Kubernetes)
- 为 Service A 和 B 分别配置 SPIRE Registration Entry:
# 为 Service A 分配 SPIFFE ID
spire-server entry create \
-spiffeID spiffe://example.org/ns/default/sa/service-a \
-selector k8s:sa:service-a \
-parentID spiffe://example.org/spire/agent/k8s_psat/default/node \
-ttl 3600
# 为 Service B 分配 SPIFFE ID
spire-server entry create \
-spiffeID spiffe://example.org/ns/default/sa/service-b \
-selector k8s:sa:service-b \
-parentID spiffe://example.org/spire/agent/k8s_psat/default/node \
-ttl 3600
-
使用 Envoy sidecar 或 SPIFFE API 获取证书并配置 TLS 通信。
-
在 TLS 握手过程中,双方通过 SPIFFE ID 相互验证身份,并建立加密通道。
示例场景 2:使用 SPIFFE + Envoy 做授权策略控制
SPIFFE 与 Envoy 结合时可以在 mTLS 完成之后使用 SPIFFE ID 进行 RBAC 授权:
# Envoy RBAC 策略配置示例
rules:
principals:
- authenticated:
principal_name:
exact: spiffe://example.org/ns/default/sa/frontend
permissions:
- header:
name: ":path"
exact_match: "/api/v1/resource"
该策略表示只有具备 spiffe://example.org/ns/default/sa/frontend
身份的请求才能访问 /api/v1/resource
。
五、SPIFFE 的优势
特性 | SPIFFE 支持 |
---|---|
自动身份分发 | SPIRE Agent 动态分发 SVID |
跨平台支持 | K8s、VM、bare-metal |
安全密钥生命周期管理 | 自动轮换证书,短生命周期 |
强身份绑定 | 与 workload 实例、节点绑定 |
与服务网格集成 | Istio、Linkerd、Consul 支持 |
与身份联邦系统兼容 | 支持 OIDC、JWT 等机制 |
六、“最底下的乌龟”意味着什么?
在零信任模型中,我们不能默认网络是安全的,也不能信任一个 IP、VPC 或者防火墙。我们必须从身份开始建立信任。而 SPIFFE 为每一个 workload 提供了统一、安全、可验证的身份表达方式。
“一切安全的开始,始于你是谁。” “而 SPIFFE,正是那个回答。”
七、结语
如果你正在构建:
- 多租户系统
- 服务网格或微服务架构
- 多云混合部署
- 以零信任为基础的系统安全模型
那么,请从底层开始思考信任模型,把 SPIFFE 作为最底下的乌龟,为你的系统打好安全的地基。
如需继续深入,可探索以下资源:
当然可以。以下是对上文的补充内容,详细说明如何在 Kubernetes 集群中安装和配置 SPIRE(SPIFFE 的官方实现),并给出一个真实的 workload 身份验证的示例。
八、在 Kubernetes 中安装 SPIRE:从 0 到跑起来
Step 1: 安装前准备
确保你已经有一个可用的 Kubernetes 集群(例如通过 minikube、Kind、EKS 等部署),并安装了以下工具:
kubectl
helm
git
- (推荐)一个 ingress 或 port-forward 方式可以访问 SPIRE 服务
Step 2: 获取 SPIRE Helm Chart
git clone https://github.com/spiffe/helm-charts
cd helm-charts/charts/spire
Step 3: 安装 SPIRE(Server + Agent)
可以使用 Helm 快速部署:
helm install spire -n spire --create-namespace . \
--set spire-server.enabled=true \
--set spire-agent.enabled=true \
--set spire-server.dataStorage.storageClassName=standard \
--set spire-server.global.trustDomain=example.org \
--set spire-agent.global.trustDomain=example.org
说明:
trustDomain
是你的身份命名空间,比如example.org
- SPIRE 默认会使用 Kubernetes API 插件进行身份选择(使用 service account)
等待 Pod 启动成功:
kubectl get pods -n spire
你应该会看到:
spire-agent-xxxxx Running
spire-server-xxxxx Running
Step 4: 查看 SPIRE 状态
端口转发 SPIRE Server:
kubectl port-forward -n spire svc/spire-server 8081:8081
使用 SPIRE CLI 工具连接服务器(或用 curl 验证 grpc 接口是否连通)。
Step 5: 注册 Workload 身份 Entry
以 namespace default
下的 service account demo-sa
为例:
kubectl create serviceaccount demo-sa -n default
注册一个 SPIFFE ID 为 spiffe://example.org/ns/default/sa/demo-sa
的 Entry:
kubectl exec -n spire deploy/spire-server -- \
/opt/spire/bin/spire-server entry create \
-spiffeID spiffe://example.org/ns/default/sa/demo-sa \
-selector k8s:sa:demo-sa \
-parentID spiffe://example.org/spire/agent/k8s_psat/default/node \
-ttl 3600
Step 6: 创建一个示例 Workload 使用 SPIFFE 身份
新建一个使用 SPIRE Agent 的 Pod,挂载 agent 的 Unix socket,从中获取身份:
apiVersion: v1
kind: Pod
metadata:
name: demo-workload
namespace: default
spec:
serviceAccountName: demo-sa
containers:
- name: demo
image: busybox
command: ["sleep", "3600"]
volumeMounts:
- name: spire-agent-socket
mountPath: /run/spire/sockets
volumes:
- name: spire-agent-socket
hostPath:
path: /run/spire/sockets
type: DirectoryOrCreate
部署:
kubectl apply -f demo-workload.yaml
Step 7: 使用 SPIFFE API 获取 SVID
进入容器后(或写程序)使用 SPIFFE Workload API 连接 /run/spire/sockets/agent.sock
,获取身份证书:
kubectl exec -it demo-workload -- sh
在容器内你可以使用 SPIRE 的 spire-agent api fetch x509
(需要额外工具),也可以自己写 gRPC 客户端。
示例(通过 SPIRE Agent 获取 X.509 SVID):
spire-agent api fetch x509 -socketPath /run/spire/sockets/agent.sock
输出类似:
SPIFFE ID : spiffe://example.org/ns/default/sa/demo-sa
Expires At : 2025-06-15 18:00 UTC
Certificate Chain : -----BEGIN CERTIFICATE-----
你就得到了这只 workload 的“身份证书”。
九、进阶:SPIFFE 认证 Demo(使用 mTLS)
以下是一个使用 SPIFFE X.509 身份进行双向 TLS 验证的 Python 示例(需安装 py-spiffe
库)。
服务端(server.py):
from pyspiffe.workloadapi.default_workload_api_client import DefaultWorkloadApiClient
from pyspiffe.bundle.x509_bundle.x509_bundle_set import X509BundleSet
import ssl, socket
client = DefaultWorkloadApiClient()
svid = client.fetch_x509_svid()
bundle = client.fetch_x509_bundle()
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.verify_mode = ssl.CERT_REQUIRED
context.load_cert_chain(certfile=svid.cert_chain_pem(), keyfile=svid.private_key_pem())
# Add trust bundle
context.load_verify_locations(cadata=bundle.x509_authorities_pem())
with socket.create_server(('0.0.0.0', 8443)) as s:
with context.wrap_socket(s, server_side=True) as ssock:
conn, addr = ssock.accept()
print(f"Got connection from {addr}")
客户端(client.py):
client = DefaultWorkloadApiClient()
svid = client.fetch_x509_svid()
bundle = client.fetch_x509_bundle()
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
context.load_cert_chain(certfile=svid.cert_chain_pem(), keyfile=svid.private_key_pem())
context.load_verify_locations(cadata=bundle.x509_authorities_pem())
with socket.create_connection(('spire-server', 8443)) as sock:
with context.wrap_socket(sock, server_hostname='spire-server') as ssock:
print("Connected with SPIFFE mTLS!")
十、为非容器化环境使用 SPIRE
如果不使用 Kubernetes,你仍然可以在 裸机(bare-metal)或虚拟机(VM)环境中部署和使用 SPIRE。SPIRE 最初就是为非容器化环境设计的,支持各种主机平台,包括:
- 本地开发机(Linux / macOS)
- 裸机服务器
- 云 VM(如 EC2、GCE、Azure VM)
1. 整体结构回顾(非 K8s 环境)
在非 K8s 环境中,SPIRE 的部署模型仍然是:
[Workload App]
│
[Workload API - SPIRE Agent] <-- 每台机器运行
│
[Node attestation + SVID 申请]
│
[SPIRE Server] <-- 集中部署
2. 安装 SPIRE 的步骤(非 K8s 环境)
我们以下以 两台 Linux 机器为例说明:
spire-server
(如 192.168.1.10)spire-agent
(如 192.168.1.11)
Step 1:下载 SPIRE 二进制
到官方 release 页面下载 https://github.com/spiffe/spire/releases
示例(在 Ubuntu 上):
curl -LO https://github.com/spiffe/spire/releases/download/v1.8.6/spire-1.8.6-linux-x86_64-glibc.tar.gz
tar -xzvf spire-1.8.6-linux-x86_64-glibc.tar.gz
sudo mv spire-1.8.6 /opt/spire
sudo ln -s /opt/spire/bin/* /usr/local/bin/
Step 2:配置 SPIRE Server(在服务器节点)
创建配置文件 /opt/spire/conf/server/server.conf
:
server {
bind_address = "0.0.0.0"
bind_port = "8081"
trust_domain = "example.org"
data_dir = "/opt/spire/data"
log_level = "INFO"
}
plugins {
NodeAttestor "join_token" {
plugin_data = {}
}
KeyManager "memory" {
plugin_data = {}
}
NodeResolver "noop" {
plugin_data = {}
}
UpstreamAuthority "disk" {
plugin_data = {
cert_file_path = "/opt/spire/conf/server/demo.ca.crt"
key_file_path = "/opt/spire/conf/server/demo.ca.key"
}
}
DataStore "sqlite" {
plugin_data = {
file_path = "/opt/spire/data/datastore.sqlite3"
}
}
}
⚠️ UpstreamAuthority "disk"
模拟 CA,实际生产中建议用 Vault、AWS PCA 等更强壮的根 CA。
Step 3:启动 SPIRE Server
spire-server run
你可以使用 systemd 或 screen 让它后台运行。
Step 4:配置 SPIRE Agent(在 client 节点)
创建配置 /opt/spire/conf/agent/agent.conf
:
agent {
data_dir = "/opt/spire/data"
log_level = "INFO"
server_address = "192.168.1.10"
server_port = "8081"
socket_path = "/tmp/spire-agent.sock"
trust_bundle_path = "/opt/spire/conf/agent/bundle.crt"
trust_domain = "example.org"
}
plugins {
NodeAttestor "join_token" {
plugin_data = {}
}
WorkloadAttestor "unix" {
plugin_data = {}
}
KeyManager "memory" {
plugin_data = {}
}
}
Step 5:创建 join token(在 server 上)
spire-server token generate -spiffeID spiffe://example.org/myagent -ttl 600
输出:
Generated token: abcdef1234567890
在 agent 机器上启动:
spire-agent run -joinToken abcdef1234567890
Step 6:注册一个 workload identity
注册一个 entry,比如你要对 /usr/local/bin/myapp
赋予 SPIFFE ID:
spire-server entry create \
-spiffeID spiffe://example.org/workload/myapp \
-selector unix:uid:1000 \
-parentID spiffe://example.org/myagent \
-ttl 3600
说明:
- selector
unix:uid:1000
表示用户 UID 为 1000 的进程将获得该身份 - parentID 是 agent 的 SPIFFE ID
Step 7:workload 获取身份(调用 SPIFFE Workload API)
你可以使用 SPIRE 自带工具测试:
spire-agent api fetch x509 -socketPath /tmp/spire-agent.sock
输出:
SPIFFE ID : spiffe://example.org/workload/myapp
Expires At : 2025-06-15 23:59 UTC
或使用语言 SDK:
示例:使用 SPIFFE 身份建立 mTLS 通信(Go)
// 使用 go-spiffe 加载证书
source, _ := workloadapi.NewX509Source(context.Background(), workloadapi.WithClientOptions(workloadapi.WithAddr("/tmp/spire-agent.sock")))
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{source.GetX509SVID().SVID()},
ClientAuth: tls.RequireAndVerifyClientCert,
RootCAs: source.GetX509Bundle().TrustBundle(),
InsecureSkipVerify: false,
}
可以作为 server 或 client 使用 TLS 进行通信,并校验对方的 SPIFFE ID。
总结:裸机环境使用 SPIRE 的建议
场景 | 建议 |
---|---|
开发测试 | join_token + UID selector |
多 VM 部署 | 使用平台 attestor(如 AWS、GCP、SSHPOP) |
workload 更细粒度 | 使用 unix:path 或 cmdline selector |
更安全根 CA | 使用 Vault 或 SPIRE upstream cert 配置 |
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。