自己紹介

渋川よしき氏:フューチャー株式会社の渋川が発表します。まず「お前誰よ?」ですが、2017年からフューチャーで働いています。いろいろ本を書いています。『Real World HTTP』のほか、昔のものですが『つまみぐい勉強法』『Goならわかるシステムプログラミング』もあります。最近はよくJavaScriptというか、TypeScriptとGoとPythonを書いています。ほかに仕事でFlutterもやっています。

著書の『Real World HTTP』はちょこちょこ増刷もされています。買ってくれた方、ありがとうございます。実は今日『エキスパートPythonプログラミング改訂3版』が発売になりました(登壇時2021年7月30日)。こちらは対象のバージョンが古いというか、少し前の本だったので、最新のバージョンに合わせていろいろ加筆もしています。

認証と認可のまとめ

今日は、みなさん当然『Real World HTTP』を読んでいるものとして進めていこうと思いますが、読んだけどつい忘れてしまった人向けに軽くまとめを書いています。

認可とは「これをやってもいいですよ」と許可を与えることで、認証というのは「お前誰よ?」と個人の特定をすることです。認可と認証は少し違います。認可のフレームワークとして使うのがOAuth2.0です。最近2.1も少し話題に出ていますが、こちらがデファクトスタンダードです。

認証のフレームワークは、Web系ではよくOpenID Connectが出てきます。エンタープライズではSAML(Security Assertion Markup Language)というものもありますが、OpenID Connectがよく使われています。OAuth2.0もOpenID Connectも、さまざまなユースケースを想定して少しずつ機能が強化されているものの、昔の説明を見て適当に実装すると、一見認証はできていますが、少しセキュリティ的に弱い。脆弱性が要注意です。

GoでWebサービスを作る時の悩み

GoでWebサービスを作る時の悩みは、net/httpなどフレームワークはなんでもいいことで、どのフレームワークでも認証のカチッとした仕組みはあまりないと思います。例えばJavaならSpringセキュリティ、Node.jsならPassportのように、「とりあえずこれ使っとけ。それのプラグインでどんな認証も対応できる」といったものはありません。

セッションストレージのミドルウェア的なものに限定すればありますが、認証部分のセットはない。自前で毎回作るのはけっこう面倒なので、とりあえず認証だけならサンプルコードを持ってきてペペッとやります。

「セッション管理? 認証したあとのセッションの管理はどうするの?」「セッションの持続時間どうしよう」「アイドルタイムアウトどうしよう」など、わりと細かくて非機能要件的なところなので、後回しにされがちだと思います。

さらに、エンタープライズな仕組みでは管理者や一般ユーザー、アルバイトなど、いろいろなユーザーごとの権限やアクセス制御でだいたい入れると思いますが、毎回実装するのは大変です。とりあえず開発開始時は認証なしで実装して、あとから入れてもまた漏れが出てきたりして面倒です。

これは少し特殊な例かもしれませんが、今の案件や少し前の案件では、E2EのテストでCypressというものを使っていました。ローカルでKeycloakを立ててやっていましたが、認証に時間がかかり、200件くらいあると毎回3秒くらい取られます。サイプレスは並列実行ができないので、「これだけで10分くらい時間をロスしてもったいないな」というのも個人的な悩みでした。

認証はつい後回しにしがちですが、ライブラリの最新の使い方、nonceやstateやPKCEなどのセキュリティのチェックをきちんとやっているか、本当はもっと強い認証にしたいとずっと思っていました。

audでは、発行先のIDプロバイダが「このサービス向けに発行した」というものがIDトークンに入っていますが、自分のクライアントIDと一致しているか、途中でリクエストを入れ替えるような攻撃をされていないかをきちんとチェックしているか。ほかに、リダイレクトのURLを書き換える攻撃の餌食になるような実装になってないかなどを、きちんと毎回やり切るよう徹底するのがけっこう大変です。

一応、OWASPのガイドラインも一通り読みましたが、最近のスマートフォン前提のシステムだと、ログアウトがないことがあまり書かれていません。最近はこのあたりをもう少しいろいろな人と議論して、最適な実装を考えたいと思っています。

少し格好いいなと思うものに、Twitterにあるいろいろなデバイスでログインしているセッションリストのようなものがあり、管理画面から「これとこれをキックする」ことができます。こういうものもやりたいと思いますが、業務システム開発でここまでじっくり機能を実装することはあまりないでしょう。

まとめるといろいろありますが、最終的には自作するしかなく、作ることにしました。

自作した認証リバースプロキシの仕組み

GitLab.comで、フューチャー社内でインキュベーション的なOSSをどんどん載せてこうみたいなオーガニゼーションで「osaki−lab」というものを作り、その中で機能を公開しています。名前は「Who Are You」の短縮系のスラングですが、会社のメンバーがみな「ルー」と読むので私もそう呼ぼうと思っています。

シンプルに作るためにいろいろ割り切りました。ユーザーは登録済み前提。弊社では社内システムなどでよく作るので、BtoCシステムのように自由にユーザーがどんどん追加されるユーザー登録の仕組みは、今回外しています。

認証はほかの信頼できるシステムに任せ、内部ではパスワードを持たない。ユーザー情報はRDBを前提にするのではなく、オンメモリで。社内システムなので、日本最大の企業でもおそらく10万人、1人あたりのデータを1キロバイトとしてもメモリは載ると思うので、全部オンメモリで処理する。

リバースプロキシにすることでGoで実装していますが、Go以外で実装したサービスでも使えるようにしたい。サービスと同一のoriginであればCORSも考えなくて済むので、サードパーティクッキーやファーストパーティクッキーの話もなくなります。

コマンドライン引数を実装するのが面倒くさかったので、すべて環境変数にして、コントロールでコマンドライン引数を採らないかたちで割り切りました。

想定ユースケースは本番環境ならプロキシとして動いて、セッションストレージは外にある。外のIDプロバイダで認証する仕組みです。

(スライドを指して)先ほどのスクリーンショットにもありましたが、いろいろな認証のIDプロバイダは複数同時利用が可能です。社内システムは第3の会社が入ってくることもあるので、いろいろなIDプロバイダをサポートできたほうがいいと思っています。ユーザー情報はCSVファイルで読み込みます。これはどこかのRDBではなく、CSVファイルでS3やローカルのテキストファイルを読み込むような仕組みになっています。

セッション情報は、本番環境では永続可能なストレージに保存するので、今のところDynamoDBやMongoDB、サーバーレスなストレージに入れるようにしていますが、Redisも実装したいと思い枠だけは作っています。

開発環境の仕組み

開発環境の場合は、セッションストレージもオンメモリです。再起動すれば消えてしまいますが、開発環境ならまったく問題ないので、このシステムだけ置いとけばいいと思って作っています。だいたいみんな開発環境で既存の認証機構を使った場合はユーザーIDとパスワードが必要になるので、それをエクセルで管理したりしていると思いますが、開発環境にパスワードなど必要ないので、開発時はユーザーのスコープを見て選ぶだけのインターフェースにしています。

Cypressでログインを実装すると、ブラウザの入力をエミュレーションするために1文字ずつタイプするので遅いです。そのため、開発環境ではページを開いてボタンを押すだけのモードで起動するようにしています。

繰り返しになりますが、リバースプロキシとして動作する。開発モードも本番モードもバックエンドで動くサーバーから見れば違いはないので、アプリケーション側で開発モードと本番モードの両方を実装する必要はありません。本番モードだけ実装すれば大丈夫です。

ユーザー情報は選択するだけです。ユーザー情報はCSVだけでなく環境変数からも直接入れて、Dockerなどで動かしやすいようにもなっています。セッション情報はオンメモリです。

認証するだけなら不要ですが、やはりセッションストレージのようなユーザーごとの情報を格納できれば便利なので、クッキーのような仕組みを実装しました。

(スライドを指して)バックエンドのサーバーから特定のセッションデータというヘッダーをつけると、このプロキシがそれを食って、セッションストレージに入れます。次に、クライアントからのリクエストがあれば、ログインしているユーザーのセッションデータを取ってきて、それをくっつけて後ろに投げるという感じです。

(スライドを指して)Goっぽい話が薄くなってきたので、技術的なことをこちらにまとめました。パラメータはコマンドラインの引数パーサを使わず、環境変数を構造体にマップするenvconfigを使って一括して取ってきています。

ほかに、標準ライブラリのリバースプロキシを使っています。セッション情報のヘッダーを付与したり、バックエンドからセッションデータの追加のリクエスト受けたり、ヘッダーを書き換えたりしているので、その場合リバースプロキシはカスタマイズポイントが3つくらいありますが、一番コアなトランスポートの置き換えが必要なので、そこでいろいろやっています。

全体のルーターは「chi」で、パフォーマンスがいいものです。最近お気に入りで、よく使っています。ストレージは「gocloud.dev」というGoogleが出しているライブラリです。URLのパラメータを切り替えるだけで、例えばDynamoとfirestoreが使い分けられるなど、いろいろなクラウドサービスのラッパーをしてくれる便利なライブラリです。画面テンプレートはgo:embedで埋め込んでいますが、オプションで差し替えることも可能です。これはGo1.16の機能です。

オープンソースの認証プロキシの比較

(スライドを指して)認証のプロキシにはいくつかオープンソースがあります。いろいろ比較しましたが、欲しいものがなかったので作りました。

だいたいOAuth 2.0プロキシやloginsrvは、スコープ管理がなくログインすればOKです。きちんと使ったりドキュメントを見たりしたわけではありませんが、見た感じこんなかなと思っています。

私の実装では、フロント側は100パーセント「http−onlyクッキー」で返して、フロント側でセッション情報には触らせず、フロント側にコードを足さなくても利用可能としています。

第4の実装みたいなものもありましたが、これは最近のKeycloakのブログになくなると書かれていました。

今後追加予定の機能とセキュリティの強化

(スライドを指して)「今後追加しても良いかな、と思う機能」をいろいろ書きました。管理画面が欲しい。ミドルウェアとしても利用可能にして、Cloud RunやApp RunnerやAWS Lambdaから使えるようにしようと思っています。コンパイルが通る状態まではいきましたが、まだ少しデバック中のような感じです。近日中には追加できると思います。

今日はフューチャーの社員の方も何人か聞いていると思いますが、社内の認証機能や社内システムの追加も当然あると思います。興味がある方は連絡してくれれば、工数を捻出してサポートや機能追加ができると思うのでお知らせください。

セキュリティの強化について。セキュリティの強化ってなんだろう。非機能要件なので、「これを突破したらOK」ということはあまりないと思いますが、利便性とセキュリティの強さにおいて、利便性を殺さずに便利にしていきたい、チャレンジしてみたい、WebAuthnで少し遊んでみたいと思っています。

現在はオープンソースで公開中

まとめです。Goで簡単に使えて確実なセキュリティが実現できるミドルウェアがなさそうだったので作ってみました。私が作ったものをそのまま使うかは別として、一度作っておけばそのコードをきちんと勉強してほかのものにも応用してもらえると思うので、オープンソースで公開しています。

今回は、ミドルウェアがないことが作ることの出発点でした。バックエンドのサーバーがNode.jsでもPythonでも何でも使えるように、リバースプロキシとして使っています。「とりあえず作りたい」から始まって作ってしまいましたが、個人的にはセッション管理などのセキュリティについてもう少しいろいろな人と議論したいと思っています。興味のある人は声をかけてください。勉強会もしたいと思っています。

(スライドを指して)最後に、軽く会社の紹介をします。「Future Tech Blog」にはGoのネタもたくさん載っているので、チェックしてもらえれば幸いです。広報というか、人事系以外のネタは「未来報」に載っているので、興味のある方はぜひ読んでください。

また、フューチャーでは人材を募集しています。今はリファラル採用でいろいろいい感じになっているので、ぜひ興味のある方は声をかけてください。以上です。