2024年11月、Amazon API Gateway が プライベート REST API に対するカスタムドメイン名のサポート を開始しました。
マネジメントコンソールで実装しているクラスメソッドさんの記事がありますので、事前に以下の記事に目を通しておくと良いかもしれません。
こんな感じのことを行いました。
図に示したとおり、プライベートカスタムドメインを実現するには、
- プライベートカスタムドメインそのもの
- 名前解決先で、実際にAPIGWを叩くインターフェースVPCエンドポイント
- ドメイン名アクセスの関連付け
- ドメインとAPIGWのマッピング
- パブリック証明書
- パブリック証明書のDNS検証を行うためのパブリックホストゾーン
- VPC内で名前解決を行うためのプライベートホストゾーン
これらのリソースを作成し、紐づけていく必要があります。
以下の公式ドキュメントに方法が載ってはいますが、それでも実装につまることはあったので、備忘録として自分の言葉で記事として残します。
VPCとVPCエンドポイントの作成
vpc.tf
# --------------------------------------------------------
# VPC
resource "aws_vpc" "main" {
cidr_block = "10.128.0.0/16"
enable_dns_hostnames = true
resource "aws_subnet" "private_subnet" {
vpc_id = aws_vpc.main.id
cidr_block = "10.128.1.0/24"
availability_zone = var.availability_zone
map_public_ip_on_launch = false
resource "aws_route_table" "private_rt" {
vpc_id = aws_vpc.main.id
}
# --------------------------------------------------------
# VPCエンドポイント(Private_API_Gateway)
resource "aws_security_group" "private_api_gateway_sg" {
vpc_id = aws_vpc.main.id
ingress {
description = "Allow CloudShell (and any in-VPC client) to reach the Private API GW endpoint"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [ aws_vpc.main.cidr_block ]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
resource "aws_vpc_endpoint" "private_api_gateway" {
vpc_id = aws_vpc.main.id
subnet_ids = [aws_subnet.private_subnet.id]
service_name = var.private_api_gateway_endpoint_service_name
security_group_ids = [aws_security_group.private_api_gateway_sg.id]
vpc_endpoint_type = "Interface"
private_dns_enabled = true
}
# --------------------------------------------------------
# Outputs (後ほどプライベートホストゾーンの定義で使用します)
output "api_gateway_endpoint_dns_name" {
value = aws_vpc_endpoint.private_api_gateway.dns_entry[0].dns_name
}
output "api_gateway_endpoint_hosted_zone_id" {
value = aws_vpc_endpoint.private_api_gateway.dns_entry[0].hosted_zone_id
}
output "vpc_endpoint_id" {
description = "Interface VPC endpoint ID for private API Gateway"
value = aws_vpc_endpoint.private_api_gateway.id
}
VPC内部での名前解決を有効化してください。
CloudShell動作確認用に443ポートを開けていますが、ここはあけなくも大丈夫です。
Route53 パブリックホストゾーンとプライベートホストゾーンの作成
ACM証明書を有効にするにはDNS検証が必要なので、その検証を行うためにパブリックホストゾーンを作成します。(ドメインの取得かサブドメインの委任は済ませてください)
また、先ほど作成したVPC内で名前解決を行うためにプライベートホストゾーンも作成します。
route53.tf
# --------------------------------------------------------
# Route53 (Public Hosted Zone)
resource "aws_route53_zone" "public_hostzone" {
name = var.hostzone_name
comment = "Public Hosted Zone for ${var.hostzone_name}"
}
# --------------------------------------------------------
# Route53 (Private Hosted Zone)
resource "aws_route53_zone" "private_hostzone" {
name = var.hostzone_name
comment = "Private Hosted Zone for ${var.hostzone_name}"
vpc {
vpc_id = var.vpc_id_apne1
vpc_region = "ap-northeast-1"
}
}
resource "aws_route53_record" "a_record" { # ここがポイント
zone_id = aws_route53_zone.private_hostzone.zone_id
name = "apigw-apne1.${var.hostzone_name}"
type = "A"
alias {
name = var.api_gateway_endpoint_dns_name_apne1
zone_id = var.api_gateway_endpoint_apne1_zone_id
evaluate_target_health = true
}
}
# --------------------------------------------------------
# Outputs
output "route53_public_zone_id" {
description = "Route53 public zone ID"
value = aws_route53_zone.public_hostzone.zone_id
}
output "route53_public_zone_name" {
description = "Route53 public zone name"
value = aws_route53_zone.public_hostzone.name
}
output "route53_private_zone_name" {
description = "Route53 private zone name"
value = aws_route53_zone.private_hostzone.name
}
ここのAレコードでエイリアスを有効にして、先ほど作成したVPCエンドポイントを名前解決先として指定することがポイントです。
ACM証明書の取得
*.example.comの証明書を取得します。
APIGateway プライベートドメインを作成するためにACM証明書が必要なので発行します。
ここについては、パブリック証明書でも問題なく動作することを確認したので、プライベート認証局を建てる必要はないです。
acm.tf
# --------------------------------------------------------
# ACM (Public Certificate)
resource "aws_acm_certificate" "cert" {
domain_name = "*.${var.domain_name}"
subject_alternative_names = [var.domain_name]
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
# --------------------------------------------------------
# DNS検証
resource "aws_route53_record" "cert_validation" {
for_each = {
for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
name = each.value.name
type = each.value.type
zone_id = var.route53_zone_id
records = [each.value.record]
ttl = 60
allow_overwrite = true
}
resource "aws_acm_certificate_validation" "cert_validation" {
certificate_arn = aws_acm_certificate.cert.arn
validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
}
# --------------------------------------------------------
# Outputs (プライベートカスタムドメインで使用します)
output "certificate_arn" {
description = "ARN of the ACM certificate"
value = aws_acm_certificate.cert.arn
}
プライベート REST API Gatewayの作成
HTTPではなく、RESTで作成してください。
リソースポリシーには、最初に作成したVPCエンドポイントを追加して、アクセスを有効化してください。
private_rest_api_gateway.tf
# --------------------------------------------------------
# プライベート REST API
resource "aws_api_gateway_rest_api" "private_api" {
name = "private-rest-api"
endpoint_configuration {
types = ["PRIVATE"]
}
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Sid = "AllowFromSpecificVPCE"
Effect = "Allow"
Principal= "*"
Action = "execute-api:Invoke"
Resource = "arn:aws:execute-api:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:*/*/*/*"
Condition = {
StringEquals = {
"aws:SourceVpce" = var.allowed_vpce_id # 先ほど作成したVPCエンドポイントを指定
}
}
},
{
Sid = "DenyOthers"
Effect = "Deny"
Principal= "*"
Action = "execute-api:Invoke"
Resource = "arn:aws:execute-api:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:*/*/*/*"
Condition = {
StringNotEquals = {
"aws:SourceVpce" = var.allowed_vpce_id # 先ほど作成したVPCエンドポイントを指定
}
}
}
]
})
}
# --------------------------------------------------------
# ルートリソースに /hello を追加(例)
resource "aws_api_gateway_resource" "hello" {
rest_api_id = aws_api_gateway_rest_api.private_api.id
parent_id = aws_api_gateway_rest_api.private_api.root_resource_id
path_part = "hello"
}
# GET メソッド
resource "aws_api_gateway_method" "get_hello" {
rest_api_id = aws_api_gateway_rest_api.private_api.id
resource_id = aws_api_gateway_resource.hello.id
http_method = "GET"
authorization = "NONE"
}
# モック統合
resource "aws_api_gateway_integration" "mock_hello" {
rest_api_id = aws_api_gateway_rest_api.private_api.id
resource_id = aws_api_gateway_resource.hello.id
http_method = aws_api_gateway_method.get_hello.http_method
type = "MOCK"
request_templates = {
"application/json" = "{\"statusCode\": 200}"
}
}
# デプロイ & ステージ
resource "aws_api_gateway_deployment" "this" {
rest_api_id = aws_api_gateway_rest_api.private_api.id
triggers = {
redeploy = sha1(jsonencode(aws_api_gateway_rest_api.private_api)) # 変更検知用
}
}
resource "aws_api_gateway_stage" "prod" {
deployment_id = aws_api_gateway_deployment.this.id
rest_api_id = aws_api_gateway_rest_api.private_api.id
stage_name = "prod"
}
プライベートカスタムドメイン、マッピング、関連付けの作成
最後に、本題のこれらのリソースを作成していきます。
こちら側にもリソースポリシーが必要ですのでお気をつけください。
api_gateway_custom_domain.tf
# --------------------------------------------------------
# # 1. プライベートカスタムドメイン
resource "aws_api_gateway_domain_name" "private_domain" {
domain_name = "apigw.${var.domain_name}"
certificate_arn = var.certificate_arn # 事前に作成したパブリック証明書を指定します。
security_policy = "TLS_1_2"
endpoint_configuration {
types = ["PRIVATE"]
}
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = "execute-api:Invoke"
Resource = "execute-api:/*"
Principal = "*"
},
{
Effect = "Deny"
Action = "execute-api:Invoke"
Resource = "execute-api:/*"
Principal = "*"
Condition = {
StringNotEquals = {
"aws:SourceVpce" = var.allowed_vpce_id # 最初に作成したVPCエンドポイントを指定
}
}
},
]
})
}
# --------------------------------------------------------
# # 2. VPCE ↔ ドメイン関連付け (DomainNameAccessAssociation)
resource "aws_api_gateway_domain_name_access_association" "vpce_association" {
access_association_source = var.all_search_account_vpce_id
access_association_source_type = "VPCE"
domain_name_arn = aws_api_gateway_domain_name.private_domain.arn
}
# --------------------------------------------------------
# # 3. API ↔ ドメインのマッピングについては、2025/04/28現在awsccのみ対応している。
resource "awscc_apigateway_base_path_mapping_v2" "base_path_mapping" {
domain_name_arn = aws_api_gateway_domain_name.private_domain.arn
rest_api_id = var.api_id
base_path = var.api_stage
stage = var.api_stage
}
以上の工程でREST Private API Gatewayに、プライベートカスタムドメインを設定することができました。
誤りや誤解を生む表現がございましたら、コメントでご指摘いただけると助かります。
Views: 0