TerraformでAmazon API Gatewayを構築する(プライベートAPI編)

# はじめに
API Gateway ＋ Terraform 記事第6弾。
Private の API Gateway は、以下のクラスメソッドの記事の通り、ちゃんと理解して使わないとセキュアではなくなってしまう。

【Developers.IO】[Amazon API Gateway プライベート API の「プライベート」を誤解してると、とても危ないという話。](https://dev.classmethod.jp/articles/private-api-is-not-private-for-you/)

このあたりを理解しながら、Terraform で API Gateway を作っていこう。

API Gateway の REST API は既に構築できている前提とする。
構築方法については、以下の記事を参考にしていただければ。

- 【Qiita】[TerraformでAmazon API Gatewayを構築する(基本編)](https://qiita.com/neruneruo/items/94a0e637ea1c6c95d9e4)
- 【Qiita】[TerraformでAmazon API Gatewayを構築する(ゲートウェイのレスポンス＆ステージ詳細編)
AWS](https://qiita.com/neruneruo/items/a568cd3f0542cde8c967)

# やること
API Gateway を Private 化するのに必要な手順は以下だ。

- VPC エンドポイントとセキュリティグループを作成する
- REST API のエンドポイント設定を ```PRIVATE``` にして VPC エンドポイントと紐付ける
- REST API のリソースポリシーを設定する

## VPC エンドポイントとセキュリティグループを作成する
以下のようにリソースを作っておこう。

```HCL
################################################################################
# VPC Endpoint                                                                 #
################################################################################
resource "aws_vpc_endpoint" "api_gateway" {
  vpc_id            = data.aws_vpc.my.id
  service_name      = data.aws_vpc_endpoint_service.execute_api.service_name
  vpc_endpoint_type = "Interface"

  subnet_ids         = data.aws_subnet_ids.my_vpc.ids
  security_group_ids = [aws_security_group.vpc_endpoint.id]

  private_dns_enabled = true
}

data "aws_vpc_endpoint_service" "execute_api" {
  service = "execute-api"
}

################################################################################
# Security Group                                                               #
################################################################################
resource "aws_security_group" "vpc_endpoint" {
  name        = local.vpcendpoint_sg_name
  description = "VPC Endpoint Security Group"
  vpc_id      = data.aws_vpc.my.id

  ingress {
    description = "HTTPS"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
```

セキュリティグループは、API Gateway 向け(HTTPS)なので、443番ポートのインバウンド設定をする。

## REST API のエンドポイント設定を ```PRIVATE``` にして VPC エンドポイントと紐付ける
これは簡単、```aws_api_gateway_rest_api``` を以下のように設定する。

```HCL
################################################################################
# API Gateway                                                                  #
################################################################################
resource "aws_api_gateway_rest_api" "private" {
  name        = local.api_gateway_name
  description = "プライベートAPI検証用API Gateway"

  endpoint_configuration {
    types            = ["PRIVATE"]
    vpc_endpoint_ids = [aws_vpc_endpoint.api_gateway.id]
  }

  policy = data.aws_iam_policy_document.private_api.json
}
```

```vpc_endpoint_ids```には先ほど作った VPC エンドポイントのIDを設定すれば良い。

## REST API のリソースポリシーを設定する
リソースポリシーは↑の ```policy``` に設定しているものである。
ここでは、作った VPC エンドポイント経由のものを Allow すれば良い。デフォルトを Allow にして不要なものを拒否するというセオリーに従うと、以下のようになる。

```HCL
data "aws_iam_policy_document" "private_api" {
  statement {
    effect = "Allow"

    principals {
      type = "*"
      identifiers = [
        "*",
      ]
    }

    actions = [
      "execute-api:Invoke",
    ]

    resources = [
      "*",
    ]
  }
  statement {
    effect = "Deny"

    principals {
      type = "*"
      identifiers = [
        "*",
      ]
    }

    actions = [
      "execute-api:Invoke",
    ]

    resources = [
      "*",
    ]

    condition {
      test     = "StringNotEquals"
      variable = "aws:SourceVpce"

      values = [
        aws_vpc_endpoint.api_gateway.id,
      ]
    }
  }
}
```

これを JSON にエンコードすると以下のようになる。

```JSON
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "*"
        },
        {
            "Sid": "",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "*",
            "Condition": {
                "StringNotEquals": {
                    "aws:SourceVpce": "vpce-xxxxxxxxxxxxxxxxx"
                }
            }
        }
    ]
}
```

これで、設定したVPCからのアクセスは、

- https://[APIのID].execute-api.[リージョン].amazonaws.com/[ステージ名]
- https://[APIのID]-vpce-[VPCエンドポイントのID].execute-api.[リージョン].amazonaws.com/[ステージ名]

で通るようになる。当然ながら、CloudShell のような非 VPC リソースやインターネット経由でのアクセスはエラーになる。