Controlling access to Lambda Function URLs
Tags: aws, security, serverless, lambda
Published: 2022-04-14 08:21:31

The new Lambda function URLs offer two methods for access control. Depending on the use case, AWS IAM or the function itself can perform the authorization process.

AWS has recently <a href="https://aws.amazon.com/blogs/aws/announcing-aws-lambda-function-urls-built-in-https-endpoints-for-single-function-microservices/" class="link" target="_blank" rel="noopener noreferrer">announced</a> a new feature called <a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html" class="link" target="_blank" rel="noopener noreferrer">Function URLs</a> for Lambda. A Function URL is an HTTPS endpoint that is mapped to a given Lambda function.

AWS recommends Function URLs for use cases where a single function with a public endpoint is needed, for example, form validations, webhook handling, or mobile payment processing.

Based on the `AuthType` property, we can create two different types of endpoints: one with `AuthType: AWS_IAM` and one with `AuthType: NONE`.

## 1. The scenarios

Alice and Bob are two cloud developers working for Example Corp., which has its whole infrastructure run on AWS <a href="https://aws.amazon.com/serverless/" class="link" target="_blank" rel="noopener noreferrer">serverless technologies</a>.

In the current sprint, Alice and Bob got different tasks.

Alice is working on a new feature and she creates a <a href="https://aws.amazon.com/lambda/" class="link" target="_blank" rel="noopener noreferrer">Lambda</a> function to perform the business logic.

Bob is integrating a 3rd-party solution into the system, and his task is to create a webhook where the 3rd-party can send notifications. The business logic will further process these notifications.

## 2. Scenario No 1. - AuthType: AWS_IAM

Alice's Lambda function needs to accept a payload object. She doesn't like using the Console for function invocation because she needs to create different test events, and she doesn't like test events. She also doesn't want to use <a href="https://aws.amazon.com/cli/" class="link" target="_blank" rel="noopener noreferrer">CLI</a>  and type `aws lambda invoke ...` all the time.

She decides to create a Function URL for the Lambda function with `AuthType: AWS_IAM`. She needs to go to the **Function URL - new** section in the Console, hit **Create function URL**, and select `AuthType: AWS_IAM`.

![Function URL in the Console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qdrvkvhij0fxx9zzd3vx.png)

Alice can create a new function with a URL or add the URL to an existing one.

### 2.1. Considerations

If we want to create a Function URL with `AuthType: AWS_IAM`, we must consider the following.

AWS recommends this type of Function URL for authenticated identities (users or roles). So the identity who invokes the URL must provide their credentials (`ACCESS_KEY_ID` and `SECRET_ACCESS_KEY`).

As the `AuthType: AWS_IAM` property implies, <a href="https://aws.amazon.com/iam/" class="link" target="_blank" rel="noopener noreferrer">IAM</a> performs the access control to the Function URL, which indicates that we should create a policy to allow the `lambda:InvokeFunctionUrl` action. Assuming same-account access, we can attach the policy to either the identity invoking the URL (identity-based policy) or the Lambda function that is behind the Function URL (resource-based policy).

One such identity-based policy can look like this:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "lambda:InvokeFunctionUrl",
      "Resource": "arn:aws:lambda:us-east-1:123456789012:function:AuthIamFunctionUrl",
      "Condition": {
        "StringEquals": {
          "lambda:FunctionUrlAuthType": "AWS_IAM"
        }
      }
    }
  ]
}
```

The `Condition` element has importance. It says that the policy grants the caller permission to invoke the function behind the URL only if we set `AuthType: AWS_IAM`.

Another important point is that the request **must be signed** using <a href="https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html" class="link" target="_blank" rel="noopener noreferrer">Signature Version 4</a> signing process. With the `AWS_IAM` authorization type Lambda will check for AWS Sigv4 signatures before invoking the function. If we don't sign the request with the correct credentials, Lambda will deny the request.

### 2.2. A solution

Alice has created the Function URL, so let's now see how she can invoke the function.

One option is to use <a href="https://www.postman.com/" class="link" target="_blank" rel="noopener noreferrer">Postman</a>, which handles Sigv4 signatures.

![AWS Signature in Postman](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jvjv1ppv59aplwb7i7m9.png)

She enters her `ACCESS_KEY_ID` and `SECRET_ACCESS_KEY`, specifies the region and the service (which should be `lambda`), and she will be able to invoke the URL! Alice doesn't have to worry about the signature because Postman will automatically sign the request based on her credentials and service data.

Alice, of course, must have the policy above attached to her identity for the request to be successful.

## 3. Scenario No. 2. - Creating a webhook

On the other hand, Bob is busy creating a webhook for receiving notifications. He doesn't want to use <a href="https://aws.amazon.com/api-gateway/" class="link" target="_blank" rel="noopener noreferrer">API Gateway</a> because he thinks it might be an overkill for a small project like this, and he wouldn't use all the great features API Gateway offers, anyway.

Bob decides to create a Function URL with `AuthType: NONE`.

### 3.1. Considerations

When we create a Function URL with `AuthType: NONE`, Lambda won't perform any authentication.

The function behind the URL must have a resource-based policy that allows **public** access, or else it won't be able to receive requests.

The following snippet is an example of a resource-based policy that allows unauthenticated, public, non-AWS users to invoke the function:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "FunctionURLAllowPublicAccess",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "lambda:InvokeFunctionUrl",
      "Resource": "arn:aws:lambda:us-east-1:123456789012:function:AuthNoneFunctionUrl",
      "Condition": {
        "StringEquals": {
          "lambda:FunctionUrlAuthType": "NONE"
        }
      }
    }
  ]
}
```

Two critical aspects of this policy are the `Principal: '*'`, which provides the public access, and the `Condition` block, which allows access to the URL if the value of `AuthType` is `NONE`.

Because the URL can be invoked by literally anyone now, we might want to implement some access control logic (JWT, Basic authentication, etc.) in the **body** of the Lambda function.

The Function URL <a href="https://aws.amazon.com/blogs/aws/announcing-aws-lambda-function-urls-built-in-https-endpoints-for-single-function-microservices/" class="link" target="_blank" rel="noopener noreferrer">announcement article</a> has an example for implementing access control in the body of the function.

### 3.2. A solution

Bob can now create a simple Lambda function with a public URL, which extracts the request headers if it has the correct `Authorization` header. If the properties in the decoded token are correct, the function will run and return a success response like this:

```ts
return {
  statusCode: 200,
  body: 'Success!'
}
```

In case of a token mismatch, `statusCode` can be `401`, and `body` can contain some friendly error message.

## 4. Conclusion

Now that Alice and Bob are happy, we can do a quick recap.

Lambda Function URLs come with two auth models, `AWS_IAM` and `NONE`. AWS recommends that developers use `AWS_IAM` for authenticated users and `NONE` for the public, unauthenticated (non-AWS user) access.

For `AWS_IAM` the identity must sign the request with their credentials and have valid permissions in the form of a policy to invoke the endpoint.

For `NONE`, the function behind the Function URL must have a resource-based policy allowing public access, and it should perform the authorization process.

## 5. References and further reading

<a href="https://docs.aws.amazon.com/lambda/latest/dg/urls-auth.html" class="link" target="_blank" rel="noopener noreferrer">Security and auth model for Lambda function URLs</a> - description of the two methods

<a href="https://docs.aws.amazon.com/lambda/latest/dg/urls-auth.html" class="link" target="_blank" rel="noopener noreferrer">Tutorial: Creating a Lambda function with a function URL</a> - the title of the article says it all