Message Board for my Blog
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/StephenSmithwick.github.io.git
cd state-cdk
To build the app:
- Install cdk library:
npm install -g aws-cdk
- Install dependencies:
npm install
- Update dependencies:
npx npm-check-updates -u
(follow withnpm install
) - Build functions and CFN Template:
npm run build
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
):
- CommentsStore
- CommentsInterface
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:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://2b4x37j357c53sshz7jnsi5zty0ybojc.lambda-url.us-west-2.on.aws/. (Reason: header ‘content-type’ is not allowed according to header ‘Access-Control-Allow-Headers’ from CORS preflight response).
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://2b4x37j357c53sshz7jnsi5zty0ybojc.lambda-url.us-west-2.on.aws/. (Reason: CORS request did not succeed). Status code: (null).
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
- Configure AWS SSO account to interact with service:setup AWS SSO
- Setup hosted backend in AWS using CDK: state-cdk
- Setup CORS
- Integrate with website
- Creation of Unit Tests for lambda functions
- Support for local integration testing (See LocalStack)
- Harden the service
Security Considerations
Lambda Function URL Security
- I am using authType: NONE, which makes the API completely open as intended but some precations such as rate limiting implemented through.
- Cross-Origin Resource Sharing (CORS): CORS does not provide any real security against malicious clients ane merely limits well behaving clients to access the service from this blog.
DynamoDB Security
- Access to all of the data is open to anyone who can use the API.
Monitoring and Auditing
- We log to CloudWatch on any modification to the Data in DynamoDB and any errors encountered which provides some visibility of the system over time.
By implementing these security measures, you can enhance the protection of your serverless comment system against potential threats and ensure compliance with best practices.