〈 A Stateful Lambda CDK

Feb 13, 2025 • edited: Mar 17, 2025 • ✎ edit

I set out to create a free tier, no privileged service to allow users to comment on my blog.

Backend

This setup builds a stateful backend using Lambda Function URLs with CORS enabled operating on a single DynamoDB table. I created the backend loosely based on the AWS provided CDK Lamda Example.

Build

Go to the infrastructure directory: state-cdk

git clone git@github.com:StephenSmithwick/playground
cd state-cdk

To build the app:

Deploy

Login to aws (preferred approach AWS SSO) Run cdk deploy --all --outputs-file comments.out.json --profile deployer to deploy / redeploy the Stack to your AWS Account. Note you can also deploy individual parts of the stack (from cdk ls):

After the deployment you will see the Functions URL saved in comments.out.json

CDK

Other useful CDK commands:

    $ cdk ls
    <list all stacks in this program>

    $ cdk synth
    <generates and outputs cloudformation template>

    $ cdk deploy
    <deploys stack to your account>

    $ cdk diff
    <shows diff against deployed stack>

Interact via curl

Define my function names based on the CDK output for use while testing:

eval $(jq -r '.CommentsInterface
    | to_entries
    | map("export \(.key)=\(.value)")
    | .[]' \
  comments.out.json
)

Create message

curl -v $createMessageFunctionurl -H 'content-type: application/json' \
-d '{ "user": "ssmithwick", "message": "test", "page": "/dev-desktop/User-Scripts" }'

List messages

curl $listMessagesFunctionurl -H 'content-type: application/json' \
-d '{ "page": "/dev-desktop/User-Scripts" }'
curl -v --request OPTIONS $list -H 'Origin: https://stephensmithwick.github.io' -H 'Access-Control-Request-Method: GET'

Update message

curl $updateMessageFunctionurl -H 'content-type: application/json' \
-d '{ "message": "edited message", "page": "/dev-desktop/User-Scripts", "eventTime":"2025-03-13T23:32:11.074Z-k1rgS" }'

Delete message

curl $deleteMessageFunctionurl -H 'content-type: application/json' \
-d '{ "page": "/dev-desktop/User-Scripts", "eventTime":"2025-03-14T14:24:45.843Z-G6NCo" }'

Front End

For the following code snippets I’ve set the following constants in javascript:

jq -r '.CommentsInterface
    | to_entries
    | map("const \(.key)=\"\(.value)\";")
    | .[]' \
  comments.out.json

Interact via javascript using console

Debugging CORS

My initial CORS configuration was not sufficient and this is how I debugged the issue:

  const lambdaUrl = lambdaFunction.addFunctionUrl({
    authType: lambda.FunctionUrlAuthType.NONE,
    cors: {
      allowedOrigins: ["https://stephensmithwick.github.io"],
    },
  });

When I checked the initial CORS configuration for my service endpoints it looked reasonable:

curl -v --request OPTIONS $list \
-H 'Origin: https://stephensmithwick.github.io' \
-H 'Access-Control-Request-Method: POST'
/* SNIP */
> OPTIONS / HTTP/1.1
> Host: 2b4x37j357c53sshz7jnsi5zty0ybojc.lambda-url.us-west-2.on.aws
> User-Agent: curl/8.7.1
> Accept: */*
> Origin: https://stephensmithwick.github.io
> Access-Control-Request-Method: POSTcurl
> Origin: https://stephensmithwick.github.io
> Access-Control-Request-Method: POST
>
/* SNIP */
< HTTP/1.1 200 OK
< Date: Fri, 14 Mar 2025 17:12:32 GMT
< Content-Type: application/json
< Content-Length: 0
< Connection: keep-alive
< x-amzn-RequestId: 788dbaff-878f-43ef-bcf7-1d68291ce99e
< Access-Control-Allow-Origin: https://stephensmithwick.github.io
< Vary: Origin
< Access-Control-Allow-Methods: *

However, when I made a fetch request from the console from stephensmithwick.github.io, I encountered the following errors:

fetch(list, {
  method: 'POST',
  headers: [['content-type', 'application/json']],
  body: JSON.stringify({ "page": "/dev-desktop/User-Scripts" })
}).then((response) => console.log(response))

I encountered the following errors:

The error message clearly indicates that the content-type header is not allowed so I updated the header:

--- Function Url Definition
+++ Function Url Definition
@@ -1,6 +1,7 @@
   const lambdaUrl = lambdaFunction.addFunctionUrl({
     authType: lambda.FunctionUrlAuthType.NONE,
     cors: {
       allowedOrigins: [https://stephensmithwick.github.io],
+      allowedHeaders: [content-type],
     },
   });

This change will give change the CORS response from the endpoints:

--- CORS response
+++ CORS response
@@ -1,9 +1,10 @@
 HTTP/1.1 200 OK
-Date: Fri, 14 Mar 2025 17:12:32 GMT
+Date: Fri, 14 Mar 2025 17:34:03 GMT
 Content-Type: application/json
 Content-Length: 0
 Connection: keep-alive
-x-amzn-RequestId: 788dbaff-878f-43ef-bcf7-1d68291ce99e
+x-amzn-RequestId: 719e4aa7-3741-41f6-b884-014562ae4717
 Access-Control-Allow-Origin: https://stephensmithwick.github.io
+Access-Control-Allow-Headers: content-type
 Vary: Origin
 Access-Control-Allow-Methods: *

By explicitly allowing the ‘content-type’ header in the CORS configuration, the issue was resolved, and the request will succeeded from the site.

Work to Do

Security Considerations

Lambda Function URL Security

DynamoDB Security

Monitoring and Auditing

By implementing these security measures, you can enhance the protection of your serverless comment system against potential threats and ensure compliance with best practices.

Leave a Comment!