記事のサマリー(TL;DR)
- freee は全エンジニアの 97% が AI コーディングエージェントを日常利用する状態に達し、次フェーズとして Claude Enterprise を Sales / CS / 企画 / 管理部門など全社へ展開中
- OneLogin(社内 IdP)→ SCIM → Custom Roles というパイプラインを Terraform で管理し、「利用層(default / ai-sandbox)× Claude Code 有無」の 2 軸でロールを 6 種類に設計
- Terraform の
checkブロックでシート上限超過を Plan 段階で検知し、GitHub Actions の workflow_dispatch で申請〜PR 作成〜監査証跡を自動化
国内 Claude Enterprise / SaaS AI 導入企業が移行前に検討すべき権限設計の要点
Claude Enterprise のエンタープライズ契約を結んだ後、最初につまずくのが「誰にどの権限を渡すか」の運用設計だ。本記事は freee が約 1,000 名規模の展開で実際に直面した問いと解決策をコードレベルで公開しており、日本国内で Claude Enterprise や同種のエンタープライズ生成 AI ツールを全社展開しようとしている企業にとって参照価値が高い。
特に注目すべきは、既存の IdP(今回は OneLogin だが Okta や Azure AD でも設計パターンはほぼ同等)を source of truth に据え、Claude 側のコンソール直接操作を排除した点だ。kintone や Salesforce など既存 SaaS の権限を IdP 連動で管理している企業であれば、同じパイプラインを Claude に延長するだけで一元管理が成立する。また、「Claude Code を使うか否か」という行動基準でロールを分けるアプローチは、職種横断で AI 活用が進む国内企業でも応用しやすい。Spend Controls を「制限」ではなく「安心して使える予算枠」として設定するスタンスも、社内展開の心理的ハードルを下げる実用的な観点だ。
詳細
背景:エンジニア組織での AI 活用から全社展開へ
freee の「AI 駆動開発(AI-Driven Development)チーム」は、プロダクト開発組織内の AI 活用定着を担ってきた。その結果、freee の開発組織では 97% のエンジニア(残り 3% は経営陣または EM)が AI コーディングエージェントを日常的に使う状態に到達した。
次の課題は自然と、この成功をエンジニア組織の中だけに閉じず、Sales / CS / 企画 / 管理部門など全社へ広げることに移り、チーム名も「AI Platform Engineering チーム」へと改称された。活動領域も、単なる Coding Agent ツール導入から「業務フロー自体を AI で再定義できる技術基盤の構築」へと広がっている。
なお、Claude Enterprise の全社展開にあたっては複数レイヤーで安全性を担保する取り組みを並行して進めており、本記事が扱う「SCIM × IaC による権限設計と運用」はその中核の一つだ。他にも以下のような対応を組み合わせている。
- Jamf を活用した端末設定の配布(エンジニア・非エンジニア双方)
managed-settings.jsonによる Claude Code の挙動制御(危険な操作の Deny)- OpenTelemetry で Claude Cowork / Code Log を収集し SIEM に転送してモニタリング
- Claude を安全に使うための社内ガイドライン・オンボーディング整備
整備前に立てた 4 つの問い
エンタープライズ契約を締結してシートを発行した瞬間がゴールではなく、「みんなが安心して触れる状態」を維持するためにもう一枚レイヤーを足す必要があった。具体的には次の 4 点だ。
- 誰がどこまで触れるか(管理者権限・課金変更・SCIM 設定など)
- どの利用フレームで渡すか(Claude Code を ON にするか OFF にするか、ヘビーユーザー / 一般 / 検証フレームなど)
- 暴走を防ぐための予算ガードレールをどこにどう敷くか
- 上記すべてを IaC で再現可能な形にできるか(人間の SaaS コンソールクリックに依存しない)
📝 本記事で扱う SCIM 連携 / Group mappings / Custom Roles / Spend Controls はいずれも Claude の Enterprise プラン で提供される機能(Team プランでは利用不可または制限あり)。
Claude Enterprise が提供する権限まわりの機能
freee 運用の観点で整理すると次のとおり。
| 機能 | できること | freee での活用先 |
|---|---|---|
| SSO / SCIM directory sync | OneLogin をマスターに据え、ユーザー / グループ情報を同期 | 既存 OneLogin ベースに権限管理を一元化 |
| Group mappings | OneLogin 側のグループ名を Claude 側 Role に自動マッピング | 「グループに入れる = ロールが付く」を IaC で再現 |
| Custom Roles | Owner / Admin / User 以外の細分化された権限プロファイルを作成 | 「Claude Code を使ってよい / ダメ」「default / ai-sandbox」などの利用フレームをロールで表現 |
| Spend Controls | 組織全体・ロール単位で予算ガードレールを設定 | 暴走時の保険、検証フレームの予算分離 |
| Usage Analytics | ユーザー / Role 単位の利用状況の可視化 | 利用フレームの見直し・sandbox の効果計測 |
| Audit Logs | 重要操作の監査証跡 | コンプライアンス・インシデント対応用 |
設計の基本方針は OneLogin を唯一の source of truth とし、Claude 側の状態はそこから同期されるアウトプットとしてのみ扱うこと。コンソールから直接手を入れるオペレーションは、できる限り作らないようにした。
なぜ User ロールではなく Custom Roles に寄せたか
Claude Enterprise の Group mappings では OneLogin 側のグループ名を Owner / Admin / User / Custom Roles の 4 種にマッピングできる。freee では User には何もマッピングせず、すべてを Custom Roles に寄せた。理由は 3 つ。
① 「Claude Code を使う / 使わない」を権限レベルで分離したかった
User ロールは「全員に同じ権限を一律配布」しかできない。Claude Code は強力だが、利用形態によっては別途レビュー・トレーニングを挟みたい場面がある。Custom Roles なら「Claude Code 有効版」と「Claude Code 無効版」を同じ利用層に対して別ロールで提供できる。
② 利用層(layer)と機能スイッチを直交(orthogonal)にしたかった
「default / ai-sandbox」という利用層と「Claude Code 有効 / 無効」という機能スイッチの 2 軸で組み合わせる設計にすることで、将来的にヘビーユーザー専用ラインなど層を増やしても自然に拡張できる。
③ 既存ユーザーの「知らないうちに昇格」を防ぎたかった
今後 User ロール側にデフォルト権限が追加されても、誰も User に入っていなければ影響はゼロ。「明示的にロールに入っている人だけがその権限を持つ」という状態を強制できる。
グループ設計:「利用層 × Claude Code 有無」の 2 軸
軸 1:利用層(layer)
| Layer | 想定ユーザー像 |
|---|---|
| default | 業務で Claude を使う一般ユーザー(圧倒的多数) |
| ai-sandbox | freee 社内の「AI 特区」制度に合わせたフレーム。一般ポリシーから切り離して新ツール・新機能を先に試せる限定エリア |
ℹ️ AI 特区とは:全社展開するにはリスクが大きいが試してみたい AI ツールを安全・迅速に検証するための社内制度。特区として認められたチーム・メンバーに限り、サンドボックス環境など定められた条件下での利用を許可するというのがコアの定義。
軸 2:Claude Code の有効 / 無効
| Variant | 用途 |
|---|---|
*-claude-code |
Claude Code を含む全機能 ON |
*-no-claude-code |
Claude Code は無効、その他の Claude 機能のみ |
この区分は権限だけでなく予算設計にも影響する。Claude Code を本格的に回す側はトークン消費が桁単位で大きくなるため、*-claude-code / *-no-claude-code をロール単位で先に分けておくことで、Spend Controls / Usage Analytics 側で異なる基準線の予算を引ける余地が確保できる。
職種ではなく「Claude Code を ON で使うか」という行動基準でラインを引いたのも重要なポイントだ。同じノンエンジニア部署でも Claude Code を本格的に回し始めた人は *-claude-code 側に、同じエンジニアでもチャット UI 中心の人は *-no-claude-code 側に自然に移れる設計になっている。
freee での Custom Roles 一覧(<org-id> は組織ごとの UUID):
anthropic-claudeai-<org-id>-owner
anthropic-claudeai-<org-id>-admin
anthropic-claudeai-<org-id>-default-claude-code
anthropic-claudeai-<org-id>-default-no-claude-code
anthropic-claudeai-<org-id>-ai-sandbox-claude-code
anthropic-claudeai-<org-id>-ai-sandbox-no-claude-code
命名規則は Anthropic が初期に設定していた default グループ名に合わせ、コンソール上の視認性と SCIM 設定の追跡性を高めている。
IaC 設計:「IaC が所有するもの」と「人が所有するもの」を分ける
すべてを IaC に押し込むのが理想だが、現実には以下の事情があった。
- OneLogin Terraform プロバイダー側に、Role / App Rule そのものを作る API が安定的に公開されていない
- Claude 側の SCIM Group は Anthropic 側で自動生成され、OneLogin の App Rule(set_groups)の発火によってはじめて紐づく
そこで責任を明確に分割した。
| 対象 | オーナー |
|---|---|
| OneLogin Role 定義そのもの | 管理者が OneLogin コンソールで 1 回作成(out-of-band) |
| OneLogin App Rule(set_groups で SCIM Group 値を push) | 管理者が OneLogin コンソールで 1 回作成(out-of-band) |
| Role に対するユーザー所属 | IaC が所有(Terraform で管理) |
Terraform 側のコードは「ロールに誰が所属しているか」だけをシンプルに表現すればよくなり、再利用しやすいモジュールに切り出して各ロールに薄いラッパーを置いた。
locals {
email_to_id = {
for k, u in var.users : u.mail => u.id
}
user_ids = [for e in var.user_emails : local.email_to_id[e]]
}
resource "onelogin_user_role_attachments" "attach" {
role_id = var.role_id
users = local.user_ids
}
module "claude-default-claude-code" {
source = "../modules/claude-role"
role_id = local.claude_roles["default-claude-code"]
users = data.terraform_remote_state.onelogin_mapping.outputs.users
user_emails = [
local.users.<...>.mail,
]
}
ロール ID は locals.tf にまとめてコードから参照する形にし、後ろが多少変わってもユーザー所属レビューのフローが崩れない構造にした。
locals {
claude_roles = {
"default-claude-code" = "<id>"
"default-no-claude-code" = "<id>"
"ai-sandbox-claude-code" = "<id>"
"ai-sandbox-no-claude-code" = "<id>"
"owner" = "<id>"
"admin" = "<id>"
}
}
段階的なロールアウト:「壊れたら戻せる順序」で適用する
新しい SCIM ロールを一度に適用するリスクを避け、PR をスタック(積み重ね)にして段階的に進めた。
Step 1:canary PR — owner ロールだけを最初に作る
- 利用者数が最小(数名)で影響範囲が極小
- Owner 権限は「実際に owner レベルの操作が必要なメンバーだけ」に絞って付与。管理ライン = 自動で owner という一律付与は避けた
- App Rule の
set_groupsアクションが正しく解釈されるかをここで初めて検証 - 想定どおり動けば Claude 管理画面に SCIM バッジ付きの新規 Group が自動生成される
Step 2:admin ロールを追加
- canary がグリーンになったことを前提に別 PR で Admin 付与を追加
- 「暫定付与」「期限見直し」などの運用メモはコード上にインラインコメントで記録
Step 3:既存ユーザーの全量マイグレーション(3 PR をスタック)
約 1,000 名規模のユーザーをレガシーの単一ロールから「層 × 機能スイッチ」の Custom Roles に移行。1 PR ですべて移さず、以下の 3 段階に分割した。
- (a) ai-sandbox / owner / admin
- (b) default 前半
- (c) default 後半 + 旧ロール解体
分割の理由は 2 点。レビュー可能性(1,000 人近い attachment 変更が入った diff は事実上レビュー不能)と API 側の限界(OneLogin Terraform Provider は大量 attachment 変更の一括 apply で Timeout / Too Many Request Error が起きやすい)。
各 PR では「移動前後でユーザー総数が一致するか」「重複や抜けがないか」を CSV と diff で機械的に検証した。
Step 4:レガシーロールの物理削除
全員の移行確認後、最後の PR で空になった旧ロール定義を削除。
日常運用:追加・削除も IaC PR フローに乗せる
権限基盤を IaC に寄せた以上、日常の「ユーザーを増やす / 減らす」も同じレールに乗せるのが鉄則だ。freee では GitHub Actions の workflow_dispatch を使い、以下を入力するだけで自動処理が走る仕組みにしてある。
- 対象 Custom Role の選択(ドロップダウン:
default-claude-code/ai-sandbox-claude-codeと各no-claude-code版) - 追加 / 削除するユーザーのメールアドレス(カンマ区切り)
- 利用目的(PR 本文・コードコメントに記録)
処理の流れは以下のとおり。
- 該当の Terraform ファイルにユーザー参照を
DO NOT DELETEマーカーの直前に挿入 terraform fmtを適用- 申請者・対象ロール・対象ユーザー・利用目的を Body にまとめた PR を自動作成
レビュアーは IaC 側で「正しい層 × 正しい claude-code 有無の組み合わせか」だけを確認すればよく、SaaS コンソールでのオペレーションは原則発生しない。「コードに反映 → PR レビュー → apply」のフローを崩さないことが、過去のインシデントから学んだ最大の教訓だと freee チームは述べている。
シート上限の自動検知
check_claude_seat_limit.tf では Terraform の check ブロックを使い、全 Custom Role に割り当てられたユニークユーザー数が契約シート上限を超えた場合に Terraform Plan の段階で自動的にエラーを出す仕組みを組み込んでいる。
locals {
claude_seat_limit = XXXX
}
check "claude_seat_limit" {
assert {
condition = length(local.claude_unique_users) <= local.claude_seat_limit
error_message = format(
"Claude seat limit exceeded: %d users are assigned, but the limit is %d.",
length(local.claude_unique_users),
local.claude_seat_limit,
)
}
}
「申請 → PR → plan → apply」のフロー上でうっかりシート超過状態を本番に適用してしまうことを防ぎ、人間がコンソールで気づく前に IaC 側で検知できる。
補助のガードレール:Usage Controls
ここまでが権限運用のメインだ。最後に、その上に薄く一枚かぶせている Usage Controls(Spend Controls) にも触れておく。これは新たな基盤構築ではなく、Anthropic 側が提供している機能を権限設計と粒度をそろえてオンにしている位置づけだ。
- 組織全体に月額予算を設定(突発的な暴走への保険)
- 必要に応じて Role 単位で予算を分離(特に ai-sandbox 系は検証目的の予算フレームとして明示的に切る)
*-claude-code/*-no-claude-codeの 2 グループで 1 人あたり必要予算の感覚を別々に取る(Claude Code を本格的に回す側はトークン消費が桁単位で大きいため)- Usage Analytics を週次で確認し、default / ai-sandbox の比率や
no-claude-code側の利用傾向を見ながら、必要に応じて層の見直し / Role 移動を提案
「上限」ではなく「ここまでなら安心して使ってよい」という**予算(Budget)**として扱うのが freee のスタンスだ。SCIM で配布した権限運用の上に Usage Controls がもう一枚軽く支えることで、初めて「みんなに渡しても大丈夫」と言える状態になる。
振り返って学んだこと
「みんなに渡す」のゴールは配った瞬間ではなく、配り続けられる状態を作ったとき
個別申請を捌いていく運用には限界がある。SCIM × IaC × 自動 PR で「申請 → コード反映 → 監査証跡」を一直線にできたことが大きな効果を発揮した。
User ロールではなく Custom Roles に寄せたのが正解だった
2 軸(利用層 × Claude Code 有無)を表現する余地が後から効いてきた。「あとで層を増やす」「あとで Claude Code 無効版を作る」が壊れずに追加できる構造になっている。
canary を最小スコープに切ることのリターンは大きい
SaaS API 連携の不確定要素は、最初の数名にぶつけにいく方が結果として最も速く・安全に検証できる。
コンソール所有 / コード所有の境界はプロバイダー機能に従う
「全部 IaC」を盲目的に追わず、プロバイダーが支えてくれる範囲だけ IaC で持つのが現実的だった。