[AWS CDK]API GatewayにIAM認証をかけてみた

[以前投稿した記事](https://qiita.com/lism/items/ebb20b402644a91d6eb9)で作成したAPI GatewayにIAM認証の設定を追加してみました。

## IAM認証とは
>HTTP API ルートに対して IAM 認証を有効にできます。IAM 認証が有効な場合、クライアントは署名バージョン 4 を使用して、AWS 認証情報でリクエストに署名する必要があります。API Gateway は、クライアントがルートに対する execute-api アクセス許可を持っている場合にのみ、API ルートを呼び出します。
https://docs.aws.amazon.com/ja_jp/ja_jp/apigateway/latest/developerguide/http-api-access-control-iam.html

とのことです。
IAM認証を有効にすると、署名バージョン4(SigV4)を使用してリクエストに署名をする必要がある、ということですね。

>署名バージョン 4 (SigV4) は、HTTP で送信される AWS API リクエストに認証情報を追加するプロセスです。セキュリティのため、AWS へのほとんどのリクエストはアクセスキーで署名する必要があります。アクセスキーは、アクセスキー ID とシークレットアクセスキーで構成されます。
https://docs.aws.amazon.com/ja_jp/general/latest/gr/signature-version-4.html

SigV4で署名するには、アクセスキー ID とシークレットアクセスキーを用いるようです。
今回は、IAMユーザーを作成しアクセスキーとシークレットアクセスキーを用意します。

## 作るもの
以前作成したAPI Gatewayは認証などを何もかけていないため、誰でも好きに実行できてしまいます。
そのため、今回はIAM認証を利用して制限をかけようと思います。
API GatewayにIAM認証の設定を行い、APIを実行できる権限を持つIAMユーザーを作成していきます。

## 環境
```
cdk --version
2.44.0 (build bf32cb1)

tsc --v      
Version 3.9.10

curl -V
curl 7.83.1
```

## API GatewayにIAM認証をかける設定
[以前投稿した記事](https://qiita.com/lism/items/ebb20b402644a91d6eb9)のコードをベースに、記述を追加します。
```typescript
// titleを指定してitemを1件取得するapi
    bookResource.addMethod('GET', new apigateway.AwsIntegration({
      service: 'dynamodb',
      action: 'GetItem',
      options: {
        credentialsRole: credentialRole,
        requestTemplates: {
          'application/json': `{
              "Key": {
                "title": {
                  "S": "$input.params('title')"
                }
              },
              "TableName": "Book"
            }`
        },
        integrationResponses: [
          {
            statusCode: '200',
            responseTemplates: {
              'application/json': `{
                "title": "$input.path('$').Item.title.S",
                "price": $input.path('$').Item.price.N,
              }`
            }
          }
        ]
      }
    }),
    {
      authorizationType: apigateway.AuthorizationType.IAM, // この記述を追加
      methodResponses: [
        {
          statusCode: '200',
        }
      ]
    });
```

デプロイしてコンソールで確認してみると確かに設定されています。

![IAM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2699394/64dae10c-f790-c5da-1714-abd4cd041245.png)



この状態でcurlしてみると、認証で弾かれていることがわかります。
{restapi_id}と{region}は作成したもので適宜置き換えてください。

```
$ curl -X GET https://{restapi_id}.execute-api.{region}.amazonaws.com/dev/books/how_to_book

{"message":"Missing Authentication Token"}% 
```

## IAMユーザー
bookApiを実行できる`invoke-user`というユーザーを作成します。

```typescript
// invoke用 IAM Role
const invokeUser = new iam.User(this, 'User', {
  userName: `invoke-user`,
});

invokeUser.attachInlinePolicy(
  new iam.Policy(this, 'Policy', {
    statements: [
      new iam.PolicyStatement({
        actions: ['execute-api:Invoke'],
        effect: iam.Effect.ALLOW,
        resources: [api.arnForExecuteApi()],
      }),
    ],
  }),
);
```

なお、`api.arnForExecuteApi()`のapiは以下を指しています。

```typescript
// API Gateway
const api = new apigateway.RestApi(this, "bookApi", {
  deployOptions: {
    stageName: "dev",
    metricsEnabled: true,
    dataTraceEnabled: true,
  }
});
```

デプロイしてユーザーを作成し、コンソールなどからアクセスキーとシークレットキーを発行します。
このアクセスキーとシークレットキーはAPI実行に必要な情報となるので忘れずにメモなどしておきます。

## 動作確認
準備が整ったので、API実行を試してみます。
まずは認証情報なしでcurlします。

```
$ curl -X GET https://{restapi_id}.execute-api.{region}.amazonaws.com/dev/books/how_to_book

{"message":"Missing Authentication Token"}% 
```

想定通り弾かれます。

次に認証情報ありでcurlします。
{アクセスキー}と{シークレットアクセスキー}はそれぞれ先ほど発行したものに置き換えて実行します。

```
$ curl -X GET https://{restapi_id}.execute-api.{region}.amazonaws.com/dev/books/how_to_book --aws-sigv4 aws:amz:{region}:execute-api --user {アクセスキー}:{シークレットアクセスキー}

{
  "title": "how_to_book",
  "price": 1000,
}% 
```

認証が通り、how_to_bookの情報が返ってきました。
なお、`--aws-sigv4`はcurlのオプションです。
https://curl.se/docs/manpage.html#--aws-sigv4

## 最後に

今回は、API GatewayにIAM認証の設定を追加してみました。
この設定を行うことで、API実行権限のあるIAMユーザーのアクセスキーとシークレットアクセスキーを知っている人しか実行できない制限をかけることができました。