diff --git a/private-apigw-custom-domain/README.md b/private-apigw-custom-domain/README.md new file mode 100644 index 000000000..2978b0663 --- /dev/null +++ b/private-apigw-custom-domain/README.md @@ -0,0 +1,61 @@ +# Private Amazon API Gateway with private custom domain name + +The AWS SAM template deploys a private Amazon API Gateway with a private custom domain name mapped to deployed stage. This template also create a Amazon Route53 A-Alias record in a private hosted zone to map the private custom domain name (e.g. `private.mydomain.com`) to the target VPC Endpoint DNS name. (e.g. `vpce-abcdefgh123456789-abcd1234.execute-api.us-east-1.vpce.amazonaws.com`). + +Learn more about this pattern at [Serverless Land Patterns](https://serverlessland.com/patterns/private-apigw-custom-domain) + +## Requirements + +* An [AWS account](https://signin.aws.amazon.com/signup?request_type=register) with an IAM user or role that has sufficient permissions to make the necessary AWS service calls and manage AWS resources. +* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured. +* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git). +* [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) (AWS SAM) installed. +* An [execute-api VPC Endpoint](https://docs.aws.amazon.com/vpc/latest/privatelink/interface-endpoints.html). +* A Route 53 [Private Hosted Zone](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/hosted-zones-private.html). (*You can also use Public Hosted Zone but it is recommnded to use Private Hosted Zone to make sure that the Domain Name is only resolvable from within the VPC*) +* An SSL/TLS certificate in [AWS Certificate Manager](https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-specify-certificate-for-custom-domain-name.html#how-to-specify-certificate-for-custom-domain-name-setup). + +## Deployment Instructions + +1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository: + ```bash + git clone https://github.com/aws-samples/serverless-patterns + ``` +2. Change directory to the pattern directory: + ```bash + cd serverless-patterns/private-apigw-custom-domain + ``` +3. From the command line, use AWS SAM to deploy the AWS resources for the pattern as specified in the template.yml file: + ```bash + sam deploy --guided + ``` +4. During the prompts: + - Enter **stack name** and desired **AWS Region**. + - Enter **DNS Name** of the **execute-api** VPC endpoint for the VpcEndpointDNSName parameter. (e.g. vpce-abcdefgh123456789-abcd1234.execute-api.us-east-1.vpce.amazonaws.com) + - Enter **Hosted Zone ID** of the **execute-api** VPC endpoint for the VPCEndpointHostedZoneID parameter. (This can be found along with the DNS Name of the VPC endpoing on the AWS Console.) + - Enter **Private Custom Domain Name** (e.g. private.mydomain.com) for the CustomDomainName parameter. + - Enter **ACM Certificate ARN** from the same region as Private Amazon API Gateway for the CertificateArn parameter. The certificate must cover the Private Custom Domain name entered in the previous step. + - Enter **Private Hosted Zone ID** that has the domain name you would like to use for the parameter PrivateHostedZoneId. + - Allow SAM to create roles with the required permissions if needed. + + Once you have run guided mode once, you can use `sam deploy` in future to use these defaults. + +1. Note the outputs from the SAM deployment process. This contain the curl command to test the Private Custom Domain Name. + +## Testing + +The stack will output the **Private Custom Domain Name**. You can use `curl` to send a HTTP request to the Private Custom Domain endpoint to test the correct mapping to your API. + +```bash +curl https://{PrivateCustomDomainName}/ +``` + +## Cleanup + +1. Delete the stack + ```bash + sam delete + ``` +1. Confirm the stack has been deleted + ```bash + aws cloudformation list-stacks --query "StackSummaries[?contains(StackName,'STACK_NAME')].StackStatus" + ``` diff --git a/private-apigw-custom-domain/example-pattern.json b/private-apigw-custom-domain/example-pattern.json new file mode 100644 index 000000000..39f557a55 --- /dev/null +++ b/private-apigw-custom-domain/example-pattern.json @@ -0,0 +1,58 @@ +{ + "title": "Private Amazon API Gateway with private custom domain name", + "description": "Create a Private API Gateway with a private custom domain name, configure access based on VPC endpoint, and set up DNS routing using Amazon Route 53 private hosted zone.", + "language": "", + "level": "200", + "framework": "SAM", + "introBox": { + "headline": "How it works", + "text": [ + "This AWS SAM template demonstrates how to create a private Amazon API Gateway with a private custom domain mame, configure secure access based on a specific VPC endpoint, and route traffic through Route 53 in a private hosted zone.", + "Private custom domain name is only accessible from a VPC endpoint, which is mapped to a stage in private Amazon API Gateway. A custom domain name is configured with an SSL/TLS certificate to provide secure access, and an associated Route 53 A-Alias record ensures that traffic is routed to the API.", + "As prerequisites for this pattern, you must have:" + "* A DNS name of execute-api VPC endpoint", + "* A custom domain name that you would like to create (e.g. private.mydomain.com)", + "* A valid certificate in ACM (Amazon Certificate Manager) in the same Region as Private Amazon API Gateway, that covers the namespace of the domain you would like to use (i.e. *.mydomain.com).", + "* A Route 53 Private Hosted Zone ID that has the domain name you would like to use (e.g. mydomain.com).", + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/private-apigw-custom-domain", + "templateURL": "serverless-patterns/private-apigw-custom-domain", + "projectFolder": "private-apigw-custom-domain", + "templateFile": "template.yaml" + } + }, + "resources": { + "bullets": [ + { + "text": "Custom domain names for private APIs in API Gateway", + "link": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-private-custom-domains.html" + } + ] + }, + "deploy": { + "text": [ + "Deploy the stack: sam deploy --guided + ] + }, + "testing": { + "text": [ + "See the GitHub repo for detailed testing instructions." + ] + }, + "cleanup": { + "text": [ + "Delete the stack: sam delete." + ] + }, + "authors": [ + { + "name": "Usama Ali Khan", + "image": "https://media.licdn.com/dms/image/v2/D4E03AQHcLMpZ1LV9UQ/profile-displayphoto-shrink_800_800/profile-displayphoto-shrink_800_800/0/1685892371158?e=1737590400&v=beta&t=RaPZkIgm7m3thW4PyKSQNn_w9fMbYBeu5PPrQ6K4vBU", + "bio": "Usama is a Technical Account Manager at Amazon Web Services.", + "linkedin": "usama-ali-khan" + } + ] +} diff --git a/private-apigw-custom-domain/pattern.json b/private-apigw-custom-domain/pattern.json new file mode 100644 index 000000000..3d82ecb86 --- /dev/null +++ b/private-apigw-custom-domain/pattern.json @@ -0,0 +1,95 @@ +{ + "title": "Private Amazon API Gateway with private custom domain name", + "description": "Create a Private API Gateway with a custom domain. Configure access via VPC endpoint and set up DNS routing with Amazon Route 53 private hosted zone.", + "language": "", + "level": "200", + "framework": "SAM", + "introBox": { + "headline": "How it works", + "text": [ + "This AWS SAM template demonstrates how to create a private Amazon API Gateway with a private custom domain mame, configure secure access based on a specific VPC endpoint, and route traffic through Route 53 in a private hosted zone.", + "Private custom domain name is only accessible from a VPC endpoint, which is mapped to a stage in private Amazon API Gateway. A custom domain name is configured with an SSL/TLS certificate to provide secure access, and an associated Route 53 A-Alias record ensures that traffic is routed to the API.", + "As prerequisites for this pattern, you must have:", + "* A DNS name of execute-api VPC endpoint", + "* A custom domain name that you would like to create (e.g. private.mydomain.com)", + "* A valid certificate in ACM (Amazon Certificate Manager) in the same Region as Private Amazon API Gateway, that covers the namespace of the domain you would like to use (i.e. *.mydomain.com).", + "* A Route 53 Private Hosted Zone ID that has the domain name you would like to use (e.g. mydomain.com)." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/private-apigw-custom-domain", + "templateURL": "serverless-patterns/private-apigw-custom-domain", + "projectFolder": "private-apigw-custom-domain", + "templateFile": "template.yaml" + } + }, + "resources": { + "bullets": [ + { + "text": "Custom domain names for private APIs in API Gateway", + "link": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-private-custom-domains.html" + } + ] + }, + "deploy": { + "text": ["Deploy the stack: sam deploy --guided"] + }, + "testing": { + "text": ["See the GitHub repo for detailed testing instructions."] + }, + "cleanup": { + "text": ["Delete the stack: sam delete."] + }, + "authors": [ + { + "name": "Usama Ali Khan", + "image": "https://media.licdn.com/dms/image/v2/D4E03AQHcLMpZ1LV9UQ/profile-displayphoto-shrink_800_800/profile-displayphoto-shrink_800_800/0/1685892371158?e=1737590400&v=beta&t=RaPZkIgm7m3thW4PyKSQNn_w9fMbYBeu5PPrQ6K4vBU", + "bio": "Usama is a Technical Account Manager at Amazon Web Services.", + "linkedin": "usama-ali-khan" + } + ], + "patternArch": { + "icon1": { + "x": 20, + "y": 50, + "service": "route53", + "label": "Amazon Route 53" + }, + "icon2": { + "x": 50, + "y": 50, + "service": "apigw", + "label": "Amazon API Gateway" + }, + "icon3": { + "x": 80, + "y": 50, + "service": "vpc-endpoint", + "label": "VPC Endpoint" + }, + "icon4": { + "x": 20, + "y": 58, + "service": "", + "label": "private hosted zone" + }, + "icon5": { + "x": 50, + "y": 58, + "service": "", + "label": "(private)" + }, + + "line1": { + "from": "icon1", + "to": "icon2", + "label": "" + }, + "line2": { + "from": "icon2", + "to": "icon3", + "label": "" + } + } +} diff --git a/private-apigw-custom-domain/template.yaml b/private-apigw-custom-domain/template.yaml new file mode 100644 index 000000000..750a3b794 --- /dev/null +++ b/private-apigw-custom-domain/template.yaml @@ -0,0 +1,112 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + Serverless patterns - Amazon Private API Gateway endpoint with Private Custom Domain Name + +Parameters: + VpcEndpointDNSName: + Type: String + VPCEndpointHostedZoneID: + Type: String + CustomDomainName: + Type: String + CertificateArn: + Type: String + PrivateHostedZoneId: + Type: String + +Resources: + PrivateApi: + Type: AWS::Serverless::Api + Properties: + EndpointConfiguration: PRIVATE + StageName: Prod + AlwaysDeploy: true + DefinitionBody: + openapi: "3.0.1" + info: + version: "1.0" + title: !Sub "PrivateApi-${AWS::StackName}" + paths: + /: + get: + responses: + "200": + description: "200 response" + content: + application/json: + schema: + $ref: "#/components/schemas/Empty" + x-amazon-apigateway-integration: + type: "mock" + responses: + default: + statusCode: "200" + responseTemplates: + application/json: '{\"message\":\"Hello from Amazon Private API Gateway\"\}' + requestTemplates: + application/json: "{\"statusCode\": 200}" + passthroughBehavior: "when_no_match" + x-amazon-apigateway-policy: + Version: "2012-10-17" + Statement: + - Effect: "Allow" + Principal: "*" + Action: "execute-api:Invoke" + Resource: "execute-api:/*" + Condition: + StringEquals: + aws:sourceVpce: !Join ["-", [!Select [0, !Split ["-", !Select [0, !Split [".", !Ref VpcEndpointDNSName]]]], !Select [1, !Split ["-", !Select [0, !Split [".", !Ref VpcEndpointDNSName]]]]]] + + PrivateDomainName: + Type: AWS::ApiGateway::DomainNameV2 + Properties: + DomainName: !Ref CustomDomainName + CertificateArn: !Ref CertificateArn + EndpointConfiguration: + Types: + - PRIVATE + SecurityPolicy: TLS_1_2 + Policy: + Statement: + - Action: 'execute-api:Invoke' + Effect: Allow + Principal: '*' + Resource: 'execute-api:/*' + - Action: 'execute-api:Invoke' + Condition: + StringNotEquals: + aws:SourceVpce : !Join ["-", [!Select [0, !Split ["-", !Select [0, !Split [".", !Ref VpcEndpointDNSName]]]], !Select [1, !Split ["-", !Select [0, !Split [".", !Ref VpcEndpointDNSName]]]]]] + Effect: Deny + Principal: '*' + Resource: 'execute-api:/*' + Version: 2012-10-17 + + BasePathMapping: + Type: AWS::ApiGateway::BasePathMappingV2 + Properties: + RestApiId: !Ref PrivateApi + DomainNameArn: !GetAtt PrivateDomainName.DomainNameArn + Stage: !Ref PrivateApi.Stage + + DomainNameAccessAssociation: + Type: AWS::ApiGateway::DomainNameAccessAssociation + Properties: + DomainNameArn: !GetAtt PrivateDomainName.DomainNameArn + AccessAssociationSource: !Join ["-", [!Select [0, !Split ["-", !Select [0, !Split [".", !Ref VpcEndpointDNSName]]]], !Select [1, !Split ["-", !Select [0, !Split [".", !Ref VpcEndpointDNSName]]]]]] + AccessAssociationSourceType: VPCE + + R53Alias: + Type: AWS::Route53::RecordSet + Properties: + Name: !Ref CustomDomainName + HostedZoneId: !Ref PrivateHostedZoneId + Type: A + AliasTarget: + DNSName: !Ref VpcEndpointDNSName + HostedZoneId: !Ref VPCEndpointHostedZoneID + +Outputs: + CURLCommand: + Description: "Curl Command to test" + Value: !Sub "curl https://${CustomDomainName}/" \ No newline at end of file