Enhancing security for Lambda function URLs
AWS has introduced support for Origin Access Control on Lambda function URLs. This new feature ensures more secure and consistent content delivery for our function URLs.
Published May 2, 2024
Last Modified May 13, 2024
AWS has recently announced the support for CloudFront Origin Access Control with Lambda function URL origins.
I've experimented with this new feature and recapped my findings in this post.
Instead of digging into detailed instructions for creating each resource, I will outline the necessary steps here. You can find links to the relevant documentation pages at the end of this article.
The first step involves setting up a Lambda function with an enabled function URL using the
AWS_IAM
auth type. I previously discussed the different function URL authorization types in an article. To summarize, the AWS_IAM
auth type secures the endpoint by only accepting signed requests, while the AuthType: NONE
configuration opens up the URL to the public.Next, we create a CloudFront distribution using the Lambda function URL as the origin. We copy the URL and specify it as the
Origin
in the distribution.The next step is to create an Origin Access Control in CloudFront and specify Lambda as the origin. We should then add the OAC to the distribution.
In a previous post on Origin Access Control, I discussed how OAC can limit direct access to an S3 bucket. We follow a similar principle here.
Finally, to allow the CloudFront distribution to invoke the function URL, we need to add the following permission to the Lambda function's resource-based policy:
Since the AWS console doesn't provide a JSON editor for creating or editing Lambda resource policies, we either use the UI or add the policy via the CLI. Here's how we can add it using the CLI:
In summary, the setup involves a Lambda function URL protected by IAM and a Lambda resource policy allowing our CloudFront distribution to invoke this URL.
Let's see the results of my experimentation.
First, I tested the function URL directly by making unsigned requests, which, as expected, were all declined. Then, when I signed the requests using my credentials, they were successfully processed.
This behaviour is expected since the
AuthType: AWS_IAM
configuration is designed to protect the endpoint. Only identities granted with the lambda:InvokeFunctionUrl
permission can successfully call the URL. Thus far, the results confirm the expected security behaviour.Next, I tested invoking the function via the CloudFront domain, simulating an external user making unsigned requests. As expected, the
GET
request was successful, and I received a response from the Lambda function.I anticipated this outcome. While the
AWS_IAM
auth type secures the function URL by only serving signed requests, the Lambda resource policy allows access to the function through the CloudFront distribution. Hence, invoking the function through the CloudFront domain name returned a valid response.But when I attempted a
POST
request, it failed with the following error message:I didn't expect this result. When I reviewed the documentation, I found the following note:
The documentation indicates the payload itself requires signing with proper credentials for
POST
requests. External users would not be able to perform this step.CloudFront enhances security with additional layers such as DDoS protection or WAF, and also provides content delivery acceleration. We can also assign a custom domain to the distribution, providing a more user-friendly name than the complex function URL domain.
However, when handling
POST
requests, the situation remains consistent whether the function URL is invoked directly or via CloudFront. All such requests must be signed so only authorized identities can execute POST
operations.So, while the new feature enables easier access for
GET
requests, POST
requests still require signed payloads. It restricts using function URLs with CloudFront distributions to those who can sign the requests with the necessary credentials.Currently, the benefits of using CloudFront with Lambda function URLs are limited to
GET
requests if we aim to have the function URL protected. For POST
, PATCH
, and PUT
methods, signing the requests with AWS credentials (access key, secret access key, and session token for roles) is required. For instance, we can't have the CloudFront distribution's domain as a webhook URL because these endpoints typically utilize POST
methods.A practical enhancement would be enabling CloudFront to automatically sign the request payloads and forward them to the function URL origin. This way, we could use CloudFront in more use cases, like webhook implementations.
Additionally, unlike S3, Lambda does not support locking function URL invocations to specific resources like a CloudFront distribution. We can't add a
Deny
statement to the function's resource policy!One way to restrict the function's business logic to invocations only from CloudFront is to add a custom header to origin requests and verify the header's presence in the Lambda function. With this approach, the Lambda function executes each time a request is made, if only to check for the header, leading to an increased number of function invocations.
The new feature is an enhancement in making function URLs a more attractive option for certain use cases. Now, we can make
GET
requests to the function URL via CloudFront without needing additional header validation code in the function's handler while maintaining the URL protection through IAM.However, the current limitations around
POST
and PUT
requests mean that we can use them with CloudFront and Origin Access Control (OAC) mostly in internal applications. In these scenarios, we can configure the resource that's making the call to sign the request payloads using its AWS credentials.UPDATE 2024-05-13
In a recent X post, fellow Community Builder David Behroozi highlighted that the architecture described above supports both
POST
and PUT
requests. To make it work, we need to compute the SHA256 hash of the body and include the hash in the x-amz-content-sha256
header.Consider this example, where the request body is:
For the above body, the SHA256 hash would be
7acb899e28935d98d2a01e27212b5f130ca860f5899c1caabf18ec0988e5df62
. This hash should then be included in the request headers as shown below:By following these steps, sending
POST
requests to the CloudFront endpoint should yield successful responses.Thanks to David for this great insight!
The integration of CloudFront Origin Access Control with Lambda function URLs introduces an additional layer of security, enabling us to keep function URLs private.
With this new feature, external users can now initiate
GET
requests via the CloudFront domain. This release makes Lambda function URLs a more viable and secure option for various use cases.Creating and managing Lambda function URLs - How to create Lambda function URLs
Creating a distribution - How to create a CloudFront distribution
Creating a new origin access control - OAC for S3 (the process is the same for Lambda origins)