In this article, I will provide step-by-step instructions on how to set up a secure webhook that can receive incoming requests and save request payload as an S3 File. By using Lambda and Terraform, you can easily create and manage your webhook, ensuring that it is always available and ready to handle incoming requests. Whether you are building a new application or looking to improve the performance of an existing one, this article will provide valuable insights on how to create and manage a secure webhook on AWS.
The idea was to expose an HTTPS endpoint for a Python 3.9 Lambda Function by writing infrastructure as code with Terraform. I will show you in this article how I did it !
Create a Terraform module
Let’s initiate our terraform project by creating following structure :
Create a main.tf file that contains the main set of configuration for our module :
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "4.40.0"
}
}
}
provider "aws" {
region = "eu-west-3"
}
Create a lambda.tf file that contains Lamda’s stuff :
# Our Lambda function
resource "aws_lambda_function" "lambda-webhook" {
filename = "${path.module}/lambda/webhook/webhook.zip"
function_name = "webhook"
role = aws_iam_role.iam_for_lambda.arn
handler = "webhook.lambda_handler"
runtime = "python3.9"
timeout = 120
kms_key_arn = "${aws_kms_key.key.arn}"
environment {
variables = {
BUCKET_NAME = "${aws_s3_bucket.bucket.id}"
}
}
}
# A ZIP archive containing python code
data "archive_file" "lambda-webhook" {
type = "zip"
source_dir = "${path.module}/lambda/webhook/"
output_path = "${path.module}/lambda/webhook/webhook.zip"
}
# Our public HTTPS endpoint
resource "aws_lambda_function_url" "lambda_function_url" {
function_name = aws_lambda_function.lambda-webhook.arn
authorization_type = "NONE"
}
output "function_url" {
description = "Function URL."
value = aws_lambda_function_url.lambda_function_url.function_url
}
# A Cloudwatch Log Group to be able to see Lambda's logs
resource "aws_cloudwatch_log_group" "lambda-webhook" {
name = "/aws/lambda/${aws_lambda_function.lambda-webhook.function_name}"
retention_in_days = 3
}
# A KMS Key to encrypt / decryt environment variables
resource "aws_kms_key" "key" {
description = "KMS key for Lambda Webhook"
deletion_window_in_days = 7
}
# IAM Role for Lambda
resource "aws_iam_role" "iam_for_lambda" {
name = "LambdaWebhookRole"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
# IAM Policy for our Lambda
resource "aws_iam_policy" "iam_for_lambda_policy" {
name = "iam_for_lambda_policy"
policy = jsonencode(
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "logs:CreateLogGroup",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
"*"
]
},
{
"Effect": "Allow",
"Action": "kms:Decrypt",
"Resource": "${aws_kms_key.key.arn}"
},
{
"Action": [
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::${aws_s3_bucket.bucket.id}",
"arn:aws:s3:::${aws_s3_bucket.bucket.id}/*"
],
"Effect": "Allow",
}
]
}
)
}
resource "aws_iam_policy_attachment" "policy_attachment_lambda" {
name = "attachmentLambdaWebhoo"
roles = ["${aws_iam_role.iam_for_lambda.name}"]
policy_arn = aws_iam_policy.iam_for_lambda_policy.arn
}
Create a s3.tf file that contains the S3 Bucket :
resource "aws_s3_bucket" "bucket" {
bucket = "webhook-results"
}
resource "aws_s3_bucket_acl" "private_bucket" {
bucket = aws_s3_bucket.bucket.id
acl = "private"
}
resource "aws_s3_bucket_public_access_block" "block_private_bucket" {
bucket = aws_s3_bucket.bucket.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
Add the python code
Create a lambda/webhook/webhook.py file that contains the Python 3.9 code :
from __future__ import print_function
from datetime import datetime
import boto3, os, json
def lambda_handler(event, context):
print("Received event: " + json.dumps(event, indent=2))
now = datetime.now()
s3 = boto3.resource('s3')
object = s3.Object(os.environ['BUCKET_NAME'], now.strftime("%d%m%Y_%H%M%S")+'.json')
object.put(Body=event["body"])
return {
"statusCode": 200,
"headers": {
"Content-Type": "text/plain"
},
"body": '[accepted]'
}
Create the Lambda function and the S3 Bucket
Now that our infrastructure code is ready, we can create the infrastructure accordingly by running terraform apply
:
Our 10 ressources have been created and we can see our function url as output !
Test the webhook
Let’s test our webhook :
curl -X POST https://ncarcz2c7iagbbzzreapdvwcj40geaij.lambda-url.eu-west-3.on.aws/ \
-H "Content-Type: application/json" \
-d "{\"firstname\":\"John\",\"lastname\":\"Doe\"}"
β We can see that a JSON file has been created in our Bucket :
Conclusion
In conclusion, creating an HTTP Endpoint with Lambda and Terraform is a simple and effective way to create a serverless web service. By using Lambda, you can easily create a serverless function that can be triggered by an HTTPS request, and by using Terraform, you can easily manage and deploy your infrastructure in a reproducible and version-controlled way. This approach can be a great way to create scalable and cost-effective backend services for your application. Overall, creating an HTTP Endpoint with Lambda is a powerful tool for building web services that are both flexible and reliable.