AWS lambda function URL with custom hostname using Pulumi
This post assumes you have working knowledge of AWS Lambda and Pulumi Framework.
AWS Lambda is great to focus on code and not on infrastructure. By default AWS gives you a URL function like https://{some_id}.lambda-url.{region}.on.aws/, but if you need to share this URL with external applications, you’ll then quickly want to have the function URL with your own domain: it’s cleaner and does not change over time. Even if it has become easier than before, it’s surpringly still a pain in the ass. You will find in this article a configuration for Pulumi (only in Python though). Here is also an alternative for Serverless Framework:
Requirements:
- A lambda function running with Pulumi. We assume we can use “myproject_lambda_function_url” object in the code (replace it!).
- A registered domain name with Route53 as the DNS service. We assume it’s “example.com” in the code (replace it!).
- You don’t need SSL certificate on ACM (Pulumi will create one)
Here’s the code to add in your “__main__.py”:
myproject_domain_name = "myproject.example.com"
# 1/ New SSL certificate in ACM
# For the certificate to be usable in CloudFront,
# we need to create it in 'us-east-1' region
aws_us_east_1 = aws.Provider('aws-us-east-1', region='us-east-1')
myproject_certificate = aws.acm.Certificate(
"myproject-ssl-certificate",
domain_name=myproject_domain_name,
validation_method="DNS",
opts=pulumi.ResourceOptions(provider=aws_us_east_1)
)
my_zone = aws.route53.get_zone(
name="example.com",
private_zone=False)
myproject_cert_validation_record = aws.route53.Record(
"myproject-cert-validation-record",
name=myproject_certificate.domain_validation_options[0].resource_record_name,
records=[myproject_certificate.domain_validation_options[0].resource_record_value],
ttl=60,
type=myproject_certificate.domain_validation_options[0].resource_record_type,
zone_id=my_zone.zone_id)
myproject_certificate_validation = aws.acm.CertificateValidation(
"myproject-cert-validation",
certificate_arn=myproject_certificate.arn,
validation_record_fqdns=[myproject_cert_validation_record.fqdn],
opts=pulumi.ResourceOptions(provider=aws_us_east_1))
# Debug output
# pulumi.export("myproject_certificate_arn", myproject_certificate_validation.certificate_arn)
# 2/ New CloudFront distribution using newly-created certificate
myproject_origin_id = "myproject-lambda-domain"
myproject_cloudfront_distribution = aws.cloudfront.Distribution(
"myproject-cloudfront-distribution",
origins=[
aws.cloudfront.DistributionOriginArgs(
origin_id=myproject_origin_id,
domain_name=myproject_lambda_function_url.function_url
.apply(lambda url: url.replace('https://', '').replace('/', '')),
custom_origin_config=aws.cloudfront.DistributionOriginCustomOriginConfigArgs(
http_port=80,
https_port=443,
origin_protocol_policy='https-only',
origin_ssl_protocols=[
'SSLv3',
'TLSv1',
'TLSv1.1',
'TLSv1.2'
],
)
)
],
aliases=[
myproject_domain_name
],
viewer_certificate=aws.cloudfront.DistributionViewerCertificateArgs(
acm_certificate_arn=myproject_certificate_validation.certificate_arn,
ssl_support_method='sni-only',
minimum_protocol_version='TLSv1'
),
enabled=True,
default_cache_behavior=aws.cloudfront.DistributionDefaultCacheBehaviorArgs(
allowed_methods=[
"DELETE",
"GET",
"HEAD",
"OPTIONS",
"PATCH",
"POST",
"PUT",
],
cached_methods=[
"GET",
"HEAD",
"OPTIONS",
],
target_origin_id=myproject_origin_id,
viewer_protocol_policy="allow-all",
forwarded_values=aws.cloudfront.DistributionDefaultCacheBehaviorForwardedValuesArgs(
cookies=aws.cloudfront.DistributionDefaultCacheBehaviorForwardedValuesCookiesArgs(
forward="all"
),
query_string=True,
# headers=["my-header"] # Forward headers to lambda
)
),
restrictions=aws.cloudfront.DistributionRestrictionsArgs(
geo_restriction=aws.cloudfront.DistributionRestrictionsGeoRestrictionArgs(
restriction_type="none",
locations=[],
),
)
)
# Debug output
# pulumi.export("Cloudfront Distribution URL", myproject_cloudfront_distribution.domain_name)
# 3/ New record in Route53 alias-ing CloudFront domain
myproject_route53_record = aws.route53.Record(
"myproject-route53-record",
zone_id=my_zone.zone_id,
name=myproject_domain_name,
type="A",
aliases=[aws.route53.RecordAliasArgs(
evaluate_target_health=False,
name=myproject_cloudfront_distribution.domain_name,
zone_id=myproject_cloudfront_distribution.hosted_zone_id
)]
)
Then “pulumi up” and enjoy your new URL!