AWS에서 WIF 사용하기
AWS에서 WIF 사용하기
AWS 워크로드는 정적 API 키 없이도 AWS가 서명한 OIDC 아이덴티티 토큰을 교환해 Claude API에 인증할 수 있어요. 권장 경로는 AWS STS GetWebIdentityToken API를 호출하는 것으로, 워크로드에 AWS 자격 증명이 있는 곳(Lambda, EC2, ECS, EKS)이라면 어디서든 작동해요. EKS 워크로드는 대신 Kubernetes 프로젝티드 토큰 경로를 사용할 수 있는데, 구성 단계가 더 적지만 파드 안에서만 작동해요.
이 가이드에서는 두 경로를 모두 보여줄게요. 기본 개념(서비스 계정, 페더레이션 발급자, 페더레이션 규칙)은 Workload Identity Federation을 참고하세요.
출처: 문서
본문
사전 요구사항
- WIF 개념에 익숙해야 해요. 서비스 계정, 페더레이션 발급자, 페더레이션 규칙.
- IAM 역할이 연결된 AWS 워크로드(EKS 파드, ECS 태스크, Lambda 함수, EC2 인스턴스).
- 워크로드에서 사용 가능한
awsCLI 또는 AWS SDK. - Anthropic 조직의 Claude Console에서 서비스 계정, 페더레이션 발급자, 페더레이션 규칙을 만들 권한.
STS 웹 아이덴티티 토큰 사용(권장)
AWS STS GetWebIdentityToken API는 호출자의 IAM 아이덴티티를 주장하는, AWS가 서명한 OIDC 토큰을 반환해요. 워크로드의 환경 AWS 자격 증명을 사용하므로, 같은 통합이 Lambda, EC2, ECS, EKS를 모두 포괄해요.
AWS 구성하기
-
계정에 아웃바운드 웹 아이덴티티 페더레이션 활성화 이것은 계정 수준 플래그로, 기본적으로 꺼져 있어요. AWS 콘솔에서 IAM을 열고 Account settings를 선택한 뒤 Outbound web identity federation을 활성화하세요. 프로그래밍 방식으로 활성화하려면:
python3 -c "import boto3; boto3.client('iam').enable_outbound_web_identity_federation()"활성화하지 않으면
GetWebIdentityToken호출이OutboundWebIdentityFederationDisabledException으로 실패해요. -
워크로드의 IAM 역할에 API 호출 권한 부여 Lambda 함수, EC2 인스턴스, ECS 태스크가 실행되는 IAM 역할에 이 정책을 연결하세요:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["sts:GetWebIdentityToken"], "Resource": "*" } ] } -
계정의 STS 발급자 URL 찾기 아웃바운드 페더레이션을 활성화한 후 IAM > Account settings 페이지에
https://<uuid>.tokens.sts.global.api.aws형태의 값인 Get Token Issuer URL 필드가 생겨요. 이 URL은 내 AWS 계정에 고유해요. 다음 단계를 위해 복사하세요. 프로그래밍 방식으로 가져오려면:python3 -c "import boto3; print(boto3.client('iam').get_outbound_web_identity_federation_info())"
Anthropic 구성하기
Claude Console에서 Settings → Workload identity를 열고 Connect workload를 클릭한 뒤 AWS 타일을 선택하세요. 마법사가 발급자 등록, 서비스 계정 만들기, 페더레이션 규칙 만들기를 안내해요.
마법사가 이 리소스들을 만들어 줘요. 마법사에 입력하든 Admin API로 보내든 다음 값을 사용하세요:
페더레이션 발급자: 이전 단계에서 복사한 계정별 STS 발급자 URL을 등록하세요. 공개 JWKS 엔드포인트를 노출하므로 discovery 모드를 사용해요.
{
"name": "aws-sts",
"issuer_url": "https://<uuid>.tokens.sts.global.api.aws",
"jwks": { "type": "discovery" }
}
페더레이션 규칙: GetWebIdentityToken에 전달하는 audience와 sub 클레임의 호출 역할 IAM 역할 ARN을 일치시키세요. sub 값은 API를 호출한 워크로드의 IAM 역할 ARN으로, arn:aws:iam::<account>:role/<role-name> 형태예요. 토큰은 https://sts.amazonaws.com/ 클레임에 aws_account, org_id, principal_id, 그리고 전달한 request_tags도 나르는데, 더 세밀한 제어를 위해 규칙의 claims 맵이나 CEL condition으로 그 값들을 일치시킬 수 있어요.
{
"name": "prod-inference",
"issuer_id": "fdis_...",
"match": {
"subject_prefix": "arn:aws:iam::123456789012:role/inference-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
}
워크로드가 허용하는 만큼 구체적으로 하세요. 정확한 역할 ARN을 일치시키고, 여러 IAM 역할이 같은 Anthropic 서비스 계정에 매핑되어야 할 때만 subject_prefix를 넓히세요(예: arn:aws:iam::123456789012:role/*).
토큰 획득과 사용
https://api.anthropic.com을 audience로 GetWebIdentityToken을 호출한 뒤, 결과를 SDK의 페더레이션 자격 증명에 전달하세요. 토큰 공급자는 호출 가능(callable)하므로 SDK는 갱신할 때마다 STS를 다시 호출해요.
참고
GetWebIdentityToken은 리전 STS 엔드포인트에서만 사용할 수 있어요.'STS' object has no attribute 'get_web_identity_token'또는 비슷한 오류를 받으면 STS 클라이언트를 리전에 고정하고(예:boto3.client("sts", region_name="us-east-1")), AWS SDK가 API를 포함할 만큼 충분히 최신인지 확인하세요.
JWT=$(aws sts get-web-identity-token \
--region us-east-1 \
--audience "https://api.anthropic.com" \
--signing-algorithm RS256 \
--duration-seconds 900 \
--query WebIdentityToken --output text)
RESPONSE=$(curl -sS https://api.anthropic.com/v1/oauth/token \
-H "content-type: application/json" \
--data @- <<JSON
{
"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
)
ACCESS_TOKEN=$(echo "$RESPONSE" | jq -r .access_token)
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 from AWS"}]
}' | jq -r '.content[] | select(.type == "text") | .text'
import os
import anthropic
import boto3
from anthropic import WorkloadIdentityCredentials
def get_sts_web_identity_token() -> str:
sts = boto3.client("sts", region_name="us-east-1")
resp = sts.get_web_identity_token(
Audience=["https://api.anthropic.com"],
SigningAlgorithm="RS256",
DurationSeconds=900,
)
return resp["WebIdentityToken"]
client = anthropic.Anthropic(
credentials=WorkloadIdentityCredentials(
identity_token_provider=get_sts_web_identity_token,
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 from AWS"}],
)
print(next(block.text for block in message.content if block.type == "text"))
import Anthropic from "@anthropic-ai/sdk";
import { oidcFederationProvider } from "@anthropic-ai/sdk/lib/credentials/oidc-federation";
import { STSClient, GetWebIdentityTokenCommand } from "@aws-sdk/client-sts";
const sts = new STSClient({ region: "us-east-1" });
async function getStsWebIdentityToken(): Promise<string> {
const out = await sts.send(
new GetWebIdentityTokenCommand({
Audience: ["https://api.anthropic.com"],
SigningAlgorithm: "RS256",
DurationSeconds: 900
})
);
return out.WebIdentityToken!;
}
const client = new Anthropic({
credentials: oidcFederationProvider({
identityTokenProvider: getStsWebIdentityToken,
federationRuleId: process.env.ANTHROPIC_FEDERATION_RULE_ID!,
organizationId: process.env.ANTHROPIC_ORGANIZATION_ID!,
serviceAccountId: process.env.ANTHROPIC_SERVICE_ACCOUNT_ID,
workspaceId: process.env.ANTHROPIC_WORKSPACE_ID,
baseURL: "https://api.anthropic.com",
fetch
})
});
const message = await client.messages.create({
model: "claude-opus-5-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello from AWS" }]
});
for (const block of message.content) {
if (block.type === "text") {
console.log(block.text);
}
}
ctx := context.TODO()
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("us-east-1"))
if err != nil {
panic(err)
}
stsClient := sts.NewFromConfig(cfg)
getStsToken := option.IdentityTokenFunc(func(ctx context.Context) (string, error) {
out, err := stsClient.GetWebIdentityToken(ctx, &sts.GetWebIdentityTokenInput{
Audience: []string{"https://api.anthropic.com"},
SigningAlgorithm: "RS256",
DurationSeconds: aws.Int32(900),
})
if err != nil {
return "", err
}
return *out.WebIdentityToken, nil
})
client := anthropic.NewClient(
option.WithFederationTokenProvider(getStsToken, 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 from AWS")),
},
})
if err != nil {
panic(err)
}
for _, block := range message.Content {
if textBlock, ok := block.AsAny().(anthropic.TextBlock); ok {
fmt.Println(textBlock.Text)
break
}
}
StsClient sts = StsClient.builder().region(Region.US_EAST_1).build();
IdentityTokenProvider getStsToken = () -> sts.getWebIdentityToken(
GetWebIdentityTokenRequest.builder()
.audience("https://api.anthropic.com")
.signingAlgorithm("RS256")
.durationSeconds(900)
.build())
.webIdentityToken();
AnthropicClient client = AnthropicOkHttpClient.builder()
.federationTokenProvider(
getStsToken,
System.getenv("ANTHROPIC_FEDERATION_RULE_ID"),
System.getenv("ANTHROPIC_ORGANIZATION_ID"),
System.getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"))
.build();
var message = client.messages().create(MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5_5)
.maxTokens(1024)
.addUserMessage("Hello from AWS")
.build());
IO.println(message.content());
using Amazon.SecurityToken;
using Amazon.SecurityToken.Model;
// ...
using Anthropic.Credentials;
// ...
var credentials = new WorkloadIdentityCredentials(new WorkloadIdentityOptions
{
FederationRuleId = Environment.GetEnvironmentVariable("ANTHROPIC_FEDERATION_RULE_ID")!,
OrganizationId = Environment.GetEnvironmentVariable("ANTHROPIC_ORGANIZATION_ID"),
ServiceAccountId = Environment.GetEnvironmentVariable("ANTHROPIC_SERVICE_ACCOUNT_ID"),
WorkspaceId = Environment.GetEnvironmentVariable("ANTHROPIC_WORKSPACE_ID"),
IdentityTokenProvider = new StsTokenProvider(),
});
using var client = new AnthropicClient(new ClientOptions { Credentials = credentials });
var message = await client.Messages.Create(new()
{
Model = Model.ClaudeOpus5_5,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Hello from AWS" }],
});
foreach (var block in message.Content)
{
if (block.Value is TextBlock textBlock)
{
Console.WriteLine(textBlock.Text);
}
}
class StsTokenProvider : IIdentityTokenProvider
{
private readonly AmazonSecurityTokenServiceClient _sts = new(Amazon.RegionEndpoint.USEast1);
public async Task<string> GetIdentityTokenAsync(CancellationToken ct = default)
{
var resp = await _sts.GetWebIdentityTokenAsync(new GetWebIdentityTokenRequest
{
Audience = ["https://api.anthropic.com"],
SigningAlgorithm = "RS256",
DurationSeconds = 900,
}, ct);
return resp.WebIdentityToken;
}
}
TOKEN_FILE=$(mktemp)
aws sts get-web-identity-token \
--region us-east-1 \
--audience "https://api.anthropic.com" \
--signing-algorithm RS256 \
--duration-seconds 900 \
--query WebIdentityToken --output text > "$TOKEN_FILE"
export ANTHROPIC_IDENTITY_TOKEN_FILE="$TOKEN_FILE"
# ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID, and
# ANTHROPIC_SERVICE_ACCOUNT_ID, and ANTHROPIC_WORKSPACE_ID are read from the environment
ant messages create \
--model claude-opus-5-5 \
--max-tokens 1024 \
--message '{role: user, content: "Hello from AWS"}'
use Anthropic\Client;
use Anthropic\Credentials\WorkloadIdentityCredentials;
use Aws\Sts\StsClient;
$sts = new StsClient(['region' => 'us-east-1', 'version' => 'latest']);
$client = new Client(credentials: new WorkloadIdentityCredentials(
identityTokenProvider: fn() => $sts->getWebIdentityToken([
'Audience' => ['https://api.anthropic.com'],
'SigningAlgorithm' => 'RS256',
'DurationSeconds' => 900,
])['WebIdentityToken'],
federationRuleId: getenv('ANTHROPIC_FEDERATION_RULE_ID'),
organizationId: getenv('ANTHROPIC_ORGANIZATION_ID'),
serviceAccountId: getenv('ANTHROPIC_SERVICE_ACCOUNT_ID'),
workspaceId: getenv('ANTHROPIC_WORKSPACE_ID') ?: null,
));
$message = $client->messages->create(
model: 'claude-opus-5-5',
maxTokens: 1024,
messages: [['role' => 'user', 'content' => 'Hello from AWS']],
);
$textBlock = array_find($message->content, static fn ($block): bool => $block->type === 'text');
echo $textBlock->text, PHP_EOL;
require "anthropic"
require "aws-sdk-sts"
sts = Aws::STS::Client.new(region: "us-east-1")
client = Anthropic::Client.new(
credentials: Anthropic::WorkloadIdentityCredentials.new(
identity_token_provider: -> {
sts.get_web_identity_token(
audience: ["https://api.anthropic.com"],
signing_algorithm: "RS256",
duration_seconds: 900,
).web_identity_token
},
federation_rule_id: ENV.fetch("ANTHROPIC_FEDERATION_RULE_ID"),
organization_id: ENV.fetch("ANTHROPIC_ORGANIZATION_ID"),
service_account_id: ENV.fetch("ANTHROPIC_SERVICE_ACCOUNT_ID"),
workspace_id: ENV["ANTHROPIC_WORKSPACE_ID"],
),
)
message = client.messages.create(
model: "claude-opus-5-5",
max_tokens: 1024,
messages: [{role: "user", content: "Hello from AWS"}]
)
puts message.content.find { it.type == :text }.text
설정 검증하기
워크로드 안에서 STS 발행 토큰을 직접 교환하고 응답을 검사하세요:
JWT=$(aws sts get-web-identity-token \
--region us-east-1 \
--audience "https://api.anthropic.com" \
--signing-algorithm RS256 \
--duration-seconds 900 \
--query WebIdentityToken --output text)
curl -sS https://api.anthropic.com/v1/oauth/token \
-H "content-type: application/json" \
-d "{
\"grant_type\": \"urn:ietf:params:oauth:grant-type:jwt-bearer\",
\"assertion\": \"$JWT\",
\"federation_rule_id\": \"fdrl_...\",
\"organization_id\": \"00000000-0000-0000-0000-000000000000\",
\"service_account_id\": \"svac_...\",
\"workspace_id\": \"wrkspc_...\"
}" | jq
성공한 교환은 sk-로 시작하는 access_token과 초 단위의 expires_in 값을 반환해요. 불투명한 401 authentication_error 응답(메시지 Authentication failed)으로 교환이 실패하면 인증 기록 페이지에서 거부 이유를 확인하고 실패한 교환 문제 해결을 참고하세요. 가장 흔한 AWS 측 원인은 iss 불일치(계정별 STS 발급자 URL이 등록된 issuer_url과 정확히 일치해야 해요)예요.
EKS 프로젝티드 서비스 계정 토큰 사용
워크로드가 EKS 파드에서 실행된다면 STS 호출을 건너뛰고 Kubernetes가 프로젝트한 서비스 계정 토큰을 디스크에서 직접 읽을 수 있어요. Kubernetes가 네이티브로 OIDC 호환 토큰을 파드에 프로젝트하고, SDK가 파일 경로에서 읽을 수 있으므로 토큰 공급자 callable이 필요 없어요. 이 경로는 STS 경로보다 AWS 구성 단계가 두 개 적지만 파드 안에서만 작동해요. 기본 메커니즘은 일반 Kubernetes 통합과 같아요.
이 경로는 IAM OIDC 공급자가 활성화된 EKS 클러스터와 클러스터에 대한 kubectl 접근이 추가로 필요해요.
EKS 클러스터 구성하기
-
클러스터의 OIDC 발급자 URL 찾기 각 EKS 클러스터는 고유한 OIDC 발급자를 가져요. AWS CLI로 가져오세요:
aws eks describe-cluster \ --name <cluster-name> \ --query "cluster.identity.oidc.issuer" \ --output text출력은
https://oidc.eks.us-west-2.amazonaws.com/id/6FA42E7BFDE8549CB...처럼 보여요. 다음 섹션에서 이 URL을 페더레이션 발급자로 등록할 거예요. -
서비스 계정 만들고 Anthropic-audience 토큰 프로젝트 EKS 파드 아이덴티티 웹훅은
eks.amazonaws.com/role-arn어노테이션을 감지하고aud: sts.amazonaws.com을 가진 토큰을 자동으로 프로젝트하며 그 경로를AWS_WEB_IDENTITY_TOKEN_FILE로 노출해요. 그 토큰은 AWS 역할 가정용이에요. Anthropic 교환용으로는audience: https://api.anthropic.com을 가진 두 번째 토큰을 프로젝트하고 전용 경로에 마운트하세요.apiVersion: v1 kind: ServiceAccount metadata: name: inference-worker namespace: inference annotations: eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/inference-workerapiVersion: v1 kind: Pod metadata: name: inference-worker namespace: inference spec: serviceAccountName: inference-worker volumes: - name: anthropic-token projected: sources: - serviceAccountToken: audience: https://api.anthropic.com expirationSeconds: 3600 path: token containers: - name: app image: your-registry/inference-worker:latest env: - name: ANTHROPIC_IDENTITY_TOKEN_FILE value: /var/run/secrets/anthropic.com/token - name: ANTHROPIC_FEDERATION_RULE_ID value: fdrl_... - name: ANTHROPIC_ORGANIZATION_ID value: 00000000-0000-0000-0000-000000000000 - name: ANTHROPIC_SERVICE_ACCOUNT_ID value: svac_... - name: ANTHROPIC_WORKSPACE_ID # required when the rule covers multiple workspaces value: wrkspc_... volumeMounts: - name: anthropic-token mountPath: /var/run/secrets/anthropic.com readOnly: true -
토큰의 클레임 형태에 주목 프로젝티드 토큰은 클러스터의 OIDC 발급자가 서명한 JSON Web Token(JWT)이에요.
sub클레임은 Kubernetes 관례system:serviceaccount:<namespace>:<service-account-name>을 따라요:{ "iss": "https://oidc.eks.us-west-2.amazonaws.com/id/6FA42E7BFDE8549CB...", "sub": "system:serviceaccount:inference:inference-worker", "aud": ["https://api.anthropic.com"], "kubernetes.io": { "namespace": "inference", "serviceaccount": { "name": "inference-worker", "uid": "..." } }, "exp": 1775527120, "iat": 1775523520 }serviceAccountToken프로젝션은aud를https://api.anthropic.com으로 설정해요.AWS_WEB_IDENTITY_TOKEN_FILE의 별도 IRSA 주입 토큰은aud: sts.amazonaws.com을 나르며 AWS API 호출용이지, 이 교환용이 아니에요.
Anthropic 구성하기
Claude Console에서 Settings → Workload identity를 열고 Connect workload를 클릭한 뒤 AWS 타일을 선택하세요. 마법사가 발급자 등록, 서비스 계정 만들기, 페더레이션 규칙 만들기를 안내해요.
마법사가 이 리소스들을 만들어 줘요. 마법사에 입력하든 Admin API로 보내든 다음 값을 사용하세요:
페더레이션 발급자: EKS 발급자는 공개 JWKS 엔드포인트를 노출하므로 discovery 모드를 사용해요. 발급자 URL은 토큰의 iss 클레임과 정확히 일치해야 해요. 클러스터당 발급자 하나를 등록하세요.
{
"name": "prod-eks-uswest2",
"issuer_url": "https://oidc.eks.us-west-2.amazonaws.com/id/6FA42E7BFDE8549CB...",
"jwks": { "type": "discovery" }
}
페더레이션 규칙: Kubernetes sub 클레임과 Anthropic audience https://api.anthropic.com을 일치시키세요. (그 audience로 전용 서비스 계정 토큰을 프로젝트하세요. IRSA 기본 sts.amazonaws.com 토큰을 재사용하지 마세요.)
{
"name": "prod-inference",
"issuer_id": "fdis_...",
"match": {
"subject_prefix": "system:serviceaccount:inference:inference-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
}
워크로드가 허용하는 만큼 구체적으로 하세요. 네임스페이스의 모든 서비스 계정이 같은 Anthropic 서비스 계정에 매핑되어야 할 때만 subject_prefix를 system:serviceaccount:inference:*로 느슨하게 하세요(끝자리 *가 접두사 일치로 만듦).
토큰 획득과 사용
파드 안에서 프로젝티드 토큰은 /var/run/secrets/anthropic.com/token에 있어요(Pod 스펙에서 ANTHROPIC_IDENTITY_TOKEN_FILE으로 노출). 그 파일을 SDK의 페더레이션 자격 증명에 전달하면 SDK가 교환과 갱신을 처리해요.
JWT=$(cat "$ANTHROPIC_IDENTITY_TOKEN_FILE")
RESPONSE=$(curl -sS https://api.anthropic.com/v1/oauth/token \
-H "content-type: application/json" \
--data @- <<JSON
{
"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
)
ACCESS_TOKEN=$(echo "$RESPONSE" | jq -r .access_token)
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 from EKS"}]
}' | jq -r '.content[] | select(.type == "text") | .text'
import os
import anthropic
from anthropic import IdentityTokenFile, WorkloadIdentityCredentials
client = anthropic.Anthropic(
credentials=WorkloadIdentityCredentials(
identity_token_provider=IdentityTokenFile(
os.environ["ANTHROPIC_IDENTITY_TOKEN_FILE"]
),
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 from EKS"}],
)
print(next(block.text for block in message.content if block.type == "text"))
import Anthropic from "@anthropic-ai/sdk";
import { oidcFederationProvider } from "@anthropic-ai/sdk/lib/credentials/oidc-federation";
import { identityTokenFromFile } from "@anthropic-ai/sdk/lib/credentials/identity-token";
const client = new Anthropic({
credentials: oidcFederationProvider({
identityTokenProvider: identityTokenFromFile(process.env.ANTHROPIC_IDENTITY_TOKEN_FILE!),
federationRuleId: process.env.ANTHROPIC_FEDERATION_RULE_ID!,
organizationId: process.env.ANTHROPIC_ORGANIZATION_ID!,
serviceAccountId: process.env.ANTHROPIC_SERVICE_ACCOUNT_ID,
workspaceId: process.env.ANTHROPIC_WORKSPACE_ID,
baseURL: "https://api.anthropic.com",
fetch
})
});
const message = await client.messages.create({
model: "claude-opus-5-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello from EKS" }]
});
for (const block of message.content) {
if (block.type === "text") {
console.log(block.text);
}
}
tokenPath := os.Getenv("ANTHROPIC_IDENTITY_TOKEN_FILE")
readToken := option.IdentityTokenFunc(func(ctx context.Context) (string, error) {
raw, err := os.ReadFile(tokenPath)
if err != nil {
return "", fmt.Errorf("read identity token: %w", err)
}
return string(raw), nil
})
client := anthropic.NewClient(
option.WithFederationTokenProvider(readToken, 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(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5_5,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello from EKS")),
},
})
if err != nil {
panic(err)
}
for _, block := range message.Content {
if textBlock, ok := block.AsAny().(anthropic.TextBlock); ok {
fmt.Println(textBlock.Text)
break
}
}
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
var message = client.messages().create(MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5_5)
.maxTokens(1024)
.addUserMessage("Hello from EKS")
.build());
IO.println(message.content());
// Reads ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
// ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID, and ANTHROPIC_IDENTITY_TOKEN_FILE
// from the pod's environment.
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 from EKS" }],
});
foreach (var block in message.Content)
{
if (block.Value is TextBlock textBlock)
{
Console.WriteLine(textBlock.Text);
}
}
# Reads ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
# ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID, and ANTHROPIC_IDENTITY_TOKEN_FILE
ant messages create \
--model claude-opus-5-5 \
--max-tokens 1024 \
--message '{role: user, content: "Hello from EKS"}'
use Anthropic\Client;
// Reads ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
// ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID, and ANTHROPIC_IDENTITY_TOKEN_FILE
$client = new Client();
$message = $client->messages->create(
model: 'claude-opus-5-5',
maxTokens: 1024,
messages: [['role' => 'user', 'content' => 'Hello from EKS']],
);
$textBlock = array_find($message->content, static fn ($block): bool => $block->type === 'text');
echo $textBlock->text, PHP_EOL;
require "anthropic"
# Reads ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
# ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID, and ANTHROPIC_IDENTITY_TOKEN_FILE
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-5-5",
max_tokens: 1024,
messages: [{role: "user", content: "Hello from EKS"}]
)
puts message.content.find { it.type == :text }.text
팁 Pod 스펙이 이미
ANTHROPIC_IDENTITY_TOKEN_FILE,ANTHROPIC_FEDERATION_RULE_ID,ANTHROPIC_ORGANIZATION_ID,ANTHROPIC_SERVICE_ACCOUNT_ID,ANTHROPIC_WORKSPACE_ID를 설정하므로, 인자 없이 클라이언트를 구성하면 SDK가 페더레이션 환경 변수를 자동으로 읽어요.
설정 검증하기
파드 안에서 프로젝티드 토큰을 직접 교환하고 응답을 검사하세요:
JWT=$(cat "$ANTHROPIC_IDENTITY_TOKEN_FILE")
curl -sS https://api.anthropic.com/v1/oauth/token \
-H "content-type: application/json" \
-d "{
\"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\"
}" | jq
성공한 교환은 sk-로 시작하는 access_token과 초 단위의 expires_in 값을 반환해요. 불투명한 401 authentication_error 응답(메시지 Authentication failed)으로 교환이 실패하면 인증 기록 페이지에서 거부 이유를 확인하고 실패한 교환 문제 해결을 참고하세요. 가장 흔한 EKS 측 원인은 프로젝티드 토큰의 aud가 규칙과 일치하지 않는 것(IRSA 기본 sts.amazonaws.com이 아니라 audience: https://api.anthropic.com으로 토큰을 프로젝트)이에요.
규칙 범위 지정하기
경고
arn:aws:iam::123456789012:role/*의subject_prefix는 계정의 모든 IAM 역할과 일치해요. 일치하는 역할 중 하나를 가정할 수 있는 모든 주체는 페더레이션 Anthropic 토큰을 얻을 수 있어요.
규칙의 match 블록을 사용 사례에 맞는 가장 좁은 범위로 잠그세요:
- 전체 역할 ARN 고정: 끝자리
*없이subject_prefix: "arn:aws:iam::<account>:role/<role-name>"을 사용해 계정의 다른 역할이 일치하지 않게 하세요. - 계정 ID 고정: 잘못 구성된 접두사에 대한 심층 방어 검사로
claims맵이나 CELcondition으로 토큰의https://sts.amazonaws.com/클레임의aws_account필드를 일치시키세요. - EKS에서 네임스페이스와 서비스 계정 고정:
system:serviceaccount:접두사 뒤에*없이 정확한system:serviceaccount:<namespace>:<name>값을 사용하세요. - 환경별로 별도 규칙: 하나의 접두사를 넓혀 모두를 포괄하기보다 프로덕션, 스테이징, 개발 워크로드마다 별개 규칙을 만드세요.
다음 단계
- WIF reference를 검토해 전체 자격 증명 우선순위, 프로필 구성, 규칙 일치 참조를 확인하세요.
- EKS가 아닌 자체 관리 Kubernetes 클러스터는 Kubernetes에서 WIF 사용하기를 참고하세요.