こんにちは。セーフィー株式会社に所属するサーバサイドエンジニアの河津です。
セーフィーにはクラウドカメラやユーザーアカウントを一括管理できる統合環境である「Safie Manager」というサービスがあり、主にエンタープライズのお客様にご活用いただいています。
エンタープライズ企業のお客様に対してもっと使いやすく、もっと効率的な管理ができるよう日々開発をしており、2023年2月には「効率的な管理」を実現させるための機能として、ディレクトリ連携機能のリリースを行いました。
今回は、ディレクトリ連携機能をどのように開発していったか、またそれを実現させる「SCIM」というものについての記事を執筆してみました。
ディレクトリ連携とは
昨今様々な企業で、社員情報を管理するためにActive Directoryのような仕組みを用いているケースが多くなってきていると思います。
Active Directory (アクティブディレクトリ) とはマイクロソフトによって開発されたオンプレミスにおけるディレクトリ・サービス・システムのことです。2013年にはそのクラウドコンピューティング版であるAzure Active Directoryが誕生しましたが、こちらを導入されている企業様は多いのではないでしょうか。
Active Directory(以下AD)で主に行えることとしてはユーザー認証とアクセス制御ですが、ADで構築した制御設定(誰がどの機能を触れるか)を他サービスにも反映させたい(=管理の手間をAD上の設定だけにしたい)という需要が生まれました。
ADの設定をそのまま他サービスに連携する、というのがディレクトリ連携のざっくりした説明になります。
Safie Managerのディレクトリ連携
Safie Managerとは、「誰が」「どのカメラを」見ることができるかを統合管理するためのアプリケーションです。 ユーザーとカメラそれぞれを管理する際、一人一人に対してカメラの視聴権限を割り振っていくのは手間になってしまうため、ユーザーとカメラそれぞれをグループごとにまとめて、グループ同士を紐づけることで「Aユーザーグループに所属しているユーザーは、A'デバイスグループ所属のカメラ全てが見れる」といった権限制御を行うことができます。
このグループの中に別のグループを作ることができ、まるでディレクトリ階層のような構造で管理を行うことができるのですが、Azure ADなども同様な階層構造でユーザー管理を行えるものであり、おそらくですがユーザーの所属部署情報や担当現場店舗ごとにグループを切る使い方が多いのではないかと思っています。
Azure ADでのグループ情報およびそこに所属しているユーザーを、Safie Managerにグループ情報ごと同期(プロビジョニング)できますというのが、Safie Managerのディレクトリ連携機能の概要になります。
※Safie Managerの階層構造権限などの詳細な仕様については割愛させていただきますが、もし興味ある方はまずはぜひ資料のご請求からお願いいたします。
SCIMについて
いざディレクトリ連携を行うとなった場合、システム管理者の立場から見ると、どのような設定を行えば良いのでしょうか。ここで使用されるのがSCIMというものになります。
SCIMとは、Identity情報(ユーザーの認証情報や権限)を自動プロビジョニングするためのプロトコルです。プロトコルと言うとHTTPのような通信規則が真っ先に思いつくかと思いますが、SCIMは通信規則ではなく標準規格です。「こういったインターフェースでやりとりしてくださいね」という取り決めのことです。
以前SAML認証を用いたSSO(シングルサインオン)を実装する記事を書きました。
- SSO(シングルサインオン)機能を実装するための取り決めとしてSAMLを使用する
- ディレクトリ連携機能を実装するための取り決めとしてSCIMを使用する
上記のような関係性となります。
SCIMはRFCにも仕様が定義されているため、SCIMを用いたディレクトリ連携の機能を開発していく上で困った時はこちらを確認すると良いと思います。
詳細な仕様は上記RFCをご確認いただくとして、この記事では私たちセーフィーがSCIMを取り扱った際にやったことや感じたことなどについて記載させていただこうと思います。
シーケンス
セーフィーがディレクトリ連携機能のサポートをしているIdP(Identify Provider)は、執筆時点ではAzure Active Directory(以下AzureAD)のみとなります。他IdPの場合では差異があるかもしれませんが、AzureADからセーフィーにプロビジョニングされるシーケンスを記載します。
上記シーケンスはMicrosoft公式のドキュメントから抜粋させていただいております。
SP(Service Provider)側はユーザー情報の追加や更新・削除ができるCRUDのAPIを用意しておき、IdP側で情報の更新が起きた際に随時APIを叩くようなシーケンスとなります。
SCIMでのプロビジョニング実行が行われる際、一番初めにGETのAPIが実行されます。このGET APIの実行結果により、
- IdP側で持っている情報と違いがないため何もしない
- IdP側には情報があるがSP側には無いため、POST APIを実行しSPに情報を作る
- IdP側とSP側で情報が異なる(例: 名前が違う)ため、PATCH APIを実行し情報を更新する
- IdP側には情報がないがSP側には情報があるため、DELETE APIを実行し情報を削除する
上記のような処理に分岐していきます。
エンドポイント設定
SCIMで同期可能な情報は、ユーザー情報とグループ情報です。(他の情報もありますがここでの説明は割愛します)
SPで実装したSCIM用のエンドポイントをIdPに登録することで、IdPから一定のタイミングで同期のリクエストが送られてきます。
情報登録を行える画面はこのようになっています。
登録できるエンドポイントは1つです。このエンドポイントを起点に、/Users
とリソース名が指定されたエンドポイントがユーザー操作の、/Groups
と指定されたエンドポイントがグループを操作するエンドポイントになります。
また、認証時のトークンを指定することができるため、SP側で用意するエンドポイントではトークン認証の仕組みを用意する必要があります。
トークンはAuthorization: Bearer xxx
形式で送られてきます。また、Content-typeヘッダがContent-Type: application/scim+json
となってリクエストが来るのも特徴です。
インターフェース例
SP側で用意したエンドポイントに対して、IdP側の情報変更があった際に都度SPにAPIリクエストが送られるというのがSCIMの概要ですが、SCIMはこのAPIリクエストの形式およびレスポンス形式についても取り決めがあります。
SCIMを行う上でのリクエスト・レスポンス形式(インターフェース)例はRFCに記載されているのと、IdPの仕様ドキュメントとしても記載されている場合が多いと思います。AzureADの場合は下記のドキュメントをもとにインターフェースの設計と実装を行いました。
ここからはインターフェースの例をいくつか紹介します。
まずPOST /Usersのケースの例を記載します。ユーザーを同期するために必要となる情報は、
- AzureAD上のユニークID
- ユーザー名
- メールアドレス
などを必須項目としており、下記のようなリクエストを受け付けられるようにしています。
{ "externalId": "1234-5678-90ab-cdef", "emails": [{"primary": True, "type": "work", "value": "test@user.com"}], "displayName": "test user", }
ちなみにexternalId
などのキー名については別の名前を使用することもできます。
AzureADには属性情報のマッピングを行う機能があり、この画面にて「Azure AD内のどの情報が」「どのキー名でやりとりされるか」を定義することが可能です。この画面からユーザー情報とSCIMのキーを紐付けます。
この辺りの設定は、IdPを操作できるお客様自身に設定いただくことになるため、基本的にはデフォルトとして設定されている内容を利用しつつ、どうしても設定いただく必要がある箇所についてはマニュアルに記載しつつお渡しするようにしています。
また、SCIMでプロビジョニングできる情報はユーザー情報だけではなく、グループの情報もプロビジョニングすることができます。グループが登録される際は、POST /Groupsに下記のようなリクエストが送られます。
{ "schemas": [ "urn:ietf:params:scim:schemas:core:2.0:Group", "http://schemas.microsoft.com/2006/11/ResourceManagement/ADSCIM/2.0/Group", ], "externalId": "1234-5678-90ab-cdef", "displayName": "test group name", "meta": { "resourceType": "Group", }, }
グループ自体の登録だけでなく、グループの階層配下にユーザーやグループを追加することもでき、その場合はPATCH /Groupsに下記のようなリクエストが送られます。
{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [ { "op": "Add", "path": "members", "value": [{"$ref": None, "value": "1234-5678-90ab-cdef"}], } ], }
valueの中のIDがユーザーIDの場合はユーザーの追加、グループIDの場合はグループの追加となります。
負荷対策について
例えばIdPに10万件のユーザーを一度に登録した際、ディレクトリ連携されているとその10万件はSPに同期されます。 一気に10万件のリクエストが来るとなると負荷対策的に少し不安になりますが、その辺りはどうなっているのでしょうか。
Azure ADについては、送られるリクエストの間隔が仕様として決められています。
正常系についてはこの仕様通りの間隔でリクエストが来るはずです。後はそれ以上のリクエストが来る場合(誰かが悪意を持って実行しまくること)を考慮し、例えばリクエスト状況をサーバキャッシュで保持しつつ処理毎に判断し、上記仕様より高い頻度でリクエストが来ている場合は処理前段でエラーとするようにしておけば、少なくともDB負荷については対策の一つとなりそうです。
まとめ
ここまでSCIMやディレクトリ連携について記載しましたが、試行錯誤しながら進めていった上で感じたことなどを記載します。
- インターフェース仕様はRFCなどで定められているものの、開発を進める上ではIdPにエンドポイントを設定しつつ、どのようなリクエストが来るかログを見ながら仕様を固めていく方が間違いない。
- 実際のリクエストで判別がつきづらいケース(エラーの場合など)は、RFCの仕様に則って実装すると良い。
- IdP側の仕様や挙動によって想定外の工数が発生する恐れがあるため、余裕を持った開発およびQAスケジュールが取れると安心だと思います。
SCIMについてはドキュメントも少ないと思われますので、この記事がどなたかの役に立てば幸いです。
セーフィーではエンジニアの採用を積極的に行っております。もし興味が出てきた際はぜひご応募いただけたらと思います。