diff --git a/README.md b/README.md index 469563b..2ceeaa9 100644 --- a/README.md +++ b/README.md @@ -56,4 +56,3 @@ cdk destroy ## License This library is licensed under the MIT-0 License. See the LICENSE file. - diff --git a/lib/cdk-context-utils.ts b/lib/cdk-context-utils.ts new file mode 100644 index 0000000..e1e0d58 --- /dev/null +++ b/lib/cdk-context-utils.ts @@ -0,0 +1,10 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +import { Node } from "constructs"; + +export const readContext = (node: Node) => ({ + boolean: (key: string, defaultValue = false) => node.tryGetContext(key) ? Boolean(node.tryGetContext(key)) : defaultValue, + number: (key: string, defaultValue: number) => node.tryGetContext(key) ? Number(node.tryGetContext(key)) : defaultValue, + string: (key: string, defaultValue: string) => node.tryGetContext(key) ? String(node.tryGetContext(key)) : defaultValue, + stringOrUndefined: (key: string) => node.tryGetContext(key) ? String(node.tryGetContext(key)) : undefined, +}); diff --git a/lib/image-optimization-stack.ts b/lib/image-optimization-stack.ts index e33228e..da6d28a 100644 --- a/lib/image-optimization-stack.ts +++ b/lib/image-optimization-stack.ts @@ -1,275 +1,77 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: MIT-0 - -import { Fn, Stack, StackProps, RemovalPolicy, aws_s3 as s3, aws_s3_deployment as s3deploy, aws_cloudfront as cloudfront, aws_cloudfront_origins as origins, aws_lambda as lambda, aws_iam as iam, Duration, CfnOutput, aws_logs as logs } from 'aws-cdk-lib'; -import { CfnDistribution } from "aws-cdk-lib/aws-cloudfront"; +import { aws_s3 as s3, aws_s3_deployment as s3deploy, CfnOutput, Duration, RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib'; import { Construct } from 'constructs'; +import { readContext } from './cdk-context-utils'; +import { sampleWebsite } from './sample-website'; +import { imageOptimizationSolution } from './image-processing'; import { getOriginShieldRegion } from './origin-shield'; -import { createHash } from 'crypto'; - -// Stack Parameters - -// related to architecture. If set to false, transformed images are not stored in S3, and all image requests land on Lambda -var STORE_TRANSFORMED_IMAGES = 'true'; -// Parameters of S3 bucket where original images are stored -var S3_IMAGE_BUCKET_NAME: string; -// CloudFront parameters -var CLOUDFRONT_ORIGIN_SHIELD_REGION = getOriginShieldRegion(process.env.AWS_REGION || process.env.CDK_DEFAULT_REGION || 'us-east-1'); -var CLOUDFRONT_CORS_ENABLED = 'true'; -// Parameters of transformed images -var S3_TRANSFORMED_IMAGE_EXPIRATION_DURATION = '90'; -var S3_TRANSFORMED_IMAGE_CACHE_TTL = 'max-age=31622400'; -// Max image size in bytes. If generated images are stored on S3, bigger images are generated, stored on S3 -// and request is redirect to the generated image. Otherwise, an application error is sent. -var MAX_IMAGE_SIZE = '4700000'; -// Lambda Parameters -var LAMBDA_MEMORY = '1500'; -var LAMBDA_TIMEOUT = '60'; -// Whether to deploy a sample website referenced in https://aws.amazon.com/blogs/networking-and-content-delivery/image-optimization-using-amazon-cloudfront-and-aws-lambda/ -var DEPLOY_SAMPLE_WEBSITE = 'false'; - -type ImageDeliveryCacheBehaviorConfig = { - origin: any; - compress: any; - viewerProtocolPolicy: any; - cachePolicy: any; - functionAssociations: any; - responseHeadersPolicy?: any; -}; - -type LambdaEnv = { - originalImageBucketName: string, - transformedImageBucketName?: any; - transformedImageCacheTTL: string, - maxImageSize: string, -} export class ImageOptimizationStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); - // Change stack parameters based on provided context - STORE_TRANSFORMED_IMAGES = this.node.tryGetContext('STORE_TRANSFORMED_IMAGES') || STORE_TRANSFORMED_IMAGES; - S3_TRANSFORMED_IMAGE_EXPIRATION_DURATION = this.node.tryGetContext('S3_TRANSFORMED_IMAGE_EXPIRATION_DURATION') || S3_TRANSFORMED_IMAGE_EXPIRATION_DURATION; - S3_TRANSFORMED_IMAGE_CACHE_TTL = this.node.tryGetContext('S3_TRANSFORMED_IMAGE_CACHE_TTL') || S3_TRANSFORMED_IMAGE_CACHE_TTL; - S3_IMAGE_BUCKET_NAME = this.node.tryGetContext('S3_IMAGE_BUCKET_NAME') || S3_IMAGE_BUCKET_NAME; - CLOUDFRONT_ORIGIN_SHIELD_REGION = this.node.tryGetContext('CLOUDFRONT_ORIGIN_SHIELD_REGION') || CLOUDFRONT_ORIGIN_SHIELD_REGION; - CLOUDFRONT_CORS_ENABLED = this.node.tryGetContext('CLOUDFRONT_CORS_ENABLED') || CLOUDFRONT_CORS_ENABLED; - LAMBDA_MEMORY = this.node.tryGetContext('LAMBDA_MEMORY') || LAMBDA_MEMORY; - LAMBDA_TIMEOUT = this.node.tryGetContext('LAMBDA_TIMEOUT') || LAMBDA_TIMEOUT; - MAX_IMAGE_SIZE = this.node.tryGetContext('MAX_IMAGE_SIZE') || MAX_IMAGE_SIZE; - DEPLOY_SAMPLE_WEBSITE = this.node.tryGetContext('DEPLOY_SAMPLE_WEBSITE') || DEPLOY_SAMPLE_WEBSITE; - - - // deploy a sample website for testing if required - if (DEPLOY_SAMPLE_WEBSITE === 'true') { - var sampleWebsiteBucket = new s3.Bucket(this, 's3-sample-website-bucket', { - removalPolicy: RemovalPolicy.DESTROY, - blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, - encryption: s3.BucketEncryption.S3_MANAGED, - enforceSSL: true, - autoDeleteObjects: true, - }); - - var sampleWebsiteDelivery = new cloudfront.Distribution(this, 'websiteDeliveryDistribution', { - comment: 'image optimization - sample website', - defaultRootObject: 'index.html', - defaultBehavior: { - origin: new origins.S3Origin(sampleWebsiteBucket), - viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, - } - }); - + // Load stack parameters related to architecture from CDK context + const context = readContext(this.node); + + const CORS_ENABLED = context.boolean('CLOUDFRONT_CORS_ENABLED', true); + const DEPLOY_SAMPLE_WEBSITE = context.boolean('DEPLOY_SAMPLE_WEBSITE'); + const LAMBDA_MEMORY = context.number('LAMBDA_MEMORY', 1500); + const LAMBDA_TIMEOUT_SECONDS = context.number('LAMBDA_TIMEOUT', 60); + const MAX_IMAGE_SIZE = context.number('MAX_IMAGE_SIZE', 4700000); + const ORIGIN_SHIELD_REGION = context.string('CLOUDFRONT_ORIGIN_SHIELD_REGION', getOriginShieldRegion(process.env.AWS_REGION || process.env.CDK_DEFAULT_REGION || 'us-east-1')); + const S3_ORIGINAL_IMAGE_BUCKET_NAME = context.stringOrUndefined('S3_IMAGE_BUCKET_NAME'); + const S3_TRANSFORMED_IMAGE_CACHE_CONTROL = context.string('S3_TRANSFORMED_IMAGE_CACHE_TTL', 'max-age=31622400'); + const S3_TRANSFORMED_IMAGE_EXPIRATION_DAYS = context.number('S3_TRANSFORMED_IMAGE_EXPIRATION_DURATION', 90); + const STORE_TRANSFORMED_IMAGES = context.boolean('STORE_TRANSFORMED_IMAGES', true); + + // If true, this stack will deploy an additional, sample website to showcase the solution + // Architecture of the sample website is described at https://aws.amazon.com/blogs/networking-and-content-delivery/image-optimization-using-amazon-cloudfront-and-aws-lambda/ + if (DEPLOY_SAMPLE_WEBSITE) { + const sampleWebsiteDelivery = sampleWebsite(this); new CfnOutput(this, 'SampleWebsiteDomain', { description: 'Sample website domain', value: sampleWebsiteDelivery.distributionDomainName }); - new CfnOutput(this, 'SampleWebsiteS3Bucket', { - description: 'S3 bucket use by the sample website', - value: sampleWebsiteBucket.bucketName - }); } - // For the bucket having original images, either use an external one, or create one with some samples photos. - var originalImageBucket; - var transformedImageBucket; - - if (S3_IMAGE_BUCKET_NAME) { - originalImageBucket = s3.Bucket.fromBucketName(this, 'imported-original-image-bucket', S3_IMAGE_BUCKET_NAME); - new CfnOutput(this, 'OriginalImagesS3Bucket', { - description: 'S3 bucket where original images are stored', - value: originalImageBucket.bucketName - }); + // For original images, use existing S3 bucket if provided, otherwise create a new one with sample images + let originalImageBucket: s3.IBucket; + if (S3_ORIGINAL_IMAGE_BUCKET_NAME) { + originalImageBucket = s3.Bucket.fromBucketName(this, 'imported-original-image-bucket', S3_ORIGINAL_IMAGE_BUCKET_NAME); } else { originalImageBucket = new s3.Bucket(this, 's3-sample-original-image-bucket', { - removalPolicy: RemovalPolicy.DESTROY, + autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, encryption: s3.BucketEncryption.S3_MANAGED, enforceSSL: true, - autoDeleteObjects: true, + removalPolicy: RemovalPolicy.DESTROY, }); - new s3deploy.BucketDeployment(this, 'DeployWebsite', { - sources: [s3deploy.Source.asset('./image-sample')], + new s3deploy.BucketDeployment(this, 'deploy-website', { destinationBucket: originalImageBucket, destinationKeyPrefix: 'images/rio/', + sources: [s3deploy.Source.asset('./image-sample')], }); - new CfnOutput(this, 'OriginalImagesS3Bucket', { - description: 'S3 bucket where original images are stored', - value: originalImageBucket.bucketName - }); - } - - // create bucket for transformed images if enabled in the architecture - if (STORE_TRANSFORMED_IMAGES === 'true') { - transformedImageBucket = new s3.Bucket(this, 's3-transformed-image-bucket', { - removalPolicy: RemovalPolicy.DESTROY, - autoDeleteObjects: true, - lifecycleRules: [ - { - expiration: Duration.days(parseInt(S3_TRANSFORMED_IMAGE_EXPIRATION_DURATION)), - }, - ], - }); - } - - // prepare env variable for Lambda - var lambdaEnv: LambdaEnv = { - originalImageBucketName: originalImageBucket.bucketName, - transformedImageCacheTTL: S3_TRANSFORMED_IMAGE_CACHE_TTL, - maxImageSize: MAX_IMAGE_SIZE, }; - if (transformedImageBucket) lambdaEnv.transformedImageBucketName = transformedImageBucket.bucketName; - - // IAM policy to read from the S3 bucket containing the original images - const s3ReadOriginalImagesPolicy = new iam.PolicyStatement({ - actions: ['s3:GetObject'], - resources: ['arn:aws:s3:::' + originalImageBucket.bucketName + '/*'], + new CfnOutput(this, 'original-images-s3-bucket', { + description: 'S3 bucket storing original images', + value: originalImageBucket.bucketName }); - // statements of the IAM policy to attach to Lambda - var iamPolicyStatements = [s3ReadOriginalImagesPolicy]; - - // Create Lambda for image processing - var lambdaProps = { - runtime: lambda.Runtime.NODEJS_20_X, - handler: 'index.handler', - code: lambda.Code.fromAsset('functions/image-processing'), - timeout: Duration.seconds(parseInt(LAMBDA_TIMEOUT)), - memorySize: parseInt(LAMBDA_MEMORY), - environment: lambdaEnv, - logRetention: logs.RetentionDays.ONE_DAY, - }; - var imageProcessing = new lambda.Function(this, 'image-optimization', lambdaProps); - - // Enable Lambda URL - const imageProcessingURL = imageProcessing.addFunctionUrl(); - - // Leverage CDK Intrinsics to get the hostname of the Lambda URL - const imageProcessingDomainName = Fn.parseDomainName(imageProcessingURL.url); - - // Create a CloudFront origin: S3 with fallback to Lambda when image needs to be transformed, otherwise with Lambda as sole origin - var imageOrigin; - - if (transformedImageBucket) { - imageOrigin = new origins.OriginGroup({ - primaryOrigin: new origins.S3Origin(transformedImageBucket, { - originShieldRegion: CLOUDFRONT_ORIGIN_SHIELD_REGION, - }), - fallbackOrigin: new origins.HttpOrigin(imageProcessingDomainName, { - originShieldRegion: CLOUDFRONT_ORIGIN_SHIELD_REGION, - }), - fallbackStatusCodes: [403, 500, 503, 504], - }); - - // write policy for Lambda on the s3 bucket for transformed images - var s3WriteTransformedImagesPolicy = new iam.PolicyStatement({ - actions: ['s3:PutObject'], - resources: ['arn:aws:s3:::' + transformedImageBucket.bucketName + '/*'], - }); - iamPolicyStatements.push(s3WriteTransformedImagesPolicy); - } else { - imageOrigin = new origins.HttpOrigin(imageProcessingDomainName, { - originShieldRegion: CLOUDFRONT_ORIGIN_SHIELD_REGION, - }); - } - - // attach iam policy to the role assumed by Lambda - imageProcessing.role?.attachInlinePolicy( - new iam.Policy(this, 'read-write-bucket-policy', { - statements: iamPolicyStatements, - }), - ); - - // Create a CloudFront Function for url rewrites - const urlRewriteFunction = new cloudfront.Function(this, 'urlRewrite', { - code: cloudfront.FunctionCode.fromFile({ filePath: 'functions/url-rewrite/index.js', }), - functionName: `urlRewriteFunction${this.node.addr}`, - }); - - var imageDeliveryCacheBehaviorConfig: ImageDeliveryCacheBehaviorConfig = { - origin: imageOrigin, - viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, - compress: false, - cachePolicy: new cloudfront.CachePolicy(this, `ImageCachePolicy${this.node.addr}`, { - defaultTtl: Duration.hours(24), - maxTtl: Duration.days(365), - minTtl: Duration.seconds(0), - queryStringBehavior: cloudfront.CacheQueryStringBehavior.all() - }), - functionAssociations: [{ - eventType: cloudfront.FunctionEventType.VIEWER_REQUEST, - function: urlRewriteFunction, - }], - } - - if (CLOUDFRONT_CORS_ENABLED === 'true') { - // Creating a custom response headers policy. CORS allowed for all origins. - const imageResponseHeadersPolicy = new cloudfront.ResponseHeadersPolicy(this, `ResponseHeadersPolicy${this.node.addr}`, { - responseHeadersPolicyName: `ImageResponsePolicy${this.node.addr}`, - corsBehavior: { - accessControlAllowCredentials: false, - accessControlAllowHeaders: ['*'], - accessControlAllowMethods: ['GET'], - accessControlAllowOrigins: ['*'], - accessControlMaxAge: Duration.seconds(600), - originOverride: false, - }, - // recognizing image requests that were processed by this solution - customHeadersBehavior: { - customHeaders: [ - { header: 'x-aws-image-optimization', value: 'v1.0', override: true }, - { header: 'vary', value: 'accept', override: true }, - ], - } - }); - imageDeliveryCacheBehaviorConfig.responseHeadersPolicy = imageResponseHeadersPolicy; - } - const imageDelivery = new cloudfront.Distribution(this, 'imageDeliveryDistribution', { - comment: 'image optimization - image delivery', - defaultBehavior: imageDeliveryCacheBehaviorConfig - }); - - // ADD OAC between CloudFront and LambdaURL - const oac = new cloudfront.CfnOriginAccessControl(this, "OAC", { - originAccessControlConfig: { - name: `oac${this.node.addr}`, - originAccessControlOriginType: "lambda", - signingBehavior: "always", - signingProtocol: "sigv4", - }, + // Create Amazon CloudFront distribution to deliver optimized images + const imageDelivery = imageOptimizationSolution(this, { + corsEnabled: CORS_ENABLED, + lambdaMemory: LAMBDA_MEMORY, + lambdaTimeout: Duration.seconds(LAMBDA_TIMEOUT_SECONDS), + maxImageSizeBytes: MAX_IMAGE_SIZE, + originalImageBucket: originalImageBucket, + originShieldRegion: ORIGIN_SHIELD_REGION, + storeTransformedImages: STORE_TRANSFORMED_IMAGES, + transformedImageCacheControl: S3_TRANSFORMED_IMAGE_CACHE_CONTROL, + transformedImageExpiration: Duration.days(S3_TRANSFORMED_IMAGE_EXPIRATION_DAYS), }); - - const cfnImageDelivery = imageDelivery.node.defaultChild as CfnDistribution; - cfnImageDelivery.addPropertyOverride(`DistributionConfig.Origins.${(STORE_TRANSFORMED_IMAGES === 'true')?"1":"0"}.OriginAccessControlId`, oac.getAtt("Id")); - - imageProcessing.addPermission("AllowCloudFrontServicePrincipal", { - principal: new iam.ServicePrincipal("cloudfront.amazonaws.com"), - action: "lambda:InvokeFunctionUrl", - sourceArn: `arn:aws:cloudfront::${this.account}:distribution/${imageDelivery.distributionId}` - }) - - new CfnOutput(this, 'ImageDeliveryDomain', { - description: 'Domain name of image delivery', + new CfnOutput(this, 'image-delivery-domain', { + description: 'Image delivery domain', value: imageDelivery.distributionDomainName }); } diff --git a/lib/image-processing.ts b/lib/image-processing.ts new file mode 100644 index 0000000..f90041c --- /dev/null +++ b/lib/image-processing.ts @@ -0,0 +1,149 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +import { + aws_cloudfront as cloudfront, + aws_cloudfront_origins as origins, + aws_iam as iam, + aws_lambda as lambda, + aws_logs as logs, + aws_s3 as s3, + Duration, Fn, RemovalPolicy, Stack +} from 'aws-cdk-lib'; + +type ImageOptimizationProps = { + corsEnabled: boolean, + originalImageBucket: s3.IBucket, + originShieldRegion: string, + lambdaMemory: number, + lambdaTimeout: Duration, + maxImageSizeBytes: number, + transformedImageCacheControl: string, + transformedImageExpiration: Duration, + storeTransformedImages: boolean, +} + +export const imageOptimizationSolution = (stack: Stack, props: ImageOptimizationProps) => { + const { + corsEnabled, + originalImageBucket, + originShieldRegion, + lambdaMemory, + lambdaTimeout, + maxImageSizeBytes, + transformedImageCacheControl, + transformedImageExpiration, + storeTransformedImages, + } = props; + + // Create Lambda function for image processing + const imageProcessing = new lambda.Function(stack, 'image-optimization', { + code: lambda.Code.fromAsset('functions/image-processing'), + environment: { + maxImageSize: String(maxImageSizeBytes), + originalImageBucketName: originalImageBucket.bucketName, + transformedImageCacheTTL: transformedImageCacheControl, + }, + handler: 'index.handler', + // let downloads of original images from S3 + initialPolicy: [ + new iam.PolicyStatement({ + actions: ['s3:GetObject'], + resources: [`arn:aws:s3:::${originalImageBucket.bucketName}/*`] + }), + ], + logRetention: logs.RetentionDays.ONE_DAY, + memorySize: lambdaMemory, + runtime: lambda.Runtime.NODEJS_20_X, + timeout: lambdaTimeout, + }); + + // Enable Lambda URL and create Amazon CloudFront origin + const imageProcessingURL = imageProcessing.addFunctionUrl(); + const imageProcessingDomainName = Fn.parseDomainName(imageProcessingURL.url); + const imageProcessingLambdaOrigin = new origins.HttpOrigin(imageProcessingDomainName, { originShieldRegion }); + + // Create custom response headers policy with CORS requests allowed for all origins + const getCorsResponsePolicy = () => new cloudfront.ResponseHeadersPolicy(stack, 'cors-response-policy', { + responseHeadersPolicyName: 'CorsResponsePolicy', + corsBehavior: { + accessControlAllowCredentials: false, + accessControlAllowHeaders: ['*'], + accessControlAllowMethods: ['GET'], + accessControlAllowOrigins: ['*'], + accessControlMaxAge: Duration.seconds(600), + originOverride: false, + }, + // Recognize image requests that were processed by this solution + customHeadersBehavior: { + customHeaders: [ + { header: 'vary', value: 'accept', override: true }, + { header: 'x-aws-image-optimization', value: 'v1.0', override: true }, + ] + } + }); + + // Create an S3 origin with fallback to Lambda + const getS3OriginWithFallbackToLambda = () => { + const transformedImageBucket = new s3.Bucket(stack, 's3-transformed-image-bucket', { + autoDeleteObjects: true, + lifecycleRules: [{ expiration: transformedImageExpiration }], + removalPolicy: RemovalPolicy.DESTROY, + }); + imageProcessing.addEnvironment('transformedImageBucketName', transformedImageBucket.bucketName); + imageProcessing.role!.addToPrincipalPolicy( + new iam.PolicyStatement({ + actions: ['s3:PutObject'], + resources: [`arn:aws:s3:::${transformedImageBucket.bucketName}/*`] + }) + ); + return new origins.OriginGroup({ + primaryOrigin: origins.S3BucketOrigin.withOriginAccessIdentity(transformedImageBucket, { originShieldRegion }), + fallbackOrigin: imageProcessingLambdaOrigin, + fallbackStatusCodes: [403, 500, 503, 504], + }); + }; + + // Create content delivery distribution with Amazon CloudFront for optimized images + const imageDelivery = new cloudfront.Distribution(stack, 'image-delivery-distribution', { + comment: 'Image Optimization - image delivery', + defaultBehavior: { + cachePolicy: new cloudfront.CachePolicy(stack, 'ImageCachePolicy', { + defaultTtl: Duration.hours(24), + maxTtl: Duration.days(365), + minTtl: Duration.seconds(0), + queryStringBehavior: cloudfront.CacheQueryStringBehavior.all() + }), + compress: false, + functionAssociations: [{ + eventType: cloudfront.FunctionEventType.VIEWER_REQUEST, + function: new cloudfront.Function(stack, 'url-rewrite', { + code: cloudfront.FunctionCode.fromFile({ filePath: 'functions/url-rewrite/index.js' }), + functionName: 'urlRewriteFunction', + }) + }], + origin: storeTransformedImages ? getS3OriginWithFallbackToLambda() : imageProcessingLambdaOrigin, + responseHeadersPolicy: corsEnabled ? getCorsResponsePolicy() : undefined, + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + } + }); + + // Add OAC between CloudFront and LambdaURL + const oac = new cloudfront.CfnOriginAccessControl(stack, 'origin-access-control', { + originAccessControlConfig: { + name: 'lambda-oac', + originAccessControlOriginType: 'lambda', + signingBehavior: 'always', + signingProtocol: 'sigv4', + } + }); + + const cfnImageDelivery = imageDelivery.node.defaultChild as cloudfront.CfnDistribution; + cfnImageDelivery.addPropertyOverride(`DistributionConfig.Origins.${storeTransformedImages ? '1' : '0'}.OriginAccessControlId`, oac.getAtt('Id')); + imageProcessing.addPermission('AllowCloudFrontServicePrincipal', { + action: 'lambda:InvokeFunctionUrl', + principal: new iam.ServicePrincipal('cloudfront.amazonaws.com'), + sourceArn: `arn:aws:cloudfront::${stack.account}:distribution/${imageDelivery.distributionId}` + }); + + return imageDelivery; +} diff --git a/lib/sample-website.ts b/lib/sample-website.ts new file mode 100644 index 0000000..3ad891c --- /dev/null +++ b/lib/sample-website.ts @@ -0,0 +1,24 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +import { RemovalPolicy, Stack } from 'aws-cdk-lib'; +import { BlockPublicAccess, Bucket, BucketEncryption } from 'aws-cdk-lib/aws-s3'; +import { Distribution, ViewerProtocolPolicy } from 'aws-cdk-lib/aws-cloudfront'; +import { S3BucketOrigin } from 'aws-cdk-lib/aws-cloudfront-origins'; + +export const sampleWebsite = (stack: Stack) => { + const bucket = new Bucket(stack, 's3-sample-website-bucket', { + autoDeleteObjects: true, + blockPublicAccess: BlockPublicAccess.BLOCK_ALL, + encryption: BucketEncryption.S3_MANAGED, + enforceSSL: true, + removalPolicy: RemovalPolicy.DESTROY + }); + return new Distribution(stack, 'website-delivery-distribution', { + comment: 'Image Optimization - sample website', + defaultBehavior: { + origin: S3BucketOrigin.withOriginAccessControl(bucket), + viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS + }, + defaultRootObject: 'index.html' + }); +} diff --git a/package.json b/package.json index 257fa82..a949365 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "image-optimization", - "version": "0.1.0", + "version": "0.2.0", "bin": { "image-optimization": "bin/image-optimization.js" }, @@ -12,17 +12,17 @@ "cdk": "cdk" }, "devDependencies": { - "@types/jest": "^29.5.11", + "@types/jest": "^29.5.13", "@types/node": "20.11.2", "@types/prettier": "2.7.3", - "aws-cdk": "2.121.1", + "aws-cdk": "2.161.0", "jest": "^29.7.0", - "ts-jest": "^29.1.1", + "ts-jest": "^29.2.5", "ts-node": "^10.9.2", - "typescript": "~5.3.3" + "typescript": "~5.6.2" }, "dependencies": { - "aws-cdk-lib": "2.121.1", + "aws-cdk-lib": "2.161.0", "constructs": "^10.3.0", "source-map-support": "^0.5.21" }