Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save joaogolias/060ddf54c9618d3e5fa3877e22448907 to your computer and use it in GitHub Desktop.
Save joaogolias/060ddf54c9618d3e5fa3877e22448907 to your computer and use it in GitHub Desktop.
Description: >-
(SO0023) - Serverless Image Handler with aws-solutions-constructs: This
template deploys and configures a serverless architecture that is optimized
for dynamic image manipulation and delivery at low latency and cost. Leverages
SharpJS for image processing. Template version v5.0.0
AWSTemplateFormatVersion: 2010-09-09
Metadata:
'AWS::CloudFormation::Interface':
ParameterGroups:
- Label:
default: CORS Options
Parameters:
- CorsEnabled
- CorsOrigin
- Label:
default: Image Sources
Parameters:
- SourceBuckets
- Label:
default: Demo UI
Parameters:
- DeployDemoUI
- Label:
default: Event Logging
Parameters:
- LogRetentionPeriod
Parameters:
CorsEnabled:
Type: String
Default: 'No'
AllowedValues:
- 'Yes'
- 'No'
Description: >-
Would you like to enable Cross-Origin Resource Sharing (CORS) for the
image handler API? Select 'Yes' if so.
CorsOrigin:
Type: String
Default: '*'
Description: >-
If you selected 'Yes' above, please specify an origin value here. A
wildcard (*) value will support any origin. We recommend specifying an
origin (i.e. https://example.domain) to restrict cross-site access to your
API.
SourceBuckets:
Type: String
Default: 'defaultBucket, bucketNo2, bucketNo3, ...'
AllowedPattern: .+
Description: >-
(Required) List the buckets (comma-separated) within your account that
contain original image files. If you plan to use Thumbor or Custom image
requests with this solution, the source bucket for those requests will be
the first bucket listed in this field.
DeployDemoUI:
Type: String
Default: 'Yes'
AllowedValues:
- 'Yes'
- 'No'
Description: >-
Would you like to deploy a demo UI to explore the features and
capabilities of this solution? This will create an additional Amazon S3
bucket and Amazon CloudFront distribution in your account.
LogRetentionPeriod:
Type: Number
Default: '1'
AllowedValues:
- '1'
- '3'
- '5'
- '7'
- '14'
- '30'
- '60'
- '90'
- '120'
- '150'
- '180'
- '365'
- '400'
- '545'
- '731'
- '1827'
- '3653'
Description: >-
This solution automatically logs events to Amazon CloudWatch. Select the
amount of time for CloudWatch logs from this solution to be retained (in
days).
AutoWebP:
Type: String
Default: 'No'
AllowedValues:
- 'Yes'
- 'No'
Description: >-
Would you like to enable automatic WebP based on accept headers? Select
'Yes' if so.
Mappings:
Send:
AnonymousUsage:
Data: 'Yes'
Conditions:
DeployDemoUICondition: !Equals
- !Ref DeployDemoUI
- 'Yes'
EnableCorsCondition: !Equals
- !Ref CorsEnabled
- 'Yes'
CDKMetadataAvailable: !Or
- !Or
- !Equals
- !Ref 'AWS::Region'
- ap-east-1
- !Equals
- !Ref 'AWS::Region'
- ap-northeast-1
- !Equals
- !Ref 'AWS::Region'
- ap-northeast-2
- !Equals
- !Ref 'AWS::Region'
- ap-south-1
- !Equals
- !Ref 'AWS::Region'
- ap-southeast-1
- !Equals
- !Ref 'AWS::Region'
- ap-southeast-2
- !Equals
- !Ref 'AWS::Region'
- ca-central-1
- !Equals
- !Ref 'AWS::Region'
- cn-north-1
- !Equals
- !Ref 'AWS::Region'
- cn-northwest-1
- !Equals
- !Ref 'AWS::Region'
- eu-central-1
- !Or
- !Equals
- !Ref 'AWS::Region'
- eu-north-1
- !Equals
- !Ref 'AWS::Region'
- eu-west-1
- !Equals
- !Ref 'AWS::Region'
- eu-west-2
- !Equals
- !Ref 'AWS::Region'
- eu-west-3
- !Equals
- !Ref 'AWS::Region'
- me-south-1
- !Equals
- !Ref 'AWS::Region'
- sa-east-1
- !Equals
- !Ref 'AWS::Region'
- us-east-1
- !Equals
- !Ref 'AWS::Region'
- us-east-2
- !Equals
- !Ref 'AWS::Region'
- us-west-1
- !Equals
- !Ref 'AWS::Region'
- us-west-2
Resources:
ImageHandlerFunctionRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: 'sts:AssumeRole'
Effect: Allow
Principal:
Service: lambda.amazonaws.com
Version: 2012-10-17
Path: /
RoleName: !Join
- ''
- - !Ref 'AWS::StackName'
- ImageHandlerFunctionRole-
- !Ref 'AWS::Region'
Metadata:
cfn_nag:
rules_to_suppress:
- id: W28
reason: >-
Resource name validated and found to pose no risk to updates that
require replacement of this resource.
ImageHandlerPolicy:
Type: 'AWS::IAM::Policy'
Properties:
PolicyDocument:
Statement:
- Action:
- 'logs:CreateLogStream'
- 'logs:CreateLogGroup'
- 'logs:PutLogEvents'
Effect: Allow
Resource: !Join
- ''
- - 'arn:'
- !Ref 'AWS::Partition'
- ':logs:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':log-group:/aws/lambda/*'
- Action:
- 's3:GetObject'
- 's3:PutObject'
- 's3:ListBucket'
Effect: Allow
Resource: !Join
- ''
- - 'arn:'
- !Ref 'AWS::Partition'
- ':s3:::*'
- Action: 'rekognition:DetectFaces'
Effect: Allow
Resource: '*'
Version: 2012-10-17
PolicyName: !Join
- ''
- - !Ref 'AWS::StackName'
- ImageHandlerPolicy
Roles:
- !Ref ImageHandlerFunctionRole
Metadata:
cfn_nag:
rules_to_suppress:
- id: W12
reason: 'rekognition:DetectFaces requires ''*'' resources.'
ImageHandlerFunction:
Type: 'AWS::Lambda::Function'
Properties:
Code:
S3Bucket: !Select
- 0
- !Split
- /
- !Select
- 5
- !Split
- ':'
- !Join
- ''
- - 'arn:'
- !Ref 'AWS::Partition'
- ':s3:::solutions-'
- !Ref 'AWS::Region'
S3Key: serverless-image-handler/v5.0.0/image-handler.zip
Handler: index.handler
Role: !GetAtt
- ImageHandlerFunctionRole
- Arn
Runtime: nodejs12.x
Description: >-
Serverless Image Handler - Function for performing image edits and
manipulations.
Environment:
Variables:
AUTO_WEBP: !Ref AutoWebP
CORS_ENABLED: !Ref CorsEnabled
CORS_ORIGIN: !Ref CorsOrigin
SOURCE_BUCKETS: !Ref SourceBuckets
REWRITE_MATCH_PATTERN: ''
REWRITE_SUBSTITUTION: ''
MemorySize: 256
Timeout: 30
DependsOn:
- ImageHandlerFunctionRole
Metadata:
cfn_nag:
rules_to_suppress:
- id: W58
reason: >-
False alarm: The Lambda function does have the permission to write
CloudWatch Logs.
ImageHandlerPermission:
Type: 'AWS::Lambda::Permission'
Properties:
Action: 'lambda:InvokeFunction'
FunctionName: !GetAtt
- ImageHandlerFunction
- Arn
Principal: apigateway.amazonaws.com
SourceArn: !Join
- ''
- - 'arn:'
- !Ref 'AWS::Partition'
- ':execute-api:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':'
- !Ref ImageHandlerApi
- /*/*/*
ImageHandlerLogGroup:
Type: 'AWS::Logs::LogGroup'
Properties:
LogGroupName: !Join
- ''
- - /aws/lambda/
- !Ref ImageHandlerFunction
RetentionInDays: !Ref LogRetentionPeriod
UpdateReplacePolicy: Retain
DeletionPolicy: Retain
ApiLogs:
Type: 'AWS::Logs::LogGroup'
UpdateReplacePolicy: Retain
DeletionPolicy: Retain
ImageHandlerApi:
Type: 'AWS::ApiGateway::RestApi'
Properties:
Body:
swagger: '2.0'
info:
title: ServerlessImageHandler
basePath: /image
schemes:
- https
paths:
'/{proxy+}':
x-amazon-apigateway-any-method:
produces:
- application/json
parameters:
- name: proxy
in: path
required: true
type: string
responses: {}
x-amazon-apigateway-integration:
responses:
default:
statusCode: '200'
uri: !Join
- ''
- - 'arn:aws:apigateway:'
- !Ref 'AWS::Region'
- ':'
- 'lambda:path/2015-03-31/functions/'
- !GetAtt
- ImageHandlerFunction
- Arn
- /invocations
passthroughBehavior: when_no_match
httpMethod: POST
cacheNamespace: xh7gp9
cacheKeyParameters:
- method.request.path.proxy
contentHandling: CONVERT_TO_TEXT
type: aws_proxy
x-amazon-apigateway-binary-media-types:
- '*/*'
EndpointConfiguration:
Types:
- REGIONAL
Name: ServerlessImageHandler
ApiLoggingRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: 'sts:AssumeRole'
Effect: Allow
Principal:
Service: apigateway.amazonaws.com
Version: 2012-10-17
Policies:
- PolicyDocument:
Statement:
- Action:
- 'logs:CreateLogGroup'
- 'logs:CreateLogStream'
- 'logs:DescribeLogGroups'
- 'logs:DescribeLogStreams'
- 'logs:PutLogEvents'
- 'logs:GetLogEvents'
- 'logs:FilterLogEvents'
Effect: Allow
Resource: !Join
- ''
- - 'arn:aws:logs:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':*'
Version: 2012-10-17
PolicyName: LambdaRestApiCloudWatchRolePolicy
ApiAccountConfig:
Type: 'AWS::ApiGateway::Account'
Properties:
CloudWatchRoleArn: !GetAtt
- ApiLoggingRole
- Arn
DependsOn:
- ImageHandlerApi
Logs:
Type: 'AWS::S3::Bucket'
Properties:
AccessControl: LogDeliveryWrite
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
VersioningConfiguration:
Status: Enabled
UpdateReplacePolicy: Retain
DeletionPolicy: Retain
Metadata:
cfn_nag:
rules_to_suppress:
- id: W35
reason: Used to store access logs for other buckets
LogsBucketPolicy:
Type: 'AWS::S3::BucketPolicy'
Properties:
Bucket: !Ref Logs
PolicyDocument:
Statement:
- Action: '*'
Condition:
Bool:
'aws:SecureTransport': 'false'
Effect: Deny
Principal: '*'
Resource: !Join
- ''
- - !GetAtt
- Logs
- Arn
- /*
Sid: HttpsOnly
Version: 2012-10-17
ImageHandlerDistribution:
Type: 'AWS::CloudFront::Distribution'
Properties:
DistributionConfig:
Comment: Image handler distribution
CustomErrorResponses:
- ErrorCachingMinTTL: 10
ErrorCode: 500
- ErrorCachingMinTTL: 10
ErrorCode: 501
- ErrorCachingMinTTL: 10
ErrorCode: 502
- ErrorCachingMinTTL: 10
ErrorCode: 503
- ErrorCachingMinTTL: 10
ErrorCode: 504
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
ForwardedValues:
Cookies:
Forward: none
Headers:
- Origin
- Accept
QueryString: false
TargetOriginId: !Ref ImageHandlerApi
ViewerProtocolPolicy: https-only
Enabled: true
HttpVersion: http2
Logging:
Bucket: !GetAtt
- Logs
- DomainName
IncludeCookies: false
Prefix: image-handler-cf-logs/
Origins:
- CustomOriginConfig:
HTTPSPort: 443
OriginProtocolPolicy: https-only
OriginSSLProtocols:
- TLSv1.1
- TLSv1.2
DomainName: !Join
- ''
- - !Ref ImageHandlerApi
- .execute-api.
- !Ref 'AWS::Region'
- .amazonaws.com
Id: !Ref ImageHandlerApi
OriginPath: /image
PriceClass: PriceClass_All
Metadata:
cfn_nag:
rules_to_suppress:
- id: W70
reason: >-
Since the distribution uses the CloudFront domain name, CloudFront
automatically sets the security policy to TLSv1 regardless of the
value of MinimumProtocolVersion
ImageHandlerApiDeployment:
Type: 'AWS::ApiGateway::Deployment'
Properties:
RestApiId: !Ref ImageHandlerApi
StageDescription:
AccessLogSetting:
DestinationArn: !GetAtt
- ApiLogs
- Arn
Format: >-
$context.identity.sourceIp $context.identity.caller
$context.identity.user [$context.requestTime] "$context.httpMethod
$context.resourcePath $context.protocol" $context.status
$context.responseLength $context.requestId
StageName: image
DependsOn:
- ApiAccountConfig
Metadata:
cfn_nag:
rules_to_suppress:
- id: W68
reason: The solution does not require the usage plan.
DemoBucket:
Type: 'AWS::S3::Bucket'
Properties:
AccessControl: Private
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
WebsiteConfiguration:
ErrorDocument: index.html
IndexDocument: index.html
UpdateReplacePolicy: Retain
DeletionPolicy: Retain
Metadata:
cfn_nag:
rules_to_suppress:
- id: W35
reason: >-
This S3 bucket does not require access logging. API calls and
image operations are logged to CloudWatch with custom reporting.
Condition: DeployDemoUICondition
DemoBucketPolicy:
Type: 'AWS::S3::BucketPolicy'
Properties:
Bucket: !Ref DemoBucket
PolicyDocument:
Statement:
- Action:
- 's3:GetObject'
Effect: Allow
Resource: !Join
- ''
- - !GetAtt
- DemoBucket
- Arn
- /*
Principal:
CanonicalUser: !GetAtt
- DemoOriginAccessIdentity
- S3CanonicalUserId
Condition: DeployDemoUICondition
DemoOriginAccessIdentity:
Type: 'AWS::CloudFront::CloudFrontOriginAccessIdentity'
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: !Join
- ''
- - access-identity-
- !Ref DemoBucket
Condition: DeployDemoUICondition
DemoDistribution:
Type: 'AWS::CloudFront::Distribution'
Properties:
DistributionConfig:
Comment: Website distribution for solution
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
CachedMethods:
- GET
- HEAD
ForwardedValues:
QueryString: false
TargetOriginId: S3-solution-website
ViewerProtocolPolicy: redirect-to-https
Enabled: true
HttpVersion: http2
IPV6Enabled: true
Logging:
Bucket: !GetAtt
- Logs
- DomainName
IncludeCookies: false
Prefix: demo-cf-logs/
Origins:
- DomainName: !GetAtt
- DemoBucket
- RegionalDomainName
Id: S3-solution-website
S3OriginConfig:
OriginAccessIdentity: !Join
- ''
- - origin-access-identity/cloudfront/
- !Ref DemoOriginAccessIdentity
ViewerCertificate:
CloudFrontDefaultCertificate: true
Metadata:
cfn_nag:
rules_to_suppress:
- id: W70
reason: >-
Since the distribution uses the CloudFront domain name, CloudFront
automatically sets the security policy to TLSv1 regardless of the
value of MinimumProtocolVersion
Condition: DeployDemoUICondition
CustomResourceRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: 'sts:AssumeRole'
Effect: Allow
Principal:
Service: lambda.amazonaws.com
Version: 2012-10-17
Path: /
RoleName: !Join
- ''
- - !Ref 'AWS::StackName'
- CustomResourceRole-
- !Ref 'AWS::Region'
Metadata:
cfn_nag:
rules_to_suppress:
- id: W28
reason: >-
Resource name validated and found to pose no risk to updates that
require replacement of this resource.
CustomResourcePolicy:
Type: 'AWS::IAM::Policy'
Properties:
PolicyDocument:
Statement:
- Action:
- 'logs:CreateLogStream'
- 'logs:CreateLogGroup'
- 'logs:PutLogEvents'
Effect: Allow
Resource: !Join
- ''
- - 'arn:'
- !Ref 'AWS::Partition'
- ':logs:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':log-group:/aws/lambda/*'
- Action:
- 's3:GetObject'
- 's3:PutObject'
- 's3:ListBucket'
Effect: Allow
Resource: !Join
- ''
- - 'arn:'
- !Ref 'AWS::Partition'
- ':s3:::*'
Version: 2012-10-17
PolicyName: !Join
- ''
- - !Ref 'AWS::StackName'
- CustomResourcePolicy
Roles:
- !Ref CustomResourceRole
CustomResourceFunction:
Type: 'AWS::Lambda::Function'
Properties:
Code:
S3Bucket: !Select
- 0
- !Split
- /
- !Select
- 5
- !Split
- ':'
- !Join
- ''
- - 'arn:'
- !Ref 'AWS::Partition'
- ':s3:::solutions-'
- !Ref 'AWS::Region'
S3Key: serverless-image-handler/v5.0.0/custom-resource.zip
Handler: index.handler
Role: !GetAtt
- CustomResourceRole
- Arn
Runtime: nodejs12.x
Description: Serverless Image Handler - Custom resource
MemorySize: 128
Timeout: 30
DependsOn:
- CustomResourceRole
Metadata:
cfn_nag:
rules_to_suppress:
- id: W58
reason: >-
False alarm: The Lambda function does have the permission to write
CloudWatch Logs.
CustomResourceLogGroup:
Type: 'AWS::Logs::LogGroup'
Properties:
LogGroupName: !Join
- ''
- - /aws/lambda/
- !Ref CustomResourceFunction
RetentionInDays: !Ref LogRetentionPeriod
UpdateReplacePolicy: Retain
DeletionPolicy: Retain
CustomResourceCopyS3:
Type: 'Custom::CustomResource'
Properties:
ServiceToken: !GetAtt
- CustomResourceFunction
- Arn
Region: !Ref 'AWS::Region'
manifestKey: serverless-image-handler/v5.0.0/demo-ui-manifest.json
sourceS3Bucket: !Join
- ''
- - solutions-
- !Ref 'AWS::Region'
sourceS3key: serverless-image-handler/v5.0.0/demo-ui
destS3Bucket: !Ref DemoBucket
version: v5.0.0
customAction: copyS3assets
DependsOn:
- CustomResourcePolicy
- CustomResourceRole
Condition: DeployDemoUICondition
CustomResourceConfig:
Type: 'Custom::CustomResource'
Properties:
ServiceToken: !GetAtt
- CustomResourceFunction
- Arn
Region: !Ref 'AWS::Region'
configItem:
apiEndpoint: !Join
- ''
- - 'https://'
- !GetAtt
- ImageHandlerDistribution
- DomainName
destS3Bucket: !Ref DemoBucket
destS3key: demo-ui-config.js
customAction: putConfigFile
DependsOn:
- CustomResourcePolicy
- CustomResourceRole
Condition: DeployDemoUICondition
CustomResourceUuid:
Type: 'Custom::CustomResource'
Properties:
ServiceToken: !GetAtt
- CustomResourceFunction
- Arn
Region: !Ref 'AWS::Region'
customAction: createUuid
DependsOn:
- CustomResourcePolicy
- CustomResourceRole
CustomResourceAnonymousMetric:
Type: 'Custom::CustomResource'
Properties:
ServiceToken: !GetAtt
- CustomResourceFunction
- Arn
Region: !Ref 'AWS::Region'
solutionId: SO0023
UUID: !GetAtt
- CustomResourceUuid
- UUID
version: v5.0.0
anonymousData: !FindInMap
- Send
- AnonymousUsage
- Data
customAction: sendMetric
DependsOn:
- CustomResourcePolicy
- CustomResourceRole
CustomResourceCheckSourceBuckets:
Type: 'Custom::CustomResource'
Properties:
ServiceToken: !GetAtt
- CustomResourceFunction
- Arn
Region: !Ref 'AWS::Region'
sourceBuckets: !Ref SourceBuckets
customAction: checkSourceBuckets
DependsOn:
- CustomResourcePolicy
- CustomResourceRole
CDKMetadata:
Type: 'AWS::CDK::Metadata'
Properties:
Modules: >-
aws-cdk=1.54.0,@aws-cdk/assets=1.57.0,@aws-cdk/aws-apigateway=1.57.0,@aws-cdk/aws-applicationautoscaling=1.57.0,@aws-cdk/aws-autoscaling-common=1.57.0,@aws-cdk/aws-certificatemanager=1.57.0,@aws-cdk/aws-cloudfront=1.57.0,@aws-cdk/aws-cloudwatch=1.57.0,@aws-cdk/aws-codeguruprofiler=1.57.0,@aws-cdk/aws-cognito=1.57.0,@aws-cdk/aws-dynamodb=1.57.0,@aws-cdk/aws-ec2=1.57.0,@aws-cdk/aws-elasticsearch=1.57.0,@aws-cdk/aws-events=1.57.0,@aws-cdk/aws-iam=1.57.0,@aws-cdk/aws-kinesis=1.57.0,@aws-cdk/aws-kinesisanalytics=1.57.0,@aws-cdk/aws-kms=1.57.0,@aws-cdk/aws-lambda=1.57.0,@aws-cdk/aws-logs=1.57.0,@aws-cdk/aws-s3=1.57.0,@aws-cdk/aws-s3-assets=1.57.0,@aws-cdk/aws-sns=1.57.0,@aws-cdk/aws-sqs=1.57.0,@aws-cdk/aws-ssm=1.57.0,@aws-cdk/aws-stepfunctions=1.57.0,@aws-cdk/cloud-assembly-schema=1.57.0,@aws-cdk/core=1.57.0,@aws-cdk/custom-resources=1.57.0,@aws-cdk/cx-api=1.57.0,@aws-cdk/region-info=1.57.0,@aws-solutions-constructs/aws-cloudfront-apigateway=1.57.0,@aws-solutions-constructs/aws-cloudfront-apigateway-lambda=1.57.0,@aws-solutions-constructs/aws-cloudfront-s3=1.57.0,@aws-solutions-constructs/core=1.57.0,jsii-runtime=node.js/v12.16.1
Condition: CDKMetadataAvailable
Outputs:
ApiEndpoint:
Description: Link to API endpoint for sending image requests to.
Value: !Sub 'https://${ImageHandlerDistribution.DomainName}'
DemoUrl:
Description: Link to the demo user interface for the solution.
Value: !Sub 'https://${DemoDistribution.DomainName}/index.html'
Condition: DeployDemoUICondition
SourceBuckets:
Description: Amazon S3 bucket location containing original image files.
Value: !Ref SourceBuckets
CorsEnabled:
Description: >-
Indicates whether Cross-Origin Resource Sharing (CORS) has been enabled
for the image handler API.
Value: !Ref CorsEnabled
CorsOrigin:
Description: >-
Origin value returned in the Access-Control-Allow-Origin header of image
handler API responses.
Value: !Ref CorsOrigin
Condition: EnableCorsCondition
LogRetentionPeriod:
Description: Number of days for event logs from Lambda to be retained in CloudWatch.
Value: !Ref LogRetentionPeriod
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment