[AWS]IAM認証をかけたAPIGatewayを別アカウントのLambdaから呼び出す構成をSAMで作成してみた

記事のタイトルが長くなってしまったのですが、主要な構成は以下です。

__アカウントA__
- APIGateway(IAM認証付き）
- Lambda

__アカウントB__
- Lambda

__フロー__
アカウントB.Lambda　⇒　アカウントA.APIGateway　⇒　アカウントA.Lambda

細かく見ていきましょう。

# Githubリポジトリ
今回記事で解説している構成を作成した際のソースをgithubにプッシュしました。
もし同じような構成を作っていて困っている方がいたら、ソースをいじってデプロイしてみてください！
※使う際はいくつかのパラメータをご自身の環境に合わせて設定していただく必要があります。

https://github.com/engineer-taro/apigateway-iamauthentication-crossaccount

# 注意
- こちらの記事はPython, AWS SAMを利用しています。
- Pythonではさらに、Boto3, [aws_requests_auth](https://github.com/DavidMuller/aws-requests-auth) のライブラリを利用しています。
- 動くところまでどうにか作ったものなので、もっと良い構成や実装、間違っている点があればご指摘頂けると勉強になります。

# 本記事の対象者
コアなケースですので、あまり対象者はいないかもしれません。それ故に情報が無かったので、記事にすることにしました。

- AWSのAPIGateway, Lambda, IAMロールが分かる人
- 「APIGatewayにIAM認証をかける」が分かる人
- SAM、又はCloudFormationを利用してAWSリソースをデプロイした経験がある人
- 他アカウントのLambdaからIAM認証をかけたAPIGatewayを呼び出したい人

# 構成について
今回実装した構成は、APIGatewayのアカウントとAPIGatewayを呼び出すLambdaのアカウントが別であり、APIGatewayにはIAM認証が必要という構成図です。
以下構成図です。
![apigatewai-iamauthentication-crossaccount.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2055433/a9eb3d6d-caa5-06c9-fcb7-53b09504fd0a.png)

## 構成の詳細： APIGateway Account
まず、こちらの構成の詳細を解説します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2055433/5b2d3055-9e6d-0b84-7e0d-b30f0c75604f.png)


APIGateway Accoutでは、IAM認証のかかったAPIGateway、呼び出し先のLambdaが基本の構成です。
こちらのAPIGatewayが別アカウントから呼び出され、最終的にLambda関数の結果が戻されます。

このアカウントでポイントとなるのが、__IAMロール__の存在です。
こちらのIAMロールのSAMテンプレートを見てみましょう。すべて見る必要は全くないです。ポイント１,ポイント２だけ見てください。

```yaml:template.yaml
  APIGatewayRole:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: !Ref RoleName
      Path: "/"
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"
      Policies:
        - PolicyName: "authorizerLambdaInvokeAccess"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
#...............ポイント1：Lambda周りの権限も必要
                  - lambda:InvokeAsync 
                  - lambda:InvokeFunction
                  - execute-api:Invoke
                Resource: "*"
#...............ポイント2：このIAMロールを他で使えるようにAssumeRolePolicyを規定する
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: "AllowApiGatewayServiceToAssumeRole"
            Effect: "Allow"
            Action:
              - "sts:AssumeRole"
            Principal:
              AWS:
                - !Sub "arn:aws:iam::${AnotherAccountID}:role/FromLambdaRole"
```

ポイント1: Lambda周りの権限も必要
ポイント2: このIAMロールを他で使えるようにAssumeRolePolicyを設定する

### Lambda周りの権限も必要
今回の構成では、APIGatewayに渡ってきた認証情報を、APIGatewayのロールとして利用します。
詳しくは、以下の記事に書いた情報を確認ください。

https://qiita.com/engineer-taro/items/073e73e65565ada27146

そのため、APIGatewayがLambdaを実行できる権限も必要なため、その権限を付けています。

### AssumeRolePolicyを設定する
AssumeRolePolicyとは、STS（Security Token Serivce ）にてAssumeRoleする際に利用するPolicyです。

では、STSのAssumeRoleとはなんでしょう？

それは、IAMロールが持っている権限を一時的に別のユーザーやロールに付与する動作のことです。
今回はAPIGateway Accountで作成したIAMロールによってAPIGatewayのIAM認証を行うので、こちらのIAMロールの権限をLambda AccountのIAMロールに付与する必要があります。
そこで、AssumeRolePolicyに権限を付与する対象を記載しているのです。
以下を見てみましょう。

```
            Principal:
              AWS:
                - !Sub "arn:aws:iam::${AnotherAccountID}:role/FromLambdaRole"
```

他アカウントのIAMロールであるFromLambdaRoleに「AssumeRoleをしてもよい」という許可を出しています。

## 構成の詳細： Lambda Account
次に、Lambdaアカウントの構成を解説します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2055433/bd879864-e011-7a2c-2540-8fb1add74240.png)

ポイントとなるのは、「APIGateway AccountのIAMロール」から権限をもらうことが許可されている、__IAMロール__です。
こちらのIAMロールをLambdaにアタッチすることで、Lambdaにて「APIGateway AccountのIAMロール」を利用することができます。そして、「APIGateway AccountのIAMロール」を使ってLambdaからAPIGatewayを呼び出すことができます。

呼び出し時のソースを見てみましょう。

```python:app.py
    credentials = get_credentials() # ...............ポイント1: IAMの認証情報を取得する
    auth = AWSRequestsAuth(
        aws_access_key=credentials['AccessKeyId'],
        aws_secret_access_key=credentials['SecretAccessKey'],
        aws_host=aws_host,
        aws_region=REGION_NAME,
        aws_service='execute-api'
    )
    headers = {'x-amz-security-token': credentials['SessionToken']}
    # ............... ポイント2: IAMの認証情報を送る
    response = requests.post(
        url, json={"foo": "bar"}, auth=auth, headers=headers)

def get_credentials():
    client = boto3.client('sts')
    IAM_ROLE_ARN = os.environ['IAM_ROLE_ARN']
    IAM_ROLE_SESSION_NAME = 'other_account_session'
    response = client.assume_role(
        RoleArn=IAM_ROLE_ARN,
        RoleSessionName=IAM_ROLE_SESSION_NAME
    )
    return response['Credentials']
```

- ポイント1: IAMの認証情報を取得する
- ポイント2: IAMの認証情報を送る

### STSを利用し、IAMの認証情報を取得する
先ほどAPIGateway Accountの解説の際に出てきたSTSのAssumeRoleを利用し、IAMロールの認証情報を取得しています。
こちらをAPIGatewayを呼び出す際にヘッダーに渡してあげることで、IAM認証のかかったAPIGatewayを呼び出すことが出来ます。

## IAMの認証情報を送る
requests.postの中で、IAMロールの認証情報を一緒に送信して上げましょう。

```py
    response = requests.post(
        url, json={"foo": "bar"}, auth=auth, headers=headers)
```

以上のようにすることで、別アカウントからのAPI呼び出しが可能になります。

# 感想
1番辛かった作業は、SAMの書き方を調べながら書くことです。
SAMは使っている人が少ないせいもあり、なかなか情報が少ないです。
また、公式ドキュメントも最低限の情報しか書いてないので中々苦労します。日本語訳が割と雑なのはまだ良いのですが、英語で読んでも欲しい情報に中々辿り着けません。

今度IaCするときは、terraformとかを使ってみようかなと思いました。


# 参考記事

https://aws.amazon.com/jp/premiumsupport/knowledge-center/iam-authentication-api-gateway/

https://zenn.dev/ohsawa0515/articles/access-iam-authentication-api-gateway