API GatewayのIAM認証が拒否された際の原因調査


API Gatewayで構築したREST APIにてIAM認証を設定した際に、APIコールがIAM認証に阻まれる原因を突き止めるのにさんざんつまづいたので、つまづいた内容や対応手順について整理しておく。

##検証環境

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/a133595e-bcbd-b871-dc8c-1ad9ffad07d6.png)


- API Gatewayで構築したAPIは[プライベートREST API](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/apigateway-private-apis.html)
    - VPC内部からAPI Gateway用のVPC Endpoint経由でのみアクセス可能
    - APIではGETメソッドを作成
    - APIにはAPI GatewayリソースポリシーとIAM認証を設定している
- プライベートAPIのバックエンドはElastic Beanstalkで構築したWebアプリケーションが稼働
    - Elastic BeanstalkはWebアプリケーションの稼働環境を簡単に用意するためのサービス。この検証環境ではNLBとEC2をElastic Beanstalkにより作成している。Elastic Beanstalkの詳細は[こちら](https://aws.amazon.com/jp/elasticbeanstalk/)

- REST APIをテストする際にはCloud9をRESTクライアントとして利用し、Cloud9のターミナルからcurlコマンドを実行
    - Cloud9はクラウドベースのIDEサービス。開発環境をホストするマネージドEC2に対するターミナルが付属している。Cloud9の詳細は[こちら](https://aws.amazon.com/jp/cloud9/)
    - curlコマンドの形式は以下の通りで、IAM認証用にIAMユーザーのアクセスキーIDとシークレットアクセスキーを利用する

```
curl https://{API ID}.execute-api.ap-northeast-1.amazonaws.com/{ステージ}/{パス} \
--aws-sigv4 "aws:amz:ap-northeast-1:execute-api" \
--user "{アクセスキー}:{シークレットアクセスキー}"
```

##検証環境での認証フロー
検証環境ではAPI GatewayリソースポリシーとIAM認証を設定済みのため、以下の認証フローとなる。
参考：[API Gateway リソースポリシーが認証ワークフローに与える影響](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/apigateway-authorization-flow.html)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/e623386c-1167-3855-b304-634eb625b3b3.png)

API Gatewayは同じアカウント内のRESTクライアントから同じアカウントのIAMユーザーの認証情報で呼び出すため、ポリシー評価は以下のようになされる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/5060945e-ccf9-eb2c-d4a8-062540e41c3a.png)
ちなみに検証環境ではAPI GatewayリソースポリシーでIAMユーザーのAPI呼び出しを許可（正確には、検証で利用していたVPC内のAPI Gateway用のVPCエンドポイント経由のAPI呼び出しを許可）する設計としていたので、IAMユーザーポリシー側でAPI呼び出しを拒否する設定がなければIAM認証が通ることになる。

##検証環境でのIAM認証のブロッカーの調査・対応

APIコールがIAM認証で阻まれてしまったので、以下の手順で調査・対応した。
最初からこの手順をとれていたわけではなく、いろいろ調べて試すうちに最終的に以下のような手順に落ち着いたのが実情。

####1. IAM認証未設定の状態でAPIコールが成功するかどうかの確認

IAM認証未設定とすると、以下の認証フローとなり、API Gatewayリソースポリシーのみがチェックされる。
そのため、API Gatewayリソースポリシーがブロッカーであるかどうかの切り分けが可能ということで最初に確認を行った。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/054441db-b003-03a7-ec8e-e58607dd53e4.png)

IAM認証用の情報の設定が不要となるため、以下の形式のcurlコマンドを実行する。

```
curl https://{API ID}.execute-api.ap-northeast-1.amazonaws.com/{ステージ}/{パス}
```

ちなみにAPI Gatewayのリソースポリシーに設定ミスがあった際はcurlコマンドのレスポンスで以下のエラーメッセージが出力された。

```
{"Message":"User: anonymous is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:ap-northeast-1:{アカウントID}:{API ID}/{ステージ}/GET/{パス}"}
```

####2. （手順1でAPIコールが失敗した場合のみ）設定を修正しIAM認証未設定の状態でAPIコールが成功することを確認

複雑なAPI Gatewayリソースポリシーを設定してない限りは修正箇所の特定と修正は大変でない。

API Gatewayリソースポリシー以外の原因の可能性については、[API Gateway のプライベート API エンドポイントへの接続に関する問題をトラブルシューティングするにはどうすればよいですか?](https://aws.amazon.com/jp/premiumsupport/knowledge-center/api-gateway-private-endpoint-connection/) の情報が参考になる。

####3. IAM認証を設定した状態でAPIコールが成功するかどうかの確認
IAM認証を設定し直した状態でまだAPIコールが失敗したが、手順1または手順2でIAM認証未設定の状態でAPIコールが成功しているので、これでIAMユーザーポリシーに原因があると分かる。

####4. （手順3が失敗した場合のみ）IAM認証をブロックしているIAMユーザーポリシーを特定・修正し、IAM認証を設定した状態でAPIコールが成功することを確認
続いて、IAMユーザーポリシーでAPI呼び出しをブロックする設定がないかを調査した。

curlコマンドのレスポンスは以下の通りで、`execute-api:Invoke` アクション実行が拒否されていることは分かるが、どのIAMポリシーがブロッカーであるかまでは分からない。

```
{"Message":"User: arn:aws:iam::{アカウントID}:user/{IAMユーザー名} is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:ap-northeast-1:{アカウントID}:{API ID}/{ステージ}/GET/{パス} with an explicit deny"}
```

検証環境ではIAMユーザーにアタッチされているポリシーが膨大にあって調べるのに時間がかかるが、ブロッカーになっているIAMポリシーを特定できるようなAWSのサービスもない。

そこで、①追加のIAMユーザー（ユーザーA）を作成して、②ユーザーAに対してもともと検証に使っていたIAMユーザーにアタッチされている個々のポリシーのアタッチ/デタッチを繰り返しながらユーザーAの認証情報を使ったAPIコールを試していき、どのポリシーがアタッチされているとIAM認証がブロックされるかを特定した。

その結果、以下の2つのポリシーが原因であると特定できた。

#####IAM認証のブロッカーその1：MFA認証を強制するポリシー

以下のポリシーは `iam:CreateVirtualMFADevice` 等の `NotAction` で指定しているiamアクション以外を実行する際にMFA認証を強制するポリシーとなっている。
curlコマンド実行時に内部的に実行される `execute-api:Invoke` アクションではMFA認証を実施できていないので、ブロッカーとなっていた。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Condition": {
                "Null": {
                    "kms:ViaService": "true"
                },
                "BoolIfExists": {
                    "aws:MultiFactorAuthPresent": "false"
                }
            },
            "Resource": "*",
            "Effect": "Deny",
            "NotAction": [
                "iam:CreateVirtualMFADevice",
                "iam:DeleteVirtualMFADevice",
                "iam:DeactivateMFADevice",
                "iam:EnableMFADevice",
                "iam:ResyncMFADevice",
                "iam:ListMFADevices",
                "iam:ChangePassword"
            ]
        }
    ]
}
```

curlコマンド実行時にMFA認証を実施する方法は調べた限り見つからなかったので、AWSアカウント管理者に許可をもらい、このポリシーの`NotAction` に `execute-api:Invoke` アクションを追加することでMFA認証なしで `execute-api:Invoke` アクションが実行できるよう対応した。


#####IAM認証のブロッカーその2：AWSアクション実行時のソースIPを制限するポリシー

以下のポリシーは `1.2.3.4/24` と `5.6.7.8/24` 以外のIPからのAWSアクション実行を禁止するポリシーとなっている。
検証環境では、会社のIP以外からのAWSコンソールアクセスを制限するためにアタッチされていた。
しかし、APIコール時にはRESTクライアントとしてCloud9を利用していたため、Cloud9に割り当てられたIPが`execute-api:Invoke` アクション実行のソースIPとなり、このポリシーもIAM認証のブロッカーとなっていた。

```json
{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Deny",
        "Action": "*",
        "Resource": "*",
        "Condition": {
            "NotIpAddress": {
                "aws:SourceIp": [
                    "1.2.3.4/24",
                    "5.6.7.8/24"
                ]
            },
            "Bool": {"aws:ViaAWSService": "false"}
        }
    }
}
```

対応としては、ポリシー内の `"Action": "*",` を　`"NotAction": "execute-api:Invoke",` と書き換えることで、`execute-api:Invoke` アクションだけは会社のIP以外からも実行可能とした。

ちなみに、以下の理由で、この変更によって `execute-api:Invoke` アクションは無制限に実行できるようになるわけではないことは確認の上、ポリシーを変更している。
* API Gatewayリソースポリシーにて、検証で利用しているVPC内のAPI Gateway用のVPCエンドポイント経由のAPI呼び出しのみを許可している 
* 検証で利用しているVPCはプライベートVPCであり、API Gateway用のVPCエンドポイントにはVPC内のリソースからしかアクセスできない
*  API Gateway用のVPCエンドポイントにアタッチしているセキュリティグループにて、API Gateway用のVPCエンドポイントへはVPC内のCloud9からのアクセスのみを許可している
*  Cloud9にアクセスできるユーザーはIAMユーザーポリシーやCloud9の設定により限定している 

また `"NotIpAddress"` に検証で使っているVPCのIPレンジを指定することも試してみたが、IAMポリシー更新時に以下のアラートが出たので、パブリックIPでないと `"NotIpAddress"` に指定しても機能させることはできない模様。

```
Private Not IP Address: 条件キー「aws:SourceIp」の値に含まれているのはプライベート IP アドレスだけで、効果は含まれていません。「aws:SourceIp」はパブリック IP アドレス範囲に対してのみ機能します。パブリック IP アドレスのみを含めるように値を更新してください。
```

検証環境のCloud9にはElastic IPを付与していなかったので `"NotIpAddress"` の修正では対応できなかった。


以上の2つのポリシーの修正後、元のIAMユーザーの認証情報を使ったAPIコールがようやく成功するようになった。