Google Workspace × GitHub EMU、公式非対応の壁を内製SCIMブリッジで越えた話
「Google Workspace 一本の会社が GitHub EMU に移行したい。でも公式にサポートされてない…」
——そんな詰みかけた状況を、SCIM ブリッジの内製で突破した記録です。
副産物として、IdP 追加導入で毎年かかるはずだった年間数百万円規模のライセンスコストも消えました。「作ってよかった」と思えたプロジェクトになりました。
TL;DR
まず結論だけ知りたい人向けのサマリです。
- GitHub Enterprise の EMU (Enterprise Managed Users) は、外部 IdP からの SCIM プロビジョニングが必須
- ところが公式サポート IdP は Entra ID / Okta / PingFederate のみで、Google Workspace は対象外
- IdP を追加導入すると「ユーザー数 × 月額」の恒久コストがかかるため、Google Workspace → GitHub EMU の SCIM ブリッジを内製した
- Python (FastAPI) + HTMX の Web コンソール付き。ドライラン・暴発ガード・監査ログを最初から組み込み、SRE 1人でも安全に運用できる形にした
- 認証は GitHub App の Enterprise SCIM permission を採用し、PAT の属人性を排除。さらに秘密鍵は AWS SSM + tmpfs(メモリ上)展開に移行し、ディスクから鍵をなくした
- インフラは当初 ECS Fargate フル構成 → 最終的に EC2 1台 + systemd + Caddy + SQLite まで減量。あわせて S3(PutObject 専用ポリシー)+ AWS Backup の二重バックアップを用意した
- 結果、IdP 追加導入コスト(年間数百万円規模)がゼロに。開発工数を差し引いてもお釣りが来た
こんな人に読んでほしい
- Google Workspace を全社の ID 基盤にしていて、GitHub EMU 移行を検討している人
- SCIM プロビジョニングの公式非対応問題にぶつかって頭を抱えている人
- 全社員のアカウントを触る危険なバッチをどう安全に作るかに興味がある人
- 「内製ツールが秘伝のタレ化する問題」を何とかしたい人
- 「既製品を買う」以外の選択肢でコストを抑えつつ、本番運用のセキュリティも妥協したくない人
背景:EMU 移行と SCIM 必須問題
GitHub Enterprise Cloud には、企業が ID 基盤でアカウントを完全管理できる EMU (Enterprise Managed Users) という形態がある。個人アカウントを社内リソースに招待する従来方式と違い、次のようなガバナンス上のメリットがある。
- アカウントのライフサイクル(入社 → 作成、退社 → 無効化)を IdP 起点で自動化できる
- 会社管理外の個人アカウントが社内コードに触れない
「退職者のアカウントが消し忘れで残っていた」といった事故がなくなるので、弊社でも Enterprise 移行に合わせて EMU を採用することにした。ここまでは順調だった。
ところが、ここで最初の壁にぶつかる。
EMU はユーザーの作成・更新・無効化をすべて SCIM プロビジョニング経由で行う前提になっている。
手でユーザーを作ることはできず、IdP 側からの SCIM リクエストがアカウント管理の唯一の入り口になる。
つまり「SCIM が繋がらない = そもそも誰もログインできない」。ここが繋がらないと EMU はただの文鎮である。
壁:Google Workspace は公式パートナーではない
GitHub が SCIM プロビジョニングを公式サポートしている IdP は次の通り(2026 年時点)。
| IdP | 公式サポート |
|---|---|
| Microsoft Entra ID | ○ |
| Okta | ○ |
| PingFederate | ○ |
| Google Workspace | × |
Google Workspace はここに入っていない。つまり Google Workspace を全社の ID 基盤にしている会社は、正攻法で進もうとすると開始5分で詰む。ドキュメントを何度読み返しても、Google の名前はどこにも出てこない。
検討した選択肢
「じゃあどうするか」で洗い出した選択肢がこちら。
| 選択肢 | 概要 | 課題 |
|---|---|---|
| ① Entra ID を追加導入 | Google WS → Entra ID → GitHub と2段連携 | ユーザー数 × 月額の恒久コスト。ID 基盤が二重になり運用も複雑化 |
| ② Okta を追加導入 | 同上 | 同上(ライセンス費はさらに高め) |
| ③ OSS のブリッジを利用 | Google 起点の SCIM 同期 OSS が存在する | 個人メンテのツールでコミットが長期間止まっており、レートリミット (429) リトライなどが未実装。障害時に自力で直せる保証がない |
| ④ 内製 | Google Admin SDK と GitHub SCIM API を自前で繋ぐ | 開発・保守コスト。ただし中身を完全に理解して持てる |
①② は要するに「GitHub にログインするためだけに、もう1個 ID 基盤を買う」ことになる。ユーザー数ぶんの月額がこの先ずっと乗り続けるうえ、ID 基盤が二重になって運用も倍。コスト面でも運用面でも、ここは見送った。
③ の OSS は、設計の参考としては非常に役立った。ただ、そのまま本番に置くにはリトライ・監査・GUI などが足りず、全社員のアカウントを預けるには心もとないというのが正直なところ。
そこで ④ 内製。ただし「動くスクリプト」ではなく、引き継ぎ可能な Web コンソール付きアプリとして作ることを最初に決めた。ここで手を抜くと、数年後に「これを触れるのは作った人だけ」という秘伝のタレが完成してしまうので。
作ったもの:SCIM Bridge Console
Google Workspace のユーザー / OU を GitHub EMU に同期するブリッジと、その管理 Web コンソールである。
+------------------------------------------------------+
| Google Workspace |
| OU: /Engineering, /Operations, ... |
+--------------------+---------------------------------+
|
| Admin SDK (readonly)
v
+------------------------------------------------------+
| SCIM Bridge Console |
| |
| +-------------+ +------------+ +-------------+ |
| | SyncEngine | | FastAPI | | Web Console | |
| | (core) |<--| REST API |<--| (HTMX UI) | |
| +------+------+ +------------+ +-------------+ |
| | |
+---------|--------------------------------------------+
|
| SCIM API (GitHub App installation token)
v
+------------------------------------------------------+
| GitHub Enterprise (EMU) |
| SCIM Users / IdP Groups (OU -> Group mapping) |
+------------------------------------------------------+
同期フロー
- Fetch — Google Admin SDK で対象 OU のユーザーと OU 構造を取得
- Flatten — ネストした OU を平坦化して GitHub IdP Group に対応づけ
- Compare — GitHub SCIM API で現状を取得し差分計算
- Provision — Create / Update / Suspend / Delete を SCIM API に適用
- Guard — 破壊的変更が閾値を超えたらブロック(後述の暴発ガード)
- Notify — エラー時は Slack Webhook に通知
設計・セキュリティで一番大事にしたこと:「事故らせない」
ID プロビジョニングは、全社員のアカウントを一撃で消せる権限を持つバッチである。
機能の豊富さより、安全装置とインフラの防衛設計を優先した。
1. 全体整合モデルは本質的に危険 → 暴発ガード
SCIM 同期の基本は「Google 側の状態を正として GitHub 側を合わせる」全体整合モデルだ。シンプルで分かりやすい。でも、裏を返すとこういうことになる。
Google API が一時的に空のリストを返したら、GitHub の全ユーザーが suspend される
設定ミス・API 障害・OU パスのタイポと、引き金はどこにでも転がっている。深夜のバッチが Google の一瞬の不調を真に受けて「全員いらないんだな」と全社員を締め出す朝は、想像するだけで恐ろしい。
そこで、suspend + delete の合計が絶対件数または全体比率の閾値を超えたら、同期そのものを失敗させるガードを入れた。
| 項目 | 意味 |
|---|---|
max_destructive_actions |
suspend + delete の絶対上限 |
max_destructive_ratio |
全ユーザーに対する破壊的変更の割合上限 |
- dry-run 時:ブロックせず警告バナーを表示し、「この同期、本当に大丈夫?」と一度立ち止まらせる
- 本番同期時:閾値超過なら適用前に中断する
参考にした OSS にもこの機構はなく、内製した意味を一番感じたのがここだった。既製品は便利だが、「うちにとっての危険」を知っているのは自分たちだけなので。
2. ディスク平置き PEM 鍵の撤廃(AWS SSM + メモリ展開)
認証には、個人に紐づく PAT を廃止し、GitHub App(Enterprise SCIM permission)の秘密鍵(PEM)を採用した。
ただ、本番 EC2 のディスク上に .pem ファイルを常時平置きしておくのは避けたい。万が一サーバーに侵入された場合、一撃で GitHub 全体の管理権限が奪われてしまう。
そこで、AWS Systems Manager(SSM)の Parameter Store(SecureString)を採用した。
[起動時(systemd)]
└ ExecStartPre: AWS CLI で SSM から SecureString 鍵を取得
└ tmpfs(メモリ上: /run/scim-console/)に一瞬だけ .pem として書き出す
└ 実行ユーザー「scim」にのみ権限(0400)を絞る
[停止・再起動時]
└ tmpfs なのでメモリ揮発と共に .pem は自動的に消滅
ディスク上の古い PEM ファイルは shred コマンドで物理消去し、「サーバーのディスクを探しても、どこにも秘密鍵が存在しない」状態を作った。これで、侵入時の秘密鍵漏洩リスクを大きく下げている。
3. PutObject 専用の S3 バックアップ
監査ログや同期設定を保存する SQLite の state.db は、定期的に S3 バケットへ自動でバックアップ(アップロード)される仕組みにした。
ここで意識したのが最小権限の原則である。EC2 インスタンスに紐づける IAM ポリシーで、許可するアクションを s3:PutObject のみに限定した。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::<自分のバックアップバケット名>/scim-bridge/state/*"
}
]
}
Resource のバケット名は、自分の環境のバケット名に置き換えてください。
このポリシーのポイントは、「書き込める(バックアップは送信できる)が、自分自身では読み取り(GetObject)も削除(DeleteObject)もできない」ところにある。
仮に EC2 サーバーが侵害され、攻撃者がバックアップの改ざんや削除を試みたとしても、S3 に隔離された過去のバックアップには手が届かない。
4. Google 側は readonly スコープのみ
Google Workspace へのアクセスはサービスアカウント + ドメイン全体委任だが、付与するスコープは読み取り専用だけにしている。
admin.directory.user.readonly
admin.directory.orgunit.readonly
このシステムが万一乗っ取られても、Google 側は1文字も書き換えられない。「同期元には書かない」を権限レベルで保証しておくと、社内のセキュリティレビューでも「これは読むだけなんですね」で話が早く終わる。
インフラ:フル構成から「身の丈」へ、そして二重のバックアップへ
当初は ECS Fargate + Internal ALB + EventBridge + ECR + DynamoDB + Secrets Manager のフル Terraform 構成を設計していた。図にすると立派で、上司に見せたら褒められそうな構成である。
しかし冷静に要件を見直すと、利用者は社内の SRE 数名のみで、落ちても困るのは「同期が少し遅れる」だけの、可用性要件が低い社内ツールだった。ここに Fargate フル構成を出すのは、ハムスターの散歩に F1 マシンを持ち出すようなものだ。
そこで、無駄なリソースを削ぎ落とした身の丈インフラに落ち着かせた。あわせて今回、バックアップの弱点を潰すためのアップデートも入れている。
インフラ構成比較
| 項目 | 当初の Fargate 構成案 | 最終的な「身の丈」構成 |
|---|---|---|
| コンピュート | ECS Fargate(2タスク常時起動) | EC2 1台(NAT GW なし。SSM Session Manager 接続で SSH ポートは閉鎖) |
| 秘密鍵管理 | Secrets Manager | AWS SSM Parameter Store (SecureString) + 起動時 tmpfs メモリ展開 |
| アプリデータ保護 | DynamoDB | SQLite + S3(PutObject 最小権限ポリシー・定期同期) |
| システム全体保護 | なし(コンテナ使い捨て前提) | AWS Backup(毎日 AM 5:00 に自動 AMI 取得・7世代保持) |
| 月額ランニングコスト | 数十ドル〜 | 数ドル台 |
ALB・NAT GW・ECR・DynamoDB が消えて、ランニングコストはほぼ無視できる水準になった。構成がシンプルになったぶん、運用の認知負荷も下がっている。
「Fargate で組めるスキルがあること」と「Fargate で組むべきであること」は別だと、自分に何度も言い聞かせた。
地味な勝因は、永続化レイヤーを最初から抽象化(memory / sqlite を差し替え可能に)していたこと。おかげでインフラ構成をここまで削っても、アプリのコードはほぼ変えずに済んだ。
今回 AWS Backup(毎日 AM 5:00 に EC2 の AMI を自動取得し、7世代でローテーションする設定)を追加したことで、万が一 EC2 インスタンス自体が消えても、数分で OS ごと元の状態にリストアできるようになった。データ側の S3 バックアップと合わせて、二重で守れる構成になっている。
テスト戦略
全社員のアカウントを触るツールなので、テストは厚めにしている。
- 800 件超のテストケース / カバレッジ 94.9%(CI でカバレッジゲートあり)
- 危険系の操作は「やらないこと」のテストを重視した
- 排他モードが全件列挙しないこと
-
delete_suspended=falseのとき delete が発行されないこと - invite-only モードが既存ユーザーに一切触れないこと
- 外部 API(Google / GitHub)はすべてモック。実 API を叩くテストは持たない(テストで本番アカウントを消したら本末転倒なので)
- デモモード(認証情報なしで全画面が動く)を実装し、GUI の動作確認と新メンバーのオンボーディングに使っている
このツールの特徴は、「何をするか」のテストと同じくらい「何をしないか」のテストを書いたこと。「消えるはずのない人が消えていないこと」を証明し続けるのが、このツールの一番の仕事だと思っている。
まとめ
- Google Workspace 一本の会社が GitHub EMU に移行するとき、公式ルートは存在しない。IdP 追加導入・OSS・内製の三択になる
- 内製する場合、コア機能(SCIM 同期そのもの)よりも、安全装置(dry-run・暴発ガード・readonly スコープ・SSM 秘密鍵のメモリ展開・監査ログ)の設計こそが肝となる
- 認証は GitHub App の Enterprise SCIM permission を使い、秘密鍵は AWS SSM Parameter Store から起動時に tmpfs へ展開する運用にすると、ディスクに鍵を残さずに済む
- インフラは要件に対して正直に。社内ツールに Fargate フル構成は過剰なことが多いが、S3(Put 専用)+ AWS Backup のバックアップは月数百円で仕込めるので入れておきたい
- 引き継ぎ可能性(GUI・チュートリアル内蔵・コピペで動く運用手順書)を最初から要件に入れておくと、内製ツールが秘伝のタレ化しない
同じ構成(Google Workspace × GitHub EMU)で悩んでいる方の参考になれば幸いです。
本記事に記載の構成・数値は、執筆時点の情報を一般化したものです。特定の組織を識別できる情報・認証情報・内部データ等は一切含んでいません。