Securing your Lambda function URLs with AWS IAM

| 3 min read

Introduction

For a long time, the only way to expose your Lambda functions over HTTP was to use AWS API Gateway service. As of April, 2022 that is no longer the case, as AWS introduced a new feature called Lambda Function URLs. With Lambda URLs, you can quickly provision a function that will be accessible over HTTP without using any additional services. In this blog post we will explore how to provision a simple Lambda Function with function URL with Serverless Framework. We will secure it with AWS IAM and see how we can call that URL from another application.

Prerequisites

We will be using Serverless Framework to build our sample application. As our language of choice, we will be using Python 3.9, so please make sure to install it ahead of time.

Setting up our service

As we will be using Serverless Framework in this example, let's bootstrap our project from a predefined template with the following command:

sls create --template aws-python --path serverless-secure-furl

It will create a project with the following structure:

serverless-secure-furl
├── README.md
├── handler.py
└── serverless.yml

Let's clean up our serverless.yml so it look like this:

service: serverless-secure-furl
frameworkVersion: '3'

provider:
name: aws
runtime: python3.9

functions:
hello:
handler: handler.hello

Additionally, let's modify our handler.py to return a friendly message when it's called:

import json


def hello(event, context):
body = {
"message": "Hello from my secure Lambda!",
}

response = {
"statusCode": 200,
"body": json.dumps(body)
}

return response

Now we're ready to configure Lambda Function URL in our service. With Serverless Framework, it's really simple, as you just need to add url: true in your function configuration:

...

functions:
hello:
handler: handler.hello
url: true

Now we're ready to deploy our service:

sls deploy

After the deployment is finished, you should see the output similar to:

Deploying serverless-secure-furl to stage dev (us-east-1)

✔ Service deployed to stack serverless-secure-furl-dev (181s)

endpoint: https://jo45vn4mvgafonvfmaignxynf40qdrhf.lambda-url.us-east-1.on.aws/
functions:
hello: serverless-secure-furl-dev-hello (287 B)

Let's grab the url and try to call it:

curl https://jo45vn4mvgafonvfmaignxynf40qdrhf.lambda-url.us-east-1.on.aws/

You should see an output similar to this:

{"message": "Hello from my secure Lambda!"}%

Yay, our Lambda Function URL is working nicely, but it can be invoked by anyone. Let's try to make it more secure.

Securing our Function URL with AWS IAM

One of the available option for securing your Lambda Function URLs is using AWS IAM authorization, which will limit access to the URL to authenticated IAM users and roles.

In order to do that, we only need to do a simple change in our serverless.yml config:

...

functions:
hello:
handler: handler.hello
url:
authorizer: aws_iam

Let's deploy our service again:

sls deploy

and try to call it again with curl:

curl https://jo45vn4mvgafonvfmaignxynf40qdrhf.lambda-url.us-east-1.on.aws/

Success, our function is now secured and you should see the output like:

{"Message":"Forbidden"}

Calling our secured Function URL

Our Lambda Function URL is now secured and protected from unauthorized access, but we still want to be able to invoke it. In order to do so, we need to provide a valid Signature Version 4 along with our request. If you'd like to dive deeper into AWS SigV4, you can read about it in official docs. In our case, we will use a dedicated library that will compute the proper signature for us. The library that we will use is called requests-auth-aws-sigv4 and is intended to be used with popular Python package, requests. Let's install it with pip:

Note: You might want to create a virtual environment to not pollute your system-wide Python installation with requests-auth-aws-sigv4 and all its dependencies.

pip install requests-auth-aws-sigv4

Now that we have all our dependencies installed, let's write a script that will call the secured Lambda Function URL that we deployed previously:

import requests
from requests_auth_aws_sigv4 import AWSSigV4

FURL = '<replace-with-your-function-url>'

def call_api():
aws_auth = AWSSigV4('lambda')
r = requests.request('GET', FURL, auth=aws_auth)
print(r.content)

if __name__ == '__main__':
call_api()

Before we run the script above, we need to make sure that the AWSSigV4 will be able to properly resolve AWS credentials and region. By default, it will pick up values from AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, and AWS_DEFAULT_REGION. Another option is to have boto3 or botocore installed. If that would be the case, AWSSigV4 will be able to use it to resolve credentials following boto3's credential resolution chain. It will also properly resolve the default region. In our case, let's assume that we don't have boto3 installed. Before running the script, we will need to set at least the following environment variables:

export AWS_ACCESS_KEY_ID=<your-access-key>
export AWS_SECRET_ACCESS_KEY=<your-secret>
export AWS_DEFAULT_REGION=us-east-1

After that, we can run our script. In our case, let's assume we named the script call_our_lambda_url.py:

python call_our_lambda_url.py

You should see output similar to:

b'{"message": "Hello from my secure Lambda!"}'

Looks like we were able to succesfully call our secured Lambda Function URL!

Note: One important thing to remember. We cannot just use any AWS credentials to call our function URL. The credentials that we use have to have lambda:InvokeFunctionUrl permission. For more details on proper configuration of permissions for AWS IAM-secured Lambda Function URLs, please see the official docs.

Summary

In a few short steps, we were able to create a simple Lambda Function that can be called via Lambda Function URL. Additionally, we also secured in using AWS IAM and wrote a simple script that allows us to call this newly created URL in a secure manner. If you'd like to check out the source code for the examples presented in this blog post, it's avaialble here. Thanks for reading!