はじめに
この記事は「MIXI DEVELOPERS Advent Calendar 2025」14日目の記事になります。
こんにちはこんにちは、しょっさん( @syossan27 )です。
今回は、より適切な権限管理を行うために、一時権限付与プロセスをAIを活用して便利にしてみた、という内容をご紹介いたします。
背景と課題
SRE にとって、サービスのセキュリティを担保するための権限管理は非常に重要です。 弊チームでは、 Slack botとOpenAI API を組み合わせ、Google Cloud プロジェクトの一時権限を自然言語で付与する仕組み を開発しました。
従来の権限付与の問題点
従来の一時権限付与はSlackでスラッシュコマンドを利用して、例えば以下のように実行していました。
/fansta grant roles/container.developer hoge@example.com
ただし、一時権限付与のたびにこれを思い出して実行してもらうのは中々開発チームにとって骨ではあります。
また「やりたい作業に必要な権限はどれか?」という質問がSREsに寄せられることもしばしばあり、運用上の壁となっていました。
目指した解決策
これらの課題を解決するために、以下のような要件を設定しました:
- 自然言語での権限要求: 「Compute Engineの管理権限を〇〇さんに付与して」のような自然な表現で権限を要求
- 一時的な権限付与: これまでと変わらず、有効期限付きの権限付与(デフォルト7日間)
- 適切な権限の付与: 自然言語で指定した適切な権限を検索・付与
- 承認フロー: 権限付与前の確認プロセス
こうした仕組みによって、開発チームとSREsのお互いにとって嬉しい状態を実現しようとしました。
また、自然言語での操作を許可する以上、意図しない権限や対象者を解釈してしまうリスクがあります。 そのため、最終的には人間の承認を挟んで安全に実行できるよう設計しました。
システム概要
アーキテクチャ
システムは以下のコンポーネントで構成されています:

コンポーネントごとの動きとしてはザッとこのようになります。
- Slack bot handler: Slackのメンションイベントを受信し、Pub/Subメッセージを送信。Slack bot は 3 秒以内に応答が必要なため、この時点では「処理中です」と返す
- Slack bot backend: Pub/Subメッセージを受信し、OpenAI API(Responses API)を呼び出す
- OpenAI API(Responses API): 自然言語を解析し、必要に応じて Function Calling で一時権限付与関数を呼び出す
- Google Cloud IAM API: 条件付きロールバインディングを使って一時権限を付与
それでは、次は実装の詳細を見ていきましょう。
実装詳細
1. Slackイベントの処理
まず、Slackからのメンション(@bot)イベントを受信するCloud Functionsの実装になります。
func handleSlackEvent(w http.ResponseWriter, r *http.Request) { // Slackイベントの解析 var eventCallback SlackEventCallback if err := json.NewDecoder(bytes.NewReader(bodyBytes)).Decode(&eventCallback); err != nil { log.Printf("Error decoding JSON: %v", err) return } switch eventCallback.Type { case "event_callback": var appMentionEvent AppMentionEvent if err := json.Unmarshal(eventCallback.Event, &appMentionEvent); err == nil { if appMentionEvent.Type == "app_mention" { // Pub/Subに送信 publishToPubSub(args, event.Channel, event.User) } } } }
まずはSlackから飛んできたリクエスト内容を読み取り、そのままSlack bot backendへPub/Subを通して投げます。
ここには記載してませんが、投げた後はSlackへ「処理中のため、ちょっと待っててね」というメッセージをメンション付きで投稿しています。
Slack bot handlerの役割はここで終わりで、この先はSlack bot backendの役目になります。
2. OpenAI APIとの連携
Slack bot backendでは、まずはOpenAI APIを使用して自然言語を構造化データに変換します。
Azure OpenAIのResponses APIを活用し、Function Callingの仕組みを利用しています。
func callAzureOpenAI(question string) (*AzureOpenAIResponse, error) { client := openai.NewClient( azure.WithAPIKey(apiKey), option.WithBaseURL(endpoint+"/openai/v1"), ) // Function定義 tools := []responses.ToolUnionParam{ { OfFunction: &responses.FunctionToolParam{ Name: "grantTemporaryPermission", Description: "ユーザーに一時的なIAMロールを付与します。", Parameters: openai.FunctionParameters{ "type": "object", "properties": map[string]interface{}{ "role": map[string]interface{}{ "type": "string", "description": "付与するGoogle Cloud IAMのロール名", }, "members": map[string]interface{}{ "type": "array", "description": "権限を付与する対象ユーザー", }, }, }, }, }, } resp, err := client.Responses.New(ctx, responses.ResponseNewParams{ Input: responses.ResponseNewParamsInputUnion{OfString: openai.String(question)}, Model: openai.ChatModelGPT4o, Tools: tools, }) return parseResponse(resp) }
ここではFunction Callingに利用するFunction定義やResponses APIの定義を書いてたりします。
Function Callingについてご存じない方への簡単な説明として「プロンプトの内容から実行が必要とされる外部関数を呼び出してくれる」という機能になります。
今回で言うと、「権限の付与」的な意味合いを持つプロンプトだと grantTemporaryPermission の関数実行が走るという感じですね。
3. 自然言語処理の仕組み
OpenAI APIには、以下の情報を含むFunction定義を提供しています:
■ ロール名の変換
例:
- 入力 → Compute Engine の管理権限
- 出力 → roles/compute.instanceAdmin.v1
この変換のために、Google CloudのIAMロール一覧をVector Storeに格納し、File Search機能を活用しています。一覧に記載されているロールのdescriptionが推測精度に大きく寄与します。
以下はVector Storeに格納するために別途作成したスクリプトになります。
vector_store = client.vector_stores.create(
name="Financial Statements",
chunking_strategy={
"type": "static",
"static": {
"max_chunk_size_tokens": 300,
"chunk_overlap_tokens": 20
}
}
)
file_batch = client.vector_stores.file_batches.upload_and_poll(
vector_store_id=vector_store.id,
files=[open("roles.txt", "rb")]
)
Vector Storeに格納するファイルは gcloud iam roles list で取得してきたIAMについての説明付きの一覧になります。以下のような感じで出力されているので、こちらの内容をもとに付与したい権限を推察させるわけですね。
description: Grants ability to create Workstation resources. etag: AA== name: roles/workstations.workstationCreator stage: GA title: Cloud Workstations Creator --- description: Grants ability to create workstations with exemption from max_usable_workstations Limit. etag: AA== name: roles/workstations.workstationLimitExemptedCreator stage: GA title: Cloud Workstations Limit Exempted Creator
また、descriptionがかなり推察に寄与する要素ですので、実際は以下のように作っております。
"role": map[string]interface{}{ "type": "string", "description": "付与するGoogle Cloud IAMのロール名を指定します。" + "例えば「roles/compute.instanceAdmin.v1」や、自然言語で「Compute Engineのインスタンス管理者権限」などが指定されます。" + "自然言語で指定された場合はFile Searchを行い、適切なロールに変換してください。" + "例として「Compute Engineのインスタンス管理者権限」を指定された場合には「roles/compute.instanceAdmin.v1」といった形式に変換してください。", },
■ ユーザー名の変換
例:
- 入力 →「〇〇さん」「しょっさん」
- 出力 → user:hoge@example.com
Function定義内に名前とメールアドレスの対応表を埋め込み、変換を実現しています。
自然言語としての広がりを持たせるためにニックネームが浸透している方はニックネームでも対象とできるようにしています。
こちらもdescriptionは以下のように作っております。
"members": map[string]interface{}{ "type": "array", "items": map[string]interface{}{ "type": "string", }, "description": "権限を付与する対象ユーザーが1人、もしくは複数人が指定されます。" + "例えば「〇〇さん」といった名前が指定されます。" + "この名前をもとに、紐づいたGoogle Cloud IAMのユーザーアカウントを指定する必要があります。" + "例えば「〇〇さん」を指定されたときには「user:hoge@example.com」といった形式に変換してください。" + "複数人指定された場合は、変換して配列で指定してください。" + "以下にフルネーム(ふりがな, ニックネーム)とユーザーアカウントの対応表を示しますので、変換の参考にしてください。\n\n" + "```\n" + "山田 太郎(やまだ たろう): user:taro.yamada@example.com\n" + "井上 翔太(いのうえ しょうた、しょっさん): user:syossan27@example.com\n" + "```\n", },
ニックネームに関しては特に調整せずとも、不安定ながら推察してユーザーを選別してくれました。
しかし、より確実性を持たせるために氏名⇔ニックネームの情報をdescriptionに明記しています。
4. 承認フロー
最後に、権限付与前にSlackのインタラクティブボタンを使用した承認フローの実装です。
人によって確認待ちかどうか?を判定するために状態をCloud Storageに保存して簡易的に判断しています。
func showConfirmationMessage(channelID, userID, functionName, functionArgs string) {
// 確認IDを生成
confirmationID := generateConfirmationID()
// 確認待ち状態をCloud Storageに保存
saveConfirmationToStorage(confirmationID, channelID, userID, functionName, functionArgs)
// インタラクティブボタン付きメッセージを送信
attachment := slack.Attachment{
CallbackID: confirmationID,
Title: "以下の操作を実行しますか?",
Text: generateFunctionDescription(functionName, functionArgs),
Actions: []slack.AttachmentAction{
{Name: "confirm", Text: "はい", Type: "button"},
{Name: "cancel", Text: "いいえ", Type: "button"},
},
}
api.PostMessage(channelID, slack.MsgOptionAttachments(attachment))
}
5. 権限付与の実行
確認が完了すると、Google Cloud IAM APIを使用して実際の権限付与を実行します。
func grantTemporaryPermission(channelID, userID string, args string) {
// 期限付きの条件を設定(7日間)
expireTime := time.Now().Add(168 * time.Hour).Format(time.RFC3339)
condition := &cloudresourcemanager.Expr{
Title: "Temporary Access",
Description: "Access expires after 7 days",
Expression: fmt.Sprintf("request.time < timestamp('%s')", expireTime),
}
// IAMポリシーの更新
service, _ := cloudresourcemanager.NewService(ctx)
policy, _ := service.Projects.GetIamPolicy(projectID, request).Do()
// 条件付きバインディングを追加
updatedBindings = append(updatedBindings, &cloudresourcemanager.Binding{
Role: role,
Members: members,
Condition: condition,
})
policy.Bindings = updatedBindings
service.Projects.SetIamPolicy(projectID, &cloudresourcemanager.SetIamPolicyRequest{
Policy: policy,
}).Do()
}
Google CloudのIAMでは 条件付きロール バインディング を利用して一時的な権限を付与することが出来ます。
一時的な権限はチームメンバーで付与できるようにSlack botを介して、恒久的な権限はIaCリポジトリからTerraformを介して付与するように使い分けていたりします。
実際の使用例
使用法
@fansta-bot Compute Engineの管理権限をしょっさんに付与して
この入力に対して、こちらの機能は以下のように動作します:
- OpenAI APIが「Compute Engineの管理権限」を
roles/compute.instanceAdmin.v1に変換 - 「しょっさん」を
user:syossan27@example.comに変換 - 承認メッセージをメンション付きで投稿
- 承認後、7日間の期限付きで対象ユーザーに権限を付与
また、以下のように複数人への付与も可能です。お手伝い頂いているQAチームへ一時権限を付与することがあるので、チームの複数人に付与できるようにしてあるのです。
@fansta-bot Cloud SQLの管理権限を〇〇さんとしょっさんに付与して
一応、モザイク多めですが実際のスクショもぺたり。

まとめ
というわけで、今回開発したSlack botを使った一時権限付与機能のお話でした。
OpenAI APIの自然言語処理をSlack botとかけ合わせて活用することで、従来の利用に少し難があった機能を大幅に簡素化できました。
特に以下の点が重要な成果です:
- 自然言語を利用することでの簡便性: 知識を必要とせず、誰でも直感的に使用可能
- 付与対象者のメールアドレス特定や、必要な権限の検索を自動化: 手動でやらなくてはいけないことを可能な限り最小限に抑制
あと、ここまで書いておいて気付いたのですが自分用に権限検索機能も欲しくなってきました。Google Cloudで権限付与する時に、「あれ?どれ付与すれば良いんだっけ?」ってたまになるので。
今回のように、SREの現場ではこのようにAIを有効活用して、よりDXの改善や自分たちのSRE活動の円滑化がしやすい世の中になりました。今後もAIを活用した運用自動化の取り組みを進めていく予定です!
ということで、開発チームが使いやすいように一時権限付与機能を使いやすくしてみたよという記事でした。この事例が何かの参考になれば幸いです!🙏