CloudFront で Lambda@Edge を使って OpenID Connect (OIDC) 認証する

CloudFront と S3 を使ってサーバーレスなウェブサイトを構築し、特定のユーザーにのみ公開するために Google, GitHub など外部の認証プロバイダ(IdP)を使って認証をおこないたい。CloudFront ではリクエスト中継時に Lambda 関数を実行し認証などの処理をおこなうことができるため、それを使う。

OpenID Connect による認証シーケンス

uml diagram

構築手順

OpenID Connect (OIDC) による認証をおこなう Lambda 関数を Widen/cloudfront-auth というツールを使って生成する。 ここでは、Google で認証し、特定ドメインのメールアドレスのユーザーのみをアクセス許可してみる。

前提条件

  • CloudFront によるウェブサイトが構築済みであること。

設定項目

以下の設定項目が必要となる。

  • Client ID/Sercet: 認証プロバイダ(IdP)で認証をおこなうために払い出したクライアント用のIDとシークレット。この場合 CloudFront で動作する Lambda 関数が OIDC のクライアントという位置付け。
  • Domain Name: CloudFront でホストしているウェブサイトの URL の host (CNAME を設定していない場合は xxxxxxxxxxxxxx.cloudfront.net)
  • Callback URL: 認証プロバイダ(IdP)からの認証結果を受け取り、処理するためのエンドポイント。「https://(Domain Name)/_callback」など、実際にサイト内に存在するファイルとは重複しないものを適当に指定する。 例) https://xxxxxxxxxxxxxx.cloudfront.net/_callback
  • Distribution Name: AWS に デプロイする際の名称 (任意)

Client ID/Secret は、認証プロバイダ(IdP)で払い出しをおこなう。Google の場合は以下の手順。

  1. Google API Console にアクセスする。
  2. プロジェクトを作成する。
  3. 「Credential」からクライアントを作成する。
    1. 「Create credentials」→「OAuth client ID」
    2. 「Application type」に「Web application」を選択
    3. 「Name」に適当な名前を入力 (例: cloudfront-auth)
    4. 「Authorized redirect URIs」に「Callback URL」を入力
    5. 「Client ID」と「Client secret」が発行されるので、メモしておく。

1. チェックアウト

Widen/cloudfront-auth をチェックアウトする。

$ git clone https://github.com/Widen/cloudfront-auth.git
$ cd cloudfront-auth

2. Lamda 関数の生成

設定スクリプトを実行して必要事項を入力すると、それ用の Lambda 関数が生成される。

  1. 設定スクリプトを実行

    shell $ ./build.sh

  2. Lambda のディストリビューション名を指定する。(任意の名前)

    ```shell

    : Enter distribution name: cloudfront-auth-xxx (例) ```

  3. 使用する認証プロバイダ(IdP)を選択

    ```shell

    : Authentication methods: (1) Google (2) Microsoft (3) GitHub (4) OKTA (5) Auth0 (6) Centrify (7) OKTA Native

    Select an authentication method:  1
    

    ```

  4. クライアント認証情報を入力

    ```shell

    : Client ID: …「Client ID」を入力する : Client Secret: …「Client secret」を入力する ```

  5. Callback URL を入力

    ```shell

    : Redirect URI: https://xxxxxxxxxxxxxx.cloudfront.net/_callback (例) ```

  6. ドメイン名を入力。これに後方一致するメールアドレスのユーザーのみアクセスが許可される。

    ```shell

    : Hosted Domain: example.jp (例) ```

  7. 認証の有効期限を入力 デフォルトの 0 だとすぐに期限切れとなって全くアクセスできないので注意。(やらかした)

    ```shell

    : Session Duration (hours): (0) 24 ```

  8. 認可の設定

    ```shell

    : Authorization methods: (1) Hosted Domain - verify email's domain matches that of the given hosted domain (2) HTTP Email Lookup - verify email exists in JSON array located at given HTTP endpoint (3) Google Groups Lookup - verify email exists in one of given Google Groups

    Select an authorization method: 1 ```

    アクセス可能なユーザーを設定する方法として、以下の 3 つから選択することができる。(Google の場合)

    1. ホスティングドメインに指定したドメインのメールアドレスのユーザーのみアクセスを許可 (今回はこれを選択)
    2. 特定の URL からダウンロードした JSON に書かれたメールアドレスのユーザーのみアクセスを許可
    3. 特定の Google グループのユーザーのみアクセスを許可
  9. 完了 生成された関数は、distributions/{distribution name}/{distribution name}.zip に出力される。

    shell Done... created Lambda function distributions/cloudfront-auth-xxx/cloudfront-auth-xxx.zip

3. Lambda 関数のデプロイ

生成された Lambda 関数を AWS にデプロイする。手動または AWS SAM を使ってデプロイすることができる。以下は SAM を使った例。

  1. template.yaml の {distribution_name} をディストリビューション名に書き換える。

    shell $ DISTRIBUTION_NAME=cloudfront-auth-xxx (例) $ sed -i.bak "s/{distribution_name}/${DISTRIBUTION_NAME}/g" template.yaml

  2. デプロイ

    shell $ sam deploy --guided ★2度め以降は sam deploy のみでOK Stack Name [sam-app]: cloudfront-auth-xxx (例) …CFnスタック名を指定 (任意) AWS Region [us-east-1]: ★Lambda@Edge 関数は us-east-1 にデプロイする必要がある ... (あとはお好み) ...

4. CloudFront ディストリビューションに Lambda@Edge を設定

  1. ホスティングしている CloudFront ディストリビューションの設定を開く

  2. Default Behaviors を Edit

  3. 「Lambda Function Associations」に以下を追加

    CloudFront Event Lambda Function ARN Include Body
    Viewer Request arn:aws:lambda:us-east-1:(アカウント番号):function:(関数名):1 チェックしない

「Lambda Function ARN」にはバージョン付きの ARN を指定する。更新してデプロイし直した場合、Lambda 関数のバージョンが上がっていくので、その都度ここも変更する必要がある。

CloudFront の設定反映は例によって数十分かかるので気長に待つ。

FAQ (やらかしメモ)

認証完了後に無限ループする

認証完了後に元のページにリダイレクトされるが、すぐに再度認証ページにリダイレクトされ、リクエストが無限ループしてしまう。実際にはブラウザが検知して数回繰り返したあとエラー画面になる。有効期限を 0 にしてしまうと、認証完了直後に期限切れとなるため、こうなる。

401 でページが参照できない

認証完了後、画面に「User (メールアドレス) is not permitted.」というエラーメッセージが表示される場合、認証はうまくいっているが認可でエラーになっている。ログインしたメールアドレスに対してアクセスが許可されているかを確認する。