
プライベートの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
感想
面倒い、、、