こんにちは、広野です。
AWS Cloud9 に代わる、使い勝手の良い IDE on AWS を作れないものか、、、日々模索しています。
今回は、code-server を HTTPS 公開して MFA 認証を付ける構成を試してみました。まずはアーキテクチャを紹介します。実装は AWS CloudFormation でしており、別記事で紹介します。
背景
この構成を検討するにあたり、以下の状況がありました。
- AWS Cloud9 のような Web IDE を AWS 上に作りたい。code-server を Amazon EC2 に立てるのがてっとり早い。個人用環境または研修用環境なのでコンテナのような本格的な構成は過剰。
- code-server はデフォルトで HTTP (80) で公開され、ユーザー認証の仕組みは組み込みの機能が一応あります、って程度のもの。そのままでは実用的でない。
- ALB や Amazon CloudFront をかぶせることで HTTPS 化はできるが、なんかこれだけのために使用するのは過剰すぎるという感覚。code-server をホストする Amazon EC2 インスタンス単体でどうにかしたい。
- HTTPS 化は自己証明書は NG。自己証明書のサイトにはアクセスできないネットワーク環境があるため。ドメインは Amazon Route 53 パブリックホストゾーンを使用して登録し、証明書は Let’s Encrypt で作成することにする。
- ユーザー認証は厳格にしたい。特に MFA は必須。Amazon Cognito を使用するのがてっとり早い。ログイン画面を開発するのは面倒なので、Amazon Cognito マネージドログインを全面的に活用する。
アーキテクチャ
ということで、考えてみた構成がこちら。先に画面をイメージして欲しいので、解説は後ろに回します。
画面紹介
実際につくったものは、以下の操作になります。
作成した code-server インスタンスの URL にブラウザアクセスすると、OAuth2 Proxy の画面が表示されます。ボタンを押して進みます。
画面が Amazon Cognito マネージドログイン画面にリダイレクトされます。画面デザインはデフォルトです。セルフサインアップも可能です。
MFA ワンタイムパスワードの入力も求められます。
ログインに成功すると、code-server が表示されます。
サインアウト用の URL は signout.md として用意しておきました。リンクを押すとサインアウトできます。ただし、今時点イケてないのはサインアウト後にログイン画面に戻ってくれないことです。いろいろ試しましたが一旦あきらめました。サインアウトはできているので。以下は Edge ブラウザでのサインアウト時画面。正常です。
アーキテクチャ解説
あらためて、アーキテクチャ図を掲載します。
なるべく処理順に説明します。
- ユーザーは、ブラウザから HTTPS で code-server にアクセスしようとします。ドメイン名は Route 53 パブリックホストゾーンに登録されていて、Amazon EC2 インスタンスの Elastic IP アドレスが A レコードとして登録されています。そのレコードに合わせて、SSL 証明書が Let’s Encrypt で作成され、nginx に登録されています。
- 安全のため、Amazon EC2 インスタンスは任意のソースグローバル IP アドレス以外からの通信を受け付けないようにしています。
- HTTPS 通信は nginx が受け取り、SSL の終端をします。以降、EC2 内部の通信は暗号化されません。
- nginx は、ユーザーが認証済みかどうかチェックするために OAuth2 Proxy に問い合わせます。認証済みであればユーザーと code-server を通信させます。認証済みでない場合は、ユーザーに認証が必要である旨の画面を返します。
- ログイン済みでないユーザーは、案内に従い Amazon Cognito マネージドログインの画面で認証 (MFA 込み) を済ませます。必要に応じてセルフサインアップをします。ログインが正常に完了すると、Amazon Cognito マネージドログインはあらかじめ指定されているコールバック URL (ここでは、OAuth2 Proxy の所定のパス) に画面をリダイレクトします。このとき、ブラウザは Cookie に認証済みコードを持った状態になっています。
- 認証済みとなったブラウザはリダイレクトされると、nginx 経由で OAuth2 Proxy が認証状況をチェックします。ここでは、認証済みコードからトークンを取得します。
- 細かい話ですが、アプリが Amazon Cognito ユーザープールに認証の通信をするためにはアプリケーションクライアントを作成する必要があります。今回の設計では、1 EC2 インスタンスあたり 1 アプリケーションクライアントの設定にしています。アプリケーションクライアントにはクライアントシークレットを持たせることができ、シークレットを知っているクライアントでないと認証をすることができません。シークレットは当該 EC2 インスタンスしか知らないようにしているので、他のデバイスが同じアプリケーションクライアント (≒ この EC2 インスタンス用 Amazon Cognito マネージドログイン画面) 経由で認証しようとしても拒否されます。正常に認証状況チェックが完了すると、EC2 インスタンスはトークンを取得できます。
- 上述、アプリケーションクライアントのクォータがあるため、デフォルトでは 1 Amazon Cognito ユーザープールあたり 1,000 アプリケーションクライアントを作成することができます。つまり、管理可能な code-server EC2 インスタンスは 1,000 台が上限となりますが、クォータの上限緩和申請や、設計を変えることで拡張する余地はあります。
- ここまでの設計では、Amazon Cognito に正常に登録されたユーザーは全員、任意の code-server インスタンスにログインできてしまいます。今回の設計では code-server は個人用環境であるため、あらかじめ指定した自分のメールアドレス情報がユーザーの属性になければ認証を通さないようにします。Amazon Cognito ユーザープールのユーザー属性に email があるので、それを OAuth2 Proxy の認証状況チェックの過程でチェックさせます。もし指定されたメールアドレス以外の認証であれば、403 forbidden 画面が表示されます。
- ログアウトについては、2箇所のサインアウト処理をする必要があります。OAuth2 Proxy と Amazon Cognito マネージドログインです。いずれもログアウトは所定の URL にアクセスさせる必要があり、1 アクションで両方に必ずアクセスさせるために URL リダイレクトを使用します。Amazon Cognito マネージドログインは必須でリダイレクト URL (ログアウト URL と呼ばれる) を指定する必要があるので、それを利用しました。ログアウト URL に、OAuth2 Proxy のログアウト用パスを指定するようにしています。ただし、それにアクセスしたときに前章で紹介したエラー画面が出てしまいます。
続編記事
作成次第、こちらに掲載します。
まとめ
いかがでしたでしょうか?
今回の題材は code-server でしたが、他の認証機能のない (または弱い) Web アプリケーションによりセキュアな認証機能を付けたいニーズにぴったりはまると思います。
本記事が皆様のお役に立てれば幸いです。