This project sets up a hackathon signup website using AWS services. Users can enter their email addresses, which are then processed by an AWS Lambda function, that sits behind an API Gateway, and stored in an secure S3 bucket. The frontend is hosted on an public S3 as a static website. Site is live at: https://chattanooga-hackathon-2024.devopschad.com/
HACK-CHATT/
├── backend/
│ ├── lambda.py
│ ├── lambda_function.zip # This will be created by zipping lambda.py
├── frontend/
│ ├── index.html
│ └── assets/
│ └── (any additional front-end assets like CSS, JS, images)
├── terraform/
│ ├── main.tf
│ ├── providers.tf
│ ├── variables.tf
├── .gitignore
└── README.md
This Lambda function handles the email submissions and stores them in an S3 bucket.
import json
import boto3
import os
s3 = boto3.client('s3')
bucket_name = os.environ['EMAIL_STORAGE_BUCKET']
def lambda_handler(event, context):
body = json.loads(event['body'])
email = body.get('email')
if email:
try:
# Read existing emails from S3
existing_emails = []
try:
response = s3.get_object(Bucket=bucket_name, Key='emails.json')
existing_emails = json.loads(response['Body'].read().decode('utf-8'))
except s3.exceptions.NoSuchKey:
# If the file doesn't exist, initialize with an empty list
existing_emails = []
# Add new email to the list
existing_emails.append(email)
# Write updated list back to S3
s3.put_object(Bucket=bucket_name, Key='emails.json', Body=json.dumps(existing_emails))
return {
'statusCode': 200,
'body': json.dumps({'message': 'Signed up successfully!'})
}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps({'message': 'Error signing up.'})
}
return {
'statusCode': 400,
'body': json.dumps({'message': 'Invalid request.'})
}
This HTML file contains the form for users to enter their email addresses.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Hackathon Signup</title>
</head>
<body>
<h1>Sign Up for the Hackathon</h1>
<form id="emailForm">
<label for="email">Email:</label>
<input type="email" id="email" name="email" required />
<button type="submit">Sign Up</button>
</form>
<script>
document
.getElementById("emailForm")
.addEventListener("submit", async function (event) {
event.preventDefault();
const email = document.getElementById("email").value;
try {
const response = await fetch(
"YOUR_API_GATEWAY_ENDPOINT/subscribe",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email }),
}
);
if (response.ok) {
alert("Signed up successfully!");
window.location.href = "success.html";
} else {
alert("Failed to sign up.");
}
} catch (error) {
console.error("Error:", error);
alert("An error occurred.");
}
});
</script>
</body>
</html>
variable "region" {
default = "us-east-1"
}
variable "lambda_function_name" {
default = "HackathonEmailSignup"
}
variable "website_bucket_name" {
default = "hackathon-website-bucket"
}
variable "email_storage_bucket_name" {
default = "hackathon-email-storage"
}
variable "lambda_s3_key" {
default = "lambda_function.zip"
}
provider "aws" {
region = var.region
}
resource "aws_s3_bucket" "website_bucket" {
bucket = var.website_bucket_name
website {
index_document = "index.html"
}
}
resource "aws_s3_bucket_website_configuration" "website_config" {
bucket = aws_s3_bucket.website_bucket.id
index_document {
suffix = "index.html"
}
}
resource "aws_s3_bucket_ownership_controls" "website_bucket_ownership" {
bucket = aws_s3_bucket.website_bucket.id
rule {
object_ownership = "BucketOwnerEnforced"
}
}
resource "aws_s3_bucket_public_access_block" "website_bucket_public_access" {
bucket = aws_s3_bucket.website_bucket.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket" "email_storage_bucket" {
bucket = var.email_storage_bucket_name
}
resource "aws_s3_bucket_ownership_controls" "email_storage_bucket_ownership" {
bucket = aws_s3_bucket.email_storage_bucket.id
rule {
object_ownership = "BucketOwnerEnforced"
}
}
resource "aws_s3_bucket_public_access_block" "email_storage_bucket_public_access" {
bucket = aws_s3_bucket.email_storage_bucket.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_iam_role" "lambda_role" {
name = "lambda_execution_role"
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Action = "sts:AssumeRole",
Effect = "Allow",
Principal = {
Service = "lambda.amazonaws.com"
},
},
],
})
}
resource "aws_iam_policy" "lambda_policy" {
name = "lambda_s3_policy"
description = "Policy for Lambda to access S3"
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Action = [
"s3:PutObject",
"s3:GetObject"
],
Effect = "Allow",
Resource = "arn:aws:s3:::${var.email_storage_bucket_name}/*",
},
],
})
}
resource "aws_iam_role_policy_attachment" "lambda_policy_attachment" {
role = aws_iam_role.lambda_role.name
policy_arn = aws_iam_policy.lambda_policy.arn
}
resource "aws_lambda_function" "email_signup" {
function_name = var.lambda_function_name
role = aws_iam_role.lambda_role.arn
handler = "lambda_function.lambda_handler"
runtime = "python3.8"
filename = "../backend/lambda_function.zip"
source_code_hash = filebase64sha256("../backend/lambda_function.zip")
environment {
variables = {
EMAIL_STORAGE_BUCKET = var.email_storage_bucket_name
}
}
}
resource "aws_api_gateway_rest_api" "email_signup_api" {
name = "EmailSignupAPI"
}
resource "aws_api_gateway_resource" "signup_resource" {
rest_api_id = aws_api_gateway_rest_api.email_signup_api.id
parent_id = aws_api_gateway_rest_api.email_signup_api.root_resource_id
path_part = "subscribe"
}
resource "aws_api_gateway_method" "post_method" {
rest_api_id = aws_api_gateway_rest_api.email_signup_api.id
resource_id = aws_api_gateway_resource.signup_resource.id
http_method = "POST"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "lambda_integration" {
rest_api_id = aws_api_gateway_rest_api.email_signup_api.id
resource_id = aws_api_gateway_resource.signup_resource.id
http_method = aws_api_gateway_method.post_method.http_method
type = "AWS_PROXY"
integration_http_method = "POST"
uri = aws_lambda_function.email_signup.invoke_arn
}
resource "aws_lambda_permission" "api_gateway" {
statement_id = "AllowAPIGatewayInvoke"
action =
"lambda:InvokeFunction"
function_name = aws_lambda_function.email_signup.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.email_signup_api.execution_arn}/*/*"
}
resource "aws_api_gateway_deployment" "api_deployment" {
depends_on = [
aws_api_gateway_integration.lambda_integration,
]
rest_api_id = aws_api_gateway_rest_api.email_signup_api.id
stage_name = "prod"
}
-
Prepare the Lambda Function Deployment Package:
-
Navigate to your
backend
directory and create a zip file of your Lambda function:cd backend zip lambda_function.zip lambda.py
-
-
Initialize and Apply Terraform Configuration:
-
Navigate to the
terraform
directory:cd terraform terraform init terraform apply
-
-
Upload
index.html
to S3 THIS STEP SHOULD BE NEEDED ANYMORE BUT IF THE WEBSITE BUCKET IS MISSING FILES FOLLOW THESE STEPS:-
Navigate to your
frontend
directory and uploadindex.html
to your website bucket:aws s3 cp index.html s3://hackathon-website-bucket/
-
Repeat for
styles.css
,success.html
, andprivacy-policy.html
(this needs to be automated but for now, do it manually). -
For the
assets
folder, create anassets
folder in the bucket and then upload the assets to theassets
folder like this:aws s3 cp assets s3://hackathon-website-bucket/assets --recursive
-
-
Set Bucket Policies:
-
IF YOU WANT STRICTER CONTROL Use the AWS CLI to set the bucket policy for the email storage bucket HOWEVER do this after you have the base website working:
aws s3api put-bucket-policy --bucket hackathon-email-storage --policy '{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" }, "Action": "s3:PutObject", "Resource": "arn:aws:s3:::hackathon-email-storage/*" }, { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::your-account-id:role/organizers-role" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::hackathon-email-storage/*" } ] }'
-
THIS POLICY IS NEEDED OR THE SITE WILL NOT WORK -> Use the AWS CLI (or set manually in the aws s3 console under premissions -> bucket policy) to set the bucket policy for the website bucket:
```sh aws s3api put-bucket-policy --bucket hackathon-website-bucket --policy '{ "Version": "2012-10-17", "Statement": [ { "Sid": "PublicReadGetObject", "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::hackathon-website-bucket/*" } ] }' ```
-
-
Update Frontend with API Gateway Endpoint:
-
Retrieve your API Gateway endpoint using the AWS CLI:
aws apigateway get-rest-apis
-
Replace
YOUR_API_GATEWAY_ENDPOINT
inindex.html
with your actual API Gateway endpoint URL and then upload to the s3 with the new updated index.html using the aws cli. First cd into the frontend directory and then run the following command:
aws s3 cp index.html s3://hackathon-website-bucket/
-
-
Testing the Lambda Function:
-
Your site should be hosting live now at the endpoint given by the website s3 bucket. Navigate to the hackathon-website-bucket then click on properties and then click on the http Bucket website endpoint. IF you want to test using the aws lambda console follow the following steps:
-
In the AWS Lambda console, create a test event with the following JSON payload:
{ "body": "{\"email\":\"test@example.com\"}" }
-
Execute the test and verify that the email is stored in the S3 bucket.
-
Following these steps will deploy your hackathon signup website using AWS services. Users can sign up with their email addresses, which will be processed by a Lambda function and stored securely in an S3 bucket. The website is hosted on S3 for a simple, scalable, and cost-effective solution. 2 KEY POINTS after the Terrafrom Apply you do still need to follow the steps above to change the index.html file to point to the correct API Gateway endpoint; (the terraform can be adjusted later to do this for you) as well as setting the bucket policy for the website-bucket only, the email bucket doesnt need a policy unless you want to restrict access to it.