diff --git a/cloudformation/README.md b/cloudformation/README.md new file mode 100644 index 0000000..6ef8b96 --- /dev/null +++ b/cloudformation/README.md @@ -0,0 +1,13 @@ +# CloudFormation Template(s) for `AWS-Lambda-Image` + +This contains cloudformation template(s) to spin up a working bucket/lambda stack. Although it only spins up one set of resources, due to limitations in CloudFormation there are 3 templates, to be run sequentally. + +There is one parameter in the template, a domain name. This will be appended onto the name of the s3 buckets in order to create uniquely named buckets, one for code and logs (`admin.`) and one for the images (`images.`). As it is currently configured, there is a `images/raw` path that accepts incoming images, and an `images/small` and `images/medium` that get the resized images. These directories are set to allow public access as a website. The included copy of `config.json` matches the paths specified in cloudformation as an example. Please change to fit your needs. + +Steps in order to spin the stack up: + + +1. Spin up stack 1. This will create 2 buckets in s3, an `images` bucket and an `admin` bucket. +2. Build the lambda archive as usual with `make`. Upload the .zip file of code to the `admin` bucket. If you do not upload it at `lambda/aws-lambda-image.zip`, you will need to change stack 2 and 3 to reflect the path +3. Update with stack 2. This will create the lambda and the lambda permission objects +4. Update with stack 3. This will setup the trigger between the s3 bucket and the lambda and grant public access to the processed paths in the bucket diff --git a/cloudformation/aws-lambda-image-1.template b/cloudformation/aws-lambda-image-1.template new file mode 100644 index 0000000..19ef376 --- /dev/null +++ b/cloudformation/aws-lambda-image-1.template @@ -0,0 +1,134 @@ + { + "AWSTemplateFormatVersion" : "2010-09-09", + "Description" : "CF Template Image buckets and resize lambda", + "Parameters" : { + "DomainName" : { + "Description" : "domain to append to the buckets", + "Type" : "String", + "Default" : "nemo.com", + "AllowedPattern" : "[a-z.-]+", + "ConstraintDescription" : "can only contain lowercase letters, - and ." + } + }, + "Resources" : { + "ImageBucket" : { + "Type" : "AWS::S3::Bucket", + "DependsOn" : "AdminBucket", + "Properties" : { + "BucketName" : { "Fn::Sub" : "images.${DomainName}"}, + "Tags" : [ + { "Key" : "Name", "Value" : "AWS Resize Images Bucket" } + ], + "LoggingConfiguration" : { + "DestinationBucketName" : {"Ref" : "AdminBucket"}, + "LogFilePrefix" : { "Fn::Sub" : "logs/s3/images.${DomainName}"} + } + } + }, + "ImageBucketPolicy": { + "Type": "AWS::S3::BucketPolicy", + "DependsOn": "ImageBucket", + "Properties": { + "Bucket": {"Ref" : "ImageBucket"}, + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": [ "s3:GetObject" ], + "Resource": [ + { "Fn::Join": [ "/", [ { "Fn::GetAtt" : ["ImageBucket", "Arn"] }, "images/small/*" ] ] }, + { "Fn::Join": [ "/", [ { "Fn::GetAtt" : ["ImageBucket", "Arn"] }, "images/medium/*" ] ] } + ] + } + ] + } + } + }, + "AdminBucket" : { + "Type" : "AWS::S3::Bucket", + "Properties" : { + "BucketName" : { "Fn::Sub" : "admin.${DomainName}"}, + "AccessControl": "LogDeliveryWrite", + "Tags" : [ + { "Key" : "Name", "Value" : "Admin code/logging Bucket" } + ] + } + }, + "ResizeAccessPolicy" : { + "Type": "AWS::IAM::ManagedPolicy", + "Properties": { + "PolicyDocument" : { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:ListAllMyBuckets", + "s3:GetBucketLocation" + ], + "Resource": [ + "arn:aws:s3:::*" + ] + }, + { + "Effect": "Allow", + "Action": [ + "s3:ListBucket" + ], + "Resource": [ + {"Fn::Sub" : "arn:aws:s3:::images.${DomainName}"} + ] + }, + { + "Effect": "Allow", + "Action": [ + "s3:GetObject" + ], + "Resource": [ + {"Fn::Join" : ["", [{"Fn::Sub" : "arn:aws:s3:::images.${DomainName}"}, "/images/raw/*"]]} + ] + }, + { + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "S3:PutObjectACL", + "s3:GetObject" + ], + "Resource": [ + {"Fn::Join" : ["", [{"Fn::Sub" : "arn:aws:s3:::images.${DomainName}"}, "/images/small/*"]]}, + {"Fn::Join" : ["", [{"Fn::Sub" : "arn:aws:s3:::images.${DomainName}"}, "/images/medium/*"]]} + ] + } + ] + } + } + }, + "ResizeLambdaRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "RoleName" : {"Fn::Sub" : "lambda-imageresizer-${DomainName}"}, + "ManagedPolicyArns" : [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole", + { "Ref": "ResizeAccessPolicy" } + ], + "AssumeRolePolicyDocument": { + "Version" : "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ "lambda.amazonaws.com" ] + }, + "Action": [ "sts:AssumeRole" ] + }] + }, + "Path": "/" + } + } + }, + "Outputs" : { + + } +} \ No newline at end of file diff --git a/cloudformation/aws-lambda-image-2.template b/cloudformation/aws-lambda-image-2.template new file mode 100644 index 0000000..d2d00c7 --- /dev/null +++ b/cloudformation/aws-lambda-image-2.template @@ -0,0 +1,163 @@ + { + "AWSTemplateFormatVersion" : "2010-09-09", + "Description" : "CF Template Image buckets and resize lambda", + "Parameters" : { + "DomainName" : { + "Description" : "domain to append to the buckets", + "Type" : "String", + "Default" : "nemo.com", + "AllowedPattern" : "[a-z.-]+", + "ConstraintDescription" : "can only contain lowercase letters, - and ." + } + }, + "Resources" : { + "ImageBucket" : { + "Type" : "AWS::S3::Bucket", + "DependsOn" : "AdminBucket", + "Properties" : { + "BucketName" : { "Fn::Sub" : "images.${DomainName}"}, + "Tags" : [ + { "Key" : "Name", "Value" : "AWS Resize Images Bucket" } + ], + "LoggingConfiguration" : { + "DestinationBucketName" : {"Ref" : "AdminBucket"}, + "LogFilePrefix" : { "Fn::Sub" : "logs/s3/images.${DomainName}"} + } + } + }, + "ImageBucketPolicy": { + "Type": "AWS::S3::BucketPolicy", + "DependsOn": "ImageBucket", + "Properties": { + "Bucket": {"Ref" : "ImageBucket"}, + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": [ "s3:GetObject" ], + "Resource": [ + { "Fn::Join": [ "/", [ { "Fn::GetAtt" : ["ImageBucket", "Arn"] }, "images/small/*" ] ] }, + { "Fn::Join": [ "/", [ { "Fn::GetAtt" : ["ImageBucket", "Arn"] }, "images/medium/*" ] ] } + ] + } + ] + } + } + }, + "AdminBucket" : { + "Type" : "AWS::S3::Bucket", + "Properties" : { + "BucketName" : { "Fn::Sub" : "admin.${DomainName}"}, + "AccessControl": "LogDeliveryWrite", + "Tags" : [ + { "Key" : "Name", "Value" : "Admin code/logging Bucket" } + ] + } + }, + "ResizeAccessPolicy" : { + "Type": "AWS::IAM::ManagedPolicy", + "Properties": { + "PolicyDocument" : { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:ListAllMyBuckets", + "s3:GetBucketLocation" + ], + "Resource": [ + "arn:aws:s3:::*" + ] + }, + { + "Effect": "Allow", + "Action": [ + "s3:ListBucket" + ], + "Resource": [ + {"Fn::Sub" : "arn:aws:s3:::images.${DomainName}"} + ] + }, + { + "Effect": "Allow", + "Action": [ + "s3:GetObject" + ], + "Resource": [ + {"Fn::Join" : ["", [{"Fn::Sub" : "arn:aws:s3:::images.${DomainName}"}, "/images/raw/*"]]} + ] + }, + { + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "S3:PutObjectACL", + "s3:GetObject" + ], + "Resource": [ + {"Fn::Join" : ["", [{"Fn::Sub" : "arn:aws:s3:::images.${DomainName}"}, "/images/small/*"]]}, + {"Fn::Join" : ["", [{"Fn::Sub" : "arn:aws:s3:::images.${DomainName}"}, "/images/medium/*"]]} + ] + } + ] + } + } + }, + "ResizeLambdaRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "RoleName" : {"Fn::Sub" : "lambda-imageresizer-${DomainName}"}, + "ManagedPolicyArns" : [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole", + { "Ref": "ResizeAccessPolicy" } + ], + "AssumeRolePolicyDocument": { + "Version" : "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ "lambda.amazonaws.com" ] + }, + "Action": [ "sts:AssumeRole" ] + }] + }, + "Path": "/" + } + }, + "ImageResizeLambda": { + "Type": "AWS::Lambda::Function", + "DependsOn" : "ResizeLambdaRole", + "Properties": { + "Description" : "Image Resizer", + "FunctionName" : "image_resizer", + "Handler": "index.handler", + "Role": { "Fn::GetAtt" : ["ResizeLambdaRole", "Arn"] }, + "MemorySize" : "384", + "Code": { + "S3Bucket": {"Ref" : "AdminBucket"}, + "S3Key": "lambda/aws-lambda-image.zip" + }, + "Runtime": "nodejs6.10", + "Timeout": "300" + } + }, + "ImageResizeLambdaPerm": { + "Type": "AWS::Lambda::Permission", + "Properties" : { + "Action": "lambda:InvokeFunction", + "FunctionName": {"Ref": "ImageResizeLambda"}, + "Principal": "s3.amazonaws.com", + "SourceAccount": {"Ref": "AWS::AccountId"}, + "SourceArn": { "Fn::Join": [":", [ + "arn", "aws", "s3", "" , "", {"Ref" : "ImageBucket"}]] + } + } + } + }, + "Outputs" : { + + } +} \ No newline at end of file diff --git a/cloudformation/aws-lambda-image-3.template b/cloudformation/aws-lambda-image-3.template new file mode 100644 index 0000000..453bff6 --- /dev/null +++ b/cloudformation/aws-lambda-image-3.template @@ -0,0 +1,183 @@ + { + "AWSTemplateFormatVersion" : "2010-09-09", + "Description" : "CF Template Image buckets and resize lambda", + "Parameters" : { + "DomainName" : { + "Description" : "domain to append to the buckets", + "Type" : "String", + "Default" : "nemo.com", + "AllowedPattern" : "[a-z.-]+", + "ConstraintDescription" : "can only contain lowercase letters, - and ." + } + }, + "Resources" : { + "ImageBucket" : { + "Type" : "AWS::S3::Bucket", + "DependsOn" : "AdminBucket", + "Properties" : { + "BucketName" : { "Fn::Sub" : "images.${DomainName}"}, + "Tags" : [ + { "Key" : "Name", "Value" : "AWS Resize Images Bucket" } + ], + "LoggingConfiguration" : { + "DestinationBucketName" : {"Ref" : "AdminBucket"}, + "LogFilePrefix" : { "Fn::Sub" : "logs/s3/images.${DomainName}"} + }, + "NotificationConfiguration": { + "LambdaConfigurations": [ + { + "Event" : "s3:ObjectCreated:*", + "Function" : { "Fn::GetAtt" : ["ImageResizeLambda", "Arn"] }, + "Filter" : { + "S3Key" : { + "Rules" : [ + { + "Name" : "prefix", + "Value" : "images/raw" + } + ] + } + } + } + ] + } + } + }, + "ImageBucketPolicy": { + "Type": "AWS::S3::BucketPolicy", + "DependsOn": "ImageBucket", + "Properties": { + "Bucket": {"Ref" : "ImageBucket"}, + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": [ "s3:GetObject" ], + "Resource": [ + { "Fn::Join": [ "/", [ { "Fn::GetAtt" : ["ImageBucket", "Arn"] }, "images/small/*" ] ] }, + { "Fn::Join": [ "/", [ { "Fn::GetAtt" : ["ImageBucket", "Arn"] }, "images/medium/*" ] ] } + ] + } + ] + } + } + }, + "AdminBucket" : { + "Type" : "AWS::S3::Bucket", + "Properties" : { + "BucketName" : { "Fn::Sub" : "admin.${DomainName}"}, + "AccessControl": "LogDeliveryWrite", + "Tags" : [ + { "Key" : "Name", "Value" : "Admin code/logging Bucket" } + ] + } + }, + "ResizeAccessPolicy" : { + "Type": "AWS::IAM::ManagedPolicy", + "Properties": { + "PolicyDocument" : { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:ListAllMyBuckets", + "s3:GetBucketLocation" + ], + "Resource": [ + "arn:aws:s3:::*" + ] + }, + { + "Effect": "Allow", + "Action": [ + "s3:ListBucket" + ], + "Resource": [ + {"Fn::Sub" : "arn:aws:s3:::images.${DomainName}"} + ] + }, + { + "Effect": "Allow", + "Action": [ + "s3:GetObject" + ], + "Resource": [ + {"Fn::Join" : ["", [{"Fn::Sub" : "arn:aws:s3:::images.${DomainName}"}, "/images/raw/*"]]} + ] + }, + { + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "S3:PutObjectACL", + "s3:GetObject" + ], + "Resource": [ + {"Fn::Join" : ["", [{"Fn::Sub" : "arn:aws:s3:::images.${DomainName}"}, "/images/small/*"]]}, + {"Fn::Join" : ["", [{"Fn::Sub" : "arn:aws:s3:::images.${DomainName}"}, "/images/medium/*"]]} + ] + } + ] + } + } + }, + "ResizeLambdaRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "RoleName" : {"Fn::Sub" : "lambda-imageresizer-${DomainName}"}, + "ManagedPolicyArns" : [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole", + { "Ref": "ResizeAccessPolicy" } + ], + "AssumeRolePolicyDocument": { + "Version" : "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ "lambda.amazonaws.com" ] + }, + "Action": [ "sts:AssumeRole" ] + }] + }, + "Path": "/" + } + }, + "ImageResizeLambda": { + "Type": "AWS::Lambda::Function", + "DependsOn" : "ResizeLambdaRole", + "Properties": { + "Description" : "Image Resizer", + "FunctionName" : "image_resizer", + "Handler": "index.handler", + "Role": { "Fn::GetAtt" : ["ResizeLambdaRole", "Arn"] }, + "MemorySize" : "384", + "Code": { + "S3Bucket": {"Ref" : "AdminBucket"}, + "S3Key": "lambda/aws-lambda-image.zip" + }, + "Runtime": "nodejs6.10", + "Timeout": "300" + } + }, + "ImageResizeLambdaPerm": { + "Type": "AWS::Lambda::Permission", + "Properties" : { + "Action": "lambda:InvokeFunction", + "FunctionName": {"Ref": "ImageResizeLambda"}, + "Principal": "s3.amazonaws.com", + "SourceAccount": {"Ref": "AWS::AccountId"}, + "SourceArn": { "Fn::Join": [":", [ + "arn", "aws", "s3", "" , "", {"Ref" : "ImageBucket"}]] + } + } + } + + + }, + "Outputs" : { + + } +} \ No newline at end of file diff --git a/cloudformation/config.json.sample b/cloudformation/config.json.sample new file mode 100644 index 0000000..0c230b6 --- /dev/null +++ b/cloudformation/config.json.sample @@ -0,0 +1,15 @@ +{ + "bucket": "images.nemo.com", + "resizes": [ + { + "size": 300, + "directory": "images/small", + "prefix": "resized-" + }, + { + "size": 450, + "directory": "images/medium", + "suffix": "_medium" + } + ] +}