X.509 인증서로 워크로드 아이덴티티 페더레이션 구성

X.509 인증서로 워크로드 아이덴티티 페더레이션 구성 (Configure workload identity federation with X.509 certificates)

X.509 워크로드 아이덴티티 페더레이션은 워크로드가 TLS 클라이언트 인증서의 아이덴티티를 수명이 짧은 OpenAI 액세스 토큰과 교환하게 해요. 그런 다음 워크로드는 액세스 토큰과 수락된 클라이언트 인증서로 OpenAI API를 호출해요. 이 흐름은 API 키를 대체하지, 클라이언트 인증서를 대체하지 않아요.

이 기능은 OpenAI API에서 사용할 수 있어요. Codex는 지원하지 않고, Codex에는 OIDC 토큰이나 SPIFFE JWT-SVID를 쓰세요. 토큰 교환 요청·응답 상세는 workload identity token exchange reference를, Mutual TLS 권한·인증서 요구사항·활성화·mTLS 호스트·회전은 Mutual TLS 가이드를 참고하세요.

출처: 문서

본문

동작 방식

X.509 워크로드 아이덴티티 교환은 다섯 부분으로 이루어져요.

  1. 조직이 기존 Mutual TLS 설정에서 신뢰하는 루트 인증서를 업로드·활성화해요.
  2. X.509 Workload Identity Provider가 검증된 클라이언트 인증서에서 openai.* 속성을 유도해요. 비어 있지 않은 openai.subject 값 하나를 유도해야 해요.
  3. 서비스 계정 매핑이 유도된 아이덴티티가 프로젝트 안의 한 OpenAI 서비스 계정을 쓰도록 승인해요.
  4. 워크로드가 mtls.auth.openai.com의 X.509 토큰 엔드포인트에 인증서를 제시해 수명이 짧은 bearer 토큰을 요청해요. 인증서는 TLS 연결에서 오고, 요청 바디에 subject_token이 없어요.
  5. 워크로드가 API 인증을 위해 mtls.api.openai.com의 API 경로에 bearer 토큰과 클라이언트 인증서를 제시해요.

bearer 토큰과 인증서는 API 요청에서 독립적으로 승인돼요. 인증서만으로는 OpenAI API 호출을 승인하지 않아요.

시작 전에

필요한 것:

  • 조직의 Mutual TLS 인증서와 Workload Identity Provider를 관리할 권한
  • 워크로드용 프로젝트와 서비스 계정
  • 클라이언트 인증서, 그 개인 키, 신뢰하는 루트로의 경로를 만드는 데 필요한 중간 인증서
  • 조직·프로젝트 수준의 활성 신뢰 루트 인증서

개인 키는 소스 컨트롤 밖에 두고 그것을 쓰는 워크로드로 접근을 제한해요. 개인 키, 인증서 내용, 반환된 액세스 토큰을 로그에 남기지 마세요.

Mutual TLS 인증서 신뢰 구성

X.509 Workload Identity Provider는 조직의 기존 Mutual TLS 인증서 구성을 재사용해요. 인증서를 업로드하거나 별도 인증서 신뢰 저장소를 유지하지 않아요. Mutual TLS 가이드를 따라 인증서 요구사항·mTLS 호스트·인증서 활성화 동작·CEL 필터·클라이언트 구성을 검토한 뒤, Organization settings > Security > Mutual TLS를 열고 PEM 형식으로 신뢰 인증서를 업로드해 조직 또는 X.509 워크로드 아이덴티티 페더레이션을 쓸 각 프로젝트에 활성화해요. 클라이언트 인증서가 중간 인증서를 통해 체인을 이룬다면 안정적인 신뢰 앵커를 구성하고 TLS 핸드셰이크 중 현재 중간 인증서를 이어서 leaf를 제시해요. OpenAI는 요청이 제공하는 중간 인증서를 쓰고 인증서 URL에서 누락된 중간 인증서를 가져오지 않아요.

X.509 provider 구성

  1. Organization settings > Security > Workload Identity Provider를 열고 Create identity provider를 선택해요.
  2. Provider type에서 X.509를 고르고 이름·선택 설명을 입력해요. X.509 provider는 OIDC 발급자·audience·discovery·JWKS 설정을 쓰지 않아요. 만든 뒤 provider 타입은 바꿀 수 없어요.
  3. Advanced에서 매핑 해석 전에 인증서를 거부하는 Attribute conditions CEL 표현식을 선택적으로 추가해요.
  4. Attribute transformations에서 필수 openai.subject 변환의 비어 있지 않은 표현식을 입력해요. X.509를 선택하면 대시보드가 subject 행을 추가하고 openai. 접두사를 표시·적용해요. 워크로드를 식별하는 안정적인 인증서 사실을 고르세요.
  5. 다른 고유 openai.* 이름으로 변환을 선택적으로 추가한 뒤 Create를 선택해요.

예를 들어 이 구성은 인증서 common name을 canonical subject로 쓰고 organizational unit을 추가 매핑 속성으로 노출해요.

[
  {
    "attribute": "openai.subject",
    "expression": "assertion.subject.common_name"
  },
  {
    "attribute": "openai.environment",
    "expression": "assertion.subject.organizational_unit"
  }
]

인증서 사실은 assertion.subject와 assertion.subject_alt_names 아래에 있어요. 매핑에 쓰이는 변환 결과는 스칼라 값이어야 해요. 추가 변환은 고유한 openai.* 이름을 가져야 해요. 예를 들어 Attribute conditions 표현식은 provider를 프로덕션 인증서로 제한할 수 있어요.

assertion.subject.organizational_unit == "Production"

서비스 계정 매핑 만들기

  1. X.509 provider 상세 페이지에서 Create mapping을 선택해요.
  2. 대상 프로젝트와 서비스 계정을 고르고 워크로드가 필요한 API 권한만 부여해요.
  3. Key·Value 필드에 정확한 openai.subject 값을 요구해요. X.509 매핑은 assertion이 없는 상태(빈 객체 {})나 키가 openai.로 시작하는 assertion을 지원해요.
  4. Create를 선택해요. 예:
키 값
openai.subject payments-service-prod

X.509 매핑은 유도된 openai.* 속성을 써요. sub, iss, aud 같은 raw JWT 클레임은 일치시키지 않아요. provider 목록에 provider ID가, 매핑 상세에 선택된 서비스 계정과 서비스 계정 ID가 표시돼요. 두 식별자를 기록해 두세요 — 워크로드가 토큰 교환 중에 보내요.

SDK로 X.509 워크로드 아이덴티티 사용

인증서 체인·개인 키·provider·서비스 계정에 대한 환경 변수를 설정해요.

export OPENAI_MTLS_CERT_CHAIN="/path/to/client-chain.pem"
export OPENAI_MTLS_KEY="/path/to/client-key.pem"
export OPENAI_IDENTITY_PROVIDER_ID="idp_example"
export OPENAI_SERVICE_ACCOUNT_ID="svc_acct_example"

인증서 체인 파일은 leaf 인증서를 먼저, 이어서 중간 인증서를 담아야 해요. 요청 바디에 인증서 자료나 subject_token을 넣지 마세요. 이 값들로 OpenAI SDK 클라이언트를 구성해요. SDK가 토큰 교환·API 요청 중 클라이언트 인증서를 제시하고, API 요청을 mTLS 엔드포인트로 보내고, 수명이 짧은 액세스 토큰을 자동 갱신해요.

이 예시들(Python)은 OpenAI SDK의 X.509 구성을 지원하는 버전이 필요해요: JavaScript 7.8.0 이상(undici 피어 의존성 포함), Python 3.6.0 이상, Go 3.54.0 이상, Java 4.55.0 이상, Ruby 0.83.0 이상.

import os
import ssl

from openai import DefaultHttpx2Client, OpenAI
from openai.auth import x509_workload_identity

tls_context = ssl.create_default_context()
tls_context.load_cert_chain(
    certfile=os.environ["OPENAI_MTLS_CERT_CHAIN"],
    keyfile=os.environ["OPENAI_MTLS_KEY"],
)

with OpenAI(
    base_url="https://mtls.api.openai.com/v1",
    workload_identity=x509_workload_identity(
        identity_provider_id=os.environ["OPENAI_IDENTITY_PROVIDER_ID"],
        service_account_id=os.environ["OPENAI_SERVICE_ACCOUNT_ID"],
    ),
    http_client=DefaultHttpx2Client(verify=tls_context, follow_redirects=False),
) as client:
    response = client.responses.create(
        model="gpt-5.6-terra",
        input="Say hello from X.509 workload identity federation.",
    )

    print(response.output_text)

Java 예시는 PKCS12 키스토어를 로드해 X509ExtendedKeyManager를 만들고 플랫폼 기본 신뢰 저장소로 X509TrustManager를 만든다. 이 예시에는 OPENAI_X509_KEYSTORE_PATH, OPENAI_X509_KEYSTORE_PASSWORD, OPENAI_X509_CERTIFICATE_ALIAS를 설정해야 해요. SDK에 PEM 기반 또는 하드웨어 기반 매니저를 제공할 수도 있어요.

수동으로 인증서 교환

토큰 교환 프로토콜을 검사하거나 직접 구현하려면 X.509 토큰 엔드포인트에 인증서를 제시해요.

curl --cert "$OPENAI_MTLS_CERT_CHAIN" \
  --key "$OPENAI_MTLS_KEY" \
  --request POST "https://mtls.auth.openai.com/oauth/token" \
  --header "Content-Type: application/json" \
  --data @- <<JSON
{
  "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
  "subject_token_type": "urn:openai:params:oauth:token-type:x509",
  "identity_provider_id": "${OPENAI_IDENTITY_PROVIDER_ID}",
  "service_account_id": "${OPENAI_SERVICE_ACCOUNT_ID}"
}
JSON

성공한 교환은 일반적인 수명이 짧은 bearer 토큰을 반환해요.

{
  "access_token": "eyJ...",
  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
  "token_type": "Bearer",
  "expires_in": 3600,
  "expires_at": 1789045200,
  "scope": "api.model.read api.model.request"
}

scope 속성은 일치하는 서비스 계정 매핑에 권한이 있을 때만 반환돼요. 만료 값은 예시이고, 검증된 클라이언트 인증서가 더 일찍 만료되면 반환된 수명이 더 짧을 수 있어요. expires_in·expires_at의 단위·의미는 token exchange response fields를 참고하세요. 성공 응답의 access_token 값을 애플리케이션 자격증명 저장소나 OPENAI_WIF_ACCESS_TOKEN 같은 환경 변수로 읽어요. 비밀로 취급하고 출력·로그·커밋하지 마세요.

수동으로 OpenAI API 호출

OPENAI_MODEL을 gpt-6-astra(현재 기본값)나 대상 프로젝트에서 쓸 수 있는 다른 모델로 설정해요. 그런 다음 API mTLS 엔드포인트에 bearer 토큰과 수락된 클라이언트 인증서를 보내요.

curl --request POST \
  --cert "$OPENAI_MTLS_CERT_CHAIN" \
  --key "$OPENAI_MTLS_KEY" \
  --header "Authorization: Bearer $OPENAI_WIF_ACCESS_TOKEN" \
  --header "Content-Type: application/json" \
  --data "{\"model\":\"$OPENAI_MODEL\",\"input\":\"Say hello in one sentence.\"}" \
  "https://mtls.api.openai.com/v1/responses"

API 키 대신 bearer 토큰을 쓰고, API 요청에 수락된 클라이언트 인증서를 계속 제시해요. bearer는 인증서에 암호학적으로 바인딩되지 않아요. 교환 인증서를 API 요청에 재사용하는 것이 가장 직접적인 구성이지만, API 요청은 독립적으로 같은 현재 API mTLS 정책을 충족하는 다른 인증서를 쓸 수도 있어요.

토큰 수명과 갱신

X.509 워크로드 아이덴티티 토큰은 최대 1시간 후에 만료되고 검증된 클라이언트 인증서보다 오래 살지 않아요. 교환은 refresh 토큰을 반환하지 않으므로, 다른 액세스 토큰을 얻으려면 인증서 교환을 반복해요. 수동 교환에서는 expires_at을 액세스 토큰과 함께 보관하고 그 타임스탬프 전에 다른 교환을 예약해요. 시계 차이와 요청 지연을 고려하세요. 예시는 token renewal guidance를 참고하세요. 중간 인증서를 회전해도 구성된 루트를 바꿀 필요는 없어요. 이후 교환·API 요청에 새 완전한 체인을 제시해요.

토큰 교환 문제 해결

X.509 토큰 교환은 일반 OAuth 오류를 반환하고 인증서·루트·provider·매핑 상세를 노출하지 않아요.

결과 일반적 원인
HTTP 403 요청이 mtls.auth.openai.com에서 정확한 POST /oauth/token이 아닌 메서드·경로를 썼음.
invalid_subject_token TLS 클라이언트 인증서 누락·무효, 제시된 체인이 활성 루트에 도달하지 못함, 인증서 유효 기간 밖, 또는 Mutual TLS 인증서 허용 규칙이 거부함.
invalid_grant provider·매핑이 무효·비활성, provider Attribute conditions 표현식이 아이덴티티 거부, 적용 가능한 루트 없음, 또는 일치 매핑 없음.
서버 오류 OpenAI가 임시 서버 오류를 반환함. 평소 일시 오류 정책에 따라 재시도.

X.509 교환은 OIDC나 일반 OAuth 흐름으로 폴백하지 않아요.

제한

  • X.509 Workload Identity Provider는 별도 인증서 신뢰 저장소를 유지하지 않아요.
  • bearer 토큰은 인증서에 바인딩되지 않고 DPoP나 cnf 클레임을 쓰지 않아요.
  • 인증서 교환은 인증서 전용 API 인증이 아니에요. API 요청은 여전히 bearer 토큰과 수락된 클라이언트 인증서가 필요해요.
  • OpenAI는 AIA URL에서 누락된 중간 인증서를 가져오지 않아요. TLS 협상 중 완전한 체인을 제시하세요.
  • OpenAI는 이 흐름에서 인증서 해지 목록(CRL)이나 OCSP 검사를 하지 않아요. Mutual TLS 루트·provider·매핑 제어와 발급 토큰의 짧은 수명을 중심으로 인증서 사고 대응을 계획하세요.
  • 이 흐름은 SPIFFE X.509-SVID 지원을 추가하지 않아요. SPIFFE 가이드는 계속 JWT-SVID를 써요.

더 알아보기 (Learn more)

워크로드 아이덴티티 페더레이션의 공통 개념은 메인 문서를, Mutual TLS 세부 사항은 Mutual TLS 가이드를 참고하세요.