Amazon API Gateway : Lambdaオーソライザーの認可結果に応じたファイルをS3から取得

# 初めに
プロジェクトを進めている際にS3からファイルを取得するAPIを外部ユーザー向けに用意することになりました。追加の制約事項は以下の通りです。

- 権限を持つユーザーのみアクセスできる
- S3上のディレクトリはユーザーごとに分かれている
（バケット名/部署ID/ユーザーIDのようなパスになっている）
- ユーザーはファイル名を指定するだけでそれぞれのフォルダからファイルを取得することができる
（S3のフォルダ構成を意識する必要がない）

SDKを利用して作成することもできたのですが、API GatewayのLambdaオーソライザーを試したかったのでそちらで作成してみました。作成手順をこの記事にて紹介させて頂きます。

# Amazon API Gateway
Amazon API GatewayはAWS上で効率的にAPIを作成し公開できるサービスです。その他AWSサービスと統合することでS3やLambdaなどのプロキシとして作成することができます。詳細は公式デベロッパーガイドをご参照ください。

https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/welcome.html

# API GatewayのLambdaオーソライザー
API Gatewayで作成したAPIの認可方法としてLambda関数を設定することができます。それがLambdaオーソライザーです。独自の認可処理を組み込めるだけでなく、認可情報に基づいてAPIの処理を分岐させることもできます。

https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html

# Lambdaオーソライザーの認可結果に応じたファイルをS3から取得するAPIの作成
今回の要件を実現するAPIの作成手順について説明します。

## 認可用Lambda関数の作成
### Lambda関数の新規作成
最初に認可用のLambda関数を用意します。画面に従って空のLambda関数を作成します。
![Lambda関数の作成.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2169128/b3568042-1021-8af3-0844-678968632b9c.png)

### 認可処理の記述
Lambda関数に認可処理を記述します。戻り値にはAPI実行を許可または拒否するIAMポリシーをJSONで返却する必要があります。今回の要件の一つに「ユーザーごとのフォルダからファイルを取得する」というものがありますので、Lambdaオーソライザーで取得した認可情報もAPIに伝えておく必要があります。そういう時に利用できるのがContextです。Contextにはカスタムプロパティを自由に追加することが可能で、またIAMポリシーとともに返却することで、後続処理にカスタマイズされた認可情報を引き渡すことができます。後続処理でのContextの利用方法は「APIの作成」で説明します。Lambdaの詳細は下記コードをご参照ください。
```python:app.py
def lambda_handler(event, context):
    user = event["headers"]["user"]
    password = event["headers"]["password"]

    user_info = validate(user, password)
    if user_info is not None:
        return generate_policy("execute-api:Invoke", "Allow", event["methodArn"], user_info)
    else:
        return generate_policy("execute-api:Invoke", "Deny", event["methodArn"])

def generate_policy(action, effect, resource, user_info=None):
    policy = {
        "principalId": "*",
        "policyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": action,
                    "Effect": effect,
                    "Resource": resource,
                }
            ],
        },
    }
    if user_info is not None:
        policy["context"] = {
            "user_id": user_info["user_id"],
            "group_id": user_info["group_id"],
        }
    return policy

def validate(user, password):
    if user == 'user1' and password == 'user1_pass':
        return {
            'user_id': '10001',
            'group_id': 'Group1',
        }
    elif user == 'user2' and password == 'user2_pass':
        return {
            'user_id': '10002',
            'group_id': 'Group2',
        }
    else:
        return None
```

## APIの作成
次にAPI GatewayにS3アクセス用のAPIを作成します。下記の通りREST APIを新規作成します。
![REST APIの作成.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2169128/144b4047-20c0-8d47-9f48-9de544fa8daf.png)

### オーソライザーの作成
APIの作成が完了したらAPI画面でオーソライザーを作成します。Lambda関数を呼び出すための権限をAPI Gatewayに追加する必要があるため、作成時に権限追加について確認するダイアログが表示されます。そのまま「付与及び作成」ボタンを押下することで自動で必要な権限が付与されます。
![オーソライザイー作成.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2169128/c784b2eb-4660-0133-4739-95725a40ccfa.png)

Lambdaイベントペイロードではトークンまたはリクエストを選択できます。トークンの場合、Lambda関数のeventパラメータに引き渡す認可用ヘッダの名前と認可用ヘッダの事前チェックを設定することができます。リクエストの場合、認可パラメータの取得元を指定します。Lambda関数にはリクエスト全体が引き渡されます。

### リソースの作成
アクションボタンからリソースを作成します。
![リソースの作成.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2169128/656b0ae3-f9de-c2c6-43cb-f7f256f50e8b.png)

### メソッドの作成
アクションボタンからS3と統合したGETメソッドを作成します。「パス上書き」には取得するファイルのS3上でのパスを指定します。ブレースで囲ったパス変数はマッピング元を指定することで動的に置き換えることができます。
![メソッドの作成.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2169128/a42d7814-1f11-1a2e-de9e-d1b2efa277d1.png)

### オーソライザーとクエリパラメーターの設定
認可に先ほど作成したLambdaオーソライザーを設定します。また、取得するファイル名を指定する必要があるためクエリパラメーターを設定します。
![オーソライザーの設定.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2169128/9e8c2c2f-4484-a2d1-c8ba-55895b0cc619.png)

### パスパラメータの設定
パス変数のマッピング元を指定します。上記の通りファイル名（<code>{file_name}</code>）はクエリ文字列のfile_nameから取得するため<code>method.request.queryString.file_name</code>を指定します。LambdaオーソライザーからContextに出力したカスタムプロパティは<code>context.authorizer.プロパティ名</code>でアクセスできます。<code>{group_id}</code>と<code>{user_id}</code>のマッピング元にそれぞれLambdaオーソライザーから出力したものを指定します。
![パスパラメータの指定.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2169128/dbd2bdf9-5e98-4624-d170-457c9365aded.png)

### デプロイ
以上で最低限の準備が整いましたのでAPIをデプロイします。
![デプロイ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2169128/8723139e-a823-4b67-e529-ad8f0d0900d3.png)

## APIのテスト
今回はテスト用に下記のようなテキストファイルを用意しました。
![ユーザー情報テキストファイル.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2169128/c94fc374-6ab0-4ce3-e412-eaee974ea286.png)

それぞれ異なるパスに配置します。
![S3ユーザー情報1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2169128/9403ce46-e43c-73d1-ea35-c66f7ae4354c.png)
![S3ユーザー情報2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2169128/572c1819-75df-e222-2c5b-66f0ee65aef0.png)

試しにPostmanで実行してみます。
![Postmanユーザー情報1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2169128/5741b3d4-2a88-8984-1d84-0abda397da4c.png)
![Postmanユーザー情報2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2169128/c8b18022-d8a9-8ef0-e920-72dbb38ebeef.png)

ユーザー情報に応じたファイルの取得に成功しました。

# 最後に
表題の要件に合致する内容がなかなか見つからなかったため簡単ですがこちらでまとめさせて頂きました。誰かの助けになれば幸いです。