Skip to content

三节点部署与复现

实战板块 那套环境从零搭起来:一个 spire-server + 一个 VM agent(join_token / unix)+ 一个 K3S agent(k8s_psat / k8s)。改改 IP 和域名就能在你自己的环境复现。

前置:能到目标网络、SPIRE 二进制 = 官方 spire-1.15.1-linux-amd64-musl.tar.gz(解包到 /opt/spire,软链到 /usr/local/bin)。下文 IP/域名是作者 homelab 的值,请替换成你自己的。

1. spire-server

server.conf

自签 CA(无 UpstreamAuthority)、SQLite、同时挂 join_tokenk8s_psat 两种节点认证。

hcl
server {
    bind_address  = "0.0.0.0"
    bind_port     = "8081"
    trust_domain  = "spire.qstarstar.com"
    data_dir      = "/opt/spire/data/server"
    log_level     = "INFO"

    # —— CA 与默认 TTL(封顶所有 SVID)——
    ca_ttl                = "24h"    # CA/签名密钥寿命,封顶所有下游 SVID
    default_x509_svid_ttl = "1h"     # X509-SVID 默认 TTL(entry 可覆盖)
    default_jwt_svid_ttl  = "5m"     # JWT-SVID 默认 TTL

    ca_subject {                     # 会出现在每张 SVID 的 issuer 里
        country      = ["CN"]
        organization = ["qstarstar-homelab"]
        common_name  = "spire.qstarstar.com"
    }
}

plugins {
    DataStore "sql" {
        plugin_data {
            database_type     = "sqlite3"
            connection_string = "/opt/spire/data/server/datastore.sqlite3"
        }
    }
    KeyManager "disk" {              # CA 私钥落盘;生产可换 KMS/HSM
        plugin_data { keys_path = "/opt/spire/data/server/keys.json" }
    }
    NodeAttestor "join_token" { plugin_data {} }   # 给 VM agent 用
    NodeAttestor "k8s_psat" {                      # 给 K3S agent 用
        plugin_data {
            clusters = {
                "rancher-k3s" = {
                    service_account_allow_list = ["spire:spire-agent"]
                    kube_config_file = "/opt/spire/k3s.kubeconfig"   # 用于 TokenReview + pod→node
                }
            }
        }
    }
}

启动后导出信任根 bundle(agent 启动要用):

bash
systemctl start spire-server
spire-server bundle show -format pem > trust-bundle.pem   # 分发给两个 agent

建注册条目(entries.sh)

-parentID 必须填 agent 认证后才知道的 SPIFFE ID(spire-server agent list 查)。

bash
TD="spire.qstarstar.com"; SOCK=/tmp/spire-server/private/api.sock
S="spire-server entry create -socketPath $SOCK"

# VM 身份:由 unix:uid:1001 决定;带一个 DNS SAN(演示 CN=DNS[0])
$S -parentID "<VM-AGENT-ID>" \
   -spiffeID "spiffe://$TD/vm/web" \
   -selector "unix:uid:1001" \
   -dns "web.vm.qstarstar.lab" \
   -x509SVIDTTL 3600

# K3S 身份:由 k8s:ns:demo + k8s:sa:svc 共同决定(AND)
$S -parentID "<K3S-AGENT-ID>" \
   -spiffeID "spiffe://$TD/ns/demo/sa/svc" \
   -selector "k8s:ns:demo" \
   -selector "k8s:sa:svc" \
   -x509SVIDTTL 3600

2. VM agent(join_token / unix)

agent.conf

hcl
agent {
    data_dir       = "/opt/spire/data/agent"
    server_address = "192.168.71.159"
    server_port    = "8081"
    socket_path    = "/run/spire/agent.sock"        # Workload API 的 Unix socket
    trust_domain   = "spire.qstarstar.com"
    trust_bundle_path = "/opt/spire/bootstrap-bundle.pem"   # 上一步导出的 trust-bundle.pem
}
plugins {
    NodeAttestor "join_token" { plugin_data {} }
    KeyManager  "disk" { plugin_data { directory = "/opt/spire/data/agent" } }
    WorkloadAttestor "unix" {
        plugin_data { discover_workload_path = true }   # 额外产出 unix:path / unix:sha256(二进制指纹)
    }
}

上线

bash
# server 上生成一次性 join token
spire-server token generate -spiffeID spiffe://spire.qstarstar.com/host
# agent 上用 token 启动(join_token 用后即删)
spire-agent run -config /opt/spire/agent.conf -joinToken <token>

3. K3S agent(k8s_psat / k8s)

以 DaemonSet 每节点一个 agent 运行,hostPID + 共享 socket 目录让同节点 Pod 能取 SVID。核心 manifest(命名空间 / RBAC / ConfigMap / DaemonSet):

yaml
apiVersion: apps/v1
kind: DaemonSet
metadata: { name: spire-agent, namespace: spire }
spec:
  selector: { matchLabels: { app: spire-agent } }
  template:
    metadata: { labels: { app: spire-agent } }
    spec:
      hostPID: true
      hostNetwork: true
      serviceAccountName: spire-agent
      containers:
        - name: spire-agent
          image: ghcr.io/spiffe/spire-agent:1.15.1
          args: ["-config", "/run/spire/config/agent.conf"]
          volumeMounts:
            - { name: spire-config, mountPath: /run/spire/config, readOnly: true }
            - { name: spire-bundle, mountPath: /run/spire/bundle, readOnly: true }
            - { name: spire-token,  mountPath: /var/run/secrets/tokens }
            - { name: spire-agent-socket-dir, mountPath: /run/spire/sockets }
      volumes:
        - { name: spire-config, configMap: { name: spire-agent } }
        - { name: spire-bundle, configMap: { name: spire-bundle } }   # 由 server bundle 生成
        - name: spire-agent-socket-dir
          hostPath: { path: /run/spire/sockets, type: DirectoryOrCreate }
        - name: spire-token
          projected:
            sources:
              - serviceAccountToken:
                  path: spire-agent
                  expirationSeconds: 7200
                  audience: spire-server        # 与 server k8s_psat 期望的受众一致

ConfigMap 里的 agent.conf 关键差异(相对 VM):节点认证换成 k8s_psat,工作负载认证换成 k8s,KeyManager 用 memory

demo 工作负载用同一个 alpine 镜像跑两个 Deployment,只有 ServiceAccount 不同,构成对照组:

yaml
# demo:  serviceAccountName: svc      -> 匹配 k8s:sa:svc      -> 拿到 /ns/demo/sa/svc
# demo-other: serviceAccountName: default -> 缺 sa:svc -> 拿不到(对照组)

4. 取证与对照(demos/)

bash
# 解读任意一张 X509-SVID 的"形式"字段
inspect-svid () {
  openssl x509 -in "$1" -noout -ext subjectAltName | grep -o 'URI:[^,]*'   # 唯一身份
  openssl x509 -in "$1" -noout -ext basicConstraints,keyUsage,extendedKeyUsage
  openssl x509 -in "$1" -noout -issuer -dates
}

# VM:换 uid 看"实际决定因素"
sudo -u '#1001' spire-agent api fetch x509 -socketPath /run/spire/agent.sock   # 拿到 /vm/web
sudo -u '#1002' spire-agent api fetch x509 -socketPath /run/spire/agent.sock   # 无身份(exit 1)

# K3S:换 ServiceAccount 看结果
kubectl -n demo exec deploy/demo       -- spire-agent api fetch x509 ...   # 拿到 /ns/demo/sa/svc
kubectl -n demo exec deploy/demo-other -- spire-agent api fetch x509 ...   # 无身份

5. 清理

bash
# 删 K3S 的 spire/demo 两个命名空间 + ESXi 上的两台 VM
bash teardown.sh

附:真实抓取的样本

这套环境真实签出的样本(证书为公开材料,已解码展示;原始 JWT 令牌属 bearer 凭据,未公开发布):

VM 工作负载 X509-SVID(spiffe://spire.qstarstar.com/vm/web):

Issuer : C=CN, O=qstarstar-homelab, CN=spire.qstarstar.com   ← 自签 CA
Subject: C=US, O=SPIRE, CN=web.vm.qstarstar.lab              ← CN 来自 entry 的 -dns
Validity: ~1h(被 ca_ttl 24h 封顶)
X509v3 Key Usage (critical): Digital Signature, Key Encipherment, Key Agreement
X509v3 Extended Key Usage : TLS Web Server Auth, TLS Web Client Auth
X509v3 Basic Constraints (critical): CA:FALSE
X509v3 Subject Alternative Name:
    DNS:web.vm.qstarstar.lab,  URI:spiffe://spire.qstarstar.com/vm/web   ← 身份 = 这个 URI SAN

JWT-SVID(同一身份,fetch jwt 取,仅展示解码后的 header/payload):

json
// header
{"alg":"ES256","kid":"iR9QHBkBgs5MSeOo5Q5UXA4rPmgjQlad","typ":"JWT"}
// payload
{"aud":["nacos"], "sub":"spiffe://spire.qstarstar.com/vm/web", "iat":..., "exp":...}

对照 Issuer 可见:两个平台的工作负载 SVID 都由同一个自签 CA 签发,因此能被同一个 trust bundle 验证。完整的字段/配置解读见 什么是 SVID三层配置实验

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