CloudFrontとLambda@Edge(Python3.9)によるBasic認証ハンズオン

## はじめに
CloudFrontを経由してS3バケットの静的画面を作成したものの、特定のユーザからしかログインできないようなBasic認証を設定してみたい。

出来ることは知っているのに、なかなか自分で手を動かして構築しない出不精ですがGWということを理由に構築をしていきたいと思います。

## Basic認証とは？
Webコンテンツの送受信で用いられるプロトコル**HTTP**(Hypertext Transfer Protocol)を利用します。

設定している**ID**と**パスワード**を入力して、正しければ画面を表示させるといった認証機能のひとつです。

参照：[IT用語辞典 BASIC認証[基本認証]](https://e-words.jp/w/BASIC%E8%AA%8D%E8%A8%BC.html)

## 構成イメージ
・前提条件：AWSのCloudFront＋S3の静的HPを作成済
参照：[AWS CLIを利用してAmazon S3静的ウェブサイトのホスティング](https://cloud5.jp/statically_page_hosting_by_s3/)

・上記で作成した構築に、Lambda@Edgeを利用してBasic認証を設定していきます
![basic認証](https://cloud5.jp/wp-content/uploads/2022/05/スクリーンショット-2022-05-05-11.11.12-640x491.png)



## ハンズオン
### 1.IAMロールの作成
#### 1.1 ポリシー作成
マネジメントコンソールから「IAM」に移動して「ポリシー」を作成します

参照：[Lambda@Edge 用の IAM アクセス権限とロールの設定](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-permissions.html#lambda-edge-permissions-required)
上記URL先にLambda@Edgeを利用する際に必要なIAMの説明があります

**IAMポリシー詳細**
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "lambda:GetFunction",
                "lambda:EnableReplication*",
                "iam:CreateServiceLinkedRole",
                "cloudfront:CreateDistribution",
                "cloudfront:UpdateDistribution"
            ],
            "Resource": "*"
        }
    ]
}
```
#### 1.2「ロール」を選択し「ロールを作成」を押下する
![file](https://cloud5.jp/wp-content/uploads/2022/05/image-1651546815663.png)
#### 1.3 「AWSのサービス」を選択、「Lambda」にチェックして「次へ」を押下する
![file](https://cloud5.jp/wp-content/uploads/2022/05/image-1651709651177.png)

#### 1.4 作成したポリシーを選択して「次へ」を押下し、作成後ロールの名前を付けて作成する

#### 1.5 作成したロールを選択して「信頼関係」タブを押下して内容を一部変更
Lambda@Edgeにもロールを引き受けることが出来るように`"edgelambda.amazonaws.com"`の一文を付け加えています
参照：[サービスプリンシパルの関数実行ロール](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-permissions.html#lambda-edge-permissions-function-execution)

**信頼関係詳細**
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": [
                    "edgelambda.amazonaws.com",
                    "lambda.amazonaws.com"
                ]
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
```

### 2.Lambda関数の作成
#### 2.1 マネジメントコンソールからリージョンを「バージニア北部」に移動する
![file](https://cloud5.jp/wp-content/uploads/2022/05/image-1651710893757.png)
#### 2.2 マネジメントコンソールから「Lambda」を選択し「関数の作成」を押下する
![file](https://cloud5.jp/wp-content/uploads/2022/05/image-1651710603757.png)

#### 2.3 「Lambda」のコードを修正してデプロイをする
```python
import base64

def authenticate(user, password):
#認証ユーザ・パスワードの設定
    return user == 'test' and password == 'test'


def lambda_handler(event, context):
    request = event['Records'][0]['cf']['request']
    headers = request['headers']

    error_response = {
        'status': '401',
        'statusDescription': 'Unauthorized',
        'body': 'Authentication Failed',
        'headers': {
            'www-authenticate': [
                {
                    'key': 'WWW-Authenticate',
                    'value': 'Basic realm="Basic Authentication"'
                }
            ]
        }
    }

    if 'authorization' not in headers:
        return error_response

    try:
        auth_values = headers['authorization'][0]['value'].split(" ")
        auth = base64.b64decode(auth_values[1]).decode().split(":")
        (user, password) = (auth[0], auth[1])
        return request if authenticate(user, password) else error_response
    except Exception:
        # フォーマット不正など
        return error_response
```
#### 2.4 作成したLambdaに「トリガーを追加」を押下し、「CloudFront」にアタッチする
![file](https://cloud5.jp/wp-content/uploads/2022/05/image-1651711940324.png)

#### 2.5 「トリガーを追加」の項目を入力して「デプロイ」する
デプロイには少し時間がかかります（私の場合ですが、5～10分程度）
![file](https://cloud5.jp/wp-content/uploads/2022/05/image-1651712259357.png)

##### 2.6 Lambdaでの画面を確認する
Lambdaのトリガーとして、CloudFrontがアイコンで表示されています
![file](https://cloud5.jp/wp-content/uploads/2022/05/image-1651713509104.png)

### 3 CloudFrontでの設定
#### 3.1 ビヘイビアの編集へ移動する
マネジメントコンソールから「CloudFront」に移動し、今回対象にしたディストリビューションの「ビヘイビア」タブを選択して「編集」を押下する
![file](https://cloud5.jp/wp-content/uploads/2022/05/image-1651714147302.png)
#### 3.2 ビヘイビアの編集へ移動する
「キャッシュキーとオリジンリクエスト」の「ヘッダー」に「Authorization」を追加する。
入力したユーザ、パスワードを、ヘッダーのAuthorization（＝承認情報）に含めて送信するため
![file](https://cloud5.jp/wp-content/uploads/2022/05/image-1651714580895.png)

##### 3.3 ビヘイビアの編集の画面下の「関数の関連付け-オプション」での表示画面を確認する
既にLambdaの画面でトリガーに設定しているため、「ビューワーリクエスト」にLambda@Edgeが紐づいていることを確認する
※異なった関連付けがなされている場合は、この画面で削除・新規付け替えが可能です
![file](https://cloud5.jp/wp-content/uploads/2022/05/image-1651714782195.png)

## 挙動の確認
#### CloudFront経由のURLを叩くと「Basic認証画面」が表示される
![file](https://cloud5.jp/wp-content/uploads/2022/05/image-1651712885407.png)

#### [おまけ]Lambda@Edgeのログはどこに出力されるのか？
**A.関数が実行される場所にもっとも近いリージョン**
上記の理由から今回は東京リージョンのCloudWatchLogsのロググループに出力されていました
![file](https://cloud5.jp/wp-content/uploads/2022/05/image-1651713375419.png)

参照：[Lambda@Edge 関数のログが見つからないときの対処方法](https://dev.classmethod.jp/articles/tsnote-lambda-edge-log-region/)

## まとめ
・実際の案件でもサクッと導入したり、ポートフォリオなど静的画面での構築などでも比較的取り入れやすいので、おすすめな認証方法です。

・Lambda@Edgeのログは、関数が実行された場所に依存するとのことなので、収集の方法については考えなければいけないということも思いました。

## 参考資料
[Lambda@Edge を使用したエッジでのカスタマイズ](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/lambda-at-the-edge.html)
[Amazon CloudFrontとAWS Lambda@EdgeでSPAのBasic認証をやってみる](https://dev.classmethod.jp/articles/cloudfront-lambdaedge-basic-spa/)
[[AWS] CloudFrontでBASIC認証を行う – Lambda@Edge + Node.js](https://blog.katsubemakito.net/aws/cloudfront-basicauth)