blog

DeNAのエンジニアが考えていることや、担当しているサービスについて情報発信しています

2021.05.27 技術記事

Google グループでツール横断権限管理!安定稼働の舞台裏

by Jun Hasegawa

#google-cloud #nodejs #google-workspace #google-group #greep #情シス

3行で

  • ツール権限管理に Google グループを超活用している話
  • グループ 45,000 件/メンバー 370,000 件を最速で取得する工夫
  • ゆるいグループ設定を即座に是正する便利機能付き

はしがき

IT 戦略部システム開発グループの長谷川です。エンジニアブログ2回目の記事になります。気付けば 前回投稿 から既に半年以上過ぎており、なんていうか気候変動とともに地球の回転も早くなっているんだろうな、などと真面目な顔でため息をついたりしています。

今日は DeNA で提供している権限管理の仕組み群をざらりとご紹介した上で、その裏側で密かに稼働している Google API Proxy (社内呼称: Greep) という内製システムの実装の工夫についてお話ししたいと思います。

ツール横断の権限管理

Google グループを使った Google 製ツールの権限管理

DeNA では Google Workspace (旧 G Suite) をメインのコラボレーションツールとして利用しています。 Google Workspace には多くの機能がある中で、ダントツで使うのはやはりメール、カレンダー、次いでドライブ (スプレッドシート、スライド、ドキュメント、…) といったところでしょうか。これら Google 製ツールは共通して Google グループで情報共有範囲を規定できるように設計されています。少なからず日常的に共働するメンバーは、部署なりチームなりの単位で Google グループを用意することになるでしょう。新規参画メンバーが出ればその Google グループに参加させるだけで

  • メーリングリストとしてのメール通知先
  • 定例カレンダーイベントの参加者
  • ドライブの権限付与範囲

に一括で追加することができます。離脱メンバーが出たときにも、することは逆ですが受けられる恩恵は同じです。言わずものがなのことでしょうけれど、こういったものをツール毎に by name で管理してしまうことのデメリットは、非効率な管理や作業漏れによる情報セキュリティリスク、あるいは業務属人化の温床となる可能性など、文字通り枚挙にいとまがありません。権限とはツール横断で、グループ単位に管理できるのがベストであり、またそうするべきなのです。

Google グループを使った Google 製「以外」のツールの権限管理

さて、当然ながら DeNA で利用するツールは Google Workspace だけではありません。チャットツールとして Slack Enterprise Grid を導入していたり、開発支援ツールとして Atlassian Jira/Confluence を提供していたり。ワークフロー/作業管理ツールとしての kintone もバックオフィスで広く使われています。 VPN やファイルサーバーといった基礎的な仕組みも勿論存在します。こういったエンタープライズツールには必ず何かしらの形で権限機構と呼べるものが備わっており、適切なメンバーに適切な権限をアサインすることの重要性は Google 製ツールのそれと何ら変わりません。それなのにできるからという理由だけで Google 製ツールの権限だけを一元管理し、それ以外のツールではツール毎での管理を看過するというのは、何か努力義務の放棄のようなものを感じませんか? (というかまあ、エンジニアとして悔しいですよね)

そういった背景から、 DeNA では Google グループと他ツールの権限機構とを連動させる仕組みを幾つか実装してきました。つい半年前にも 21新卒の岸さんが Google グループを Slack のチャンネル/ユーザーグループに同期させる Slack App を新たに作ってくれましたが、それまでにも kintone のグループ (ロール) やシステム障害発生時の緊急連絡用の (Google Workspace と別のドメインで内部運用している) メーリングリスト、また Active Directory のセキュリティグループといった各種権限機構への同期をそれぞれ稼働させてきました。特に最後に挙げた Active Directory へのグループ同期は、 Active Directory とグループ同期ができる Jira/Confluence/VPN/ファイルサーバー、さらに ID フェデレーションサービス (DeNA では Okta を利用しています) などを連鎖的に管理することができたため、非常に費用対効果の高い施策だったと捉えています。

多少蛇足感もありつつ補記すると、 DeNA では会社標準選定でなくとも (セキュリティと法務のチェックに堪える限り) 現場業務にフィットしたツールは現場で導入できるルールになっています。これら現場ツールを ID フェデレーションサービス経由でログインさせ、その認可のコントロールを Google グループと連動させることで、現場ツールの管理効率とセキュリティを向上させている…というパターンもよく観察されます。

そういえば、 Google Cloud Platform (GCP) も Google グループで権限管理ができましたね。 DeNA ではさらに、前述の Active Directory と ID フェデレーションサービスを基盤として AWS のコンソールログインを Google グループと連動させる仕組みを実現 してもいます。 クラウド移行を推進し完了させた DeNA にとってクラウドサービスの権限管理はまさに生命線ともいうべきポイントであり、これまでご紹介してきた Google グループによる権限の横断的管理が深く全社に浸透していることの表れといえるでしょう。

Greep とは何か

Google グループを使った権限管理の話が一段落ついたところで、今度は Greep というシステムについてお話ししていきましょう。

Google グループと他ツールの権限機構とを連動させるためには、とりもなおさず Google グループの情報…すなわち、どのグループに、誰が所属しているか?という情報が必要です。システム的にこれを取得する場合、十中八九 Directory API を叩くことになります。しかしこの Google 提供の標準的 API にはそれなりに手酷い落とし穴が用意されていて、まさにこの落とし穴を回避するために Google API Proxy たる Greep は生まれました。 Greep 構築以前、当時の担当者の手による素朴なシステムが複数回に渡ってこの落とし穴に転げ落ちていたことが思い出されます。そんな苦い記憶をよぎらせる前システムも今月で完全シャットダウンされる予定であり、この記事を一種の送辞のような心持ちでしたためています。

構成概要

先にお断りしておくと、 Greep は所謂 API Proxy という用語で想起されるような、薄く透過的なサービスではありません

システム構成概要図

システム構成概要図

概要図の下半分が Greep で、上には Greep とやり取りを行うシステムが並んでいます。左上の Other Systems として示しているのが、たとえば Active Directory にメンバー情報を同期させようとしている仕組みであったり、 kintone にメンバー情報を同期させようとしている仕組みであったりします。それに対して Greep は API (Greep WEB API) を用意し、Google グループの情報を提供しているわけです。

それでは肝心の Google Workspace はどこにいるかというと、右にひとつ飛ばして中央上部です。ここに Admin SDK (Directory API を含む管理者用 API 群の総称) などの Google 提供 API が複数種類公開されています。また Greep 内部に目を向けると、 Google App Engine でマイクロサービス構成を組み、ひとつひとつのマイクロサービスすべてに対となるタスクキュー ( Cloud Tasks ) を置いているのが特徴です。詳細は後述しますが、 Other Systems が Google グループの情報を得るために Greep WEB API を叩いたとしても、そう直接的に Directory API が叩かれるわけではないというイメージが伝わるでしょうか。

Google API の Rate Limit …そしてペナルティ

さて、前回の記事でも触れたのですが、 Directory API に限らず、 Google 提供のものにも限らず、世の API というものはおよそすべからく Rate Limit というものを持っており、これを超えた頻度でリクエストを投げないことを API 利用者は求められます。 API 提供者側のインフラを守るためには当然の措置かと思います。では具体的に Directory API にはどんな Rate Limit があるかというと、たとえば以下2つのようなリクエスト頻度上限値になります。

  • 1,500 per 100 seconds per users
  • 5,000,000 per day

個々の環境に依存するため API 利用者は都度 Google Developers Console から上限値を確認するよう求められており、あくまでこれらは Greep で観察された値であることをご了承ください。ただ大事なのはこの値そのものではなく、上限値に抵触したときの挙動です。特に2つ目の日次上限値が苛烈で、うっかり超えると1日1回しかないペナルティリセットの時間までの間すべての API リクエストが失敗します。これが「落とし穴」です。1つ目の上限値は Exponential Backoff で再送するなり、 API リクエストを行うユーザー (サービスアカウント) を増やすなりすれば回避できるものの、2つ目の方は本当になすすべなく、ただリセットのときを待つしかありません。ペナルティリセットは PST/PDT でいうところの 0:00 なので、日本時間だと 16:00 とか 17:00 という中途半端な時間になります。当然すべての権限情報反映が遅延します。そのたびに我々はエンドユーザーへの説明に困窮していました。ああ思い出したくない。

ちなみに Google Developers Console での API 発行数カウントの様子を見る限り、サービスアカウントが属する GCP プロジェクトそのものを増やして負荷分散すれば日次上限値も実質的に突破できると考えています。しかしそのようなやり方で制約をかいくぐったとき、本当に自分達の運用する Google Workspace 本番環境にダメージを与えていないのか?ということについては慎重に検証すべきでしょう。それよりも、そもそものリクエスト発行数を抑えるような可能性について検討を重ねる方が、処理速度の観点からも余程有意義な筈です。

期待される処理量

この記事を書きながら確認してみたところ、 Google グループが 44,512 件、またそれらのメンバー累計が 373,211 件存在していました。ユーザー数が 5,000 にも満たない Google Workspace でこれだけの利用率はなかなか見られないのではないかと思います。この数字が誇らしいというような文意ではありません。 Rate Limit が意外なほどすぐ近くに見えているという話です。

ここに 45,000 件のグループが並んでいたとして、各グループに平等に 10 件ずつのメンバーが所属しているとして、 Directory API でその全情報を取得するとしましょう。そうするとグループ毎のメンバー取得リクエストの数が支配的になり、おおよそ 45,000 回程度の API リクエストを投げることになります。もし10分に1回ずつの頻度で全情報の取得を実行し続けたとすると、1日に 6,480,000 回の API リクエストを投げることになり、あっさりと落とし穴の蓋を踏み抜く計算になります。それでは困るのです。しかもこれは Directory API を利用したい仕組みがひとつしかないときの試算です。 Slack に同期させる仕組み、 kintone に同期させる仕組み、 Active Directory に同期させる仕組み…がそれぞれ別々に Directory API を要求している中で、 Greep が単純に透過的にリクエストを投げているようでは到底 Rate Limit を守ることはできません。

Rate Limit に抵触しないための工夫① 〜 キャッシュ機構

まずはごく順当な対策として、キャッシュ機構を置きました。まあ置きますよね。前もって収集した Google グループの情報を Greep 内部に保持しておき、読み取り要求に対してはそこから返却するようにして Google への API リクエスト数を節約します。今回の要件のように複数の仕組みが短時間に同じデータを要求してくる確率が高い状況であれば、効果は覿面でしょう。

Datastore にキャッシュする

Datastore にキャッシュする

具体的なキャッシュ保存先としては Datastore を選びました。正直何であってもよかったのですが、 Google 提供 API が返却してくる各種データをほぼそのまま保持するという意味で、スキーマ自由度の高い NoSQL は相性がよいだろうという判断です。

Rate Limit に抵触しないための工夫② 〜 Push Notification

合わせて採用したのが Push Notification という Webhook の仕組みです。これは Google Workspace 上のデータ更新イベントが、予め指定した URL に HTTP リクエストとして通知されてくるというものです。これを利用すればどの Google グループに誰が参加したのか/離脱したのかという情報をほぼリアルタイムに知ることができるため、対応するキャッシュデータを差分更新しさえすればよく、定期的な全件更新に比べて劇的に API リクエスト数を節約できます。以下の図では若干わかりづらくて恐縮ですが、 Google Workspace (右上) から Greep のマイクロサービスのひとつ (左下) に向かって伸びている線がそれに当たります。

リアルタイムなイベント通知

リアルタイムなイベント通知

細かな実装の話ですが、 Webhook が飛んできたからといってすぐに Google に API リクエストを投げるようにはしていません。数分待った上で、且つ同一リクエストをひとつにまとめてから投げています。理由は以下2点です。

  • 更新イベントが通知されたといっても Google Workspace 内部で更新データが伝搬しきっているとは限らない。このためすぐに API リクエストを投げてしまうと、更新前の古いデータ状態を返却される可能性があり、 API リクエスト数を無駄遣いしてしまう。
  • ユーザーの行動として、同一の Google グループを連続して更新することが多い。 AJAX 中心の UI ではその傾向が顕著だし、一連の更新内容が1回の API リクエストで得られるのであれば当然そうしたい。

Rate Limit に抵触しないための工夫③ 〜 API リクエスト頻度の直接制御

ここまでの API リクエスト発行数の節約の工夫により、少なくとも日次上限値を超えてしまうような事態はほぼ起きないと言えるまでになりました。しかしお忘れかもしれませんが、 API ユーザー当たりの、短い時間窓で適用される Rate Limit はまだ残っています。

この Rate Limit は抵触すると数秒〜数分の間 HTTP ステータスコード 429 (Too Many Requests) で失敗する、という程度のものなので、前述したとおり Exponential Backoff で再送するような実装さえ準備していれば、いつかは成功するでしょう。ただ…これも前回の記事と同じ主張なのですが、そもそもリクエスト流量を Rate Limit 上限ぴったりにコントロールしさえすれば、常にリクエスト失敗率は最小化され、全体スループットは最大化される筈です。それを実現するために取った構成が、 Greep 内部の紹介でちらりと触れた「マイクロサービスとタスクキューの1対1対応」です。

Rate Limit に合わせたタスクキュー実行頻度

Rate Limit に合わせたタスクキュー実行頻度

正確には Google 提供 API 〜 マイクロサービス 〜 タスクキューを1対1対1で対応させています。図では People API の列にフォーカスを当てていますが、 Admin SDK でも同じです。ここには以下のルールがあります。

  • ある種類の API を叩くのはそれに対応するマイクロサービスのみ
  • マイクロサービスを呼び出すのはそれに対応するタスクキューのみ
  • マイクロサービスの1回の呼び出しの中で API を叩くのは1回まで

Greep の開発に携わるエンジニアがこれらのルールを守っていさえすれば、タスクキューの実行頻度が API リクエスト頻度と完全に同一になります。大変都合のいいことに…というかだからこそこの構成を採用したのですが、タスクキューとして利用している Cloud Tasks は実行頻度をパラメータで指定できます。アプリケーションのソースコードの中で微妙な sleep 間隔や並列実行度を気にするような実装は一切必要ありません。あとは適切なパラメータを計算、設定してやるだけで、最大のスループットを確実に実現できるというわけです。

余談 〜 Google グループ設定の是正

最後に話を大きく戻して、 Google グループを権限管理に利用する際の注意事項について少しだけ触れておこうと思います。

Google グループには細やかな設定項目が幾つも用意されており、その中には誰がどのようにしてグループに参加できるのか、グループメンバーを制御できるのは誰なのか、といった大変重要な項目が紛れています。それらの設定内容によっては、 Google グループはザルになります。たとえば グループに参加できるユーザー という項目で「組織内のユーザーのみ」という値を選んでいる場合―一瞬「のみ」という言葉に騙されそうになりますが (あれ、なりません?) ―これは Google Workspace 内のユーザーでありさえすれば誰でもその Google グループのメンバーになれてしまうという意味です。そんなザル Google グループのメンバー情報が万一、何かしらの重要ツールの権限として同期されてしまっていたら…

Google グループ設定画面

Google グループ設定画面

そういった危険をできるだけ簡便に回避できるよう、 Greep でグループ設定是正機能を実装しました。これは Greep が Google Workspace 上の (エンドユーザーが行った) 操作内容をリアルタイムに捕捉できるという特性を活かして、望ましくない設定内容に更新されてしまっていた場合直ちに修正をかけ、 Google グループを可能な限りセキュアな状態に保つという機能です。

        general: [
          {
            field: 'whoCanViewGroup',
            needles: ['ANYONE_CAN_VIEW', 'ALL_IN_DOMAIN_CAN_VIEW'],
            replace: 'ALL_MEMBERS_CAN_VIEW',
          },
        ],
        secureGroup: [
          {
            field: 'whoCanModerateMembers',
            needles: ['ALL_MEMBERS'],
            replace: 'OWNERS_AND_MANAGERS',
          },
        ],

上記は設定是正機能周りのルールパラメータを一部抜粋したものです。雰囲気だけ掴んでもらえればよいかなと思います。たとえば前半の一般 (general) ルールは『whoCanViewGroup (会話を閲覧できるユーザー) というプロパティにもし ANYONE_CAN_VIEW (ウェブ上のすべてのユーザー) ないし ALL_IN_DOMAIN_CAN_VIEW (組織全体) といった値が入っていたら、 ALL_MEMBERS_CAN_VIEW (グループメンバー) に置き換えること』と読めます。

今回のお話で特に大事なのは後半のセキュアなグループ用 (secureGroup) のルールで、これはつまり メンバーの管理 を行えるユーザーをオーナーないしマネージャーに限定しています (厳密にいうとこれは DeNA の Google Workspace 全体の設定との合わせ技になっていて、組織の誰でもメンバー管理できるような最上級にザルな Google グループはそもそも作れないよう設定で阻止されています)。重要ツールの権限に同期するような Google グループならこういう設定が維持されるべきです。ただ、このように厳しいルールは必要なく、誰でもいつでも参加/離脱ができるような自由な Google グループを使うユースケースというものもあるでしょう。そのため Greep では、対象の Google グループに secure_anchor@dena.com という特別な識別子ユーザーがメンバーにいたときのみセキュアなルールで設定是正を行い、そうでないグループに対しては一般のルールで設定是正を行うように実装しています。

おわりに

DeNA での Google グループを活用した権限管理の仕組み、そしてそれを安全、且つ効率的に実現するための Google API Proxy の工夫を紹介させて頂きました。表立って目立つことは決してないものの、確かに我々の日常を支えてくれている、そんなシステムになってくれればいいなと思います。

今回の記事がまた誰かの糧になりますよう。


この記事を読んで「面白かった」「学びがあった」と思っていただけた方、よろしければ Twitter や facebook、はてなブックマークにてコメントをお願いします!

また DeNA 公式 Twitter アカウント @DeNAxTech では、 Blog記事だけでなく色々な勉強会での登壇資料も発信してます。ぜひフォローして下さい!

最後まで読んでいただき、ありがとうございます!
この記事をシェアしていただける方はこちらからお願いします。

recruit

DeNAでは、失敗を恐れず常に挑戦し続けるエンジニアを募集しています。