OpenAI のサービスアカウントは、プロジェクトが所有する非人間 ID です。Terraform では、デフォルトのロールを割り当てずにアカウントを作成し、必要最小限の権限セットを定義して、グループを通じて割り当てることができます。サービスアカウントの API キーは、Terraform の外部で管理 API を通じて作成・管理します。
このガイドでは、サービスアカウントの一般的な導入ワークフローに沿って説明します。
- デフォルトのプロジェクトロールや API キーを持たないサービスアカウントを作成します。
- グループを通じてカスタムプロジェクトロールを割り当て、ワークロードに必要な権限だけを付与します。
- スコープを限定した API キーを作成し、シークレットマネージャーに保存します。
事前準備
Terraform プロバイダーのセットアップを完了し、管理 API キーを OPENAI_ADMIN_KEY として、既存プロジェクトの ID を PROJECT_ID として環境変数にエクスポートします。
サービスアカウントの作成、インポート、置き換え、削除を試す際は、テスト用の組織を使用してください。
デフォルトのロールを持たないサービスアカウントの作成
Terraform でサービスアカウントを作成します。
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 キーの生成やデフォルトのプロジェクトロールの割り当てを行わずに、サービスアカウントの ID を作成します。Terraform は、サービスアカウントの ID とその他の機密ではないメタデータをステートに保存します。この段階では、サービスアカウントにプロジェクトの権限はありません。
必要最小限の権限の割り当て
ワークロードに必要な権限だけを含むカスタムプロジェクトロールを定義します。グループを作成してサービスアカウントを追加し、そのグループにロールを割り当てます。この例では、グループのメンバーがレスポンスを作成できるようにします。
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 を、ワークロード用に承認された必要最小限の権限セットに置き換えてください。グループを通じたプロジェクトへのアクセスについて詳しくは、プロジェクトとアクセスを参照してください。
構成をレビューして適用します。
terraform plan
terraform apply
カスタムプロジェクトロールでワークロードに必要な権限を付与できる場合は、
組み込みの member ロールや owner ロールを割り当てないでください。アクセスは、
承認された権限セットの範囲に限定してください。
スコープを限定した API キーの作成
Terraform の構成を適用したら、プロジェクトサービスアカウントの API キーを作成エンドポイントを通じて API キーを作成します。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 を表示せずに、承認済みのシークレットマネージャーのワークフローに渡します。シークレットマネージャーでシークレットを保存して検証したら、レスポンスファイルを削除します。
rm service-account-api-key.json
service-account-api-key.json は、削除するまでシークレットとして扱ってください。このファイルをコミットしたり、キーを Terraform の構成に記述したり、Terraform の出力を通じて公開したり、Terraform の変数として渡したりしないでください。
API リファレンスには、レスポンスの構造と言語別の例が記載されています。ワークロード ID フェデレーションに対応するワークロードでは、API キーを作成せずに、同じサービスアカウントと最小権限のロールを使用できます。
既存のサービスアカウントのインポート
Terraform で作成したサービスアカウントをインポートする必要はありません。Terraform の外部で作成したサービスアカウントを管理対象にするには、既存のアカウントと同じプロジェクト ID と名前で宣言します。
resource "openai_project_service_account" "application" {
project_id = "proj_123"
name = "example-application-development-service-account"
}
通常の適用を実行する前に、既存の ID をインポートします。
SERVICE_ACCOUNT_ID="<existing-service-account-id>"
terraform import \
openai_project_service_account.application \
"$PROJECT_ID/$SERVICE_ACCOUNT_ID"
terraform plan
インポート後の最初のプランでは、サービスアカウントへの変更が提案されないことを確認してください。置き換えが提案される場合は、適用する前に、構成内の名前とプロジェクトを既存のアカウントに一致させてください。
インポートによって API キーが復元・保存されたり、サービスアカウントの既存のプロジェクトロールが変更されたり、グループメンバーシップがインポートされたりすることはありません。既存の openai_project_role、openai_group、openai_group_user、openai_project_group_role リソースを Terraform で管理する場合は、それらを宣言してインポートしてください。ワークロードは引き続きシークレットマネージャーから既存のシークレットを読み取ります。
リソース宣言を適用する前に、サービスアカウントをインポートしてください。先に適用すると、Terraform は既存の ID を管理対象に取り込むのではなく、別のサービスアカウントを作成します。
認証情報の復旧またはローテーション
API キーの完全な値を取得できるのは、API キー作成時のレスポンスだけです。その後にプロジェクトの API キーを取得しても、値はマスキングされて返されるため、紛失したキーを復元することはできません。
ワークロードを中断することなく、紛失した認証情報やローテーション対象の認証情報を置き換えます。
- 古いアカウントとは異なる Terraform リソース名を使い、置き換え用のアカウントを新しい
openai_project_service_accountリソースとして宣言します。 - 構成を適用して、置き換え用のサービスアカウントを作成します。
openai_group_userを使って置き換え用のアカウントを既存のグループに追加し、最小権限のプロジェクトロールを継承させます。- 管理 API を通じて置き換え用のアカウントの API キーを作成し、承認済みのシークレットマネージャーのワークフローでキーを保存します。
- 置き換え用のキーをデプロイし、置き換え用のアカウントでワークロードの動作を検証します。
- 古い
openai_project_service_accountと、それに対応するopenai_group_userリソースを Terraform の構成から削除します。置き換え用のサービスアカウントが引き続き使用するロール、グループ、グループへのロール割り当ては残してください。 - 古いサービスアカウントとそのグループメンバーシップを削除するプランをレビューして適用し、その後
terraform planを実行して、変更なしという結果になることを必ず確認してください。
openai_project_service_account リソースを削除すると、リモートのサービスアカウントが削除されます。この変更には明示的なレビューを必須にしてください。特に、古い認証情報がまだトラフィックの処理に使われている場合は注意が必要です。
ステートへの取り込みと削除の動作について詳しくは、インポートと整合性の確保を参照してください。
完全なサンプルの実行
これまでの個別の例では、具体的な値を使って、サービスアカウントの作成、ロールの割り当て、API キーの作成を説明しました。完全な構成では、プロジェクト固有の値や権限を変数に置き換え、複数の環境で再利用できるようにします。
次の構成を main.tf として保存します。
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
}
既存のプロジェクト ID、一意のサービスアカウント名、承認された必要最小限のプロジェクト権限セットを指定して、terraform.tfvars を作成します。
project_id = "proj_123"
service_account_name = "example-application-development-service-account"
project_role_permissions = [
"api.responses.write",
]
Terraform を初期化してから、保存したプランをレビューして適用します。
terraform init
terraform fmt
terraform validate
terraform plan -out=tfplan
terraform show tfplan
terraform apply tfplan
最初のプランには、追加するリソースとして、サービスアカウント、そのカスタムプロジェクトロール、グループ、グループメンバーシップ、グループへのロール割り当ての 5 つが含まれていることを確認してください。terraform plan を再度実行し、この構成による追加の変更がないことを確認します。
Terraform の外部でサービスアカウントの API キーを作成します。
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 の構成、ステート、出力に保存しないでください。