SPIFFE에서 WIF 사용하기
SPIFFE에서 WIF 사용하기
SPIFFE는 워크로드에 아이덴티티를 발행하는 CNCF 표준이에요. SPIRE는 그 오픈소스 레퍼런스 구현이며, 여러 상용 제품도 SPIFFE 호환 아이덴티티를 발행해요. Anthropic은 OIDC 호환 JWT-SVID를 발행하는 모든 SPIFFE 구현과 페더레이션해요. 구현 목록은 SPIFFE 프로젝트 사이트의 SPIFFE를 구현하는 상용 소프트웨어를 참고하세요.
페더레이션은 공개 HTTPS URL의 OIDC discovery 문서(discovery 모드, URL 제약 적용) 또는 JWKS를 직접 등록하는(inline 모드) 방식으로 작동해요.
JWT-SVID 사양은 sub를 워크로드의 SPIFFE ID로 정의하고, SPIFFE Workload API는 호출자가 가져올 때 aud를 제공하도록 요구하므로 이 클레임들은 구현 전반에 걸쳐 같아요. Anthropic은 추가로 iss와 iat를 요구하는데, 둘 다 JWT-SVID 사양이 강제하지 않으므로 구현을 둘 다 채우도록 구성하세요(SPIRE에서 iss는 jwt_issuer 서버 설정이고 iat는 자동으로 설정돼요). 이것들이 있으면 이 가이드의 Anthropic 구성, 토큰 획득과 사용, 규칙 범위 지정하기 섹션이 어떤 SPIFFE 구현에도 적용돼요.
SPIFFE는 모든 워크로드에 spiffe://<trust-domain>/<path> 형태의 안정된 아이덴티티 URI를 할당하고, SPIRE는 Workload API를 통해 그 아이덴티티를 요청 시 JWT-SVID로 발행해요. JWT-SVID는 sub 클레임이 워크로드의 SPIFFE ID이고 aud 클레임이 가져올 때 워크로드가 제공하는 일반 서명 JWT예요.
SPIRE 트러스트 도메인에서 표준 OIDC로 가는 다리는 SPIRE OIDC Discovery Provider로, 트러스트 도메인의 JWT 서명 키에 대한 /.well-known/openid-configuration과 JWKS 엔드포인트를 게시하는 독립형 헬퍼예요. Discovery 프로바이더가 실행 중이면 JWT-SVID는 다른 OIDC 토큰처럼 검증돼요. discovery URL을 페더레이션 발급자로 등록하고, 워크로드의 SPIFFE ID에 일치하는 페더레이션 규칙을 작성하고, 워크로드가 JWT-SVID를 Anthropic의 토큰 교환 엔드포인트에 제시하게 하세요.
이 페이지의 예시는 SPIRE를 사용하며 SPIRE Agent가 실행되는 모든 곳(Kubernetes 파드, 가상 머신, 베어메탈 호스트)에 적용돼요.
참고 Kubernetes 클러스터가 SPIRE를 실행하지 않고 클러스터의 네이티브 프로젝티드 서비스 계정 토큰으로 인증하고 싶다면 Kubernetes에서 WIF 사용하기를 참고하세요.
출처: 문서
본문
사전 요구사항
- WIF 개념에 익숙해야 해요. 서비스 계정, 페더레이션 발급자, 페더레이션 규칙.
- 워크로드 아이덴티티가 발행된 SPIFFE 배포(이 페이지의 예시는 SPIRE Server와 Agent 사용)와 Claude API를 호출해야 하는 워크로드에 대한 등록 항목.
- 트러스트 도메인의 OIDC discovery 엔드포인트(SPIRE에서 OIDC Discovery Provider)가 공개적으로 도달 가능한 HTTPS 엔드포인트로 실행되거나,
inline등록용으로 내보낸 JWKS. - JWT-SVID의
iss클레임을 페더레이션 발급자의issuer_url로 등록할 값으로 설정하도록 구성된 SPIFFE 발급자.discovery모드에서 이것은 discovery 엔드포인트의 공개 URL(SPIRE에서는jwt_issuer서버 설정)이에요. - 워크로드에 사용 가능한 JWT-SVID. WIF는 JWT-SVID만 받고 X.509-SVID는 받지 않아요.
- Anthropic 조직의 Claude Console에서 서비스 계정, 페더레이션 발급자, 페더레이션 규칙을 만들 권한.
JWT-SVID를 가져올 때 요청할 audience 값은 항상 https://api.anthropic.com이에요. 이 값은 spiffe-helper의 jwt_audience, Workload API FetchJWTSVID 호출, 페더레이션 규칙의 audience 매처에 사용하세요.
SPIRE 구성하기
이 섹션의 지침은 SPIRE 전용이에요. 다른 SPIFFE 발급자를 사용한다면 자체 문서에 따라 OIDC discovery 엔드포인트와 JWT-SVID 검색을 구성하고 Anthropic 구성에서 계속하세요.
OIDC Discovery Provider와 함께 SPIRE를 이미 실행 중이라면 Anthropic과 페더레이션하려면 SPIRE 쪽에서 세 가지가 필요해요. discovery URL과 일치하는 jwt_issuer, Claude API를 호출할 워크로드의 등록 항목, 그 워크로드가 Anthropic audience로 JWT-SVID를 가져올 방법. 다음 하위 섹션들이 각각을 안내해요. 구성 스니펫은 완전한 SPIRE 배포 설정이 아니라 Anthropic 페더레이션에 관련된 설정만 보여줘요.
팁 처음으로 SPIRE를 설정하나요? SPIRE 퀵스타트에 따라 SPIRE Server와 Agent를 배포한 뒤 SPIRE Server 곁에 OIDC Discovery Provider를 별도 서비스로 추가하세요. Discovery 모드 페더레이션은 프로바이더가 배포되고 공개적으로 도달 가능해야 해요. 프로바이더는 기본 SPIRE 설치의 일부가 아니에요.
JWT 발급자 검증
Anthropic은 JWT-SVID의 iss 클레임을 등록된 페더레이션 발급자와 일치시키고 그 발급자의 discovery 문서에서 JWKS를 가져와 JWT-SVID를 검증해요. 두 SPIRE 설정이 같은 URL에 동의해야 해요. SPIRE Server의 jwt_issuer(발행되는 모든 JWT-SVID의 iss 클레임이 됨)와 OIDC Discovery Provider의 domains 목록(discovery 문서와 JWKS가 제공되는 호스트를 결정). 그 공유 URL이 Anthropic에 등록하는 것이에요.
트러스트 도메인과 발급자 URL은 독립적이에요. 트러스트 도메인(spiffe://prod.example.com)은 sub 클레임을 범위 지어요. 발급자 URL(https://oidc-discovery.prod.example.com)은 Anthropic이 서명 키를 가져오는 곳이에요. 호스트 이름을 공유할 필요는 없어요.
jwt_issuer가 SPIRE Server의 구성에 설정되고 discovery 프로바이더의 공개 URL을 가리키는지 확인하세요. 다음 예시는 기본 JWT-SVID 수명도 보여줘요. SPIRE의 내장 기본값은 5분으로, 지속적인 회전이 필요할 정도로 짧아요(spiffe-helper 실행 참고). Anthropic의 토큰 교환 엔드포인트는 수명이 페더레이션 발급자의 구성된 최대값(기본 1시간)을 초과하는 어떤 아이덴티티 토큰도 거부해요(검증 규칙 참고). 이 검사는 SPIRE뿐 아니라 모든 SPIFFE 구현에 적용되므로 default_jwt_svid_ttl(또는 항목별 재정의)을 그 최대값 이하로 유지하세요.
server {
trust_domain = "prod.example.com"
jwt_issuer = "https://oidc-discovery.prod.example.com"
default_jwt_svid_ttl = "5m"
# ...
}
OIDC Discovery Provider의 구성에서 같은 호스트 이름이 domains 아래에 나타나야 하고, 프로바이더는 SPIRE Server의 API 소켓에 도달할 수 있어야 해요. 프로바이더는 HTTPS로 discovery 문서와 JWKS를 제공해요. 내장 ACME 지원으로 TLS를 종료하거나 그렇게 하는 로드 밸런서로 앞세우세요.
domains = ["oidc-discovery.prod.example.com"]
server_api {
address = "unix:///run/spire/sockets/private/api.sock"
}
acme {
email = "[email protected]"
tos_accepted = true
}
참고 예시는 discovery 프로바이더를 SPIRE Server의 특권 API 소켓에 연결하는
server_api를 사용해요. 프로바이더는workload_api블록(socket_path와trust_domain포함)도 받는데, 이것은 SPIRE Agent의 Workload API를 통해 번들을 얻어요. discovery 프로바이더가 Server API에 접근해서는 안 되거나 Server에 도달할 수 없는 노드에서 실행할 때 그것을 사용하세요.
워크로드 등록
Claude API를 호출하는 각 워크로드는 런타임 셀렉터를 SPIFFE ID에 매핑하는 SPIRE 등록 항목이 필요해요. 워크로드가 이미 등록되어 있다면 페더레이션 규칙의 subject_prefix에 사용할 SPIFFE ID를 기록하세요. 아니면 등록하세요. Kubernetes 파드의 경우 셀렉터는 보통 네임스페이스와 Kubernetes 서비스 계정이에요:
# Replace NODE_UID with the node's UID:
# kubectl get node <node-name> -o jsonpath='{.metadata.uid}'
spire-server entry create \
-spiffeID spiffe://prod.example.com/ns/inference/sa/worker \
-parentID spiffe://prod.example.com/spire/agent/k8s_psat/prod-cluster/NODE_UID \
-selector k8s:ns:inference \
-selector k8s:sa:worker
참고 표시된
parentID는 단일 노드의 자동 생성 에이전트 ID예요. 클러스터 전체 등록의 경우 SPIRE Kubernetes 퀵스타트가 하듯 노드 별칭에 항목을 부모로 배치해 모든 노드의 워크로드와 일치하게 하세요.
Kubernetes 밖의 워크로드는 unix:uid:1000 같은 호스트 수준 셀렉터를 사용해요(unix:path도 가능하지만 에이전트의 unix 워크로드 증명기 구성에서 discover_workload_path = true가 필요해요). spire-controller-manager를 실행하는 클러스터는 spire-server entry create를 직접 호출하는 대신 ClusterSPIFFEID 커스텀 리소스로 항목을 선언할 수 있어요.
spiffe-helper 실행
spiffe-helper는 SPIRE Agent 소켓에 연결하고, 주어진 audience에 대한 JWT-SVID를 가져와 파일에 쓰고, 만료 전에 다시 가져오는 사이드카 유틸리티예요. 헬퍼는 기본적으로 데몬 모드로 실행돼요. 다음 예시는 daemon_mode = true를 명시적으로 설정해요.
agent_address = "/run/spire/sockets/agent.sock"
# The JWT-SVID file is written under cert_dir
cert_dir = "/var/run/secrets/anthropic.com"
daemon_mode = true
jwt_svids = [{
jwt_audience = "https://api.anthropic.com"
jwt_svid_file_name = "token"
}]
Kubernetes에서 spiffe-helper를 응용 컨테이너와 메모리 백업 emptyDir 볼륨(medium: Memory)을 공유하는 사이드카 컨테이너로 실행해 베어러 SVID가 노드 디스크에 절대 떨어지지 않게 하세요. SPIRE Agent 소켓을 호스트에서 사이드카로 마운트하고, 공유 볼륨을 두 컨테이너 모두 /var/run/secrets/anthropic.com에 마운트하며, 응용 컨테이너에 ANTHROPIC_IDENTITY_TOKEN_FILE=/var/run/secrets/anthropic.com/token을 설정하세요. VM과 베어메탈에서는 spiffe-helper를 워크로드 곁의 시스템 서비스로 실행하고 둘 다 공유 디렉터리를 가리키게 하세요.
Anthropic 구성하기
Claude Console에서 Settings → Workload identity를 열고 Connect workload를 클릭한 뒤 Custom OIDC를 선택하세요. 마법사가 발급자 등록, 서비스 계정 만들기, 페더레이션 규칙 만들기를 안내해요.
마법사가 이 리소스들을 만들어 줘요. 마법사에 입력하든 Admin API로 보내든 다음 값을 사용하세요:
페더레이션 발급자: OIDC Discovery Provider의 공개 URL을 discovery 모드로 등록하세요. Anthropic은 이 URL에서 /.well-known/openid-configuration을 가져오고 반환된 jwks_uri를 따라 트러스트 도메인의 서명 키를 검색해요.
{
"name": "spire-prod",
"issuer_url": "https://oidc-discovery.prod.example.com",
"jwks": { "type": "discovery" }
}
Discovery 프로바이더가 공개 인터넷에서 도달할 수 없으면 직접 JWKS를 가져와(curl https://oidc-discovery.prod.example.com/keys) 반환된 keys 배열의 내용으로 "jwks": {"type": "inline", "keys": [...]} 발급자를 등록하세요. inline 모드에서 issuer_url은 JWT-SVID의 iss 클레임과만 비교되고 Anthropic은 절대 도달하려 하지 않아요.
경고 SPIRE는 JWT 서명 키를 자주 회전하며, 기본적으로 CA와 같은 주기(
ca_ttl, 24시간)로 회전해요. discovery URL 대신 inline JWKS로 발급자를 등록하면 SPIRE가 회전할 때마다 JWKS를 업데이트해야 해요. 워크로드가 새 키를 제시하기 전에 새 키를 추가하고, 그것으로 서명된 토큰이 만료된 후에는 교체된 키를 제거하세요. inline JWKS에 남은 오래된 키는 무기한 신뢰됩니다.
공개 discovery 엔드포인트를 노출하지 않고 JWKS 업데이트를 자동화하려면 format = "jwks"로 SPIRE Server BundlePublisher 플러그인(aws_s3, gcp_cloudstorage, k8s_configmap)을 구성해 매 회전마다 JWT 서명 키를 외부 저장소로 푸시한 뒤, Admin API로 발급자의 inline 키를 업데이트하세요.
페더레이션 규칙: JWT-SVID의 sub(SPIFFE ID)와 spiffe-helper가 요청하도록 구성한 aud에 일치시키세요. SPIFFE ID는 URI 문자열이고 subject_prefix는 그것을 불투명 텍스트로 일치시키므로 정확한 값이나 끝자리 * 접두사 일치가 둘 다 작동해요. 더 복잡한 패턴은 CEL condition을 사용하세요.
{
"name": "spire-inference-worker",
"issuer_id": "fdis_...",
"match": {
"subject_prefix": "spiffe://prod.example.com/ns/inference/sa/worker",
"audience": "https://api.anthropic.com"
},
"target": {
"type": "service_account",
"service_account_id": "svac_..."
},
"workspace_id": "wrkspc_...",
"oauth_scope": "workspace:developer",
"token_lifetime_seconds": 600
}
token_lifetime_seconds는 교환이 반환하는 Anthropic 접근 토큰의 수명이지 JWT-SVID의 수명이 아니에요. SDK는 접근 토큰을 자동으로 갱신해요.
워크로드가 허용하는 만큼 구체적으로 하세요. 그 경로 아래 등록된 모든 워크로드가 같은 Anthropic 서비스 계정에 매핑되어야만 subject_prefix를 spiffe://prod.example.com/ns/inference/*로 느슨하게 하세요. 규칙의 fdrl_... ID를 워크로드의 ANTHROPIC_FEDERATION_RULE_ID 환경 변수에 추가하세요.
토큰 획득과 사용
Anthropic SDK는 spiffe-helper가 유지하는 파일에서 JWT-SVID를 읽거나 토큰 공급자 callable을 통해 SPIFFE Workload API를 직접 호출할 수 있어요. 파일 경로는 가장 간단한 통합이고 모든 SDK 언어에서 작동해요. callable 경로는 사이드카를 제거하지만 응용 언어로 SPIFFE Workload API 클라이언트가 필요해요.
spiffe-helper로 파일 기반
spiffe-helper가 새 JWT-SVID를 /var/run/secrets/anthropic.com/token에 쓴다면, ANTHROPIC_IDENTITY_TOKEN_FILE을 그 경로로 설정하고 ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID와 함께 설정하세요. SDK는 매 토큰 교환마다 파일을 읽으므로 가장 최근에 회전된 SVID를 항상 집어내고, 만료 전에 Anthropic 접근 토큰을 자동으로 갱신해요. 각 값이 어디서 오는지는 환경 변수를 참고하세요.
JWT=$(cat "$ANTHROPIC_IDENTITY_TOKEN_FILE")
ACCESS_TOKEN=$(curl -sS https://api.anthropic.com/v1/oauth/token \
-H "content-type: application/json" \
--data @- <<JSON | jq -r .access_token
{
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": "$JWT",
"federation_rule_id": "$ANTHROPIC_FEDERATION_RULE_ID",
"organization_id": "$ANTHROPIC_ORGANIZATION_ID",
"service_account_id": "$ANTHROPIC_SERVICE_ACCOUNT_ID",
"workspace_id": "$ANTHROPIC_WORKSPACE_ID"
}
JSON
)
curl https://api.anthropic.com/v1/messages \
-H "authorization: Bearer $ACCESS_TOKEN" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-5-5",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello, Claude"}]
}' | jq -r '.content[] | select(.type == "text") | .text'
# Reads the JWT-SVID that spiffe-helper writes to
# ANTHROPIC_IDENTITY_TOKEN_FILE, plus ANTHROPIC_FEDERATION_RULE_ID,
# ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID, and ANTHROPIC_WORKSPACE_ID.
ant messages create \
--model claude-opus-5-5 \
--max-tokens 1024 \
--message '{role: user, content: "Hello, Claude"}'
import anthropic
# Reads the JWT-SVID that spiffe-helper writes to
# ANTHROPIC_IDENTITY_TOKEN_FILE, plus ANTHROPIC_FEDERATION_RULE_ID,
# ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID, and ANTHROPIC_WORKSPACE_ID.
client = anthropic.Anthropic()
message = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, Claude"}],
)
print(next(block.text for block in message.content if block.type == "text"))
import Anthropic from "@anthropic-ai/sdk";
// Reads the JWT-SVID that spiffe-helper writes to
// ANTHROPIC_IDENTITY_TOKEN_FILE, plus ANTHROPIC_FEDERATION_RULE_ID,
// ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID, and ANTHROPIC_WORKSPACE_ID.
const client = new Anthropic();
const message = await client.messages.create({
model: "claude-opus-5-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello, Claude" }]
});
for (const block of message.content) {
if (block.type === "text") {
console.log(block.text);
}
}
// Reads the JWT-SVID that spiffe-helper writes to
// ANTHROPIC_IDENTITY_TOKEN_FILE, plus ANTHROPIC_FEDERATION_RULE_ID,
// ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID, and ANTHROPIC_WORKSPACE_ID.
using var client = new AnthropicClient();
var message = await client.Messages.Create(new()
{
Model = Model.ClaudeOpus5_5,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Hello, Claude" }],
});
foreach (var block in message.Content)
{
if (block.Value is TextBlock textBlock)
{
Console.WriteLine(textBlock.Text);
}
}
// Reads the JWT-SVID that spiffe-helper writes to
// ANTHROPIC_IDENTITY_TOKEN_FILE, plus ANTHROPIC_FEDERATION_RULE_ID,
// ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID, and ANTHROPIC_WORKSPACE_ID.
client := anthropic.NewClient()
message, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5_5,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello, Claude")),
},
})
if err != nil {
panic(err)
}
for _, block := range message.Content {
if textBlock, ok := block.AsAny().(anthropic.TextBlock); ok {
fmt.Println(textBlock.Text)
break
}
}
// Reads the JWT-SVID that spiffe-helper writes to
// ANTHROPIC_IDENTITY_TOKEN_FILE, plus ANTHROPIC_FEDERATION_RULE_ID,
// ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID, and ANTHROPIC_WORKSPACE_ID.
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
var message = client.messages().create(MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5_5)
.maxTokens(1024)
.addUserMessage("Hello, Claude")
.build());
IO.println(message.content());
use Anthropic\Client;
// Reads the JWT-SVID that spiffe-helper writes to
// ANTHROPIC_IDENTITY_TOKEN_FILE, plus ANTHROPIC_FEDERATION_RULE_ID,
// ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID, and ANTHROPIC_WORKSPACE_ID.
$client = new Client();
$message = $client->messages->create(
model: 'claude-opus-5-5',
maxTokens: 1024,
messages: [['role' => 'user', 'content' => 'Hello, Claude']],
);
$textBlock = array_find($message->content, static fn ($block): bool => $block->type === 'text');
echo $textBlock->text, PHP_EOL;
require "anthropic"
# Reads the JWT-SVID that spiffe-helper writes to
# ANTHROPIC_IDENTITY_TOKEN_FILE, plus ANTHROPIC_FEDERATION_RULE_ID,
# ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID, and ANTHROPIC_WORKSPACE_ID.
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-5-5",
max_tokens: 1024,
messages: [{role: "user", content: "Hello, Claude"}]
)
puts message.content.find { it.type == :text }.text
SPIFFE Workload API를 통한 callable
SPIFFE Workload API 클라이언트를 연결하는 워크로드는 spiffe-helper를 건너뛰고 에이전트 소켓에서 새 JWT-SVID를 가져오는 callable을 SDK에 전달할 수 있어요. SDK는 매 토큰 교환 전에 callable을 호출하므로 워크로드는 항상 만료되지 않은 SVID를 제시해요. Python(py-spiffe)과 Go(go-spiffe)는 성숙한 Workload API 클라이언트를 가져요.
import os
import anthropic
from anthropic import WorkloadIdentityCredentials
from spiffe import JwtSource
AUDIENCE = "https://api.anthropic.com"
# Connects to the SPIRE Agent socket at SPIFFE_ENDPOINT_SOCKET.
jwt_source = JwtSource()
def fetch_jwt_svid() -> str:
svid = jwt_source.fetch_svid(audience={AUDIENCE}) # audience is a set of strings
return svid.token
client = anthropic.Anthropic(
credentials=WorkloadIdentityCredentials(
identity_token_provider=fetch_jwt_svid,
federation_rule_id=os.environ["ANTHROPIC_FEDERATION_RULE_ID"],
organization_id=os.environ["ANTHROPIC_ORGANIZATION_ID"],
service_account_id=os.environ["ANTHROPIC_SERVICE_ACCOUNT_ID"],
workspace_id=os.environ.get("ANTHROPIC_WORKSPACE_ID"),
),
)
message = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, Claude"}],
)
print(next(block.text for block in message.content if block.type == "text"))
import (
"context"
"fmt"
"os"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
"github.com/spiffe/go-spiffe/v2/svid/jwtsvid"
"github.com/spiffe/go-spiffe/v2/workloadapi"
)
// ...
const audience = "https://api.anthropic.com"
ctx := context.Background()
source, err := workloadapi.NewJWTSource(ctx)
if err != nil {
panic(err)
}
defer source.Close()
fetchJWTSVID := func(ctx context.Context) (string, error) {
svid, err := source.FetchJWTSVID(ctx, jwtsvid.Params{Audience: audience})
if err != nil {
return "", err
}
return svid.Marshal(), nil
}
client := anthropic.NewClient(
option.WithFederationTokenProvider(fetchJWTSVID, option.FederationOptions{
FederationRuleID: os.Getenv("ANTHROPIC_FEDERATION_RULE_ID"),
OrganizationID: os.Getenv("ANTHROPIC_ORGANIZATION_ID"),
ServiceAccountID: os.Getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"),
WorkspaceID: os.Getenv("ANTHROPIC_WORKSPACE_ID"),
}),
)
message, err := client.Messages.New(ctx, anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5_5,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello, Claude")),
},
})
if err != nil {
panic(err)
}
for _, block := range message.Content {
if textBlock, ok := block.AsAny().(anthropic.TextBlock); ok {
fmt.Println(textBlock.Text)
break
}
}
참고 다른 언어에서는 런타임의 SPIFFE Workload API 클라이언트로 JWT-SVID를 가져오고(또는
spire-agent api fetch jwt로 셸 아웃), 파일에 쓴 다음 파일 기반 탭처럼ANTHROPIC_IDENTITY_TOKEN_FILE을 그 경로로 설정하세요.
설정 검증하기
SDK를 연결하기 전에 SPIRE Agent에서 JWT-SVID를 직접 가져와 클레임이 내 페더레이션 규칙이 기대하는 것과 일치하는지 확인하세요. 다른 SPIFFE 구현을 사용한다면 그 CLI나 Workload API 클라이언트로 JWT-SVID를 가져오고 같은 방식으로 페이로드를 디코딩하세요.
참고 Workload API는 호출 프로세스를 증명해요. Kubernetes 등록 항목의 경우 항목의 셀렉터를 충족하고 에이전트 소켓이 마운트된 파드 안에서 이 명령을 실행하세요(예:
kubectl exec사용). VM과 베어메탈에서는 항목의unix:셀렉터와 일치하는 사용자나 프로세스로 실행하세요. 증명되지 않은 호스트 셸에서 실행하면no identity issued가 반환되며, 이것이 가장 흔한 verify 단계 실패예요.
spire-agent api fetch jwt \
-audience https://api.anthropic.com \
-socketPath /run/spire/sockets/agent.sock \
-output json \
| jq -r '.[0].svids[0].svid' \
| jq -rR 'split(".")[1] | gsub("-";"+") | gsub("_";"/") | @base64d | fromjson'
-output json 플래그는 SVID 응답과 번들 응답을 두 요소 JSON 배열로 반환하므로 jq -r '.[0].svids[0].svid'가 베어 토큰을 추출해요. -output 없는 오래된 SPIRE 버전에서는 명령이 대신 레이블된 블록을 출력해요. 그 경우 기본 출력을 awk '/^[[:space:]]*eyJ/{print $1; exit}'로 파이프해 토큰 줄을 추출하세요. iss가 등록한 OIDC Discovery Provider URL이고 sub가 워크로드의 SPIFFE ID이며 aud에 https://api.anthropic.com이 포함되는지 확인하세요. 그런 다음 토큰 획득과 사용의 cURL 예시를 실행하세요. 성공한 교환은 sk-로 시작하는 access_token을 반환해요. 불투명한 401 authentication_error 응답(메시지 Authentication failed)으로 교환이 실패하면 인증 기록 페이지에서 거부 이유를 확인하고 실패한 교환 문제 해결을 참고하세요. 가장 흔한 SPIRE 측 원인은 SPIRE Server의 jwt_issuer와 페더레이션 발급자로 등록된 URL 사이의 불일치예요.
규칙 범위 지정하기
SPIFFE ID 경로 관례는 운영자가 정의하므로 페더레이션 규칙의 subject_prefix 매처는 내 등록 항목이 사용하는 경로 스킴을 반영해야 해요. 흔한 스킴은 spiffe://<trust-domain>/ns/<namespace>/sa/<service-account>(spire-controller-manager의 ClusterSPIFFEID 리소스가 기본으로 발행)와 VM·베어메탈 워크로드용 spiffe://<trust-domain>/host/<hostname>/<service>예요.
경고
spiffe://prod.example.com/*의subject_prefix는 트러스트 도메인의 모든 워크로드와 일치해요.audience매처가 없으면 규칙은 어떤 audience로든 발행된 JWT-SVID를 받아, 워크로드가 무관한 신뢰 당사자를 위해 요청한 것까지 포함해요.
규칙의 match 블록을 사용 사례에 맞는 가장 좁은 범위로 잠그세요:
- 한 워크로드로 고정:
subject_prefix를 끝자리*없이 전체 SPIFFE ID로 설정하세요. - 항상 audience 설정: 규칙에
audience를 요구하고 spiffe-helper(또는 Workload API 호출)를 같은 값으로 구성해 다른 신뢰 당사자를 위해 발행된 SVID가 거부되게 하세요. - 경로 세그먼트로 범위:
spiffe://prod.example.com/ns/inference/*로 네임스페이스 아래 등록된 모든 워크로드를 부여하고, 하나의 규칙을 넓히기보다 네임스페이스마다 별도 규칙과 Anthropic 서비스 계정을 만드세요. - 트러스트 도메인당 발급자 하나: 각 SPIRE 트러스트 도메인은 자체 서명 키와 OIDC Discovery Provider를 가져요. 각각을 별도 페더레이션 발급자로 등록하고, 규칙을 그들이 일치하는 SPIFFE ID를 소유하는 발급자에 바인딩하세요.
다음 단계
- Okta에서 WIF 사용하기 — Okta 서비스 애플리케이션 아이덴티티를 Workload Identity Federation으로 Claude API에 페더레이션.
- Workload Identity Federation — 오래 지속되는 정적 API 키 대신 내 아이덴티티 공급자의 수명이 짧은 아이덴티티 토큰으로 워크로드를 Claude API에 인증.
- WIF reference — Workload Identity Federation의 환경 변수, 검증 규칙, 프로필 구성, 오류 참조.
- Kubernetes에서 WIF 사용하기 — 프로젝티드 서비스 계정 토큰으로 자체 관리 Kubernetes 클러스터에서 Claude API에 인증.