自己紹介

高橋建太氏(以下:高橋):「認証認可とGo」について説明します。まず自己紹介です。現在Showcase GigのPlatformチームに所属しています。主に認証認可の機能を担当しています。高橋建太です。

週1回会社でGoの知見を共有したり、ドキュメントを読んだり、Goについてみんなで話す緩い勉強会を主催で開催しています。もし合同で勉強会やりたい方がいれば、気軽に連絡してください。ぜひやりましょう。

「O:der」について

まず前提知識として、「O:derとは?」について、あらためて説明します。ちょっと内部寄りな説明となり、実際のものは若干スライドの図と異なったりはしますが、「O:der」は、OMO(Online Merges with Offline)とも呼ばれるオンラインとオフラインの融合を実現して、次世代の店舗体験を提供することをミッションとした次世代店舗創出プラットフォームです。

機能としてはおおまかに、テイクアウトや店内飲食、店頭端末決済などを飲食・小売店に提供しています。

図にもあるように、O:der にはさまざまなクライアントの皆さんに導入いただいています。ひとえに導入と言っても、かなり多くのものを店舗で動かすのですが、お客さまが実際にご自身のスマホで操作するプロダクトのみならず、ホールの店員さんが操作する管理画面や注文情報をキッチンに通知して調理状況を管理するキッチン用の管理画面などがあります。

そして、プラットフォーム内部にもたくさんのサービスが存在しています。あくまで一例ですが、キッチンサービスやレストランサービス、メニューサービスなどです。

O:der ID Serviceの3つの役割

今日話すところです。今ちょうど僕がスライドの青い部分のO:der ID Serviceを担当しているので、説明します。

そもそも「O:der IDとは?」ですが、O:der IDはO:der におけるDegital Identityというところでやっています。O:der ID Serviceが担っている役割が、大きく分けて3つあります。

まず1つ目は、生産性の向上です。先ほどたくさんのクライアントがいるとお伝えしたと思いますが、登録やログインなど、アカウント関連の機能を汎用的に提供することで、開発の効率化を図るためにやっています。

2つ目は、お客さまとO:der のRelationshipのところです。お客さまは、さまざまなクライアントからO:derにアクセスなどをしてきます。そういったユーザーのデータを1ヶ所に一元管理するためのIdentityを定義しています。

将来的にはメニューのレコメンデーションだったり、アレルギー検知だったり、あと不正利用などもできたらなと考えています。

3つ目は、リソースに対するアクセス制限です。

先ほどのプラットフォームの図の中ですが、例えば管理画面が決済サービスを呼ぼうとする流れがあったとして、これはもちろんやってはいけないので、ちゃんとアクセス制限するのも役割として担っています。

Degital Identityなどについてはけっこう長く語れちゃうので、またどこかの機会で共有できたらと思っています。

マイクロサービスの7つの難しいところ

アジェンダです。まず実際に僕たちがやってきたマイクロサービスセキュリティの中で大変だったなところと、あと、実際にその認証認可を僕たちはどうやってやったのかを説明したいと思います。

マイクロサービスセキュリティの難しいところは、大きく7つあると感じました。

まず1つ目です。攻撃対象の領域が広いほど、攻撃のリスクが高くなるところです。マイクロサービスといえどAPI群ではあるので、いろいろなエントリポイントがあり、エントリポイントの数だけセキュリティホールが存在している感じになっています。そのため、マイクロサービスが増えていけば増えていくほど、その管理がすごく大変だと感じています。

2つ目は、外部リクエストの検証によるパフォーマンスの低下です。よくあるサービスで、セッションサービスみたいなものがあって。そこでユーザーのログインを管理しており、ログイン済みのユーザーが会計を行います。その時のサービスのリクエストのフローは、Payment Serviceからの、Ordering Serviceからの、Delivery Serviceと。

あくまで例ですが、こうなった時に、各マイクロサービスがリクエストごとにセッションを問い合わせなければいけないことで、ネットワークをまたいだリクエストになってしまいます。その分、負荷も上がるし、速度もパフォーマンスも落ちてしまうということで、問題点としてあります。

マイクロサービス間の信頼性の構築です。単純にマイクロサービス同士で認証を行う時は、お互いに鍵を管理したりすると思いますが、マイクロサービスが増えるたびに鍵管理を増やしたりするのが、ちょっとつらいなというところで。

例えばローテーションだったり、有効期限切れだったりは、けっこうつらみではあるかなと考えています。

複数のマイクロサービスにまたがるリクエストの追跡を先ほどのフローを例にたとえると、Payment ServiceからOrdering Serviceのリクエストをした時に、データの欠損や、どのリクエストで失敗したのかが、なにも決めずにやってしまうと、やはりすごく大変だったりします。

サービス認証情報やアクセス制御のポリシーの維持です。というのも、マイクロサービスは、基本的にコンテナでやるのが一般的になっているのかなと思います。

コンテナはイミュータブルなサーバーなので、変更を途中で加えたりはけっこう難しかったりします。そのため、鍵の更新だったり、例えばアクセス制御が変更になった時に、再度デプロイが必要になったりとか。マイクロサービスが増えていけば増えていくほど、デプロイ忘れが出てきたりするのかなと思っています。

分散性によるユーザーコンテキストの共有です。こちらも先ほどの例を使うと、ログイン済みとなっているけれど、管理者などがユーザーをBANしたりロックした時などにも、ちゃんと各リクエストごとにユーザーのステータスを管理しなければいけないのが、やはりすごく大変だったりします。

最後ですが、実装レベルのセキュリティです。マイクロサービスというのは、基本的にはどの言語、技術スタックを使ってもいいとなっているとは思います。ただそれ相応に、ちゃんと同様のセキュリティレベルをちゃんと担保しなければいけないところが、けっこうつらいところではあります。ということで、弊社ではバックエンドにGoを採用しています。

認証認可の方法:Open ID Connect

これら問題を踏まえ、僕たちがどうやって認証認可を行っているのかというところで、今やっていることが大きく2つあります。Open ID Connectの実装とSDKの実装です。

Open ID Connectについての説明はちょっと割愛しますが、おおまかには認証と認可と、プラスProfile APIの機能を実装しています。

認証と認可について軽く触れます。認証は、ユーザーやシステムが、“本当にその人なんですよ”という本人性を検証するものです。

認可は、ユーザー、システムに対して、リソースのアクセス権限を与えること。“誰”というのはあまり重要ではないです。

先ほどの例で挙げると、先ほど言った管理画面のユーザーが、Payment Service 使って決済することができないとかが認可にあたります。

どうやっているのかですが、まず各クライアントがOpen ID Connectを利用して、O:der ID Serviceに問い合わせます。O:der ID Serviceは認証画面を提供して、そこでユーザー認証を行い、認可、アクセストークンを返却します。クライアントが受け取ったアクセストークンを使ってマイクロサービスにリクエストを行う流れになっています。

そのアクセストークンですが、中身が署名済みのJWT形式になっています。その中にスコープの値も入っていて、各マイクロサービスはそのスコープを見てアクセスの可否を判断したりしています。各マイクロサービスがアクセストークンの検証を自分自身で行い、都度セッションのチェックなどをしなくていいようになっています。

アクセストークン自体の有効期限はすごく短く、同時にリフレッシュトークンを渡しているので、そこでリフレッシュをしてもらい、また新しいトークンを発行するような流れになっています。署名のためのキーローテーションはここで必須です。

実際のフローです。まずO:der ID Serviceが、各マイクロサービスに対して公開鍵を配布します。

各マイクロサービスはその公開鍵をメモリなどにキャッシュして、アクセストークンが渡ってきたら、各マイクロサービスはアクセストークンを先ほど配布された公開鍵を使って、自身で検証を行うような流れになっています。

認証認可の方法:SDKの実装

SDKの実装です。ここで実装しているのが、キーローテンション機能とアクセストークン検証の機能です。

キーローテーションはけっこう重要だと思っていて、サービスが増えていくことで、やはり鍵の管理がすごく大変になったりします。どのサービスに配布しているのか(の把握)もすごく大変だったり、ローテーションした時に、管理しきれないところがあって、もうすでにつらい状態になっていたり。

署名に使っているトークンの鍵が漏れたりすると、なりすましなど、トークン自体を改ざんすることが可能になってしまいます。そこで、退職した時などの鍵ローテーションでつらみが出てきたりします。

鍵ローテーションの実装を軽く紹介しようと思います。まず、O:der ID Serviceが定期的に鍵をストレージに保存します。Kitchen ServiceやPayment Serviceは、定期的に公開鍵を問い合わせに行きます。

実際のクライアント側の実装ですが、ちょっと見づらいですかね。すごくシンプルですが、main関数などでKey Rotatorを初期化して、その時にアプリケーション起動時に有効な鍵をO:der ID Serviceから取得してきます。

例えば、クライアントがgRPCを使っているのであれば、SDKでgRPCのミドルウェアを提供しているので、gRPCのミドルウェアにKey Rotatorを渡してあげます。最後、下のRun関数の中で、O:der IDにRun関数を実行すると、定期的にO:der ID Serviceに鍵を取得しにいっく実装になっています。

ここでけっこうポイントなのが、errgroupを使って、サーバーと Key Rotator を 並行実行を並行実装をしています。ちょっと書くのは忘れましたが、WithContextを使っているのもちょっとポイントです。

先ほどRotator側のローテートクライアントを初期化したと思いますが、その中にKeyStorageというものがあります。これは単なるmapで、実際のインターフェースはわかりやすいように書いているだけですが、Key valueでPublicKeyを持っていて、Mutexを使って書き込み、読み込みの時にロックを取得するようにしています。

Runの中がどうなっているのかというと、time.NewTickerを使っています。main関数ではerrgroupを使っていたと思いますが、そこでWithContextを呼ぶと、contextのキャンセルを受け取れるようになるので、Runの中で受け取れるようにしています。

for と select を組み合わせて無限ループを行っています。そこで、context と ticker の結果を非同期で受信しています。time.Ticker を使うと、将来の任意のタイミングで処理を実行できます。context を使ってキャンセルを待ちつつも、一定の時間で処理を行うようになっています。

time.NewTickerを使って定期的にintervalTimeを設けて、そこでfetchして鍵を取得していくような実装になっています。

まだいろいろ課題はあります。キャッシュされた古い鍵問題だったり、鍵の取得リクエストが失敗した時のリトライ処理などです。

スライド47今はエラーになっていますが、実際はログに出したり、処理は止めずにログを出したりしています。リファレンスはスライドのとおりになります。

マイクロサービスセキュリティはやることが無限にある

まとめます。マイクロサービスセキュリティはすごく難しくて、やることとが無限にある。まだまだやれてないことがたくさんあります。

認証認可については、Open ID Connectを利用して認証認可を提供しています。各マイクロサービスにSDKの実装を提供して、なるべくセキュリティを担保したものを作ろうと思っています。

ご清聴ありがとうございました。