AWS WAF で Basic 認証をかける

これは フェンリル デザインとテクノロジー Advent Calendar 2021 12 日目の記事です。

GIMLE チームの野田です。

ウェブサービスを構築する際、公開前のサイトに対して、Basic 認証を使って一時的にアクセス制限をかけたいことがあります。 CDN に Amazon CloudFront を利用する場合、以前から Lambda@Edge や CloudFront Functions を利用して Basic 認証をかけることはできました。 ここでは 2021 年 3 月にリリースされた AWS WAF のカスタム応答機能 を利用して、AWS WAF の設定だけで Basic 認証をかけてみます。

Basic 認証のフロー

MDN のドキュメントより、HTTP 認証ではクライアントとサーバーの間で以下のようなチャレンジとレスポンスがおこなわれます。

  1. サーバーは少なくとも1回のチャレンジで、クライアントに 401 (Unauthorized) レスポンスステータスを返し、 WWW-Authenticate レスポンスヘッダーを含めて認証方法に関する情報を提供します。
  2. サーバーで自身を認証したいクライアントは Authorization リクエストヘッダフィールドに資格情報を含めることでそれを行うことができます。
  3. 通常、クライアントはユーザーにパスワードのプロンプトを表示し、正しい Authorization ヘッダーを含むリクエストを発行します。

このフローの 1. でサーバーから提供される WWW-Authenticate レスポンスヘッダーは、WWW-Authenticate: <type> realm=<realm> のような形式になります。 Basic 認証を要求する場合は <type>Basic となります。

フローの 2. でクライアントからサーバーに送る Authorization リクエストヘッダフィールドは、Authorization: <type> <credentials> のような形式になります。 Basic 認証の場合、 <type>Basic になります。 <credentials> にセットする資格情報は、ユーザー ID とパスワードをコロン(:)区切りで結合し、Base64 でエンコードした文字列になります。 (MDN のドキュメントにも記載されていますが、Base64 は可逆エンコードのため Basic 認証は安全でなく、HTTPS / TLS を組み合わせる必要があります)

AWS WAF のルールを組み立てる

Basic 認証のフローに合わせて、AWS WAF の Rule builder で独自のルールを作成します。 ここでは、基本的な方針として、Web ACL のデフォルトアクションを許可(Allow)とし、個別のルールにマッチした場合にブロックします。

f:id:horo1717:20211211162149p:plain
Rule builder を利用して、Basic 認証のカスタムルールを生成する

ルールステートメントの設定

ルールステートメントを以下の通り設定し、Authorization ヘッダーに対して特定の文字列が設定されているかを検証します。

  • Inspect: Header
  • Header field name: Authorization
  • Match type: Exactly matches string
  • String to match: Basic <credentials>
  • Text transformation: None

String to match フィールドに指定する <credentials> は、Basic 認証で使われる資格情報なので、base64 コマンドを利用して次のように変換できます。 echo コマンドの -n オプションは、改行文字を出力しないようにするための指定です。

$ echo -n aladdin:opensesame | base64
YWxhZGRpbjpvcGVuc2VzYW1l

f:id:horo1717:20211211162156p:plain
WAF のルールステートメントの設定例

正しい資格情報が送信され、ルールステートメントに一致した場合はアクセスを許可したいため、If a request には doesn't match the statement (NOT) を指定し、ルールステートメントに一致しなかった場合、アクセスを拒否するよう設定します。

ルールアクションの設定

ルールステートメントに一致しなかった場合のアクションを Block に設定し、Custom response の Enable 欄にチェックを入れて、以下の通り応答をカスタマイズします。

  • Response code: 401 (Unauthorized)
  • Response headers:
    • Key: WWW-Authenticate
    • Value: Basic realm="<realm>"

<realm> には、認証が必要な領域であることを示すメッセージを設定しておきます。

f:id:horo1717:20211211162207p:plain
WAF のルールアクションの設定例

確認

Rule builder で独自のルールを使用する CloudFront 用の Web ACL を作成し、CloudFront Distribution に割り当てます(Web ACL や CloudFront の設定は割愛します)。

ウェブブラウザで開発者コンソールを表示しながら CloudFront のエンドポイントにアクセスしてみると、HTTP ステータスコード 401 で www-authenticate ヘッダーが返ることが確認できました。

f:id:horo1717:20211211162214p:plain
ウェブブラウザの開発者ツールで、レスポンスヘッダを確認する

ユーザー名とパスワードを確認するダイアログが表示されるため、正しいユーザー名とパスワードを入力すると、サイトにアクセスできます。

AWS のサービス設計

AWS WAF には、Basic 認証そのものをサポートする機能や UI はありませんが、カスタム応答機能を利用することで Basic 認証が実現できました。 Basic 認証機能を提供するのではなく、レスポンスコードやレスポンスヘッダーをカスタマイズできるという、より汎用的な手段としてデザインされているところが、AWS らしくて良いなと思います。 利用する側としては、HTTP 認証のフローや Basic 認証で使われる HTTP ヘッダーについて知らないといけないので、多少の知識が要求されてしまうところではありますが。

なお、WAF のルールステートメントに設定する文字列は KMS による暗号化ができないため、資格情報の格納手段としてあまりセキュアではありません。 IAM ポリシーで WAF (wafv2) の読み込み権限が付与されていたり、wafv2:GetWebACLwafv2:GetRuleGroup アクションが許可されていたりする場合は、設定した資格情報が参照できてしまう可能性があります。 そのため、この方法がアクセス権限制御等の要件に合うか、十分ご留意ください。