From 0cebf3329180194f4b075aa4bee4544a73e95773 Mon Sep 17 00:00:00 2001 From: Petros Kalos Date: Fri, 1 Nov 2024 17:57:20 +0200 Subject: [PATCH] add custom domain support for apigw Be aware that by enabling this feature the automatically generated apigw URL will be disabled. As a prereq users must already have a custom domain in Route53 and have an ACM certificate. The domain must be setup according to this https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-edge-optimized-custom-domain-name.html. To enable this feature one must specify the following in cdk.json. ``` "context": { "DeploymentEnvironments": [ { ... "apigw_custom_domain": { "hosted_zone_name": "custom.domain.com", "hosted_zone_id": "..." "certificate_arn": "arn:aws:acm:us-east-1:..." # edge optimized domain thus cert must be in us-east-1 } ... } } ``` --- deploy/stacks/backend_stack.py | 2 ++ deploy/stacks/backend_stage.py | 2 ++ deploy/stacks/lambda_api.py | 37 ++++++++++++++++++++++++++++++++-- deploy/stacks/pipeline.py | 1 + 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/deploy/stacks/backend_stack.py b/deploy/stacks/backend_stack.py index 583069473..b2531cd69 100644 --- a/deploy/stacks/backend_stack.py +++ b/deploy/stacks/backend_stack.py @@ -44,6 +44,7 @@ def __init__( vpc_endpoints_sg=None, internet_facing=True, custom_domain=None, + apigw_custom_domain=None, ip_ranges=None, apig_vpce=None, prod_sizing=False, @@ -201,6 +202,7 @@ def __init__( email_custom_domain=ses_stack.ses_identity.email_identity_name if ses_stack is not None else None, ses_configuration_set=ses_stack.configuration_set.configuration_set_name if ses_stack is not None else None, custom_domain=custom_domain, + apigw_custom_domain=apigw_custom_domain, custom_auth=custom_auth, custom_waf_rules=custom_waf_rules, allowed_origins=allowed_origins, diff --git a/deploy/stacks/backend_stage.py b/deploy/stacks/backend_stage.py index 4185c5623..4ba97f47e 100644 --- a/deploy/stacks/backend_stage.py +++ b/deploy/stacks/backend_stage.py @@ -21,6 +21,7 @@ def __init__( vpc_endpoints_sg=None, internet_facing=True, custom_domain=None, + apigw_custom_domain=None, ip_ranges=None, apig_vpce=None, prod_sizing=False, @@ -56,6 +57,7 @@ def __init__( vpc_endpoints_sg=vpc_endpoints_sg, internet_facing=internet_facing, custom_domain=custom_domain, + apigw_custom_domain=apigw_custom_domain, ip_ranges=ip_ranges, apig_vpce=apig_vpce, prod_sizing=prod_sizing, diff --git a/deploy/stacks/lambda_api.py b/deploy/stacks/lambda_api.py index 1c5f788e5..9825af74d 100644 --- a/deploy/stacks/lambda_api.py +++ b/deploy/stacks/lambda_api.py @@ -14,12 +14,16 @@ aws_kms as kms, aws_sqs as sqs, aws_logs as logs, + aws_route53 as r53, + aws_route53_targets as r53_targets, Duration, CfnOutput, Fn, RemovalPolicy, BundlingOptions, ) +from aws_cdk.aws_apigateway import DomainNameOptions, EndpointType, SecurityPolicy +from aws_cdk.aws_certificatemanager import Certificate from aws_cdk.aws_ec2 import ( InterfaceVpcEndpoint, InterfaceVpcEndpointAwsService, @@ -56,13 +60,14 @@ def __init__( email_custom_domain=None, ses_configuration_set=None, custom_domain=None, + apigw_custom_domain=None, custom_auth=None, allowed_origins='*', log_retention_duration=None, **kwargs, ): super().__init__(scope, id, **kwargs) - + self.apigw_custom_domain = apigw_custom_domain self.log_retention_duration = log_retention_duration log_level = 'INFO' if prod_sizing else 'DEBUG' @@ -662,6 +667,7 @@ def set_up_graphql_api_gateway( types=[apigw.EndpointType.PRIVATE], vpc_endpoints=[api_vpc_endpoint] ), policy=api_policy, + disable_execute_api_endpoint=bool(self.apigw_custom_domain), ) else: gw = apigw.RestApi( @@ -669,8 +675,35 @@ def set_up_graphql_api_gateway( backend_api_name, rest_api_name=backend_api_name, deploy_options=api_deploy_options, + disable_execute_api_endpoint=bool(self.apigw_custom_domain), + ) + + if self.apigw_custom_domain: + certificate = Certificate.from_certificate_arn( + self, 'CustomDomainCertificate', self.apigw_custom_domain['certificate_arn'] + ) + gw.add_domain_name( + 'ApiGwCustomDomainName', + certificate=certificate, + domain_name=self.apigw_custom_domain['hosted_zone_name'], + endpoint_type=EndpointType.EDGE if internet_facing else EndpointType.PRIVATE, + security_policy=SecurityPolicy.TLS_1_2, + ) + r53.ARecord( + self, + 'ApiGwARecordId', + zone=r53.HostedZone.from_hosted_zone_attributes( + self, + 'ApiGwHostedZoneId', + hosted_zone_id=self.apigw_custom_domain['hosted_zone_id'], + zone_name=self.apigw_custom_domain['hosted_zone_name'], + ), + target=r53.RecordTarget.from_alias(r53_targets.ApiGateway(gw)), ) - api_url = gw.url + api_url = f'https://{gw.domain_name.domain_name}/' + else: + api_url = gw.url + integration = apigw.LambdaIntegration(api_handler) request_validator = apigw.RequestValidator( self, diff --git a/deploy/stacks/pipeline.py b/deploy/stacks/pipeline.py index 427a117c5..a7ba892f6 100644 --- a/deploy/stacks/pipeline.py +++ b/deploy/stacks/pipeline.py @@ -662,6 +662,7 @@ def set_backend_stage(self, target_env, repository_name): vpc_restricted_nacls=target_env.get('vpc_restricted_nacl', False), internet_facing=target_env.get('internet_facing', True), custom_domain=target_env.get('custom_domain'), + apigw_custom_domain=target_env.get('apigw_custom_domain'), ip_ranges=target_env.get('ip_ranges'), apig_vpce=target_env.get('apig_vpce'), prod_sizing=target_env.get('prod_sizing', True),