あめがえるのITブログ

頑張りすぎない。ほどほどに頑張るブログ。

【AWS】プライベートのAPIGatewayの接続を試してみた・2


プライベートのAPIGatewayの接続を試してみた

プライベートAPIGatewayへの接続方法

下記に記載があり、いくつかあるが大きく下記3つある。
docs.aws.amazon.com 1.接続元のAPIGateway InterfaceエンドポイントとプライベートAPIGatewayを関連付けする。
2.接続元のAPIGatewayInterfaceエンドポイントでプライベートDNSを有効にする。
3.上記を行わず、パブリックDNS名でプライベート接続を行う。

やること

今回は接続方法の2のパターンを実施。
※他のパターンは下記を参照
パターン1:
amegaeru.hatenablog.jp パターン3:
https://amegaeru.hatenablog.jp/entry/2025/01/16/000000amegaeru.hatenablog.jp Terraformで通常のAPIGatewayと接続元のEC2を作成し、プライベートで必要な箇所をGUIで変更する。

構成

接続元と接続先でアカウントを分けて実施。リージョンはどちらもap-northeast-1。
※リージョン間の接続もそのうちやってみたい。


実践!

1.コード作成
1-1.フォルダ作成
1-1-1.下記フォルダを作成
 ・ec2
 ・apigateway
1-2.ec2フォルダに移動し、接続元アカウン(EC2側)にコード作成
version.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  required_version = ">= 1.0.0"
}

variables.tf

variable "env" {
  type = map(any)
  default = {
    env = "test"
  }
}

variable "region" {
  description = "The AWS region to deploy resources in"
  type        = string
  default     = "ap-northeast-1"
}

variable "vpc_network" {
  type = map(any)

  default = {
    ser01 = "10.2.160.0/21"
  }
}

variable "area" {
  type = map(any)

  default = {
    region = "ap-northeast-1"
    az01   = "ap-northeast-1a"
    az02   = "ap-northeast-1c"
  }
}

variable "sub_network" {
  type = map(any)

  default = {
    pub_st-01 = "10.2.162.0/24"
    pub_st-02 = "10.2.163.0/24"

    pub_dy-01 = "10.2.160.0/24"
    pub_dy-02 = "10.2.161.0/24"

    pri_st-01 = "10.2.166.0/24"
    pri_st-02 = "10.2.167.0/24"

    pri_dy-01 = "10.2.164.0/24"
    pri_dy-02 = "10.2.165.0/24"
  }
}

provider.tf

provider "aws" {
  profile = "testvault2"
  region  = "ap-northeast-1"
}

data.tf

data "aws_caller_identity" "current" {}

ec2.tf

### VPC
resource "aws_vpc" "vpc01" {
  cidr_block           = var.vpc_network["ser01"]
  enable_dns_hostnames = true

  tags = {
    Name = "${var.env["env"]}vpc01"
  }
}

### Subnet
resource "aws_subnet" "dyprsub01" {
  vpc_id            = aws_vpc.vpc01.id
  cidr_block        = var.sub_network["pri_dy-01"]
  availability_zone = var.area["az01"]

  tags = {
    Name = "${var.env["env"]}dyprsub01"
  }
}

resource "aws_subnet" "dyprsub02" {
  vpc_id            = aws_vpc.vpc01.id
  cidr_block        = var.sub_network["pri_dy-02"]
  availability_zone = var.area["az02"]

  tags = {
    Name = "${var.env["env"]}dyprsub02"
  }
}

resource "aws_subnet" "dypusub01" {
  vpc_id            = aws_vpc.vpc01.id
  cidr_block        = var.sub_network["pub_dy-01"]
  availability_zone = var.area["az01"]

  tags = {
    Name = "${var.env["env"]}dypusub01"
  }
}

resource "aws_subnet" "dypusub02" {
  vpc_id            = aws_vpc.vpc01.id
  cidr_block        = var.sub_network["pub_dy-02"]
  availability_zone = var.area["az02"]

  tags = {
    Name = "${var.env["env"]}dypusub02"
  }
}

### InternetGateway
resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.vpc01.id

  tags = {
    Name = "${var.env["env"]}igw"
  }
}

### EIP
resource "aws_eip" "nat_eip" {
  domain = "vpc"
}

### NATGateway
resource "aws_nat_gateway" "nat_gw" {
  allocation_id = aws_eip.nat_eip.id
  subnet_id     = aws_subnet.dypusub01.id

  tags = {
    Name = "${var.env["env"]}nat-gw"
  }
}

### RouteTable
resource "aws_route_table" "public_rt" {
  vpc_id = aws_vpc.vpc01.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.igw.id
  }

  tags = {
    Name = "${var.env["env"]}public_rt"
  }
}

resource "aws_route_table_association" "public_rt_assoc" {
  subnet_id      = aws_subnet.dypusub01.id
  route_table_id = aws_route_table.public_rt.id
}

resource "aws_route_table" "private_rt" {
  vpc_id = aws_vpc.vpc01.id

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.nat_gw.id
  }

  tags = {
    Name = "${var.env["env"]}private_rt"
  }
}

resource "aws_route_table_association" "private_rt_assoc" {
  subnet_id      = aws_subnet.dyprsub01.id
  route_table_id = aws_route_table.private_rt.id
}

resource "aws_route_table_association" "private_rt_assoc_endpoint_01" {
  subnet_id      = aws_subnet.dyprsub01.id
  route_table_id = aws_route_table.private_rt.id
}

resource "aws_route_table_association" "private_rt_assoc_endpoint_02" {
  subnet_id      = aws_subnet.dyprsub02.id
  route_table_id = aws_route_table.private_rt.id
}

### EC2
resource "aws_iam_role" "ec2_ssm_role" {
  name = "${var.env["env"]}ec2_ssm_role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Principal = {
        Service = "ec2.amazonaws.com"
      }
    }]
  })
}

resource "aws_iam_policy_attachment" "ec2_ssm_role_attachment" {
  name       = "${var.env["env"]}ec2_ssm_role_attachment"
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
  roles      = [aws_iam_role.ec2_ssm_role.name]
}

resource "aws_instance" "example_ec2" {
  ami                    = "ami-00c79d83cf718a893"
  instance_type          = "t3.micro"
  subnet_id              = aws_subnet.dyprsub01.id
  iam_instance_profile   = aws_iam_instance_profile.example_profile.name
  vpc_security_group_ids = [aws_security_group.ec2_sg.id]

  root_block_device {
    encrypted = true
  }
  metadata_options {
    http_tokens = "required"
  }

  tags = {
    Name = "${var.env["env"]}example_ec2"
  }
}

resource "aws_iam_instance_profile" "example_profile" {
  name = "${var.env["env"]}example_profile"
  role = aws_iam_role.ec2_ssm_role.name
}

# SecurityGroup
resource "aws_security_group" "ec2_sg" {
  vpc_id      = aws_vpc.vpc01.id
  description = "Security group for EC2 to connect to private API Gateway"

  egress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${var.env["env"]}ec2_sg"
  }
}

1-3.apigatewayフォルダに移動し、接続先アカウント(プライベートAPIGateway側)にコード作成
※version.tf、provider.tf、data.tfはEC2と共通
variables.tf

variable "env" {
  type = map(any)
  default = {
    env = "test"
  }
}

variable "region" {
  description = "The AWS region to deploy resources in"
  type        = string
  default     = "ap-northeast-1"
}

apigateway.tf

resource "aws_api_gateway_rest_api" "example" {
  name        = "${var.env["env"]}-example-api"
  description = "Example Public API With Cognito Authentication"
  endpoint_configuration {
    types = ["REGIONAL"] 
  }
}

resource "aws_api_gateway_resource" "example_resource" {
  rest_api_id = aws_api_gateway_rest_api.example.id
  parent_id   = aws_api_gateway_rest_api.example.root_resource_id
  path_part   = "example"
}

resource "aws_api_gateway_method" "example_method" {
  rest_api_id   = aws_api_gateway_rest_api.example.id
  resource_id   = aws_api_gateway_resource.example_resource.id
  http_method   = "GET"
  authorization = "NONE"
}

resource "aws_api_gateway_integration" "example_integration" {
  rest_api_id             = aws_api_gateway_rest_api.example.id
  resource_id             = aws_api_gateway_resource.example_resource.id
  http_method             = aws_api_gateway_method.example_method.http_method
  integration_http_method = "POST"
  type                    = "MOCK"

  request_templates = {
    "application/json" = <<EOF
    {
       "statusCode": 200,
       "message": "Operation was succcessful"
    }
    EOF
  }
}

resource "aws_api_gateway_integration_response" "example_integration_response" {
  rest_api_id = aws_api_gateway_rest_api.example.id
  resource_id = aws_api_gateway_resource.example_resource.id
  http_method = aws_api_gateway_method.example_method.http_method
  status_code = "200"

  response_templates = {
    "application/json" = <<EOF
    {
       "statusCode": 200,
       "message": "Operation was successful"
    }
    EOF
  }

  selection_pattern = ".*"
  depends_on        = [aws_api_gateway_integration.example_integration]
}

resource "aws_api_gateway_method_response" "example_method_response" {
  rest_api_id = aws_api_gateway_rest_api.example.id
  resource_id = aws_api_gateway_resource.example_resource.id
  http_method = aws_api_gateway_method.example_method.http_method
  status_code = "200"

  response_models = {
    "application/json" = "Empty"
  }
}

resource "aws_api_gateway_deployment" "example_deployment" {
  depends_on = [
    aws_api_gateway_integration.example_integration,
    aws_api_gateway_method_response.example_method_response,
    aws_api_gateway_integration_response.example_integration_response
  ]

  rest_api_id = aws_api_gateway_rest_api.example.id
  stage_name  = "test"
}

resource "aws_api_gateway_stage" "example_stage" {
  rest_api_id   = aws_api_gateway_rest_api.example.id
  stage_name    = "prod"
  deployment_id = aws_api_gateway_deployment.example_deployment.id
}

1-3.それぞれのコードを実行

> terraform init
> terraform plan
> terraform apply


2.接続確認
2-1.APIGatewayの接続URL確認
2-1-1.AWS -APIGateway
2-1-2.API - 作成したAPIを選択

2-1-3.「ステージ」- 接続するメソッドを選択し、表示されているURLをコピー

2-2.EC2にセッションマネージャで接続
2-2-1.AWS - EC2 - 作成したEC2を選択し、「接続」

2-2-2.「セッションマネージャ」タブ - 「接続」

2-2-3.コンソールが表示されることを確認

2-3.APIGatewayへ接続
2-3-1.下記を実行し、200 が返ってくることを確認

> curl -v <APIGatewayURL>
sh-5.2$ curl -v https://qbi0s8hc45.execute-api.ap-northeast-1.amazonaws.com/prod/example
* Host qbi0s8hc45.execute-api.ap-northeast-1.amazonaws.com:443 was resolved.
* IPv6: (none)
* IPv4: 43.207.24.177, 52.197.4.42, 57.181.241.0, 52.199.221.131, 35.78.1.216, 52.196.9.211, 52.69.68.237, 35.74.214.0
※グローバルのIPで接続していることを確認
*   Trying 43.207.24.177:443...
* Connected to qbi0s8hc45.execute-api.ap-northeast-1.amazonaws.com (43.207.24.177) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/pki/tls/certs/ca-bundle.crt
*  CApath: none
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / RSASSA-PSS
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=*.execute-api.ap-northeast-1.amazonaws.com
*  start date: May  2 00:00:00 2024 GMT
*  expire date: May 31 23:59:59 2025 GMT
*  subjectAltName: host "qbi0s8hc45.execute-api.ap-northeast-1.amazonaws.com" matched cert's "*.execute-api.ap-northeast-1.amazonaws.com"
*  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M02
*  SSL certificate verify ok.
*   Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
*   Certificate level 1: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
*   Certificate level 2: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://qbi0s8hc45.execute-api.ap-northeast-1.amazonaws.com/prod/example
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: qbi0s8hc45.execute-api.ap-northeast-1.amazonaws.com]
* [HTTP/2] [1] [:path: /prod/example]
* [HTTP/2] [1] [user-agent: curl/8.5.0]
* [HTTP/2] [1] [accept: */*]
> GET /prod/example HTTP/2
> Host: qbi0s8hc45.execute-api.ap-northeast-1.amazonaws.com
> User-Agent: curl/8.5.0
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/2 200
< date: Fri, 27 Dec 2024 05:20:52 GMT
< content-type: application/json
< content-length: 87
< x-amzn-requestid: 14dd707c-daac-4111-a516-85aadf16fce8
< x-amz-apigw-id: Db3gMGimtjMEu7g=
<
    {
       "statusCode": 200,
       "message": "Operation was successful"
    }
* Connection #0 to host qbi0s8hc45.execute-api.ap-northeast-1.amazonaws.com left intact


3.プライベート接続設定
3-1.EC2側アカウントにAPIGatewayInterfaceエンドポイント用SecurityGroup作成
3-1-1.AWS - VPC
3-1-2.「セキュリティグループ」-「セキュリティグループを作成」

3-1-3.下記を入力し、「セキュリティグループを作成」
 セキュリティグループ名:<セキュリティグループ名>
 説明:<セキュリティグループ名>
 VPC:作成したVPC
 インバウンドルール:
  タイプ:HTTPS
  ソース:0.0.0.0/0 ※いったん全許可。EC2のSecurityGroupを許可とかが良いと思われる。
 アウトバウンド:
  タイプ:すべてのトラフィック
  送信先:0.0.0.0/0


3-2.EC2側アカウントにAPIGatewayInterfaceエンドポイント作成
3-2-1.AWS - VPC
3-2-2.「エンドポイント」-「エンドポイントを作成」

3-2-3.下記を入力し、「エンドポイントを作成」
 名前タグ:<エンドポイント名>
 タイプ:AWSのサービス
 サービス:com.amazonaws.ap-northeast-1.excute-api
 ネットワーク:作成したVPC
 DNS名:を有効化:✅
 IPv4:✅
 サブネット:作成したプライベートサブネット
 セキュリティグループ:作成したセキュリティグループ
 ポリシー:フルアクセス




3-3.APIGateway側アカウントでAPIGatewayをPrivateに変更
3-3-1.AWS - APIGateway
3-3-2.「API」- 作成したAPIを選択

3-3-3.「APIの設定」-「編集」

3-3-4.下記を入力し、「変更を保存」
 APIエンドポイントタイプ:プライベート
 VPCエンドポイントID:空白

3-4.APIGateway側アカウントでAPIGatewayにリソースポリシーを設定
3-4-1.AWS - APIGateway
3-4-2.「API」- 作成したAPIを選択

3-4-3.「リソースポリシー」-「ポリシーを作成」

3-4-4.下記を入力し、「変更を保存」

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": "execute-api:Invoke",
      "Resource": "arn:aws:execute-api:<リージョン名>:<アカウントID>:<APIGatewayID>/*",
      "Condition": {
        "StringEquals": {
          "aws:SourceVpce": "<エンドポイントID>"
        }
      }
    },
    {
      "Effect": "Deny",
      "Principal": "*",
      "Action": "execute-api:Invoke",
      "Resource": "arn:aws:execute-api:<リージョン名>:<アカウントID>:<APIGatewayID>/*",
      "Condition": {
        "StringNotEquals": {
          "aws:SourceVpce": "<エンドポイントID>"
        }
      }
    }
  ]
}


3-5.APIGateway側アカウントでAPIGatewayをデプロイ
3-5-1.AWS - APIGateway
3-5-2.「API」- 作成したAPIを選択

3-5-3.「リソース」-「APIをデプロイ」

3-5-4.「ステージ」で「prod」を選択し、「デプロイ」


4.プライベート接続確認
4-1.EC2から下記を実行し、200 が返ってくることを確認

> curl -v <APIGatewayURL>
sh-5.2$ curl -v https://n7sj3hwbxl.execute-api.ap-northeast-1.amazonaws.com/prod/example
* Host n7sj3hwbxl.execute-api.ap-northeast-1.amazonaws.com:443 was resolved.
* IPv6: (none)
* IPv4: 10.2.164.177, 10.2.165.110 ※内部のIPを使用していることを確認
*   Trying 10.2.164.177:443...
* Connected to n7sj3hwbxl.execute-api.ap-northeast-1.amazonaws.com (10.2.164.177) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/pki/tls/certs/ca-bundle.crt
*  CApath: none
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 / prime256v1 / rsaEncryption
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
*  subject: CN=*.execute-api.ap-northeast-1.amazonaws.com
*  start date: Oct 17 00:00:00 2024 GMT
*  expire date: Oct 24 23:59:59 2025 GMT
*  subjectAltName: host "n7sj3hwbxl.execute-api.ap-northeast-1.amazonaws.com" matched cert's "*.execute-api.ap-northeast-1.amazonaws.com"
*  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M03
*  SSL certificate verify ok.
*   Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
*   Certificate level 1: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
*   Certificate level 2: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* using HTTP/1.x
> GET /prod/example HTTP/1.1
> Host: n7sj3hwbxl.execute-api.ap-northeast-1.amazonaws.com
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: Server
< Date: Fri, 27 Dec 2024 04:37:22 GMT
< Content-Type: application/json
< Content-Length: 87
< Connection: keep-alive
< x-amzn-RequestId: 2030b2da-27a1-4f38-8ffb-319d24268941
< x-amz-apigw-id: DbxIaFL6tjMF1kw=
<
    {
       "statusCode": 200,
       "message": "Operation was successful"
    }
* Connection #0 to host n7sj3hwbxl.execute-api.ap-northeast-1.amazonaws.com left intact



感想

面倒い、、、