AWS_ALB+Lambda(Pyhton)でBasic認証＋α

# 概要
AWSで何度かALBに対しBasic認証を行う機会があり、毎回調べながら試行錯誤して作っていたので備忘を目的に記載。
(個人的な感覚で、Node.jsの記事は良く見かけるが、Pythonの記事はあまり見かけないので、いつも一苦労・・・)

# なんでPython？
個人的に、Node.jsよりPythonの方がバージョンのサポート期間が長いイメージなので、Pythonを使いたい。
参考：
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-runtimes.html

# 構成
こんな感じ。
![basic認証.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505716/221af1a1-5455-f176-c5f1-88aba89425d5.png)

Basic認証未済のリクエストはLambdaへ、Basic認証済のリクエストはEC2へ。

# AWS設定
以下の順序
1.Lambda作成
2.EC2作成
3.ターゲットグループ作成
4.ALB作成

## 1.Lambda作成
以下条件で作成。
・ランタイム
　Python12
・VPC
　非VPC(VPCの場合だとSecurityGroupの設定が面倒だったため)
・IAM
　デフォルトのまま

```python:BasicAuthLambda
import json
import base64

def lambda_handler(event, context):
    # eventのヘッダからuserAgentを取得する
    useragent = event.get('headers').get('user-agent')
    # ALBヘルスチェックはBasic認証を除外。ALBヘルスチェックはuserAgentがELB-HealthChecker/2.0となるので、これに注目。
    if useragent == 'ELB-HealthChecker/2.0':
        return {
            'statusCode': 200,
            'Content-Type': 'text/html; charset=utf-8'
        }
    # ALBヘルスチェック以外はBasic認証対象。このLambdaに着信するリクエストは全てBasic認証を応答する。
    else:
        return {
            'statusCode': 401,
            'statusDescription': '401 Unauthorized',
            'body': 'Unauthorized',
            'isBase64Encoded': False,
            'headers': {
                'WWW-Authenticate': 'Basic',
                'Content-Type': 'text/html'
            }
        }
```

## 2.EC2作成
適当なEC2を作成しapacheをインストールする。
以下はAmazonlinux2023でapacheをインストールした際の例
```sh:command
> sudo yum -y install httpd
> sudo systemctl httpd status
> sudo systemctl enable httpd.service
> sudo systemctl start httpd.service
```
インストール後、動くか確認
```sh:command
> curl http://localhost
<html><body><h1>It works!</h1></body></html>
> 
```

## 3.ターゲットグループ作成
2つ作成。Basic認証Lambda用とWebServerEC2用。

### Basic認証Lambda
ターゲットタイプはLambda関数
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505716/07398d95-9a3f-d8a5-05de-84e4726f65d5.png)

ヘルスチェックは有効にしておく
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505716/cc7ce7e1-34e0-1afe-ebd6-6f5e724f463e.png)

1.で作成したLambda関数を設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505716/2bba2e18-bed0-bd29-d23d-aacb1743c9e5.png)

### WebServerEC2用
Basic認証Lambdaに倣い、2で作成したEC2のターゲットグループを作成。

# 4.ALB作成
ALBは、今回は外部からのアクセスのためインターネットフェイシングで作成。
ポイントはリスナールール。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505716/04400adf-2a98-2cda-9141-752d6d3a7d17.png)
青枠の部分が特徴で、HTTPヘッダーのAuthorizationが**Basic XXXXX**の時、WebServerであるEC2のターゲットグループへルーティングする、というルールである。

## 「Basic XXXXX」の生成方法
basic認証で入力するユーザ・パスワードはbase64エンコードされ、この値を登録している。シェルが叩けるサーバやCloudShellで、ユーザ・パスワードのbase64エンコード文字列を生成する。
> echo -n <ユーザ>:<パスワード> | base64
```sh:command
[cloudshell-user@ip-XXX-XXX-XXX-XXX ~]$ echo -n user1:pass1 | base64
<エンコード文字列>
[cloudshell-user@ip-XXX-XXX-XXX-XXX ~]$ 
```

注意点としては、Basic認証で入力したユーザ・パスワードのエンコードは「**Basic XXXXX**」という形となる。このため、ALBのリスナールールにも、base64でエンコードした上記の結果に、**Basic**を付与する。
上記の例だと「**Basic <エンコード文字列>**」(←ルール登録時、「」は不要)。

# 検証
ALBのDNSへアクセス。Basic認証が求められる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505716/d84168c1-6a31-07e7-9701-fa622e728d0f.png)

ユーザにuser1、パスワードにpass1を入力しログインを押下すると、問題なく稼働
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505716/8e882320-e1f9-6f6a-2b0f-6bf609c464ac.png)

# ＋α(一度認証に失敗したリクエストをError画面へ遷移)
上記だとBasic認証に失敗した場合、常にLambdaへリクエストが飛ぶ。
通常のリクエストであれば問題はないが、ブルートフォース等によるパスワードクラックの可能性を踏まえると、認証の回数制限などを設けることが良いと考えている。

回数制限の実装は大掛かりになるので、本章では楽に実装が可能な一度認証に失敗したリクエストをError画面に遷移する方法を紹介する。

具体的にはALBのルールに一つ追加するのみ。
(便宜上、不要なところは削除)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505716/41b6fe29-2d71-3cbe-9723-56dcc04672cd.png)

初回アクセス時は、Authorizationヘッダが存在しないため、一番下のデフォルトルールに到達し、2回目以降は、空白であってもAuthorizationヘッダはつくため、1番目か2番目のルールで評価となる。
正しい認証情報(ユーザ・パスワード)以外はAuthorizationが*の箇所で評価され、固定レスポンスを応答する。という流れ。

これにより、むやみやたらにBasic認証用Lambdaが叩かれる心配が減る。
なお、ブラウザキャッシュをクリアすればAuthorizationヘッダもクリアされるため、再度Basic認証にトライ可能。