From 64975835ad1452e89c9fb42f3cf09dd6a612a597 Mon Sep 17 00:00:00 2001 From: Howard Katz Date: Mon, 30 Mar 2026 19:51:54 -0400 Subject: [PATCH 1/3] Fixes for AWS * added AWS profile and region for login session * corrected misnamed "ssm:GetParameters" to "ssm:GetParameter" * added S3 access to images bucket * corrected lamda_ssm.json Resources * increased timeout to 30 seconds for resize lambda --- deployment/terraform/main.tf | 57 ++++++++++++++++--- deployment/terraform/policies/lambda_ssm.json | 4 +- .../resize_lambda_s3_buckets.json.tpl | 2 + .../policies/resize_lambda_sns.json.tpl | 5 -- 4 files changed, 54 insertions(+), 14 deletions(-) diff --git a/deployment/terraform/main.tf b/deployment/terraform/main.tf index c647b4b..fdabc1d 100644 --- a/deployment/terraform/main.tf +++ b/deployment/terraform/main.tf @@ -6,16 +6,37 @@ locals { images_bucket = "localstack-thumbnails-app-images" image_resized_bucket = "localstack-thumbnails-app-resized" website_bucket = "localstack-website" - failure_notifications_email = "my-email@example.com" + + failure_notifications_email = "failure-notifications@example.com" +} + +# AWS profile +provider "aws" { + region = "us-east-1" + profile = "myuser-sso-admin" } # S3 resource "aws_s3_bucket" "images_bucket" { bucket = local.images_bucket + force_destroy = true } resource "aws_s3_bucket" "image_resized_bucket" { bucket = local.image_resized_bucket + force_destroy = true +} + +resource "aws_s3_bucket_cors_configuration" "images_bucket_cors" { + bucket = aws_s3_bucket.images_bucket.id + + cors_rule { + allowed_headers = ["*"] + allowed_methods = ["POST", "PUT"] + allowed_origins = ["https://${aws_cloudfront_distribution.cdn.domain_name}"] + expose_headers = ["ETag"] + max_age_seconds = 3000 + } } # SSM @@ -82,6 +103,14 @@ resource "aws_lambda_function" "presign_lambda" { resource "aws_lambda_function_url" "presign_lambda_function" { function_name = aws_lambda_function.presign_lambda.function_name authorization_type = "NONE" + + cors { + allow_credentials = false + allow_origins = ["https://${aws_cloudfront_distribution.cdn.domain_name}"] + allow_methods = ["GET"] + allow_headers = ["content-type"] + max_age = 86400 + } } # List images lambda @@ -126,6 +155,14 @@ resource "aws_lambda_function" "list_lambda" { resource "aws_lambda_function_url" "list_lambda_function" { function_name = aws_lambda_function.list_lambda.function_name authorization_type = "NONE" + + cors { + allow_credentials = false + allow_origins = ["https://${aws_cloudfront_distribution.cdn.domain_name}"] + allow_methods = ["GET"] + allow_headers = ["content-type"] + max_age = 86400 + } } # Resize lambda @@ -138,6 +175,7 @@ resource "aws_iam_role" "resize_lambda_role" { resource "aws_iam_policy" "resize_lambda_s3_buckets" { name = "ResizeLambdaS3Buckets" policy = templatefile("policies/resize_lambda_s3_buckets.json.tpl", { + images_bucket = aws_s3_bucket.images_bucket.bucket, images_resized_bucket = aws_s3_bucket.image_resized_bucket.bucket }) } @@ -150,8 +188,7 @@ resource "aws_iam_role_policy_attachment" "resize_lambda_s3_buckets" { resource "aws_iam_policy" "resize_lambda_sns" { name = "ResizeLambdaSNS" policy = templatefile("policies/resize_lambda_sns.json.tpl", { - failure_notifications_topic_arn = aws_sns_topic.failure_notifications.arn, - resize_lambda_arn = aws_lambda_function.resize_lambda.arn + failure_notifications_topic_arn = aws_sns_topic.failure_notifications.arn }) } @@ -170,6 +207,7 @@ resource "aws_lambda_function" "resize_lambda" { filename = "${local.root_dir}/lambdas/resize/lambda.zip" handler = "handler.handler" runtime = "python3.11" + timeout = 30 role = aws_iam_role.resize_lambda_role.arn source_code_hash = filebase64sha256("${local.root_dir}/lambdas/resize/lambda.zip") @@ -230,7 +268,6 @@ resource "aws_s3_object" "website_file_index" { source = "${local.root_dir}/website/index.html" etag = filemd5("${local.root_dir}/website/index.html") content_type = "text/html" - acl = "public-read" } resource "aws_s3_object" "website_file_js" { @@ -239,7 +276,6 @@ resource "aws_s3_object" "website_file_js" { source = "${local.root_dir}/website/app.js" etag = filemd5("${local.root_dir}/website/app.js") content_type = "application/javascript" - acl = "public-read" } resource "aws_s3_object" "website_file_icon" { @@ -248,7 +284,6 @@ resource "aws_s3_object" "website_file_icon" { source = "${local.root_dir}/website/favicon.ico" etag = filemd5("${local.root_dir}/website/favicon.ico") content_type = "image/x-icon" - acl = "public-read" } resource "aws_cloudfront_origin_access_identity" "cdn_identity" { @@ -284,13 +319,20 @@ resource "aws_cloudfront_distribution" "cdn" { default_cache_behavior { target_origin_id = aws_s3_bucket.website_bucket.bucket - allowed_methods = ["GET", "HEAD"] + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] cached_methods = ["GET", "HEAD"] viewer_protocol_policy = "redirect-to-https" min_ttl = 0 default_ttl = 86400 max_ttl = 31536000 + + forwarded_values { + query_string = false + cookies { + forward = "none" + } + } } restrictions { @@ -309,6 +351,7 @@ resource "aws_s3_bucket_public_access_block" "website_block_public_access" { restrict_public_buckets = true } + # Outputs output "presign_lambda_function_url" { diff --git a/deployment/terraform/policies/lambda_ssm.json b/deployment/terraform/policies/lambda_ssm.json index d4c8ee0..433480b 100644 --- a/deployment/terraform/policies/lambda_ssm.json +++ b/deployment/terraform/policies/lambda_ssm.json @@ -4,10 +4,10 @@ { "Effect": "Allow", "Action": [ - "ssm:GetParameters" + "ssm:GetParameter" ], "Resource": [ - "arn:aws:ssm:::parameter/localstack-thumbnail-app/*" + "arn:aws:ssm:*:*:parameter/localstack-thumbnail-app/*" ] } ] diff --git a/deployment/terraform/policies/resize_lambda_s3_buckets.json.tpl b/deployment/terraform/policies/resize_lambda_s3_buckets.json.tpl index c21fcfe..1bfc2e7 100644 --- a/deployment/terraform/policies/resize_lambda_s3_buckets.json.tpl +++ b/deployment/terraform/policies/resize_lambda_s3_buckets.json.tpl @@ -9,6 +9,8 @@ "s3:PutObject" ], "Resource": [ + "arn:aws:s3:::${images_bucket}", + "arn:aws:s3:::${images_bucket}/*", "arn:aws:s3:::${images_resized_bucket}", "arn:aws:s3:::${images_resized_bucket}/*" ] diff --git a/deployment/terraform/policies/resize_lambda_sns.json.tpl b/deployment/terraform/policies/resize_lambda_sns.json.tpl index 34a7540..27caaa5 100644 --- a/deployment/terraform/policies/resize_lambda_sns.json.tpl +++ b/deployment/terraform/policies/resize_lambda_sns.json.tpl @@ -7,11 +7,6 @@ ], "Effect": "Allow", "Resource": "${failure_notifications_topic_arn}" - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": "${resize_lambda_arn}" } ] } \ No newline at end of file From 738cac7b8c47e813156492c2d6cc4384ed28f592 Mon Sep 17 00:00:00 2001 From: Howard Katz Date: Mon, 30 Mar 2026 19:52:45 -0400 Subject: [PATCH 2/3] Added AWS CloudWatch log groups --- deployment/terraform/main.tf | 52 ++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/deployment/terraform/main.tf b/deployment/terraform/main.tf index fdabc1d..98a1b58 100644 --- a/deployment/terraform/main.tf +++ b/deployment/terraform/main.tf @@ -351,6 +351,58 @@ resource "aws_s3_bucket_public_access_block" "website_block_public_access" { restrict_public_buckets = true } +# CloudWatch Log Groups + +resource "aws_cloudwatch_log_group" "presign_lambda_logs" { + name = "/aws/lambda/presign" + retention_in_days = 14 +} + +resource "aws_cloudwatch_log_group" "list_lambda_logs" { + name = "/aws/lambda/list" + retention_in_days = 14 +} + +resource "aws_cloudwatch_log_group" "resize_lambda_logs" { + name = "/aws/lambda/resize" + retention_in_days = 14 +} + +resource "aws_iam_policy" "lambda_cloudwatch_logs" { + name = "LambdasCloudWatchLogs" + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ] + Resource = [ + "${aws_cloudwatch_log_group.presign_lambda_logs.arn}:*", + "${aws_cloudwatch_log_group.list_lambda_logs.arn}:*", + "${aws_cloudwatch_log_group.resize_lambda_logs.arn}:*" + ] + } + ] + }) +} + +resource "aws_iam_role_policy_attachment" "presign_lambda_cloudwatch" { + role = aws_iam_role.presign_lambda_role.name + policy_arn = aws_iam_policy.lambda_cloudwatch_logs.arn +} + +resource "aws_iam_role_policy_attachment" "list_lambda_cloudwatch" { + role = aws_iam_role.list_lambda_role.name + policy_arn = aws_iam_policy.lambda_cloudwatch_logs.arn +} + +resource "aws_iam_role_policy_attachment" "resize_lambda_cloudwatch" { + role = aws_iam_role.resize_lambda_role.name + policy_arn = aws_iam_policy.lambda_cloudwatch_logs.arn +} # Outputs From e77a6ff763bf42883d608cb2ee13283d2fc9c123 Mon Sep 17 00:00:00 2001 From: Howard Katz Date: Wed, 1 Apr 2026 12:25:38 -0400 Subject: [PATCH 3/3] AWS profile comment out of main.ts, use environmental vars instead --- deployment/terraform/main.tf | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/deployment/terraform/main.tf b/deployment/terraform/main.tf index 98a1b58..955b241 100644 --- a/deployment/terraform/main.tf +++ b/deployment/terraform/main.tf @@ -10,11 +10,16 @@ locals { failure_notifications_email = "failure-notifications@example.com" } -# AWS profile -provider "aws" { - region = "us-east-1" - profile = "myuser-sso-admin" -} +# AWS profile should not be set here since +# it needs to be blank for .github/workflows/integration_tests.yml +# to work with the default LocalStack profile. Use environment variables instead. +# +# bash example: AWS_PROFILE=my-profile AWS_REGION=us-east-1 terraform apply +# +#provider "aws" { +# region = "us-east-1" +# profile = "myuser-sso-admin" +#} # S3 resource "aws_s3_bucket" "images_bucket" {