サービス間の認証を API Gateway + IAM で行う

API Gateway + lambda でマイクロサービスを作る時にサービス間での認証（と認可）をすることでセキュリティを強化できます。今回は Security Token Service（STS） を使って Lambda で認証情報を取得し、API Gateway の AWS_IAM の認証に利用します。

# 前提
- Lambda を利用した API Gateway の AWS_IAM 認証方法について主に紹介
- API Gateway + lambda の詳しいサービスの作り方説明しません

# 想定するフロー
![Untitled (1).png](https://qiita-image-store.s3.amazonaws.com/0/230552/bbf135d5-91a9-133c-2c5e-01c36b44ba0c.png)

1. 公開されているサービスの API Gateway にリクエスト
2. バックエンドの Lambda が他のサービスを API Gateway を利用して呼び出す
    3. IAM の認証情報を Security Token Service（STS） で取得
    3. API Gateway へのリクエストに IAM の認証情報を用いる

今回このような流れを想定した時のサービス間認証方法について紹介します。
大切なのは認証方法の部分なので、Public に公開された API Gateway は実装しません。

# Lambda の作成
## ロールの作成
ここが一番のポイントです。
呼び出し側の lambda に与えるロールにアタッチする IAM ポリシーを２つ作成します。１つは **呼び出したいサービスの API Gateway へのアクセスを許可するポリシー**、もうひとつは **STS でロールの一時認証情報を取得するためのポリシー**です。

- API Gateway のアクセスを許可するポリシー

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "execute-api:Invoke",
                "execute-api:InvalidateCache"
            ],
            "Resource": "arn:aws:execute-api:us-east-1:account_id:restapis/*/*/*"
        }
    ]
}
```
Resource の書き方は「[API を管理するためのアクセスの制御 - Amazon API Gateway](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-control-access-using-iam-policies-to-create-and-manage-api.html#api-gateway-control-access-using-iam-policies)」を参照してください。ここには呼び出し側の Lambda でアクセスを許す API Gateway の ARN を指定します。

- STS で Role の一時認証情報を取得するためのポリシー

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "arn:aws:iam::account_id:role/service-role/roleName"
        }
    ]
}
```
Resource には STS で一時認証情報を取得したいロールの ARN を指定します。

## 呼び出される側 Lambda ソースコード
```python
def lambda_handler(event, context):
    return "Success!!"
```

## 呼び出す側 Lambda ソースコード
説明はコードの後に記述。

```python
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import requests
import os
import boto3
from aws_requests_auth.aws_auth import AWSRequestsAuth

def lambda_handler(event, context):
    client = boto3.client('sts')

    IAM_ROLE_ARN = os.environ['IAM_ROLE_ARN']
    IAM_ROLE_SESSION_NAME = 'test_session'
    REGION_NAME = 'us-east-1'

    # IAM の認証情報の取得
    response = client.assume_role(
            RoleArn=IAM_ROLE_ARN,
            RoleSessionName=IAM_ROLE_SESSION_NAME
    )
    #print("Receive credential: ", end="")
    #print(response)
    credentials=response['Credentials']

    # API のホスト名を指定
    aws_host='XXXXXXXX.execute-api.us-east-1.amazonaws.com'
    auth = AWSRequestsAuth(aws_access_key=credentials['AccessKeyId'],
                           aws_secret_access_key=credentials['SecretAccessKey'],
                           aws_host=aws_host,
                           aws_region='us-east-1',
                           aws_service='execute-api')

    # additional credential
    headers = {'x-amz-security-token':credentials['SessionToken']}

    response = requests.get('https://' + aws_host + '/prod/functionA-1', auth=auth, headers=headers)
    return "Call Function A -> code:" + str(response.status_code) + " content:" + response.text
```
### IAM の一時的な認証情報を取得する
```python
    client = boto3.client('sts')
    IAM_ROLE_ARN = os.environ['IAM_ROLE_ARN']
    IAM_ROLE_SESSION_NAME = 'test_session'
    REGION_NAME = 'us-east-1'

    # IAM の認証情報の取得
    response = client.assume_role(
            RoleArn=IAM_ROLE_ARN,
            RoleSessionName=IAM_ROLE_SESSION_NAME
    )
```

STS を使って一時認証情報を取得しています。
Lambda の環境変数を使って IAM_ROLE_ARN を取得します。IAM_ROLE_ARN には呼び出したいサービスの API Gateway を Invoke できるロールを指定します。

<details><summary>参考：何故 additional credential を header に入れるのか</summary><div>
※認証認可初心者の推測も入っています。事実と意見わけて書きます。
OAuth の認可情報 access_token を利用した OAuth 認証はセキュリティホールがある。[単なる OAuth 2.0 を認証に使うと、車が通れるほどのどでかいセキュリティー・ホールができる](https://www.sakimura.org/2012/02/1487/)自分の解釈としては他のサイトで利用したトークンを利用してなりすましができるということだと思う。そんな経緯から今は認証の署名情報が入った id_token と認可情報 access_token を利用した OpenID Connect の利用が推奨されている。 STS で一時認証情報を一式を得て、 auth 変数に入れているのは認可情報、ヘッダーに追加しているのははなりすまし予防のセッション情報だと考えられる。サイトを見ていると header を追記していないサイトが多かったが、昔はいらなかったのかもしれない。

### リクエストに署名する


```python
    credentials=response['Credentials']

    # API のホスト名を指定
    aws_host='XXXXXXXX.execute-api.us-east-1.amazonaws.com'
    auth = AWSRequestsAuth(aws_access_key=credentials['AccessKeyId'],
                           aws_secret_access_key=credentials['SecretAccessKey'],
                           aws_host=aws_host,
                           aws_region='us-east-1',
                           aws_service='execute-api')

    # additional credential
    headers = {'x-amz-security-token':credentials['SessionToken']}

    response = requests.get('https://' + aws_host + '/prod/functionA-1', auth=auth, headers=headers)
```
`aws_requests_auth` を利用して署名を行います。
ポイントは headers で `x-amz-security-token` を追加しているところです。IAMの一時認証情報を利用する場合は`secreat_access_key`を`x-amz-security-token`として一緒に送らなければいけません。（[REST リクエストの署名と認証 - Amazon Simple Storage Service](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/RESTAuthentication.html)）

# API Gateway の作成
# AWS_IAM 有効化
IAM での認証を有効化します。AMC ではメソッドリクエストから変更できますが、実際に量が増えると CLI でやらないとかなり面倒ですね。
![Screen Shot 2018-04-07 at 13.45.01.png](https://qiita-image-store.s3.amazonaws.com/0/230552/a3c911ad-02d5-6592-062a-ecf934f30628.png)

# 呼び出し側の Lambda を動かしてみる
これで準備ができたのでテストで Lambda を呼び出してみます。

![Screen Shot 2018-04-07 at 13.51.28.png](https://qiita-image-store.s3.amazonaws.com/0/230552/4cef8e1f-74e9-7f22-aeb0-b4be4db2d7b5.png)

無事認証され、レスポンスを受け取れました！！


# アクセスできないことも確かめる（おまけ）
そもそも認証がうまく働いているの？というのを確かめるために、API Gateway を呼び出すためのポリシーを呼び出し側のロールからとってみます。

![Screen Shot 2018-04-07 at 13.01.01.png](https://qiita-image-store.s3.amazonaws.com/0/230552/7cefaecf-4cd2-581d-3c10-8542b07cf4e4.png)

アクセスが拒否されました。AWS_IAMで認証ができていたことがわかります。

# 参考サイト
- [IAM アクセス権限により API へのアクセスを制御する - Amazon API Gateway](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/permissions.html)
- [署名バージョン 4 を使用して AWS リクエストに署名する - アマゾン ウェブ サービス](https://docs.aws.amazon.com/ja_jp/general/latest/gr/sigv4_signing.html)
- [STS を使用した完全なバージョン 4 署名プロセスの例 (Python) | せむにるの日記](https://blog.semnil.com/post-212.html)
- [DavidMuller/aws-requests-auth: AWS signature version 4 signing process for the python requests module](https://github.com/DavidMuller/aws-requests-auth)
- * [よくわかる認証と認可 ｜ Developers.IO](https://dev.classmethod.jp/security/authentication-and-authorization/)
- [【AWS】Lambdaでpipしたいと思ったときにすべきこと - Qiita](https://qiita.com/Hironsan/items/0eb5578f3321c72637b4)