For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
主导航

为 SPIFFE 配置工作负载身份联合

通过将 SPIFFE JWT-SVID 交换为短期 OpenAI 访问令牌,将 SPIFFE 用作工作负载身份提供方。这样,经 SPIRE 或其他兼容 SPIFFE 的身份提供方认证的工作负载无需存储长期 API 密钥,即可调用 OpenAI API。

对于 Codex,请按照本页说明获取并检查 JWT-SVID。然后配置 Codex 工作负载身份,将该 Token 写入文件,并让 Codex 从该文件读取。本页的服务账户映射和 SDK 示例适用于 OpenAI API。

OpenAI 支持可作为 JWT 主体 Token 进行验证的 SPIFFE JWT-SVID,这些 JWT-SVID 需包含签发者、受众、过期时间、签发时间戳以及可通过 JWKS 验证的签名。OpenAI 不支持将 SPIFFE X.509-SVID 用作工作负载身份联合的主体 Token。

JWT-SVID 规范要求包含 subaudexp 声明。要在 OpenAI 中使用 JWT-SVID,Token 还必须包含 issiat 声明以及 kid 标头,以便 OpenAI 根据工作负载身份提供方的配置验证 Token。

JWT-SVID 不是 OpenID Connect ID Token。SPIRE OIDC Discovery Provider 提供发现元数据和 JWKS 密钥,以便 OpenAI 验证 JWT-SVID;它不会改变 Token 的 SPIFFE 语义,也不要求使用 OIDC 登录流程。

有关 SPIFFE 术语和 Token 要求,请参阅 SPIFFE JWT-SVID 规范Workload API 规范

设置 SPIFFE

配置您的 SPIFFE 提供方,为需要调用 OpenAI API 的工作负载签发 JWT-SVID。这些说明使用 SPIRE 术语,但同样的 OpenAI 配置也适用于任何兼容 SPIFFE 的提供方,只要其签发的 JWT-SVID 包含 OpenAI 可验证的签发者信息,并提供相应的 JWKS 签名材料。

您的 SPIFFE 配置必须提供:

  • 工作负载的稳定 SPIFFE ID,例如 spiffe://example.org/ns/production/sa/openai-wif
  • 一个专用于访问 OpenAI 的 JWT-SVID 受众,例如 https://api.openai.com/v1,或您选择的其他不透明值。
  • 出现在 JWT-SVID 的 iss 声明中的 JWT 签发者 URL,供 OpenAI 验证。
  • JWT-SVID 签名密钥对应的公钥 JWKS,可通过 OIDC 发现或上传 JWKS 的方式提供。
  • 工作负载端从 SPIFFE Workload API 获取新 JWT-SVID 的方式。

受众是需要精确匹配的标识符,不一定是接收 JWT-SVID 的端点。您可以使用 https://api.openai.com/v1 或其他特定于服务的值,只要 SPIFFE Workload API 请求与 OpenAI 提供方配置中的值一致即可。

尽可能通过您的 SPIRE OIDC Discovery Provider 对外提供 SPIFFE 签发者信息。将 SPIRE Server 的 jwt_issuer 和 OIDC Discovery Provider 的 jwt_issuer 设置为同一个 HTTPS 签发者 URL,并在 OpenAI 中配置相同的 URL。

在 SPIRE Server 配置中:

server {
  trust_domain = "example.org"
  jwt_issuer   = "https://spire-oidc.example.org"
}

在单独的 SPIRE OIDC Discovery Provider 配置中:

# Relevant issuer fields only
domains    = ["spire-oidc.example.org"]
jwt_issuer = "https://spire-oidc.example.org"

OIDC Discovery Provider 配置还需要指定密钥材料来源,例如 server_apiworkload_apifile,以及提供服务的机制,例如 ACME、TLS 证书或 Unix 套接字。有关完整的配置选项,请参阅 SPIRE OIDC Discovery Provider 文档

SPIFFE 信任域和 JWT 签发者是不同的概念。在此示例中,JWT-SVID 的主体是 example.org 信任域中的 SPIFFE ID,而签发者是 HTTPS 签发者 URL:

{
  "sub": "spiffe://example.org/ns/production/sa/openai-wif",
  "iss": "https://spire-oidc.example.org"
}

SPIRE OIDC Discovery Provider 提供 OIDC 发现文档和 JWKS 端点;当 使用上传的 JWKS 验证 Token 处于禁用状态时,OpenAI 可以使用它们。

如果 OpenAI 无法访问您的签发者发现端点,请改用上传 JWKS 模式。在此模式下,OpenAI 仍会将工作负载身份提供方的签发者与 JWT-SVID 的 iss 声明进行比较,但会使用您保存在工作负载身份提供方中的 JWKS JSON 验证签名。

注意: SPIFFE JWT-SVID 规范将 JWT 标头 kid 定义为可选项,但 OpenAI 要求 JWT 主体 Token 包含 kid 标头,以便从配置的 JWKS 中选择签名密钥。如果您的 SPIFFE 提供方允许省略 kid,请将其配置为包含该标头,以用于 OpenAI 工作负载身份联合。

要检查能够调用 SPIFFE Workload API 的工作负载所获取的 JWT-SVID,请使用您将在 OpenAI 中配置的同一受众发起请求。请在与应用相同的工作负载上下文中运行此命令,因为 Workload API 的授权取决于调用进程的身份。

TOKEN=$(spire-agent api fetch jwt \
  -socketPath /run/spire/sockets/agent.sock \
  -audience "https://api.openai.com/v1" | sed -n '2p')
export TOKEN

如果您的工作负载有多个 SPIFFE ID,请在请求中指定具体身份:

TOKEN=$(spire-agent api fetch jwt \
  -socketPath /run/spire/sockets/agent.sock \
  -spiffeID "spiffe://example.org/ns/production/sa/openai-wif" \
  -audience "https://api.openai.com/v1" | sed -n '2p')
export TOKEN

验证 Token

在配置工作负载身份联合之前,请将 JWT-SVID 导出为环境变量 TOKEN,然后在本地运行以下任一示例,检查其标头和声明:

const parts = process.env.TOKEN?.split(".") ?? [];
if (parts.length !== 3) {
  throw new Error("Expected a compact JWT with three segments");
}

const decode = (segment) => {
  if (!/^[A-Za-z0-9_-]+$/.test(segment) || segment.length % 4 === 1) {
    throw new Error("JWT segment is not valid Base64URL");
  }
  const bytes = Buffer.from(segment, "base64url");
  if (bytes.toString("base64url") !== segment) {
    throw new Error("JWT segment is not valid Base64URL");
  }
  const decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
  const value = JSON.parse(decoded);
  if (value === null || Array.isArray(value) || typeof value !== "object") {
    throw new Error("JWT segment is not a JSON object");
  }
  return decoded;
};

console.log("Header:");
console.log(decode(parts[0]));
console.log("\nPayload:");
console.log(decode(parts[1]));

每个示例都会解码 JWT,但不会验证 Token 签名。请使用本地解码器处理生产环境 Token,避免将生产环境 Token 粘贴到第三方工具中。

解码后的 SPIFFE JWT-SVID 类似如下:

{
  "alg": "ES256",
  "kid": "jwt-svid-key-1"
}
{
  "iss": "https://spire-oidc.example.org",
  "aud": ["https://api.openai.com/v1"],
  "sub": "spiffe://example.org/ns/production/sa/openai-wif",
  "iat": 1716235422,
  "exp": 1716235722
}

在交换 Token 之前,请根据解码结果,将收到的 Token 与 OpenAI 配置进行比较。检查标头中的 algkid,以及载荷中的 issaudsubiatexpalg 的具体值取决于您的 SPIRE Server JWT 签名密钥配置。

设置工作负载身份联合

在 OpenAI 中为 SPIFFE JWT-SVID 签发者创建工作负载身份提供方,然后添加与您信任的 SPIFFE ID 匹配的服务账户映射。

设置工作负载身份提供方

  1. 创建工作负载身份提供方。名称 设置为唯一值,例如 spiffe-prod。填写 描述,例如 Production SPIFFE workloads,以帮助管理员识别该提供方。

  2. 设置签发者和受众。OIDC 签发者 URL 设置为与 JWT-SVID 的 iss 声明完全相同的值,例如 https://spire-oidc.example.org。将 受众 设置为向 SPIFFE Workload API 发起请求时使用的受众值。在此示例中,该值为 https://api.openai.com/v1

  3. 选择 JWKS 来源。 当 OpenAI 可以访问您的 SPIRE OIDC Discovery Provider 时,请保持 使用上传的 JWKS 验证 Token 处于禁用状态。OpenAI 会使用 OIDC 发现及其发现的 JWKS 来验证 JWT-SVID 签名。

    如果 OpenAI 无法访问签发者,请启用 使用上传的 JWKS 验证 Token,然后将 JWKS JSON 设置为 JWT-SVID 签名密钥对应的公钥集。上传完整的公钥 JWKS 对象,包括包裹公钥的 keys 数组。请勿包含私钥材料。

  4. 仅在需要派生映射属性时添加属性转换。 直接根据 sub 进行映射时,无需属性转换。仅在需要从一个或多个 Token 声明派生映射值时使用属性转换。有关转换行为,请参阅工作负载身份联合主指南

设置服务账户映射

  1. 创建服务账户映射。名称 设置为在该工作负载身份提供方内唯一的值,例如 production-openai-wif。填写 描述,例如 Production SPIFFE workload for OpenAI API access,以说明哪个工作负载可以使用此映射。

  2. 匹配 SPIFFE ID。 设置为 sub,将 设置为工作负载的 SPIFFE ID,例如 spiffe://example.org/ns/production/sa/openai-wif

    对于具有特权的工作负载,优先使用精确的 SPIFFE ID 匹配。只有当该前缀下的每个 SPIFFE ID 都应能够获取新签发的 OpenAI 访问令牌时,才使用尾部通配符。例如,spiffe://example.org/ns/production/sa/* 允许任何匹配的生产环境服务账户路径。

  3. 选择 OpenAI 目标。项目 设置为目标服务账户所属的 OpenAI 项目。将 服务账户 设置为 SPIFFE 工作负载可以使用的 OpenAI 服务账户,例如 spiffe-prod-openai-wif。如果您希望为此映射创建新的服务账户,而不是复用现有账户,请勾选 Create a new service account in this project

  4. 根据需要缩小 API 权限范围。 选择适当的 权限 ,例如 api.model.requestapi.vector_store.read,以进一步限制通过此映射签发的访问令牌的权限。将权限留空可避免添加 WIF 特有的作用域限制;Token 仍以映射到的服务账户身份获得授权。

在代码中使用 Token

配置您的 OpenAI SDK 客户端,将新的 SPIFFE JWT-SVID 交换为 OpenAI 签发的访问令牌。

以下 SDK 示例假定您的 SPIFFE 集成会刷新 JWT-SVID 并将其写入 /var/run/spiffe/openai.jwt。请确保只有该工作负载可以读取此文件。由于 JWT-SVID 有效期较短,请在 Token 过期之前刷新文件。另一种方法是,在条件允许时,在主体 Token 提供程序中使用对应编程语言的 SPIFFE 库,直接从 SPIFFE Workload API 获取 JWT-SVID,以避免使用过期的 Token 文件。

在工作负载环境中设置 OPENAI_IDENTITY_PROVIDER_IDOPENAI_SERVICE_ACCOUNT_ID。Token 文件包含外部主体 Token。OPENAI_IDENTITY_PROVIDER_ID 标识 OpenAI 工作负载身份提供方,OPENAI_SERVICE_ACCOUNT_ID 标识目标 OpenAI 服务账户。然后,OpenAI 会根据 Token 声明,为该提供方和服务账户查找匹配的映射。

使用 SPIFFE JWT-SVID 进行身份认证
import { readFile } from "node:fs/promises";
import OpenAI from "openai";

const tokenPath = "/var/run/spiffe/openai.jwt";
const identityProviderId = process.env.OPENAI_IDENTITY_PROVIDER_ID;
const serviceAccountId = process.env.OPENAI_SERVICE_ACCOUNT_ID;

if (!identityProviderId || !serviceAccountId) {
  throw new Error(
    "Set OPENAI_IDENTITY_PROVIDER_ID and OPENAI_SERVICE_ACCOUNT_ID"
  );
}

function spiffeJwtSvidProvider(path) {
  return {
    tokenType: "jwt",
    getToken: async () => {
      const token = (await readFile(path, "utf8")).trim();
      if (!token) {
        throw new Error("The SPIFFE JWT-SVID file is empty.");
      }
      return token;
    },
  };
}

const client = new OpenAI({
  workloadIdentity: {
    identityProviderId,
    serviceAccountId,
    provider: spiffeJwtSvidProvider(tokenPath),
  },
});

const response = await client.responses.create({
  model: "gpt-5.6-terra",
  input: "Say hello from SPIFFE workload identity federation.",
});

console.log(response.output_text);

SPIFFE 最佳实践

  • 使用 JWT-SVID 进行 OpenAI 工作负载身份联合。X.509-SVID 适用于双向 TLS,但 OpenAI Token 交换端点不接受它。
  • 使用单一的专用受众来访问 OpenAI。避免使用范围过广的受众,例如整个信任域或环境名称。
  • 尽可能精确匹配 SPIFFE ID。仅在有意共享信任边界时使用通配符映射。
  • 将 JWT-SVID 的有效期设得较短,以降低持有者 Token 的重放风险。OpenAI 访问令牌的过期时间绝不会晚于用于交换的外部主体 Token 的过期时间。
  • 谨慎轮换签名密钥。在轮换窗口内通过 OIDC 发现同时发布新旧公钥,或者在签发包含新 kid 的 JWT-SVID 之前,更新上传的公钥 JWKS。
  • 保持 SPIRE Server 和工作负载的时钟同步。较大的时钟偏差可能导致原本有效的 JWT-SVID 因被判定为尚未生效、签发时间过早或已过期而被拒绝。
  • 保护 SPIFFE Workload API 套接字。能够获取工作负载 JWT-SVID 的进程可以尝试用它换取 OpenAI 访问权限。
  • 使 OpenAI 服务账户的边界与您的应用和环境的权限边界保持一致。不要在互不相关的 SPIFFE 工作负载之间共享高权限服务账户。
  • 监控因签发者、受众、签名密钥或映射不匹配而导致的 Token 交换失败。