Skip to content
Published on

SPIFFE/SPIREワークロードアイデンティティ — シークレットなしのサービス間認証

Authors

はじめに — シークレットを配布しない認証は可能か

サービス間認証の伝統的な解法はシークレットの配布でした。APIキー、共有パスワード、静的な証明書を作って各サービスに配る方式です。その結果が今日の**シークレットスプロール(secret sprawl)**です。環境変数、CI変数、コードリポジトリ、チャットログに散らばった資格情報は漏えい事故の常連原因であり、ローテーションは誰もが先送りする宿題になりました。

2026年の流れは明確です。マシン、サービス、パイプライン、そしてAIエージェントまで — non-human identityが人間のアカウント数を数十倍以上上回る環境では、シークレットを「配る」モデルはもはや運用不可能です。代替案は、ワークロードが自身の属性(どこで実行されているか、誰が起動したか)によって身元を証明され、短命の暗号学的な身元文書を自動発行されるモデルです。このモデルの標準がSPIFFE(Secure Production Identity Framework For Everyone)であり、リファレンス実装がSPIREです。

本記事ではSPIFFE IDとSVIDの概念からSPIREアーキテクチャ、Kubernetesデプロイの実践、Envoy SDS統合による自動mTLS、federation、Vault/cert-manager比較、そしてtransaction tokensとAIエージェントidentityへの拡張までを扱います。

シークレットスプロール問題とnon-human identityトレンド

まず、従来のシークレットベース認証の構造的な問題を整理します。

問題内容
ブートストラップの逆説シークレットを安全に届けるには、また別のシークレット(アクセス資格)が必要
ローテーション負担寿命が長いほど漏えい被害は大きいのに、ローテーションは手動で壊れやすい
所有者不明このAPIキーを誰がなぜ作ったのか、6か月後には誰も知らない
複製の容易さシークレットはコピーされた瞬間に追跡不能、どこで使われても区別不可
監査の困難「鍵を持つ誰か」しか分からず、ワークロード単位の識別が不可能

ここにnon-human identityの爆発的増加が重なります。マイクロサービス、バッチジョブ、CIランナー、サーバーレス関数に続きAIエージェントが加わり、アイデンティティ管理の重心は人間からワークロードへ移りました。業界の調査はnon-human identityが人間identityの数十倍に達すると報告しており、その資格情報管理は侵害事故の主要原因として指摘されています。

SPIFFEの答えは発想の転換です。**シークレットを配布するのではなく、アイデンティティを発行せよ。**ワークロードは実行環境の属性で自身を証明し(attestation)、プラットフォームは短命の身元文書(SVID)を自動的に発行/更新します。人間が触るシークレットが消えます。

SPIFFEの中核概念 — SPIFFE IDとSVID

SPIFFE ID

SPIFFE IDはワークロードを識別するURIです。形式はspiffeスキーム + 信頼ドメイン + パスで構成されます。

spiffe://prod.example.com/ns/orders/sa/orders-sa
└─┬──┘ └──────┬───────┘ └─────────┬──────────┘
 スキーム    信頼ドメイン        ワークロードパス
        (trust domain)    (例: ネームスペース/サービスアカウント)
  • **信頼ドメイン(trust domain)**はアイデンティティ発行の権威の単位です。通常は組織や環境(本番/ステージング)単位で分けます。
  • パスは組織が自由に設計します。Kubernetesならネームスペースとサービスアカウントをエンコードするパターンが一般的です。

SVID — 身元文書

SVID(SPIFFE Verifiable Identity Document)はSPIFFE IDを含む検証可能な文書で、2つの形式があります。

項目X.509-SVIDJWT-SVID
形式X.509証明書(SAN URIにSPIFFE ID)JWT(subクレームにSPIFFE ID)
用途mTLS接続の双方向認証L7プロキシ経由などTLSが終端する区間の認証
寿命数分〜数時間(デフォルト1時間程度)数分(audience指定が必須)
リプレイリスク低い(鍵所有の証明)あり(窃取されると寿命内は再利用可能)
推奨基本の選択肢mTLSを維持できない区間の補助手段

X.509-SVIDが基本で、JWT-SVIDはmTLSを最後まで維持できない区間(L7ロードバランサー経由など)の補助手段です。どちらの形式も核心は短い寿命 + 自動更新であり、漏えいしても被害時間が分単位に制限されます。

信頼バンドル

検証者は信頼ドメインごとのCA公開鍵の束であるtrust bundleでSVIDを検証します。バンドルの配布と更新もSPIREが自動化します。

SPIREアーキテクチャ — server、agent、attestation

SPIREはSPIFFEのリファレンス実装で、serverとagentの2層構造です。

+--------------------------------------------------------------+
|                        SPIRE Server                           |
|  - 登録エントリ(registration entries)を保存                   |
|  - CAとしてSVIDに署名(またはupstream CAへ委譲)                |
|  - node attestationを検証                                     |
+------------------------------+-------------------------------+
                               | (1) node attestation
                               |     「このノード/エージェントは本物か」
+------------------------------+-------------------------------+
|                  SPIRE Agent (ノードごとのDaemonSet)          |
|  - Workload API(unixソケット)を公開                           |
|  - workload attestationを実施                                 |
|  - SVIDのキャッシュ/更新                                      |
+------------------------------+-------------------------------+
                               | (2) workload attestation
                               |     「このプロセスはどのワークロードか」
            +------------------+------------------+
            |                  |                  |
       +----+----+        +----+----+        +----+----+
       | Pod A   |        | Pod B   |        | Pod C   |
       | (orders)|        | (pay)   |        | (envoy) |
       +---------+        +---------+        +---------+

動作の流れは次のとおりです。

  1. Node attestation — agentがserverに「自分は正当なノードで実行されている」ことを証明します。Kubernetesでは、agentのサービスアカウントトークンをserverがTokenReview APIで検証するk8s_psat方式が標準です。AWS/GCP/Azureではインスタンス身元文書ベースのattestorを使います。
  2. Workload attestation — ワークロードがagentのWorkload APIソケットに接続すると、agentは呼び出しプロセスのカーネルレベル情報(Kubernetesでは該当Podのネームスペース、サービスアカウント、ラベルなど)を収集します。
  3. 登録エントリのマッチング — 収集されたセレクターがserverに登録されたエントリと一致すれば、該当SPIFFE IDのSVIDを発行します。
  4. 自動更新 — agentはSVIDの期限前に自動で再発行を受け、ワークロードへプッシュします。

核心は、ワークロードがいかなるシークレットも事前に持つ必要がないことです。アイデンティティは「何を知っているか(シークレット)」ではなく「どこでどのように実行されているか(属性)」から導出されます。

Kubernetesデプロイ実践

本番環境では公式のSPIRE Helmチャートを推奨しますが、構造理解のため中核マニフェストを直接見ていきます。まずserverの設定です。

apiVersion: v1
kind: ConfigMap
metadata:
  name: spire-server
  namespace: spire
data:
  server.conf: |
    server {
      bind_address = "0.0.0.0"
      bind_port = "8081"
      trust_domain = "prod.example.com"
      data_dir = "/run/spire/data"
      log_level = "INFO"
      ca_ttl = "24h"
      default_x509_svid_ttl = "1h"
    }
    plugins {
      DataStore "sql" {
        plugin_data {
          database_type = "sqlite3"
          connection_string = "/run/spire/data/datastore.sqlite3"
        }
      }
      NodeAttestor "k8s_psat" {
        plugin_data {
          clusters = {
            "prod-cluster" = {
              service_account_allow_list = ["spire:spire-agent"]
            }
          }
        }
      }
      KeyManager "disk" {
        plugin_data {
          keys_path = "/run/spire/data/keys.json"
        }
      }
      Notifier "k8sbundle" {
        plugin_data {
          namespace = "spire"
        }
      }
    }
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: spire-server
  namespace: spire
spec:
  serviceName: spire-server
  replicas: 1
  selector:
    matchLabels:
      app: spire-server
  template:
    metadata:
      labels:
        app: spire-server
    spec:
      serviceAccountName: spire-server
      containers:
        - name: spire-server
          image: ghcr.io/spiffe/spire-server:1.12.0
          args: ['-config', '/run/spire/config/server.conf']
          ports:
            - containerPort: 8081
          volumeMounts:
            - name: spire-config
              mountPath: /run/spire/config
              readOnly: true
            - name: spire-data
              mountPath: /run/spire/data
      volumes:
        - name: spire-config
          configMap:
            name: spire-server
  volumeClaimTemplates:
    - metadata:
        name: spire-data
      spec:
        accessModes: ['ReadWriteOnce']
        resources:
          requests:
            storage: 1Gi

agentはDaemonSetとしてすべてのノードにデプロイします。

apiVersion: v1
kind: ConfigMap
metadata:
  name: spire-agent
  namespace: spire
data:
  agent.conf: |
    agent {
      data_dir = "/run/spire"
      log_level = "INFO"
      server_address = "spire-server.spire.svc.cluster.local"
      server_port = "8081"
      socket_path = "/run/spire/sockets/agent.sock"
      trust_domain = "prod.example.com"
      trust_bundle_path = "/run/spire/bundle/bundle.crt"
    }
    plugins {
      NodeAttestor "k8s_psat" {
        plugin_data {
          cluster = "prod-cluster"
        }
      }
      KeyManager "memory" {
        plugin_data {}
      }
      WorkloadAttestor "k8s" {
        plugin_data {
          disable_container_selectors = false
        }
      }
    }
---
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
      serviceAccountName: spire-agent
      containers:
        - name: spire-agent
          image: ghcr.io/spiffe/spire-agent:1.12.0
          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-agent-socket
              mountPath: /run/spire/sockets
      volumes:
        - name: spire-config
          configMap:
            name: spire-agent
        - name: spire-bundle
          configMap:
            name: spire-bundle
        - name: spire-agent-socket
          hostPath:
            path: /run/spire/sockets
            type: DirectoryOrCreate

最後にワークロードを登録します。ordersネームスペースのorders-saサービスアカウントで実行されるPodにSPIFFE IDを付与するエントリです。

kubectl exec -n spire spire-server-0 -- \
  /opt/spire/bin/spire-server entry create \
  -spiffeID spiffe://prod.example.com/ns/orders/sa/orders-sa \
  -parentID spiffe://prod.example.com/spire/agent/k8s_psat/prod-cluster/NODE_UUID \
  -selector k8s:ns:orders \
  -selector k8s:sa:orders-sa

手動登録は規模が大きくなると維持できません。実務ではSPIRE Controller Managerを併せてデプロイし、CRD(ClusterSPIFFEID)で登録を宣言的に管理します。

apiVersion: spire.spiffe.io/v1alpha1
kind: ClusterSPIFFEID
metadata:
  name: default-workload-id
spec:
  spiffeIDTemplate: 'spiffe://prod.example.com/ns/{{ .PodMeta.Namespace }}/sa/{{ .PodSpec.ServiceAccountName }}'
  podSelector:
    matchLabels:
      spiffe.io/spire-managed-identity: 'true'

これでラベルの付いたすべてのPodが、ネームスペース/サービスアカウントに基づくSPIFFE IDを自動的に受け取ります。

Envoy SDS統合 — 自動mTLS

アプリケーションコードを変えずにmTLSを適用する標準パターンは、Envoyサイドカー + SPIRE agentのSDS(Secret Discovery Service)統合です。SPIRE agentはSDSサーバーの役割を果たせるため、Envoyは証明書をファイルではなくAPIで受け取ります。更新も無停止で行われます。

# Envoyサイドカー設定(抜粋) — ordersサービス
static_resources:
  clusters:
    # SPIRE agentのWorkload APIをSDSクラスターとして登録
    - name: spire_agent
      connect_timeout: 1s
      http2_protocol_options: {}
      load_assignment:
        cluster_name: spire_agent
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    pipe:
                      path: /run/spire/sockets/agent.sock
    # アップストリーム: paymentsサービスへのmTLS接続
    - name: payments_upstream
      connect_timeout: 2s
      type: STRICT_DNS
      load_assignment:
        cluster_name: payments_upstream
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: payments.payments.svc.cluster.local
                      port_value: 8443
      transport_socket:
        name: envoy.transport_sockets.tls
        typed_config:
          '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
          common_tls_context:
            # 自分の身元(X.509-SVID)をSDSで受け取る
            tls_certificate_sds_secret_configs:
              - name: spiffe://prod.example.com/ns/orders/sa/orders-sa
                sds_config:
                  api_config_source:
                    api_type: GRPC
                    transport_api_version: V3
                    grpc_services:
                      - envoy_grpc:
                          cluster_name: spire_agent
            # 相手の検証: 信頼バンドル + 期待するSPIFFE ID
            combined_validation_context:
              default_validation_context:
                match_typed_subject_alt_names:
                  - san_type: URI
                    matcher:
                      exact: spiffe://prod.example.com/ns/payments/sa/payments-sa
              validation_context_sds_secret_config:
                name: spiffe://prod.example.com
                sds_config:
                  api_config_source:
                    api_type: GRPC
                    transport_api_version: V3
                    grpc_services:
                      - envoy_grpc:
                          cluster_name: spire_agent

この設定一式で次が自動化されます。

  • ordersのサイドカーは自身のX.509-SVIDをSPIREから受け取り、TLSクライアント証明書として使用します。
  • payments側の証明書が信頼バンドルで検証され、SAN URIが期待するSPIFFE IDと完全一致するかを確認します。同じCAだから通すのではなく、ワークロード単位で認可するのがポイントです。
  • 証明書の更新(1時間の寿命)はSDSプッシュにより無停止で処理されます。人間が触る証明書ファイルは存在しません。

サイドカーなしでアプリケーションから直接使うには、go-spiffeのようなSDKでWorkload APIを呼び出します。

// go-spiffe v2でmTLSクライアントを作る(抜粋)
source, err := workloadapi.NewX509Source(ctx)
if err != nil {
    log.Fatal(err)
}
defer source.Close()

serverID := spiffeid.RequireFromString("spiffe://prod.example.com/ns/payments/sa/payments-sa")
tlsConfig := tlsconfig.MTLSClientConfig(source, source, tlsconfig.AuthorizeID(serverID))

client := &http.Client{
    Transport: &http.Transport{TLSClientConfig: tlsConfig},
}
resp, err := client.Get("https://payments.payments.svc.cluster.local:8443/healthz")

IstioとSPIFFEの互換性

IstioのmTLSアイデンティティ体系は最初からSPIFFE形式です。サイドカー(またはambientのztunnel)が受け取る証明書のSAN URIは次の形式です。

spiffe://cluster.local/ns/orders/sa/orders-sa

したがって「Istioを使えばSPIREは不要か」という問いには次のように答えられます。

  • メッシュ内部だけで足りるなら — Istio内蔵CA(istiod)で十分です。別途SPIREなしでSPIFFE形式のワークロードアイデンティティと自動mTLSが得られます。
  • メッシュ外(VM、他クラスター、メッシュ未適用ワークロード、CIランナー)まで単一のアイデンティティ体系が必要なら — SPIREをアイデンティティの単一ソースに据える構成が有効です。IstioはSPIREを外部CAとして統合でき(SDS経由)、その場合メッシュ内外のワークロードが同じ信頼ドメインのSVIDで相互認証します。
  • 注意点: Istioのデフォルト信頼ドメインはcluster.localなので、組織レベルの信頼ドメイン戦略(例: prod.example.com)と揃えるには、meshConfigのtrustDomain設定とSPIREのtrust_domainを整合させる必要があります。

Federation — 信頼ドメイン間の認証

異なる信頼ドメイン(別クラスター、別組織、別クラウド)のワークロードが相互認証するには、trust bundleを交換する必要があります。SPIREのfederation機能がこれを自動化します。

# SPIRE Server設定にfederationを追加(server.conf抜粋)
# federates_withで相手ドメインのバンドルエンドポイントを指定
federation {
  bundle_endpoint {
    address = "0.0.0.0"
    port = 8443
  }
  federates_with "partner.example.org" {
    bundle_endpoint_url = "https://spire.partner.example.org:8443"
    bundle_endpoint_profile "https_spiffe" {
      endpoint_spiffe_id = "spiffe://partner.example.org/spire/server"
    }
  }
}

登録エントリにもfederation対象を明示します。

spire-server entry create \
  -spiffeID spiffe://prod.example.com/ns/orders/sa/orders-sa \
  -selector k8s:ns:orders -selector k8s:sa:orders-sa \
  -federatesWith spiffe://partner.example.org

これでordersワークロードは自身のSVIDとともに相手ドメインのtrust bundleを受け取り、partnerドメインのワークロードとmTLSを確立できます。バンドルの更新は両側のserverが定期的に同期します。マルチクラスター、ハイブリッドクラウド、組織間B2B連携で「共有CAなし」の相互認証を構成する標準的な方法です。

Vault / cert-managerとの比較

「証明書を自動発行する」という点で似て見えるツールとの役割の違いを整理します。

項目SPIREHashiCorp Vaultcert-manager
本質ワークロードアイデンティティ発行基盤シークレット管理 + PKI発行エンジンKubernetes証明書ライフサイクル管理
身元の証明attestation(無資格ブートストラップ)認証方法が必要(k8s authなど)直接の証明なし(リソース権限ベース)
発行対象ワークロード単位のSVID(X.509/JWT)汎用シークレット、PKI証明書主にTLSサーバー証明書(Ingressなど)
寿命の哲学分〜時間単位、完全自動更新設定次第(短くも長くも)通常数十日、自動更新
標準SPIFFE(CNCF graduated)独自APIACMEなどの証明書標準
相互補完SVIDをVaultの認証手段に使えるVault PKIをSPIREのupstream CAにできるメッシュ外の証明書と併用

要点は競争ではなく組み合わせです。よく使われる構成は次のとおりです。

  • SPIREがアイデンティティを、Vaultがシークレットを — ワークロードがSVIDでVaultにログイン(JWT/cert auth)し、DBパスワードのような残存シークレットを受け取ります。「シークレットにアクセスするためのシークレット」が消え、ブートストラップの逆説が解けます。
  • Vault PKIをSPIREのUpstreamAuthorityに — 組織のPKI体系の中にSPIRE CAを従属させ、ガバナンスを維持します。
  • cert-managerはエッジTLSを — 外部公開ドメインのサーバー証明書(Let’s Encryptなど)はcert-managerが、内部ワークロード間のmTLSはSPIREが担当する分業が自然です。

ワークロードidentityとユーザーidentityの接続

実際のリクエストには2つのアイデンティティが共存します。「ordersサービス(ワークロード)がuser-1234(ユーザー)の代わりにpaymentsを呼び出す」をどう表現するのでしょうか。

  • トランスポート層 — mTLS(X.509-SVID)で呼び出し元ワークロードを認証します。
  • リクエスト層 — ユーザーコンテキストはJWTで運びます。前回扱ったtoken exchange(RFC 8693)が委譲チェーンを記録します。
  • transaction tokens — OAuth WGで議論中のTransaction Tokensドラフトはこのパターンを標準化します。外部トークンを信頼境界への進入時点で短命(分単位)の内部専用トークンに交換し、そこにユーザー身元 + リクエストコンテキスト + 呼び出しチェーンを載せて内部呼び出し全体へ伝播します。このときtransaction tokenサービスに交換を要求する主体の認証が、まさにワークロードidentity(SVID)です。
[ユーザーJWT] --> Gateway --(exchange)--> [Txn-Token: ユーザー + コンテキスト + チェーン]
                              |
                              v
        orders (SVIDでmTLS) --> payments (SVIDでmTLS)
        リクエストごと: Txn-Token検証 + 呼び出し元SPIFFE ID確認

ワークロードの身元(SPIFFE)とユーザーの身元(OIDC)が別々の体系ではなく、1つのリクエストの中で結合されること — これが2026年のZero Trustアーキテクチャの完成形です。

AIエージェントidentityへの拡張

non-human identityの最新の変曲点はAIエージェントです。エージェントは従来のサービスより動的に生成/消滅し、ユーザーの代わりに行動し、ツール呼び出しで権限を行使します。SPIFFEの観点からの適用方向は次のとおりです。

  • **エージェントのランタイムもワークロードです。**エージェントを実行するプロセス/PodにSVIDを付与すれば、「どのエージェントランタイムがこのツールを呼んだのか」が暗号学的に識別できます。静的APIキーをエージェントに埋め込む慣行を置き換えます。
  • 委譲チェーンの結合 — エージェントがユーザーの代わりに行動するとき、ワークロード身元(SVID) + ユーザー委譲(token exchangeのactクレームまたはtransaction token)を併せて検証します。「このエージェントが、このユーザーの代わりに、この範囲内で」がすべてトークンと証明書で証明されます。
  • MCPエコシステムとの接点 — MCP(Model Context Protocol)サーバーがOAuth保護リソースとして標準化されるにつれ(Keycloak 26.6のCIMD実験的サポートなど)、エージェントのツールアクセスにも標準トークンフローが適用されつつあります。ツールサーバー間のmTLSにはSPIFFEが自然な相棒です。
  • 短命の価値の最大化 — エージェントは行動範囲が広く、資格情報漏えいの波及が大きくなります。分単位のSVIDとaudienceの狭いJWT-SVIDの組み合わせは、エージェント環境で特に効果的です。

導入時の運用課題

SPIRE導入で実際に直面する課題と対応を整理します。

  1. SPIRE自体の可用性 — SVIDの寿命が1時間なら、SPIREが1時間以上停止すると更新が止まります。serverを冗長化(共有データストア + 複数レプリカ)し、agentキャッシュで短期障害を吸収し、SVID寿命と障害許容時間を一体で設計する必要があります。
  2. 登録エントリのガバナンス — セレクターを緩く設定すると(例: ネームスペースのみ)、意図より広いワークロードが同じアイデンティティを受け取ります。ClusterSPIFFEIDテンプレートをコードレビューの対象として管理し、アイデンティティ付与の基準を明文化します。
  3. 段階的な適用経路 — 全サービスの同時移行は不可能です。permissive(平文+mTLS併用)段階を経てSTRICTへ絞る経路、そしてmTLS適用率を測定するダッシュボードが必要です。
  4. 非SPIFFEシステムとの境界 — レガシーDBや外部SaaSは依然としてパスワード/APIキーを要求します。これらの残存シークレットはVaultに集約し、VaultへのアクセスをSVIDで認証することで、「シークレットの根」を1つに減らします。
  5. 可観測性 — SVID発行/更新の失敗、attestationの失敗、バンドル同期の遅延をメトリクスで監視しアラートを設定します。証明書期限切れによる連鎖障害は静かに始まるため、期限切れ間近のSVID比率がよい先行指標です。
  6. 時刻同期と鍵の保護 — 短命証明書はクロックスキューに敏感です。NTP監視は必須であり、serverの署名鍵はKMS/HSMバックエンド(KeyManagerプラグイン)で保護することが推奨されます。

おわりに

SPIFFE/SPIREが提示する転換を一文に要約するとこうなります。**シークレットを動かすのではなく、アイデンティティを発行せよ。**attestationでブートストラップの逆説を解き、短命のSVIDでローテーションを無意味にし、SPIFFE IDという標準的な命名でワークロード単位の認可とfederationを可能にします。Envoy SDSやIstio統合を使えば、アプリケーションコードの修正なしに自動mTLSへ到達できます。

そしてこの基盤はワークロードを越えて拡張されています。ユーザーの身元(OIDC)、委譲(token exchange、transaction tokens)、AIエージェントidentityがSPIFFEワークロード身元と1つのリクエストの中で結合されるのが、2026年のZero Trustスタックの標準形です。シークレットスプロールに疲れた組織なら、アイデンティティベース認証への転換をこれ以上先送りする理由はありません。

参考資料