OpenAI Terraform 服务账户管理

2026-11-09 15:53 更新

OpenAI 服务账户是归属于项目的非人类身份。Terraform 可以创建不带默认角色的账户、定义遵循最小权限原则的权限集合,再通过群组分配该集合。服务账户 API 密钥应在 Terraform 之外通过管理 API 创建和管理。

本文采用典型的服务账户接入流程:

  1. 创建不带默认项目角色或 API 密钥的服务账户。
  2. 通过群组分配自定义项目角色,仅授予工作负载所需的权限。
  3. 创建限定作用域的 API 密钥,并将其保存在密钥管理器中。

开始之前

完成 Terraform 提供程序配置,将管理 API 密钥导出为 OPENAI_ADMIN_KEY,将已有项目 ID 导出为 PROJECT_ID。

评估服务账户创建、导入、替换和删除流程时,使用测试组织。

创建不带默认角色的服务账户

使用 Terraform 创建服务账户:

来源示例 1(terraform):

## [中文] 创建不带默认项目角色和API密钥的服务账户,并输出其service_account_id;账户此时还没有项目权限。
resource "openai_project_service_account" "application" {
  project_id = "proj_123"
  name       = "example-application-development-service-account"
}

output "service_account_id" {
  value = openai_project_service_account.application.service_account_id
}

将 proj_123 替换为归属该服务账户的已有项目 ID。

提供程序创建服务账户身份时,不会生成 API 密钥,也不会分配默认项目角色。Terraform 将服务账户 ID 及其他非敏感元数据保存在状态中。在此阶段,该服务账户没有项目权限。

分配最小权限

定义仅包含工作负载所需权限的自定义项目角色,创建群组,将服务账户加入群组,再向群组分配角色。此示例允许群组成员创建响应:

来源示例 2(terraform):

## [中文] 创建最小权限项目角色与组织群组,将服务账户身份加入群组,再将角色分配给群组;id用于成员关系,service_account_id用于账户API。
resource "openai_project_role" "application" {
  project_id  = openai_project_service_account.application.project_id
  role_name   = "Application response writer"
  description = "Allows the application to create responses"
  permissions = ["api.responses.write"]
}

resource "openai_group" "application_access" {
  name = "example-application-development-access"
}

resource "openai_group_user" "application" {
  group_id = openai_group.application_access.group_id
  user_id  = openai_project_service_account.application.id
}

resource "openai_project_group_role" "application_access" {
  project_id = openai_project_service_account.application.project_id
  group_id   = openai_group.application_access.group_id
  role_id    = openai_project_role.application.role_id
}

openai_project_role 定义最小权限集合,openai_group_user 将服务账户加入群组,openai_project_group_role 将角色分配给群组。加入该群组的每个服务账户都会继承相同的项目角色。将 api.responses.write 替换为工作负载经过批准的最小权限集合。基于群组的项目访问详见项目与访问权限。

检查并应用配置:

来源示例 3(bash):

## [中文] 先查看计划,再应用角色与群组配置;apply会修改远程身份和授权关系。
terraform plan
terraform apply

如果自定义项目角色已能提供工作负载所需权限,就不要分配内置的 member 或 owner 角色。将访问权限限制在经过批准的权限集合内。

创建限定作用域的 API 密钥

应用 Terraform 配置后,通过创建项目服务账户 API 密钥端点创建密钥。API 仅返回一次完整密钥值,因此发起请求前应先保护响应文件:

来源示例 4(bash):

## [中文] 读取账户ID并以umask 077限制新文件权限,调用管理API创建限定作用域的密钥,将仅返回一次的完整密钥保存到受保护响应文件;不得打印或提交该文件。
SERVICE_ACCOUNT_ID="$(terraform output -raw service_account_id)"
umask 077

curl -X POST \
  "https://api.openai.com/v1/organization/projects/$PROJECT_ID/service_accounts/$SERVICE_ACCOUNT_ID/api_keys" \
  -H "Authorization: Bearer $OPENAI_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production App",
    "scopes": ["api.responses.write"]
  }' \
  --output service-account-api-key.json

选择工作负载所需的最窄作用域。API 密钥作用域可以进一步收紧服务账户权限,但不能授予其项目角色之外的权限。

将 service-account-api-key.json 中的 value 交给批准的密钥管理流程,过程中不要将其打印出来。密钥管理器完成保存并验证后,删除响应文件:

来源示例 5(bash):

## [中文] 确认密钥已保存并验证于批准的密钥管理器后,删除本地含密钥响应文件;本命令不删除远程API密钥。
rm service-account-api-key.json

只要 service-account-api-key.json 仍然存在,就应将它视为机密文件。不要将其提交到版本库,不要把密钥写入 Terraform 配置、通过 Terraform 输出公开,也不要将它作为 Terraform 变量传入。

API 参考提供响应结构和各语言示例。支持工作负载身份联合的工作负载可以使用同一服务账户和最小权限角色,而无须创建 API 密钥。

导入已有服务账户

由 Terraform 创建的服务账户无须再次导入。要接管在 Terraform 之外创建的服务账户,应使用相同的项目 ID 和名称进行声明:

来源示例 6(terraform):

## [中文] 按已有账户的项目ID与名称声明资源;此时应先导入,直接apply会创建另一个账户。
resource "openai_project_service_account" "application" {
  project_id = "proj_123"
  name       = "example-application-development-service-account"
}

在运行常规应用操作前,先导入已有身份:

来源示例 7(bash):

## [中文] 用项目ID/服务账户ID导入已有身份到状态,再生成计划;不恢复密钥,也不自动导入群组和角色关系。
SERVICE_ACCOUNT_ID="<existing-service-account-id>"

terraform import \
  openai_project_service_account.application \
  "$PROJECT_ID/$SERVICE_ACCOUNT_ID"

terraform plan

导入后的第一个计划不应提出修改服务账户。如果提出替换,先让配置中的名称和项目与已有账户一致,再应用。

导入不会恢复或保存 API 密钥,不会改变服务账户已有的项目角色,也不会导入其群组成员关系。如果 Terraform 应管理这些关系,还须声明并导入已有的 openai_project_role、openai_group、openai_group_user 和 openai_project_group_role 资源。工作负载继续从密钥管理器读取原有机密。

先导入服务账户,再应用资源声明。如果先应用,Terraform 会创建另一个服务账户,而不是接管已有身份。

恢复或轮换凭证

完整 API 密钥值仅在创建密钥的响应中提供。以后检索项目 API 密钥时只会得到经过遮蔽的值,因此无法找回遗失的密钥。

要在不中断工作负载的情况下替换遗失的密钥或轮换凭证:

  1. 将替代账户声明为新的 openai_project_service_account 资源,使用与旧账户不同的 Terraform 资源名称。
  2. 应用配置,创建替代服务账户。
  3. 通过 openai_group_user 将替代账户加入已有群组,使其继承遵循最小权限原则的项目角色。
  4. 通过管理 API 为替代账户创建 API 密钥,并使用批准的密钥管理流程保存。
  5. 部署替代密钥,并验证工作负载能够使用替代账户运行。
  6. 从 Terraform 配置中移除旧的 openai_project_service_account 及其 openai_group_user 资源;保留替代服务账户仍在使用的角色、群组及群组角色分配。
  7. 检查并应用删除旧服务账户及其群组成员关系的计划,然后运行 terraform plan,要求结果为无变更。

删除 openai_project_service_account 资源会删除远程服务账户。此变更必须经过明确检查,尤其在旧凭证仍承载流量时。

更广泛的状态接管及资源移除行为见导入与状态协调。

运行完整示例

前面的聚焦示例使用具体值解释服务账户创建、角色分配和 API 密钥创建。完整配置将项目相关值和权限改为变量,使其可跨环境复用。

将以下配置保存为 main.tf:

来源示例 8(terraform):

## [中文] 完整创建服务账户、最小权限角色、群组、成员关系与角色分配;输出非敏感ID,不生成或输出API密钥。
terraform {
  required_version = ">= 1.0"

  required_providers {
    openai = {
      source  = "openai/openai"
      version = ">= 1.0.0"
    }
  }
}

provider "openai" {}

variable "project_id" {
  type        = string
  description = "ID of the existing OpenAI project."
}

variable "service_account_name" {
  type        = string
  description = "Name of the application service account."
}

variable "project_role_permissions" {
  type        = list(string)
  description = "Least-privilege project permissions for the application."

  validation {
    condition     = length(var.project_role_permissions) > 0
    error_message = "Provide at least one approved project permission."
  }
}

resource "openai_project_service_account" "application" {
  project_id = var.project_id
  name       = var.service_account_name
}

resource "openai_project_role" "application" {
  project_id  = var.project_id
  role_name   = "Application API access"
  description = "Least-privilege permissions approved for the application"
  permissions = var.project_role_permissions
}

resource "openai_group" "application_access" {
  name = "${var.service_account_name}-access"
}

resource "openai_group_user" "application" {
  group_id = openai_group.application_access.group_id
  user_id  = openai_project_service_account.application.id
}

resource "openai_project_group_role" "application_access" {
  project_id = var.project_id
  group_id   = openai_group.application_access.group_id
  role_id    = openai_project_role.application.role_id
}

output "project_id" {
  value = var.project_id
}

output "service_account_id" {
  value = openai_project_service_account.application.service_account_id
}

output "group_id" {
  value = openai_group.application_access.group_id
}

output "project_role_id" {
  value = openai_project_role.application.role_id
}

创建 terraform.tfvars,填入已有项目 ID、唯一的服务账户名称,以及经过批准的最小项目权限集合:

来源示例 9(terraform):

## [中文] 填写已有项目、唯一账户名称及获准的最小项目权限;示例只允许创建Responses。
project_id           = "proj_123"
service_account_name = "example-application-development-service-account"

project_role_permissions = [
  "api.responses.write",
]

初始化 Terraform,然后检查并应用保存的计划:

来源示例 10(bash):

## [中文] 初始化、格式化并检查,保存并显示计划后应用;预期新增五项身份和权限资源,之后须再次计划确认无变更。
terraform init
terraform fmt
terraform validate
terraform plan -out=tfplan
terraform show tfplan
terraform apply tfplan

第一个计划应包含五个待新增资源:服务账户、自定义项目角色、群组、群组成员关系、群组角色分配。再次运行 terraform plan,确认配置不会继续产生变更。

在 Terraform 之外创建服务账户 API 密钥:

来源示例 11(bash):

## [中文] 从Terraform输出读取两个ID,在Terraform之外通过管理API创建密钥,响应写入受保护文件;随后应移入密钥管理器并删除文件,不进入Terraform状态或输出。
PROJECT_ID="$(terraform output -raw project_id)"
SERVICE_ACCOUNT_ID="$(terraform output -raw service_account_id)"
umask 077

curl -X POST \
  "https://api.openai.com/v1/organization/projects/$PROJECT_ID/service_accounts/$SERVICE_ACCOUNT_ID/api_keys" \
  -H "Authorization: Bearer $OPENAI_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production App",
    "scopes": ["api.responses.write"]
  }' \
  --output service-account-api-key.json

将返回的 API 密钥值移入批准的密钥管理器,然后删除 service-account-api-key.json。不要把密钥存入 Terraform 配置、状态或输出。

以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号