AWS lambda function URL with custom hostname using Pulumi

Etienne Callies
2 min readDec 22, 2022

--

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!

--

--

Etienne Callies
0 Followers

Research Engineer @ Ouihelp 🇫🇷