diff --git a/.circleci/config.yml b/.circleci/config.yml index 7c0c79e..f96189a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -26,10 +26,16 @@ jobs: command: pip3 install -r requirements-dev.txt --upgrade - localstack/wait + - run: + name: Build lambdas + command: + bin/build_lambdas.sh + - run: name: Deploy infrastructure command: - bin/deploy.sh + deployment/awslocal/deploy.sh + - run: name: Export state command: localstack state export ls-state.zip diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..8c63a9f --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +LOCALSTACK_AUTH_TOKEN=YOUR_TOKEN diff --git a/.github/workflows/cloudpod_release.yml b/.github/workflows/cloudpod_release.yml index c40f941..e695759 100644 --- a/.github/workflows/cloudpod_release.yml +++ b/.github/workflows/cloudpod_release.yml @@ -45,9 +45,13 @@ jobs: env: LOCALSTACK_API_KEY: ${{ secrets.LOCALSTACK_API_KEY }} + - name: Build lambdas + run: | + bin/build_lambdas.sh + - name: Deploy infrastructure run: | - bin/deploy.sh + deployment/awslocal/deploy.sh - name: Run Tests env: diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 7704f8c..a3630e4 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -58,10 +58,13 @@ jobs: install-awslocal: 'true' env: LOCALSTACK_API_KEY: ${{ secrets.LOCALSTACK_API_KEY }} + - name: Build lambdas + run: | + bin/build_lambdas.sh - name: Deploy infrastructure run: | - bin/deploy.sh + deployment/awslocal/deploy.sh - name: Run Tests env: diff --git a/.github/workflows/preview_create.yml b/.github/workflows/preview_create.yml index 9166fb0..93d1e63 100644 --- a/.github/workflows/preview_create.yml +++ b/.github/workflows/preview_create.yml @@ -46,4 +46,4 @@ jobs: preview-cmd: | # Add your custom deployment commands here. # Below is an example for the Image resizer application. - bin/deploy.sh \ No newline at end of file + bin/build_lambdas.sh && deployment/awslocal/deploy.sh \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7419686..5471905 100644 --- a/.gitignore +++ b/.gitignore @@ -59,4 +59,29 @@ volume/ # lambda packages lambdas/*/package/ -lambdas/*/lambda.zip \ No newline at end of file +lambdas/*/lambda.zip +terraform/*/*.zip + +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log +crash.*.log + +*.tfvars +*.tfvars.json + +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +.terraform.tfstate.lock.info + +.terraformrc +terraform.rc \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bbd9714..72a3082 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -56,7 +56,8 @@ deploy: - .github/* - when: always script: - - ./bin/deploy.sh + - ./bin/build_lambdas.sh + - ./deployment/awslocal/deploy.sh - localstack state export ./ls-state-pod.zip test: diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..064ce5f --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +export AWS_ACCESS_KEY_ID ?= test +export AWS_SECRET_ACCESS_KEY ?= test +SHELL := /bin/bash + +include .env + +usage: ## Show this help + @grep -F -h "##" $(MAKEFILE_LIST) | grep -F -v grep -F | sed -e 's/\\$$//' -e 's/##//' + +install: ## Install dependencies + @pip install -r requirements-dev.txt + +build: ## Build lambdas in the lambdas folder + bin/build_lambdas.sh; + +awslocal-setup: ## Deploy the application locally using `awslocal`, a wrapper for the AWS CLI + $(MAKE) build + deployment/awslocal/deploy.sh + +terraform-setup: ## Deploy the application locally using `tflocal`, a wrapper for Terraform CLI + $(MAKE) build + cd deployment/terraform; \ + tflocal init; \ + echo "Deploying Terraform configuration 🚀"; \ + tflocal apply --auto-approve; \ + echo "Paste the function URLs above to the WebApp 🎉"; + +terraform-destroy: ## Destroy all resources created locally using terraform scripts + cd deployment/terraform; \ + tflocal destroy --auto-approve; + +start: ## Start the LocalStack Pro container in the detached mode + @LOCALSTACK_AUTH_TOKEN=$(LOCALSTACK_AUTH_TOKEN) localstack start -d + +stop: ## Stop the LocalStack Pro container + localstack stop + +.PHONY: usage install build awslocal-setup terraform-setup terraform-destroy start stop diff --git a/README.md b/README.md index aa18679..cf8c338 100644 --- a/README.md +++ b/README.md @@ -69,22 +69,41 @@ source .venv/bin/activate pip install -r requirements-dev.txt ``` +## Instructions + +You can set up and deploy the sample application on LocalStack by executing the commands in our Makefile. First, create a `.env` file using the provided `.env.example` file as a template, and include your LocalStack token in it. Then, run `make start` to initiate LocalStack on your machine. + +Next, execute `make install` to install needed dependencies. + +After that, launch `make terraform-setup` to provision the infrastructure on LocalStack using Terraform CLI and its scripts. Alternatively, run `make awslocal-setup` to set up the infrastructure using `awslocal`, a wrapper for the AWS CLI. + +If you prefer, you can also follow these step-by-step instructions for a manual deployment. + ### LocalStack -Start LocalStack Pro with the appropriate CORS configuration for the S3 Website: +Start LocalStack Pro with Auth Token: ```bash -LOCALSTACK_AUTH_TOKEN=... localstack start +LOCALSTACK_AUTH_TOKEN=... localstack start (-d) ``` -## Instructions +### Terraform + +To create the infrastructure using Terraform, run the following commands: + +```shell +cd deployment/terraform +tflocal init +tflocal apply --auto-approve +``` + +We are using the `tflocal` wrapper to configure the local service endpoints, and send the API requests to LocalStack, instead of AWS. -You can create the AWS infrastructure on LocalStack by running `bin/deploy.sh`. -Make sure you have Python 3.11 activated before running the script. +### AWS CLI -Here are instructions to deploy it manually step-by-step. +You can execute the following commands to set up the infrastructure using `awslocal`. All the commands are also available in the `deployment/awslocal/deploy.sh` script. -### Create the buckets +#### Create the buckets The names are completely configurable via SSM: @@ -93,14 +112,14 @@ awslocal s3 mb s3://localstack-thumbnails-app-images awslocal s3 mb s3://localstack-thumbnails-app-resized ``` -### Put the bucket names into the parameter store +#### Put the bucket names into the parameter store ```bash awslocal ssm put-parameter --name /localstack-thumbnail-app/buckets/images --type "String" --value "localstack-thumbnails-app-images" awslocal ssm put-parameter --name /localstack-thumbnail-app/buckets/resized --type "String" --value "localstack-thumbnails-app-resized" ``` -### Create the DLQ Topic for failed lambda invokes +#### Create the DLQ Topic for failed lambda invokes ```bash awslocal sns create-topic --name failed-resize-topic @@ -115,9 +134,9 @@ awslocal sns subscribe \ --notification-endpoint my-email@example.com ``` -### Create the lambdas +#### Create the lambdas -#### S3 pre-signed POST URL generator +##### S3 pre-signed POST URL generator This Lambda is responsible for generating pre-signed POST URLs to upload files to an S3 bucket. @@ -143,7 +162,7 @@ awslocal lambda create-function-url-config \ Copy the `FunctionUrl` from the response, you will need it later to make the app work. -### Image lister lambda +#### Image lister lambda The `list` Lambda is very similar: @@ -166,7 +185,7 @@ awslocal lambda create-function-url-config \ --auth-type NONE ``` -### Resizer Lambda +#### Resizer Lambda ```bash ( @@ -189,7 +208,7 @@ awslocal lambda create-function \ --environment Variables="{STAGE=local}" ``` -### Connect the S3 bucket to the resizer lambda +#### Connect the S3 bucket to the resizer lambda ```bash awslocal s3api put-bucket-notification-configuration \ @@ -197,7 +216,7 @@ awslocal s3api put-bucket-notification-configuration \ --notification-configuration "{\"LambdaFunctionConfigurations\": [{\"LambdaFunctionArn\": \"$(awslocal lambda get-function --function-name resize | jq -r .Configuration.FunctionArn)\", \"Events\": [\"s3:ObjectCreated:*\"]}]}" ``` -### Create the static s3 webapp +#### Create the static s3 webapp ```bash awslocal s3 mb s3://webapp @@ -205,7 +224,7 @@ awslocal s3 sync --delete ./website s3://webapp awslocal s3 website s3://webapp --index-document index.html ``` -### Using the application +#### Using the application Once deployed, visit http://webapp.s3-website.localhost.localstack.cloud:4566 diff --git a/bin/build_lambdas.sh b/bin/build_lambdas.sh new file mode 100755 index 0000000..5dc6c45 --- /dev/null +++ b/bin/build_lambdas.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +(cd lambdas/presign; rm -f lambda.zip; zip lambda.zip handler.py) + +(cd lambdas/list; rm -f lambda.zip; zip lambda.zip handler.py) + +os=$(uname -s) +if [ "$os" == "Darwin" ]; then + ( + cd lambdas/resize + rm -rf libs lambda.zip + docker run --platform linux/x86_64 --rm -v "$PWD":/var/task "public.ecr.aws/sam/build-python3.11" /bin/sh -c "pip3 install -r requirements.txt -t libs; exit" + + cd libs && zip -r ../lambda.zip . && cd .. + zip lambda.zip handler.py + rm -rf libs + ) +else + ( + cd lambdas/resize + rm -rf package lambda.zip + mkdir package + pip3 install -r requirements.txt --platform manylinux2014_x86_64 --only-binary=:all: -t package + zip lambda.zip handler.py + cd package + zip -r ../lambda.zip *; + ) +fi \ No newline at end of file diff --git a/buildspec.yml b/buildspec.yml index 6127a83..8b1471f 100644 --- a/buildspec.yml +++ b/buildspec.yml @@ -25,7 +25,8 @@ phases: - localstack start -d pre_build: commands: - - bin/deploy.sh + - bin/build_lambdas.sh + - deployment/awslocal/deploy.sh build: commands: - pytest tests diff --git a/bin/deploy.sh b/deployment/awslocal/deploy.sh similarity index 79% rename from bin/deploy.sh rename to deployment/awslocal/deploy.sh index 37e146b..d4eb22d 100755 --- a/bin/deploy.sh +++ b/deployment/awslocal/deploy.sh @@ -14,7 +14,6 @@ awslocal sns subscribe \ --protocol email \ --notification-endpoint my-email@example.com -(cd lambdas/presign; rm -f lambda.zip; zip lambda.zip handler.py) awslocal lambda create-function \ --function-name presign \ --runtime python3.11 \ @@ -30,7 +29,6 @@ awslocal lambda create-function-url-config \ --function-name presign \ --auth-type NONE -(cd lambdas/list; rm -f lambda.zip; zip lambda.zip handler.py) awslocal lambda create-function \ --function-name list \ --runtime python3.11 \ @@ -46,28 +44,6 @@ awslocal lambda create-function-url-config \ --function-name list \ --auth-type NONE -os=$(uname -s) -if [ "$os" == "Darwin" ]; then - ( - cd lambdas/resize - rm -rf libs lambda.zip - docker run --platform linux/x86_64 --rm -v "$PWD":/var/task "public.ecr.aws/sam/build-python3.11" /bin/sh -c "pip3 install -r requirements.txt -t libs; exit" - - cd libs && zip -r ../lambda.zip . && cd .. - zip lambda.zip handler.py - rm -rf libs - ) -else - ( - cd lambdas/resize - rm -rf package lambda.zip - mkdir package - pip3 install -r requirements.txt --platform manylinux2014_x86_64 --only-binary=:all: -t package - zip lambda.zip handler.py - cd package - zip -r ../lambda.zip *; - ) -fi awslocal lambda create-function \ --function-name resize \ diff --git a/deployment/terraform/main.tf b/deployment/terraform/main.tf new file mode 100644 index 0000000..a70c437 --- /dev/null +++ b/deployment/terraform/main.tf @@ -0,0 +1,338 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "<= 5.72.1" + } + } +} + +provider "aws" { + region = "us-east-1" + skip_requesting_account_id = true +} + +locals { + # TO-DO: The environment variable STAGE is required for Lambdas to connect to LocalStack endpoints. + # The environment variable can be removed once Lambdas are adapted to support transparent endpoint injection. + env_variables = { STAGE = "local" } + root_dir = "${path.module}/../.." + images_bucket = "localstack-thumbnails-app-images" + image_resized_bucket = "localstack-thumbnails-app-resized" + website_bucket = "localstack-website" + failure_notifications_email = "my-email@example.com" +} + +# S3 +resource "aws_s3_bucket" "images_bucket" { + bucket = local.images_bucket +} + +resource "aws_s3_bucket" "image_resized_bucket" { + bucket = local.image_resized_bucket +} + +# SSM + +resource "aws_ssm_parameter" "images_bucket_ssm" { + name = "/localstack-thumbnail-app/buckets/images" + type = "String" + value = aws_s3_bucket.images_bucket.bucket +} + +resource "aws_ssm_parameter" "images_resized_bucket_ssm" { + name = "/localstack-thumbnail-app/buckets/resized" + type = "String" + value = aws_s3_bucket.image_resized_bucket.bucket +} + +## Lambdas + +# IAM SSM Policy + +resource "aws_iam_policy" "lambdas_ssm" { + name = "LambdasAccessSsm" + policy = file("policies/lambda_ssm.json") +} + +# Presign Lambda + +resource "aws_iam_role" "presign_lambda_role" { + name = "PresignLambdaRole" + assume_role_policy = file("policies/lambda.json") +} + +resource "aws_iam_policy" "presign_lambda_s3_buckets" { + name = "PresignLambdaS3AccessPolicy" + policy = templatefile("policies/presign_lambda_s3_buckets.json.tpl", { + images_bucket = aws_s3_bucket.images_bucket.bucket + }) +} + +resource "aws_iam_role_policy_attachment" "presign_lambda_s3_buckets" { + role = aws_iam_role.presign_lambda_role.name + policy_arn = aws_iam_policy.presign_lambda_s3_buckets.arn +} + +resource "aws_iam_role_policy_attachment" "presign_lambda_ssm" { + role = aws_iam_role.presign_lambda_role.name + policy_arn = aws_iam_policy.lambdas_ssm.arn +} + +resource "aws_lambda_function" "presign_lambda" { + function_name = "presign" + filename = "${local.root_dir}/lambdas/presign/lambda.zip" + handler = "handler.handler" + runtime = "python3.11" + timeout = 10 + role = aws_iam_role.presign_lambda_role.arn + source_code_hash = filebase64sha256("${local.root_dir}/lambdas/presign/lambda.zip") + + environment { + variables = local.env_variables + } +} + +resource "aws_lambda_function_url" "presign_lambda_function" { + function_name = aws_lambda_function.presign_lambda.function_name + authorization_type = "NONE" +} + +# List images lambda + +resource "aws_iam_role" "list_lambda_role" { + name = "ListLambdaRole" + assume_role_policy = file("policies/lambda.json") +} + +resource "aws_iam_policy" "list_lambda_s3_buckets" { + name = "ListLambdaS3AccessPolicy" + policy = templatefile("policies/list_lambda_s3_buckets.json.tpl", { + images_bucket = aws_s3_bucket.images_bucket.bucket, + images_resized_bucket = aws_s3_bucket.image_resized_bucket.bucket + }) +} + +resource "aws_iam_role_policy_attachment" "list_lambda_s3_buckets" { + role = aws_iam_role.list_lambda_role.name + policy_arn = aws_iam_policy.list_lambda_s3_buckets.arn +} + +resource "aws_iam_role_policy_attachment" "list_lambda_ssm" { + role = aws_iam_role.list_lambda_role.name + policy_arn = aws_iam_policy.lambdas_ssm.arn +} + +resource "aws_lambda_function" "list_lambda" { + function_name = "list" + filename = "${local.root_dir}/lambdas/list/lambda.zip" + handler = "handler.handler" + runtime = "python3.11" + timeout = 10 + role = aws_iam_role.list_lambda_role.arn + source_code_hash = filebase64sha256("${local.root_dir}/lambdas/list/lambda.zip") + + environment { + variables = local.env_variables + } +} + +resource "aws_lambda_function_url" "list_lambda_function" { + function_name = aws_lambda_function.list_lambda.function_name + authorization_type = "NONE" +} + +# Resize lambda + +resource "aws_iam_role" "resize_lambda_role" { + name = "ResizeLambdaRole" + assume_role_policy = file("policies/lambda.json") +} + +resource "aws_iam_policy" "resize_lambda_s3_buckets" { + name = "ResizeLambdaS3Buckets" + policy = templatefile("policies/resize_lambda_s3_buckets.json.tpl", { + images_resized_bucket = aws_s3_bucket.image_resized_bucket.bucket + }) +} + +resource "aws_iam_role_policy_attachment" "resize_lambda_s3_buckets" { + role = aws_iam_role.resize_lambda_role.name + policy_arn = aws_iam_policy.resize_lambda_s3_buckets.arn +} + +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 + }) +} + +resource "aws_iam_role_policy_attachment" "resize_lambda_sns" { + role = aws_iam_role.resize_lambda_role.name + policy_arn = aws_iam_policy.resize_lambda_sns.arn +} + +resource "aws_iam_role_policy_attachment" "resize_lambda_ssm" { + role = aws_iam_role.resize_lambda_role.name + policy_arn = aws_iam_policy.lambdas_ssm.arn +} + +resource "aws_lambda_function" "resize_lambda" { + function_name = "resize" + filename = "${local.root_dir}/lambdas/resize/lambda.zip" + handler = "handler.handler" + runtime = "python3.11" + role = aws_iam_role.resize_lambda_role.arn + source_code_hash = filebase64sha256("${local.root_dir}/lambdas/resize/lambda.zip") + + environment { + variables = local.env_variables + } + + dead_letter_config { + target_arn = aws_sns_topic.failure_notifications.arn + } +} + +# SNS Topic for failure notifications +resource "aws_sns_topic" "failure_notifications" { + name = "image_resize_failures" +} + +resource "aws_sns_topic_subscription" "email_sub" { + topic_arn = aws_sns_topic.failure_notifications.arn + protocol = "email" + endpoint = local.failure_notifications_email +} + +# S3 Bucket Notification for Lambda trigger +resource "aws_s3_bucket_notification" "bucket_notification" { + bucket = aws_s3_bucket.images_bucket.id + + lambda_function { + lambda_function_arn = aws_lambda_function.resize_lambda.arn + events = ["s3:ObjectCreated:*"] + } +} + +resource "aws_lambda_permission" "s3_invoke_resize_lambda" { + statement_id = "AllowS3InvokeLambda" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.resize_lambda.function_name + principal = "s3.amazonaws.com" + source_arn = aws_s3_bucket.images_bucket.arn +} + +# CloudFront + +resource "aws_s3_bucket" "website_bucket" { + bucket = local.website_bucket +} + +resource "aws_s3_bucket_website_configuration" "website_configuration" { + bucket = aws_s3_bucket.website_bucket.bucket + index_document { + suffix = "index.html" + } +} + +resource "aws_s3_object" "website_file_index" { + bucket = aws_s3_bucket.website_bucket.bucket + key = "index.html" + 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" { + bucket = aws_s3_bucket.website_bucket.bucket + key = "app.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" { + bucket = aws_s3_bucket.website_bucket.bucket + key = "favicon.ico" + 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" { + comment = "OAI for CloudFront to access S3 bucket" +} + +resource "aws_s3_bucket_policy" "website_bucket_policy" { + bucket = aws_s3_bucket.website_bucket.bucket + policy = templatefile("policies/website_s3_bucket.json.tpl", { + cdn_identity_arn = aws_cloudfront_origin_access_identity.cdn_identity.iam_arn + website_bucket_arn = aws_s3_bucket.website_bucket.arn + }) +} + +resource "aws_cloudfront_distribution" "cdn" { + origin { + domain_name = aws_s3_bucket.website_bucket.bucket_regional_domain_name + origin_id = aws_s3_bucket.website_bucket.bucket + + s3_origin_config { + origin_access_identity = aws_cloudfront_origin_access_identity.cdn_identity.cloudfront_access_identity_path + } + } + + enabled = true + is_ipv6_enabled = true + default_root_object = "index.html" + + viewer_certificate { + cloudfront_default_certificate = true + } + + default_cache_behavior { + target_origin_id = aws_s3_bucket.website_bucket.bucket + + allowed_methods = ["GET", "HEAD"] + cached_methods = ["GET", "HEAD"] + + viewer_protocol_policy = "redirect-to-https" + min_ttl = 0 + default_ttl = 86400 + max_ttl = 31536000 + } + + restrictions { + geo_restriction { + restriction_type = "none" + } + } +} + +resource "aws_s3_bucket_public_access_block" "website_block_public_access" { + bucket = aws_s3_bucket.website_bucket.bucket + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +# Outputs + +output "presign_lambda_function_url" { + value = aws_lambda_function_url.presign_lambda_function.function_url +} + +output "list_lambda_function_url" { + value = aws_lambda_function_url.list_lambda_function.function_url +} + +output "cloudfront_url" { + value = "Now open the Web app under: http://${aws_cloudfront_distribution.cdn.domain_name}" +} \ No newline at end of file diff --git a/deployment/terraform/policies/lambda.json b/deployment/terraform/policies/lambda.json new file mode 100644 index 0000000..b531d5d --- /dev/null +++ b/deployment/terraform/policies/lambda.json @@ -0,0 +1,12 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + } \ No newline at end of file diff --git a/deployment/terraform/policies/lambda_ssm.json b/deployment/terraform/policies/lambda_ssm.json new file mode 100644 index 0000000..d4c8ee0 --- /dev/null +++ b/deployment/terraform/policies/lambda_ssm.json @@ -0,0 +1,14 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ssm:GetParameters" + ], + "Resource": [ + "arn:aws:ssm:::parameter/localstack-thumbnail-app/*" + ] + } + ] +} \ No newline at end of file diff --git a/deployment/terraform/policies/list_lambda_s3_buckets.json.tpl b/deployment/terraform/policies/list_lambda_s3_buckets.json.tpl new file mode 100644 index 0000000..0278bf1 --- /dev/null +++ b/deployment/terraform/policies/list_lambda_s3_buckets.json.tpl @@ -0,0 +1,18 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:ListBucket", + "s3:GetObject" + ], + "Resource": [ + "arn:aws:s3:::${images_bucket}", + "arn:aws:s3:::${images_bucket}/*", + "arn:aws:s3:::${images_resized_bucket}", + "arn:aws:s3:::${images_resized_bucket}/*" + ] + } + ] +} \ No newline at end of file diff --git a/deployment/terraform/policies/presign_lambda_s3_buckets.json.tpl b/deployment/terraform/policies/presign_lambda_s3_buckets.json.tpl new file mode 100644 index 0000000..1788bf1 --- /dev/null +++ b/deployment/terraform/policies/presign_lambda_s3_buckets.json.tpl @@ -0,0 +1,17 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:ListBucket", + "s3:GetObject", + "s3:PutObject" + ], + "Resource": [ + "arn:aws:s3:::${images_bucket}", + "arn:aws:s3:::${images_bucket}/*" + ] + } + ] +} \ No newline at end of file diff --git a/deployment/terraform/policies/resize_lambda_s3_buckets.json.tpl b/deployment/terraform/policies/resize_lambda_s3_buckets.json.tpl new file mode 100644 index 0000000..c21fcfe --- /dev/null +++ b/deployment/terraform/policies/resize_lambda_s3_buckets.json.tpl @@ -0,0 +1,17 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:ListBucket", + "s3:GetObject", + "s3:PutObject" + ], + "Resource": [ + "arn:aws:s3:::${images_resized_bucket}", + "arn:aws:s3:::${images_resized_bucket}/*" + ] + } + ] +} \ No newline at end of file diff --git a/deployment/terraform/policies/resize_lambda_sns.json.tpl b/deployment/terraform/policies/resize_lambda_sns.json.tpl new file mode 100644 index 0000000..34a7540 --- /dev/null +++ b/deployment/terraform/policies/resize_lambda_sns.json.tpl @@ -0,0 +1,17 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sns:Publish" + ], + "Effect": "Allow", + "Resource": "${failure_notifications_topic_arn}" + }, + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": "${resize_lambda_arn}" + } + ] +} \ No newline at end of file diff --git a/deployment/terraform/policies/website_s3_bucket.json.tpl b/deployment/terraform/policies/website_s3_bucket.json.tpl new file mode 100644 index 0000000..0e11a91 --- /dev/null +++ b/deployment/terraform/policies/website_s3_bucket.json.tpl @@ -0,0 +1,13 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": "${cdn_identity_arn}" + }, + "Action": "s3:GetObject", + "Resource": "${website_bucket_arn}/*" + } + ] +} \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt index 2e13e4f..9bf6a99 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,3 +9,4 @@ black pytest awscli awscli-local +terraform-local