🗂

SAMを利用してPrivate ApiGatewayに対して、Privateのカスタムドメインを作成する

に公開

はじめに

SAMでPrivate ApiGatewayを作成していて、カスタムドメインを作成したいケースがありました。
2024年11月のアップデートで、Privateのカスタムドメインを作成できるようになりました。
https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-private-custom-domains.html

SAMで作成する方法の情報が少なかったので、実際に作成してみました。

こちらを参考にしていきました。
https://aws.amazon.com/jp/blogs/compute/implementing-custom-domain-names-for-private-endpoints-with-amazon-api-gateway/

template

リソースに対する補足はコメントで記載しています。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  private-works
Globals:
  Function:
    LoggingConfig:
      LogFormat: JSON
  Api:
    OpenApiVersion: 3.0.3

Resources:
  # api gateway private 設定
  PrivateWorksApi:
    Type: AWS::Serverless::Api
    Properties:
      Name: PrivateWorksApi
      StageName: Prod
      EndpointConfiguration:
        Type: PRIVATE
        # 特定の VPC エンドポイントのみからのアクセスを許可
        VpcEndpointIds: !Ref VpcEndpointIds
      Auth:
        # リソースポリシーは必須
        ResourcePolicy:
          CustomStatements:
            - Effect: Allow
              Principal: "*"
              Action: execute-api:Invoke
              Resource: "arn:aws:execute-api:*:*:*/*/*/*"
              Condition:
                StringEquals:
                  aws:SourceVpce: !Ref VpceId

  # 実行用のlambda
  StartFunctionLambda:
    Type: AWS::Serverless::Function
    Properties:
      Handler: app.lambda_handler
      Runtime: python3.9
      CodeUri: sample_workflows/function_start_execution
      Timeout: 5
      MemorySize: 128
      Role: !GetAtt PrivateWorksStartExecutionLambdaRole.Arn
      Events:
        CatchAll:
          Type: Api
          Properties:
            Path: /private_works
            Method: POST
            RestApiId: 
              Ref: PrivateWorksApi

  PrivateWorksStartExecutionLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: AllowCloudWatchLogs
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*"

  # private custom domain 設定
  PrivateDomainName:
    DependsOn: PrivateWorksApi
    Type: AWS::ApiGateway::DomainNameV2
    Properties:
      CertificateArn: !Ref CertificateArn
      DomainName: !Ref CustomDomainName
      EndpointConfiguration:
        Types:
          - PRIVATE
      SecurityPolicy: TLS_1_2
      Policy: !Sub |
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": "*",
              "Action": "execute-api:Invoke",
              "Resource": "execute-api:/*",
              "Condition": {
                "StringEquals": {
                  "aws:SourceVpce": ["${VpceId}"]
                }
              }
            },
            {
              "Effect": "Deny",
              "Principal": "*",
              "Action": "execute-api:Invoke",
              "Resource": "execute-api:/*",
              "Condition": {
                "StringNotEquals": {
                  "aws:SourceVpce": ["${VpceId}"]
                }
              }
            }
          ]
        }

  # custom domain と api gateway の紐付け
  Mapping:
    DependsOn: PrivateDomainName
    Type: AWS::ApiGateway::BasePathMappingV2
    Properties:
      DomainNameArn: !GetAtt PrivateDomainName.DomainNameArn
      RestApiId: !Ref PrivateWorksApi
      Stage: Prod

  Association:
    Type: AWS::ApiGateway::DomainNameAccessAssociation
    Properties:
      AccessAssociationSource: !Ref VpceId
      AccessAssociationSourceType: VPCE
      DomainNameArn: !GetAtt PrivateDomainName.DomainNameArn

  # ホストゾーンにcustom domainのCNAMEを設定
  ApiCustomDomainRecord:
    Type: AWS::Route53::RecordSet
    Properties:
      HostedZoneId: !Ref HostedZoneId
      Name: !Ref CustomDomainName
      Type: CNAME
      TTL: "60"
      ResourceRecords: !Ref VpcEndpointDns


Parameters:
  Env:
    Type: String
    Description: "Environment name (e.g., local, stg, prod)"
  VpcEndpointIds:
    Type: List<String>
    Description: "VPC Endpoint IDs for the API Gateway"
  VpceId:
    Type: String
    Description: ID of the VPC Endpoint to allow
  CustomDomainName:
    Type: String
    Description: "Custom Domain Name (e.g., api.example.com)"
  CertificateArn:
    Type: String
    Description: "ARN of the SSL/TLS Certificate in ACM"
  HostedZoneId:
    Type: String
    Description: "Route 53 Hosted Zone ID for the custom domain"
  VpcEndpointDns:
    Type: List<String>
    Description: "DNS name of the VPC Endpoint"

samconfig.toml

# More information about the configuration file can be found here:
# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html
version = 0.1

[stg]
[stg.global.parameters]
stack_name = "stg-private"

[stg.deploy.parameters]
capabilities = "CAPABILITY_IAM"
confirm_changeset = true
resolve_s3 = true
s3_prefix = "stg-private"

parameter_overrides = [
    "Env=stg",
    "VpcId=<vpcId>",
    "VpcEndpointIds=<vpcエンドポイントId>",
    "VpceId=<vpcエンドポイントId>",
    "VpcEndpointDns=<vpcエンドポイントDns名>",
    "SubnetIds=<sunetId>,<sunetId>,<sunetId>",
    "CustomDomainName=private.stg.com",
    "CertificateArn=<acm arn>",
    "HostedZoneId=<private host zone id>",
]

[stg.package.parameters]
resolve_s3 = true

Discussion