再谈 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 的核心理念、实现方式,并通过实际示例展示其在 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 提供了什么?

  1. 统一的身份格式(SPIFFE ID)
  2. 基于证书的强身份认证(X.509 SVID 或 JWT SVID)
  3. 动态身份分发机制(通过 SPIRE 或其他实现)
  4. 无需人为干预或共享密钥的工作负载认证能力
  5. 与 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)

  1. 为 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
  1. 使用 Envoy sidecar 或 SPIFFE API 获取证书并配置 TLS 通信。

  2. 在 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:pathcmdline selector
更安全根 CA 使用 Vault 或 SPIRE upstream cert 配置

本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。