SPB Git

Repository Public

x509.md 20.2 KB 449 lines markdown

1# Configure workload identity federation with X.509 certificates23> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.45X.509 workload identity federation lets a workload exchange an identity from a TLS client certificate for a short-lived OpenAI access token. The workload then calls the OpenAI API with both the access token and an accepted client certificate. This flow replaces the API key, not the client certificate.67X.509 workload identity federation is available for the OpenAI API. Codex does8  not support it. For Codex, use an OIDC token or SPIFFE JWT-SVID.910For token exchange request and response details, see the [workload identity token exchange reference](https://developers.openai.com/api/reference/workload-identity-federation#exchange-an-x509-certificate). For Mutual TLS permissions, certificate requirements, activation, mTLS hosts, and rotation, see the [Mutual TLS guide](https://developers.openai.com/api/docs/guides/mutual-tls).1112## How it works1314An X.509 workload identity exchange has five parts:15161. Your organization uploads and activates a trusted root certificate in its existing Mutual TLS settings.172. An X.509 Workload Identity Provider derives `openai.*` attributes from the verified client certificate. It must derive one non-empty `openai.subject` value.183. A service account mapping authorizes the derived identity to use one OpenAI service account within a project.194. The workload presents its certificate to the X.509 token endpoint on `mtls.auth.openai.com` and requests a short-lived bearer token. The certificate comes from the TLS connection; the request body doesn't contain a `subject_token`.205. The workload presents the bearer token and a client certificate to an API route on `mtls.api.openai.com` for API authorization.2122The bearer token and the certificate are authorized independently on the API request. A certificate by itself doesn't authorize an OpenAI API call.2324## Before you begin2526You need:2728- Permission to manage Mutual TLS certificates and Workload Identity Providers for your organization.29- A project and service account for the workload.30- A client certificate, its private key, and any intermediate certificates required to build a path to your trusted root.31- An active trusted root certificate at the organization or project level.3233Keep private keys outside source control and restrict access to the workload that uses them. Don't log private keys, certificate contents, or returned access tokens.3435## Configure Mutual TLS certificate trust3637X.509 Workload Identity Providers reuse your organization's existing Mutual TLS certificate configuration. They don't upload certificates or maintain a separate certificate trust store.3839Follow the [Mutual TLS guide](https://developers.openai.com/api/docs/guides/mutual-tls) to review certificate40requirements, mTLS hosts, certificate activation behavior, CEL filters, and41client configuration. Then open [Organization settings > Security > Mutual42TLS](https://platform.openai.com/settings/organization/security/mtls), upload43the trusted certificate in PEM format, and activate it for the organization or44for each project that will use X.509 workload identity federation.4546If your client certificate chains through an intermediate certificate, configure the stable trust anchor and present the leaf followed by the current intermediate certificates during the TLS handshake. OpenAI uses intermediates provided by the request and doesn't retrieve missing intermediates from certificate URLs.4748## Configure an X.509 provider4950To configure an X.509 provider:51521. Open [Organization settings > Security > Workload Identity Provider](https://platform.openai.com/settings/organization/security/workload-identity-provider), then select **Create identity provider**.532. Choose **X.509** for **Provider type**, then enter a name and optional description. X.509 providers don't use OIDC issuer, audience, discovery, or JWKS settings. You can't change the provider type after you create it.543. Under **Advanced**, optionally add an **Attribute conditions** CEL expression to reject certificates before mapping resolution.554. Under **Attribute transformations**, enter a non-empty expression for the required `openai.subject` transformation. The dashboard adds the `subject` row when you select X.509 and displays and applies the `openai.` prefix. Choose a stable certificate fact that identifies the workload.565. Optionally add transformations with other unique `openai.*` names, then select **Create**.5758For example, this configuration uses the certificate common name as the canonical subject and exposes the organizational unit as an additional mapping attribute:5960```json61[62  {63    "attribute": "openai.subject",64    "expression": "assertion.subject.common_name"65  },66  {67    "attribute": "openai.environment",68    "expression": "assertion.subject.organizational_unit"69  }70]71```7273Certificate facts are available under `assertion.subject` and `assertion.subject_alt_names`. Transformation results used for mappings must be scalar values. Additional transformations must have unique `openai.*` names.7475For example, an **Attribute conditions** expression can restrict the provider to production certificates:7677```text78assertion.subject.organizational_unit == "Production"79```8081## Create a service account mapping82831. From the X.509 provider details page, select **Create mapping**.842. Select the target project and service account, and grant only the API permissions the workload needs.853. In the **Key** and **Value** fields, require an exact `openai.subject` value. X.509 mappings support either no assertions, represented as an empty object (`{}`), or assertions whose keys start with `openai.`.864. Select **Create**.8788For example:8990| Key              | Value                   |91| ---------------- | ----------------------- |92| `openai.subject` | `payments-service-prod` |9394X.509 mappings use derived `openai.*` attributes. They don't match raw JWT claims such as `sub`, `iss`, or `aud`.9596The provider list displays the provider ID, and the mapping details display the selected service account and its service account ID. Record both identifiers; the workload sends them during token exchange.9798## Use X.509 workload identity with an SDK99100Set environment variables for the certificate chain, private key, provider, and service account:101102```bash103export OPENAI_MTLS_CERT_CHAIN="/path/to/client-chain.pem"104export OPENAI_MTLS_KEY="/path/to/client-key.pem"105export OPENAI_IDENTITY_PROVIDER_ID="idp_example"106export OPENAI_SERVICE_ACCOUNT_ID="svc_acct_example"107```108109The certificate-chain file should contain the leaf certificate first, followed by any intermediate certificates. Don't include certificate material or a `subject_token` in the request body.110111Configure an OpenAI SDK client with these values. The SDK presents the client certificate during token exchange and API requests, routes API requests to the mTLS endpoint, and renews short-lived access tokens automatically.112113Authenticate with an X.509 client certificate114115```javascript116import { readFile } from "node:fs/promises";117118import OpenAI from "openai";119import { workloadIdentity } from "openai/auth/x509-transport";120121const certificatePath = process.env.OPENAI_MTLS_CERT_CHAIN;122const privateKeyPath = process.env.OPENAI_MTLS_KEY;123const identityProviderId = process.env.OPENAI_IDENTITY_PROVIDER_ID;124const serviceAccountId = process.env.OPENAI_SERVICE_ACCOUNT_ID;125126if (127  !certificatePath ||128  !privateKeyPath ||129  !identityProviderId ||130  !serviceAccountId131) {132  throw new Error(133    "Set OPENAI_MTLS_CERT_CHAIN, OPENAI_MTLS_KEY, OPENAI_IDENTITY_PROVIDER_ID, and OPENAI_SERVICE_ACCOUNT_ID"134  );135}136137const credential = workloadIdentity.fromX509({138  certificateChain: await readFile(certificatePath, "utf8"),139  privateKey: await readFile(privateKeyPath, "utf8"),140  identityProviderId,141  serviceAccountId,142});143144try {145  const client = new OpenAI({ credential });146  const response = await client.responses.create({147    model: "gpt-5.6-terra",148    input: "Say hello from X.509 workload identity federation.",149  });150151  console.log(response.output_text);152} finally {153  await credential.close();154}155```156157```python158import os159import ssl160161from openai import DefaultHttpx2Client, OpenAI162from openai.auth import x509_workload_identity163164tls_context = ssl.create_default_context()165tls_context.load_cert_chain(166    certfile=os.environ["OPENAI_MTLS_CERT_CHAIN"],167    keyfile=os.environ["OPENAI_MTLS_KEY"],168)169170with OpenAI(171    base_url="https://mtls.api.openai.com/v1",172    workload_identity=x509_workload_identity(173        identity_provider_id=os.environ["OPENAI_IDENTITY_PROVIDER_ID"],174        service_account_id=os.environ["OPENAI_SERVICE_ACCOUNT_ID"],175    ),176    http_client=DefaultHttpx2Client(verify=tls_context, follow_redirects=False),177) as client:178    response = client.responses.create(179        model="gpt-5.6-terra",180        input="Say hello from X.509 workload identity federation.",181    )182183    print(response.output_text)184```185186```go187package main188189import (190	"context"191	"crypto/tls"192	"fmt"193	"log"194	"net/http"195	"os"196197	"github.com/openai/openai-go/v3"198	"github.com/openai/openai-go/v3/auth"199	"github.com/openai/openai-go/v3/option"200	"github.com/openai/openai-go/v3/responses"201)202203func main() {204	certificate, err := tls.LoadX509KeyPair(205		os.Getenv("OPENAI_MTLS_CERT_CHAIN"),206		os.Getenv("OPENAI_MTLS_KEY"),207	)208	if err != nil {209		log.Fatal(err)210	}211212	transport, err := auth.NewX509Transport(&http.Transport{213		TLSClientConfig: &tls.Config{214			Certificates: []tls.Certificate{certificate},215			MinVersion:   tls.VersionTLS12,216		},217	})218	if err != nil {219		log.Fatal(err)220	}221	defer transport.Close()222223	client := openai.NewClient(224		option.WithX509WorkloadIdentity(auth.X509WorkloadIdentity{225			IdentityProviderID: os.Getenv("OPENAI_IDENTITY_PROVIDER_ID"),226			ServiceAccountID:   os.Getenv("OPENAI_SERVICE_ACCOUNT_ID"),227			Transport:          transport,228		}),229	)230231	response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{232		Model: "gpt-5.6-terra",233		Input: responses.ResponseNewParamsInputUnion{234			OfString: openai.String("Say hello from X.509 workload identity federation."),235		},236	})237	if err != nil {238		log.Fatal(err)239	}240241	fmt.Println(response.OutputText())242}243```244245```java246import com.openai.client.OpenAIClient;247import com.openai.client.okhttp.OpenAIOkHttpClient;248import com.openai.client.okhttp.X509Transport;249import com.openai.client.okhttp.X509WorkloadIdentity;250import com.openai.models.responses.ResponseCreateParams;251import java.io.InputStream;252import java.nio.file.Files;253import java.nio.file.Paths;254import java.security.KeyStore;255import java.time.Duration;256import java.util.Arrays;257import javax.net.ssl.KeyManagerFactory;258import javax.net.ssl.TrustManagerFactory;259import javax.net.ssl.X509ExtendedKeyManager;260import javax.net.ssl.X509TrustManager;261262char[] password = System.getenv("OPENAI_X509_KEYSTORE_PASSWORD").toCharArray();263try {264  KeyStore keyStore = KeyStore.getInstance("PKCS12");265  try (InputStream input =266      Files.newInputStream(Paths.get(System.getenv("OPENAI_X509_KEYSTORE_PATH")))) {267    keyStore.load(input, password);268  }269270  KeyManagerFactory keyManagers =271      KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());272  keyManagers.init(keyStore, password);273  X509ExtendedKeyManager keyManager =274      Arrays.stream(keyManagers.getKeyManagers())275          .filter(X509ExtendedKeyManager.class::isInstance)276          .map(X509ExtendedKeyManager.class::cast)277          .findFirst()278          .orElseThrow(() -> new IllegalStateException("No X.509 key manager available"));279280  TrustManagerFactory trustManagers =281      TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());282  trustManagers.init((KeyStore) null);283  X509TrustManager trustManager =284      Arrays.stream(trustManagers.getTrustManagers())285          .filter(X509TrustManager.class::isInstance)286          .map(X509TrustManager.class::cast)287          .findFirst()288          .orElseThrow(() -> new IllegalStateException("No X.509 trust manager available"));289290  X509Transport transport =291      X509Transport.builder()292          .keyManager(keyManager)293          .certificateAlias(System.getenv("OPENAI_X509_CERTIFICATE_ALIAS"))294          .trustManager(trustManager)295          .build();296  X509WorkloadIdentity identity =297      X509WorkloadIdentity.builder()298          .identityProviderId(System.getenv("OPENAI_IDENTITY_PROVIDER_ID"))299          .serviceAccountId(System.getenv("OPENAI_SERVICE_ACCOUNT_ID"))300          .transport(transport)301          .refreshBuffer(Duration.ofMinutes(10))302          .build();303304  OpenAIClient client = OpenAIOkHttpClient.builder().x509WorkloadIdentity(identity).build();305  try {306    ResponseCreateParams params =307        ResponseCreateParams.builder()308            .model("gpt-5.6-terra")309            .input("Say hello from X.509 workload identity federation.")310            .build();311312    client.responses().create(params).output().stream()313        .flatMap(item -> item.message().stream())314        .flatMap(message -> message.content().stream())315        .flatMap(content -> content.outputText().stream())316        .forEach(outputText -> System.out.println(outputText.text()));317  } finally {318    client.close();319  }320} finally {321  Arrays.fill(password, '\0');322}323```324325```ruby326require "openai"327require "openssl"328329certificate_chain = OpenSSL::X509::Certificate.load(330  File.binread(ENV.fetch("OPENAI_MTLS_CERT_CHAIN"))331)332certificate, *intermediates = certificate_chain333private_key = OpenSSL::PKey.read(File.binread(ENV.fetch("OPENAI_MTLS_KEY")))334335http_client = OpenAI::NetHTTPClient.new do |connection|336  connection.cert = certificate337  connection.extra_chain_cert = intermediates338  connection.key = private_key339end340341workload_identity = OpenAI::Auth::X509WorkloadIdentity.new(342  identity_provider_id: ENV.fetch("OPENAI_IDENTITY_PROVIDER_ID"),343  service_account_id: ENV.fetch("OPENAI_SERVICE_ACCOUNT_ID"),344  http_client: http_client345)346347begin348  client = OpenAI::Client.new(api_key: nil, workload_identity: workload_identity)349  response = client.responses.create(350    model: "gpt-5.6-terra",351    input: "Say hello from X.509 workload identity federation."352  )353354  puts(response.output_text)355ensure356  http_client.close357end358```359360361These examples require OpenAI SDK versions that support the X.509 configuration shown here: JavaScript 7.8.0 or later with the `undici` peer dependency installed, Python 3.6.0 or later, Go 3.54.0 or later, Java 4.55.0 or later, and Ruby 0.83.0 or later.362363The Java example loads a PKCS12 keystore to construct its `X509ExtendedKeyManager` and uses the platform default trust store to construct its `X509TrustManager`. Set `OPENAI_X509_KEYSTORE_PATH`, `OPENAI_X509_KEYSTORE_PASSWORD`, and `OPENAI_X509_CERTIFICATE_ALIAS` for this example. You can instead supply PEM-backed or hardware-backed managers to the SDK.364365## Exchange the certificate manually366367To inspect or implement the token exchange protocol directly, present the certificate to the X.509 token endpoint:368369```bash370curl --cert "$OPENAI_MTLS_CERT_CHAIN" \371  --key "$OPENAI_MTLS_KEY" \372  --request POST "https://mtls.auth.openai.com/oauth/token" \373  --header "Content-Type: application/json" \374  --data @- <<JSON375{376  "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",377  "subject_token_type": "urn:openai:params:oauth:token-type:x509",378  "identity_provider_id": "${OPENAI_IDENTITY_PROVIDER_ID}",379  "service_account_id": "${OPENAI_SERVICE_ACCOUNT_ID}"380}381JSON382```383384A successful exchange returns an ordinary short-lived bearer token:385386```json387{388  "access_token": "eyJ...",389  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",390  "token_type": "Bearer",391  "expires_in": 3600,392  "expires_at": 1789045200,393  "scope": "api.model.read api.model.request"394}395```396397The `scope` property is returned only when the matching service account mapping has permissions.398399The expiration values are illustrative. The returned lifetime can be shorter when the verified client certificate expires sooner. See the [token exchange response fields](https://developers.openai.com/api/reference/workload-identity-federation#response) for the units and meaning of `expires_in` and `expires_at`.400401Read the `access_token` value from the successful response into your application's credential store or an environment variable such as `OPENAI_WIF_ACCESS_TOKEN`. Treat it as a secret and don't print, log, or commit it.402403## Call the OpenAI API manually404405Set `OPENAI_MODEL` to `gpt-6-astra`, the current default, or another model available to the target project. Then send the bearer token and an accepted client certificate to the API mTLS endpoint:406407```bash408curl --request POST \409  --cert "$OPENAI_MTLS_CERT_CHAIN" \410  --key "$OPENAI_MTLS_KEY" \411  --header "Authorization: Bearer $OPENAI_WIF_ACCESS_TOKEN" \412  --header "Content-Type: application/json" \413  --data "{\"model\":\"$OPENAI_MODEL\",\"input\":\"Say hello in one sentence.\"}" \414  "https://mtls.api.openai.com/v1/responses"415```416417Use the bearer token instead of an API key, and continue to present an accepted client certificate on the API request.418419The bearer isn't cryptographically bound to the certificate. Reusing the exchange certificate for the API request is the most direct configuration, but the API request can use another certificate that independently satisfies the same current API mTLS policy.420421## Token lifetime and renewal422423An X.509 workload identity token expires after at most one hour and never outlives the verified client certificate. The exchange doesn't return a refresh token. Repeat the certificate exchange to obtain another access token.424425For manual exchanges, keep `expires_at` with the access token and schedule another exchange before that timestamp. Allow for clock differences and request latency. See [token renewal guidance](https://developers.openai.com/api/docs/guides/workload-identity-federation#renew-the-access-token) for an example.426427Rotating an intermediate certificate doesn't require changing the configured root. Present the new complete chain on subsequent exchanges and API requests.428429## Troubleshoot token exchange430431X.509 token exchange returns generic OAuth errors and doesn't expose certificate, root, provider, or mapping details.432433| Result                  | Typical causes                                                                                                                                                                                           |434| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |435| HTTP `403`              | The request used a method or path other than exact `POST /oauth/token` on `mtls.auth.openai.com`.                                                                                                        |436| `invalid_subject_token` | The TLS client certificate is missing or invalid, the presented chain can't reach an active root, the certificate is outside its validity period, or a Mutual TLS certificate-admission rule rejects it. |437| `invalid_grant`         | The provider or mapping is invalid or disabled, a provider **Attribute conditions** expression rejects the identity, no applicable roots are active, or no mapping matches.                              |438| Server error            | OpenAI returned a temporary server error. Retry according to your normal transient-error policy.                                                                                                         |439440An X.509 exchange never falls back to an OIDC or ordinary OAuth flow.441442## Limitations443444- X.509 Workload Identity Providers don't maintain a separate certificate trust store.445- The bearer token isn't certificate-bound and doesn't use DPoP or a `cnf` claim.446- The certificate exchange isn't certificate-only API authorization. API requests still require the bearer token and an accepted client certificate.447- OpenAI doesn't fetch missing intermediate certificates from AIA URLs. Present the complete chain during TLS negotiation.448- OpenAI doesn't perform certificate revocation list (CRL) or OCSP checks during this flow. Plan certificate incident response around Mutual TLS root, provider, and mapping controls and the short lifetime of issued tokens.449- This flow doesn't add support for SPIFFE X.509-SVIDs. The [SPIFFE guide](https://developers.openai.com/api/docs/guides/workload-identity-federation/spiffe) continues to use JWT-SVIDs.
Files main
Menu

Theme