ID基盤をリプレースしました
こんにちは、ティアフォーで認証認可基盤を開発している澤田です。
本記事では、内製のID基盤をORY社のOSS(Kratos, Hydra, Oathkeeper)とGo言語を使ってリプレースした事例について紹介させていただきます。
なお、ティアフォーではマイクロサービスを支える認証認可基盤を一緒に開発いただけるメンバーを募集しています。ご興味のある方は下記ページからご応募ください。
リプレースの背景
ティアフォーID基盤について
運行管理システム(FMS)や遠隔監視・操縦システム、自動運転用の地図作成ツールなど複数のサービスを開発しており、ティアフォーのアカウントで、それらのサービスを利用することができるようになっています。問題が発生したときの影響範囲は大きいですが、提供している機能自体はアカウント管理・連携のみの小さいコンポーネントです。
認可基盤が別であることもあり、ID基盤は非常にコンパクトです。本記事では触れませんが、認可基盤については過去に勉強会で発表した資料を御覧ください。
なぜこのタイミングでリプレースしたか
リプレース前のID基盤は2018年にDjangoで作られたもので、そこまで古くはありませんでした。しかし、利用していたOpenID Providerのライブラリはメンテナンスがされなくなってしまい、forkしてパッチを当てる必要があったり、リクエスト数も増えてきて、大して多くないリクエストで詰まってしまったり、じわじわとメモリリークを起こしていたり、と火種を抱えている状態でした。「リプレースしないと耐えられない」という状況ではありませんでしたが、今後、サービスが拡大して、より多くのトラフィックが流れ、より高い信頼性を求められるようになった際に耐えられないと判断していたこと、ちょうど不要になった機能を削ぎ落としていて、アプリケーションがかなり小さくなっていたこと、まだ利用者が少なく、データも移行しやすいこのタイミングでリプレースすることにしました。
既存データを移行できるリプレース
リプレースするにあたり、可能な限りマネージドサービス(AWSを使っているのでCognito。ユーザー数課金のマネージドサービスはサービスとマッチしないので不可)やOSSを使いたいと考えており、自前で実装するのは最終手段と考えておりました。しかし、既存データの移行を考えると選択肢は狭まります。
具体的には以下の要件が必要か検討しました。
- アカウントのIDは変更可能か?
- OpenID ConnectのClientのID・Secretは変更可能か?
- ユーザーのパスワードはリセット可能か?
アカウントのIDは変更可能か
不可。ティアフォーではマイクロサービスアーキテクチャを採用しており、アカウントのIDは様々なサービスに永続化されています。それらをすべて洗い出して残さずマイグレーションをかけることは困難です。
Djangoが払い出していたIDはAuto IncrementなIDだったこともあり、既存のアカウントのIDをそのままインポートできるサービス・OSSは見つけることはできませんでした。
しかし、払い出されたIDをそのまま使わず、変換する機構を挟むことができればこの問題は解決できます。後述しますが、新しいID基盤ではこの実装をしています。
OpenID ConnectのClient ID・Secretは変更可能か
可。もちろん移行できることに越したことはないですが、社内のClientがほとんどで、社外で使われているClientも調整可能な範囲でした。
ユーザーのパスワードはリセット可能か
不可。ユーザービリティとして悪いのはもちろんですが、過去、送信可能なメールアドレスかを検証をしていなかった時期もあり、パスワードリセットができないユーザーがいる可能性もありました。
PBKDF2でハッシュ化されたパスワードを移行先のシステムが解釈できる形式に変換してインポートできる必要がありますが、この要件により、AWS Cognitoは選択肢から外れました。
新しいID基盤のアーキテクチャ
新しいID基盤ではIdentity ServerにORY Kratos、OpenID ProviderにORY Hydra、IAP(Identity & Access Proxy)にORY Oathkeeperを採用し、独自のサービスロジックやKratosやHydraを連携させたり、画面の表示などを自前で実装しました。
OSSのID基盤と言えば、Keycloakが有名ですが、今回、ORY社のOSSを採用した理由としては以下です。
- 各機能がコンポーネント的に提供されているため、既存データや機能を移行したときに互換性を保ちやすい
- チームとしてはGo言語を普段から使っており、問題や疑問が発生したときにソースコードを追いやすい
- 私自身、過去にORY Hydraを採用したり、コントリビュートした経験があり、使い勝手がなんとなくわかったり、問題があってもコントリビュートしながら解決していける
それぞれのコンポーネントについて簡単に紹介します。
ORY Kratos
ユーザ管理と認証を実装しているヘッドレスなIdentity ServerのOSSです。
実装されている主な機能は以下の通りです。
- ログイン
- ログアウト
- ユーザ登録
- プロフィール管理
- アカウントリカバリー
- Email検証
- MFA
2021年のアドベントカレンダーでも紹介しているので、ご興味のある方はこちらをご覧ください。
https://qiita.com/sawadashota/items/e76e96408ffd9e11f268
Kratosを採用できるかどうかは既存のPBKDF2でハッシュ化されたパスワードをインポートできるかどうかにかかっていました。Issueを書いて、PRを投げ、無事マージされたので、採用することができました。KratosはPBKDF2ハッシュを解釈できるようになり、パスワードが合っていた場合は、設定されているアルゴリズム(Argon2idもしくはbcrypt)にアップグレードできるようになりました!
https://github.com/ory/kratos/issues/1659
KratosのAdmin APIにはアカウント作成時にクレデンシャルをImportする機能があります。
私たちがリリースしたタイミングではこの機能はなかったため、Kratosのデータベースに直接パスワードハッシュを保存する実装をする必要がありましたが、これにより、既存のID基盤も少しだけ移行しやすくなったと思います。
https://www.ory.sh/docs/kratos/reference/api#operation/adminCreateIdentity
ORY Hydra
OpenID Connect CertifiedなOpenID ProviderのOSSです。
Client作成時に、Client ID・Secretは自動生成されますが、指定することも可能なため、リプレース前のOAuth 2.0 Clientの移行は容易です。
ORY Oathkeeper
アクセスコントロールをしてくれるプロキシのOSSです。
https://github.com/ory/oathkeeper
セッションユーザーの情報をUpstreamに伝播してくれたり、Access TokenのScope検証をしてくれます。ユーザーの認可をしたい場合は、ORY KetoやOPA(Open Policy Agent)と組み合わせる必要がありますが、今回は使用していません。
KratosやHydraの管理系APIを保護するのにも活用しています。長期的にはユーザー認可で制御したいですが、今の体制であれば、管理系APIを叩く人用にOAuth 2.0のClientを払い出して、Client Credentials GrantでScope認可で十分と判断しました。
自前実装
雑多に足りないものを実装しているコンポーネントになりますが、具体的には以下の機能を提供しています。
- Kratosが提供しているそれぞれの機能のUI
- KratosにPOSTされる前に追加で行いたいバリデーションなどの処理
- KratosとHydraの連携
- リプレース前のIDをKratosが採番しているIDのマッパー
- リプレース前のID基盤でも実装されていたAPI
リプレース前のID基盤はPython(Django)製でしたが、今回はGo言語で実装しました。
最後に
ID基盤をどのようにリプレースしたか、ご紹介させていただきました。特に問題なくやりきることができ、内心ホッとしております。実施前に不要な機能が削ぎ落とされおり、移行対象が最小化されていたことがうまくいった大きな要因だと感じています。不要な機能を削除するために事業部や法務の関係者と連携を取り、削除に向けてユーザーへの告知や実装を経て機能削除に至ります。スムーズに進行としたと感じていますが、それでも全体としてかなり時間がかかります。リプレースをここから始めないといけなかった場合は相当な時間が必要だったと思いますが、幸い、これが終わったタイミングでのリプレースだったため、非常に良いタイミングでした。
リプレースは3ヶ月程度で完遂できました。OSSを活用したことで、スピーディーに構築することができたと思います。まだ元の機能に合わせてリプレースしただけなので、今後、機能を拡充していきたいと思っています。