配置第一个工作负载

本教程将指导您如何在应用程序中集成 SPIRE 获取身份凭证。

概述

工作负载通过 Workload API 从 SPIRE Agent 获取 SVID。有多种集成方式:

  1. go-spiffe 库: Go 应用程序

  2. java-spiffe 库: Java 应用程序

  3. spiffe-helper: 任意应用程序

  4. Envoy SDS: 服务网格

Go 应用程序

安装依赖

go get github.com/spiffe/go-spiffe/v2

获取 X.509-SVID

package main

import (
    "context"
    "log"
    "time"

    "github.com/spiffe/go-spiffe/v2/workloadapi"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    // 创建 X.509 源
    source, err := workloadapi.NewX509Source(ctx)
    if err != nil {
        log.Fatalf("无法创建 X509Source: %v", err)
    }
    defer source.Close()

    // 获取 SVID
    svid, err := source.GetX509SVID()
    if err != nil {
        log.Fatalf("无法获取 SVID: %v", err)
    }

    log.Printf("SPIFFE ID: %s", svid.ID)
    log.Printf("证书过期时间: %v", svid.Certificates[0].NotAfter)
}

mTLS 服务端

package main

import (
    "context"
    "log"
    "net/http"

    "github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig"
    "github.com/spiffe/go-spiffe/v2/workloadapi"
)

func main() {
    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())

    server := &http.Server{
        Addr:      ":8443",
        TLSConfig: tlsConfig,
    }

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, SPIFFE!"))
    })

    log.Println("服务器启动在 :8443")
    log.Fatal(server.ListenAndServeTLS("", ""))
}

mTLS 客户端

package main

import (
    "context"
    "io"
    "log"
    "net/http"

    "github.com/spiffe/go-spiffe/v2/spiffeid"
    "github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig"
    "github.com/spiffe/go-spiffe/v2/workloadapi"
)

func main() {
    ctx := context.Background()

    source, err := workloadapi.NewX509Source(ctx)
    if err != nil {
        log.Fatal(err)
    }
    defer source.Close()

    // 只允许特定 SPIFFE ID
    serverID := spiffeid.RequireFromString("spiffe://example.org/server")
    tlsConfig := tlsconfig.MTLSClientConfig(source, source, tlsconfig.AuthorizeID(serverID))

    client := &http.Client{
        Transport: &http.Transport{
            TLSClientConfig: tlsConfig,
        },
    }

    resp, err := client.Get("https://localhost:8443/")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    log.Printf("响应: %s", body)
}

Java 应用程序

Maven 依赖

<dependency>
    <groupId>io.spiffe</groupId>
    <artifactId>java-spiffe</artifactId>
    <version>0.8.0</version>
</dependency>

获取 SVID

import io.spiffe.workloadapi.WorkloadApiClient;
import io.spiffe.workloadapi.X509Context;

public class Main {
    public static void main(String[] args) throws Exception {
        try (WorkloadApiClient client = WorkloadApiClient.newClient()) {
            X509Context context = client.fetchX509Context();
            
            System.out.println("SPIFFE ID: " + context.getDefaultSvid().getSpiffeId());
            System.out.println("证书: " + context.getDefaultSvid().getChain());
        }
    }
}

spiffe-helper

对于非 Go/Java 应用程序,可以使用 spiffe-helper 将 SVID 写入文件。

安装

go install github.com/spiffe/spiffe-helper/cmd/spiffe-helper@latest

配置

创建 helper.conf

agent_address = "/tmp/spire-agent/public/api.sock"
cmd = "/path/to/your/app"
cmd_args = "--cert=/certs/svid.pem"
cert_dir = "/certs"
renew_signal = "SIGHUP"
svid_file_name = "svid.pem"
svid_key_file_name = "svid-key.pem"
svid_bundle_file_name = "bundle.pem"

运行

spiffe-helper -config helper.conf

Envoy SDS

SPIRE Agent 提供 Envoy SDS API,可直接集成 Envoy。

Envoy 配置

node:
  id: "envoy-proxy"
  cluster: "cluster1"

static_resources:
  listeners:
    - name: listener_0
      address:
        socket_address:
          address: 0.0.0.0
          port_value: 8443
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                stat_prefix: ingress_http
                route_config:
                  name: local_route
                  virtual_hosts:
                    - name: backend
                      domains: ["*"]
                      routes:
                        - match: { prefix: "/" }
                          route: { cluster: backend }
                http_filters:
                  - name: envoy.filters.http.router
          transport_socket:
            name: envoy.transport_sockets.tls
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
              common_tls_context:
                tls_certificate_sds_secret_configs:
                  - name: "spiffe://example.org/envoy"
                    sds_config:
                      resource_api_version: V3
                      api_config_source:
                        api_type: GRPC
                        transport_api_version: V3
                        grpc_services:
                          - envoy_grpc:
                              cluster_name: spire_agent
                combined_validation_context:
                  default_validation_context:
                    match_subject_alt_names:
                      - prefix: "spiffe://example.org/"
                  validation_context_sds_secret_config:
                    name: "spiffe://example.org"
                    sds_config:
                      resource_api_version: V3
                      api_config_source:
                        api_type: GRPC
                        transport_api_version: V3
                        grpc_services:
                          - envoy_grpc:
                              cluster_name: spire_agent

  clusters:
    - name: spire_agent
      connect_timeout: 1s
      type: STATIC
      lb_policy: ROUND_ROBIN
      typed_extension_protocol_options:
        envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
          "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
          explicit_http_config:
            http2_protocol_options: {}
      load_assignment:
        cluster_name: spire_agent
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    pipe:
                      path: /tmp/spire-agent/public/api.sock

注册工作负载

无论使用哪种集成方式,都需要在 SPIRE Server 上创建注册条目:

# Unix 进程
spire-server entry create \
    -spiffeID spiffe://example.org/myapp \
    -parentID spiffe://example.org/agent \
    -selector unix:user:appuser

# Kubernetes Pod
spire-server entry create \
    -spiffeID spiffe://example.org/myapp \
    -parentID spiffe://example.org/agent \
    -selector k8s:ns:default \
    -selector k8s:sa:myapp-sa

# Docker 容器
spire-server entry create \
    -spiffeID spiffe://example.org/myapp \
    -parentID spiffe://example.org/agent \
    -selector docker:label:app:myapp

最佳实践

建议

  1. 使用库而非文件: 优先使用 go-spiffe/java-spiffe 库

  2. 实现优雅轮换: 监听 SVID 更新事件

  3. 验证对端身份: 不仅仅接受任何 SVID

  4. 处理错误: Agent 不可用时有降级策略