diff --git a/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/README.md b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/README.md index 1959fb6f1..7373caa67 100644 --- a/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/README.md @@ -55,11 +55,12 @@ _Parameters_ | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| |existingLambdaObj?|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Existing instance of Lambda Function object, providing both this and `lambdaFunctionProps` will cause an error.| -|lambdaFunctionProps?|[`lambda.FunctionProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.FunctionProps.html)|User provided props to override the default props for the Lambda function.| +|lambdaFunctionProps?|[`lambda.FunctionProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.FunctionProps.html)|Optional user provided props to override the default props for the Lambda function.| |apiGatewayProps?|[`api.LambdaRestApiProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.LambdaRestApiProps.html)|Optional user provided props to override the default props for API Gateway| |cloudFrontDistributionProps?|[`cloudfront.DistributionProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudfront.DistributionProps.html)|Optional user provided props to override the default props for CloudFront Distribution| |insertHttpSecurityHeaders?|`boolean`|Optional user provided props to turn on/off the automatic injection of best practice HTTP security headers in all responses from CloudFront| -|logGroupProps?|[`logs.LogGroupProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-logs.LogGroupProps.html)|User provided props to override the default props for for the CloudWatchLogs LogGroup.| +|logGroupProps?|[`logs.LogGroupProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-logs.LogGroupProps.html)|Optional user provided props to override the default props for for the CloudWatchLogs LogGroup.| +|cloudFrontLoggingBucketProps?|[`s3.BucketProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.BucketProps.html)|Optional user provided props to override the default props for the CloudFront Logging Bucket.| ## Pattern Properties diff --git a/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/lib/index.ts index 95f5c5e13..60704faf1 100644 --- a/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/lib/index.ts @@ -33,7 +33,7 @@ export interface CloudFrontToApiGatewayToLambdaProps { */ readonly existingLambdaObj?: lambda.Function, /** - * User provided props to override the default props for the Lambda function. + * Optional user provided props to override the default props for the Lambda function. * * @default - Default props are used */ @@ -58,11 +58,17 @@ export interface CloudFrontToApiGatewayToLambdaProps { */ readonly insertHttpSecurityHeaders?: boolean, /** - * User provided props to override the default props for the CloudWatchLogs LogGroup. + * Optional user provided props to override the default props for the CloudWatchLogs LogGroup. * * @default - Default props are used */ readonly logGroupProps?: logs.LogGroupProps + /** + * Optional user provided props to override the default props for the CloudFront Logging Bucket. + * + * @default - Default props are used + */ + readonly cloudFrontLoggingBucketProps?: s3.BucketProps } export class CloudFrontToApiGatewayToLambda extends Construct { @@ -113,7 +119,8 @@ export class CloudFrontToApiGatewayToLambda extends Construct { const apiCloudfront: CloudFrontToApiGateway = new CloudFrontToApiGateway(this, 'CloudFrontToApiGateway', { existingApiGatewayObj: this.apiGateway, cloudFrontDistributionProps: props.cloudFrontDistributionProps, - insertHttpSecurityHeaders: props.insertHttpSecurityHeaders + insertHttpSecurityHeaders: props.insertHttpSecurityHeaders, + cloudFrontLoggingBucketProps: props.cloudFrontLoggingBucketProps }); this.cloudFrontWebDistribution = apiCloudfront.cloudFrontWebDistribution; diff --git a/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/integ.customCloudfrontLoggingBucket.expected.json b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/integ.customCloudfrontLoggingBucket.expected.json new file mode 100644 index 000000000..a19ef02d9 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/integ.customCloudfrontLoggingBucket.expected.json @@ -0,0 +1,859 @@ +{ + "Description": "Integration Test for aws-cloudfront-apigateway-lambda custom Cloudfront Logging Bucket", + "Resources": { + "cfapigwlambdaLambdaFunctionServiceRole9B40D826": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/lambda/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaFunctionServiceRolePolicy" + } + ] + } + }, + "cfapigwlambdaLambdaFunctionServiceRoleDefaultPolicy388158BB": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "xray:PutTraceSegments", + "xray:PutTelemetryRecords" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "cfapigwlambdaLambdaFunctionServiceRoleDefaultPolicy388158BB", + "Roles": [ + { + "Ref": "cfapigwlambdaLambdaFunctionServiceRole9B40D826" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W12", + "reason": "Lambda needs the following minimum required permissions to send trace data to X-Ray and access ENIs in a VPC." + } + ] + } + } + }, + "cfapigwlambdaLambdaFunction10C09D31": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c.zip" + }, + "Role": { + "Fn::GetAtt": [ + "cfapigwlambdaLambdaFunctionServiceRole9B40D826", + "Arn" + ] + }, + "Environment": { + "Variables": { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1" + } + }, + "Handler": "index.handler", + "Runtime": "nodejs10.x", + "TracingConfig": { + "Mode": "Active" + } + }, + "DependsOn": [ + "cfapigwlambdaLambdaFunctionServiceRoleDefaultPolicy388158BB", + "cfapigwlambdaLambdaFunctionServiceRole9B40D826" + ], + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W58", + "reason": "Lambda functions has the required permission to write CloudWatch Logs. It uses custom policy instead of arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole with tighter permissions." + }, + { + "id": "W89", + "reason": "This is not a rule for the general case, just for specific use cases/industries" + }, + { + "id": "W92", + "reason": "Impossible for us to define the correct concurrency for clients" + } + ] + } + } + }, + "cfapigwlambdaApiAccessLogGroup16C73450": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain", + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W86", + "reason": "Retention period for CloudWatchLogs LogGroups are set to 'Never Expire' to preserve customer data indefinitely" + }, + { + "id": "W84", + "reason": "By default CloudWatchLogs LogGroups data is encrypted using the CloudWatch server-side encryption keys (AWS Managed Keys)" + } + ] + } + } + }, + "cfapigwlambdaLambdaRestApi775C255B": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Name": "LambdaRestApi" + } + }, + "cfapigwlambdaLambdaRestApiDeployment33C24C7D5b6eb6dc887b9e8b9bde9a765f4aacbb": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "cfapigwlambdaLambdaRestApi775C255B" + }, + "Description": "Automatically created by the RestApi construct" + }, + "DependsOn": [ + "cfapigwlambdaLambdaRestApiproxyANY68181290", + "cfapigwlambdaLambdaRestApiproxy6A768910", + "cfapigwlambdaLambdaRestApiANY81C176E9" + ], + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W45", + "reason": "ApiGateway has AccessLogging enabled in AWS::ApiGateway::Stage resource, but cfn_nag checkes for it in AWS::ApiGateway::Deployment resource" + } + ] + } + } + }, + "cfapigwlambdaLambdaRestApiDeploymentStageprod83104011": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "RestApiId": { + "Ref": "cfapigwlambdaLambdaRestApi775C255B" + }, + "AccessLogSetting": { + "DestinationArn": { + "Fn::GetAtt": [ + "cfapigwlambdaApiAccessLogGroup16C73450", + "Arn" + ] + }, + "Format": "{\"requestId\":\"$context.requestId\",\"ip\":\"$context.identity.sourceIp\",\"user\":\"$context.identity.user\",\"caller\":\"$context.identity.caller\",\"requestTime\":\"$context.requestTime\",\"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\",\"status\":\"$context.status\",\"protocol\":\"$context.protocol\",\"responseLength\":\"$context.responseLength\"}" + }, + "DeploymentId": { + "Ref": "cfapigwlambdaLambdaRestApiDeployment33C24C7D5b6eb6dc887b9e8b9bde9a765f4aacbb" + }, + "MethodSettings": [ + { + "DataTraceEnabled": false, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*" + } + ], + "StageName": "prod", + "TracingEnabled": true + } + }, + "cfapigwlambdaLambdaRestApiproxy6A768910": { + "Type": "AWS::ApiGateway::Resource", + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "cfapigwlambdaLambdaRestApi775C255B", + "RootResourceId" + ] + }, + "PathPart": "{proxy+}", + "RestApiId": { + "Ref": "cfapigwlambdaLambdaRestApi775C255B" + } + } + }, + "cfapigwlambdaLambdaRestApiproxyANYApiPermissioncustomCloudfrontLoggingBucketcfapigwlambdaLambdaRestApi1C5998E7ANYproxyA3ACBFF5": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "cfapigwlambdaLambdaFunction10C09D31", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "cfapigwlambdaLambdaRestApi775C255B" + }, + "/", + { + "Ref": "cfapigwlambdaLambdaRestApiDeploymentStageprod83104011" + }, + "/*/*" + ] + ] + } + } + }, + "cfapigwlambdaLambdaRestApiproxyANYApiPermissionTestcustomCloudfrontLoggingBucketcfapigwlambdaLambdaRestApi1C5998E7ANYproxyAF2D9C87": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "cfapigwlambdaLambdaFunction10C09D31", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "cfapigwlambdaLambdaRestApi775C255B" + }, + "/test-invoke-stage/*/*" + ] + ] + } + } + }, + "cfapigwlambdaLambdaRestApiproxyANY68181290": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "ANY", + "ResourceId": { + "Ref": "cfapigwlambdaLambdaRestApiproxy6A768910" + }, + "RestApiId": { + "Ref": "cfapigwlambdaLambdaRestApi775C255B" + }, + "AuthorizationType": "NONE", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "cfapigwlambdaLambdaFunction10C09D31", + "Arn" + ] + }, + "/invocations" + ] + ] + } + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W59", + "reason": "AWS::ApiGateway::Method AuthorizationType is set to 'NONE' because API Gateway behind CloudFront does not support AWS_IAM authentication" + } + ] + } + } + }, + "cfapigwlambdaLambdaRestApiANYApiPermissioncustomCloudfrontLoggingBucketcfapigwlambdaLambdaRestApi1C5998E7ANY3C46A898": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "cfapigwlambdaLambdaFunction10C09D31", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "cfapigwlambdaLambdaRestApi775C255B" + }, + "/", + { + "Ref": "cfapigwlambdaLambdaRestApiDeploymentStageprod83104011" + }, + "/*/" + ] + ] + } + } + }, + "cfapigwlambdaLambdaRestApiANYApiPermissionTestcustomCloudfrontLoggingBucketcfapigwlambdaLambdaRestApi1C5998E7ANY2F5B90FD": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "cfapigwlambdaLambdaFunction10C09D31", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "cfapigwlambdaLambdaRestApi775C255B" + }, + "/test-invoke-stage/*/" + ] + ] + } + } + }, + "cfapigwlambdaLambdaRestApiANY81C176E9": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "ANY", + "ResourceId": { + "Fn::GetAtt": [ + "cfapigwlambdaLambdaRestApi775C255B", + "RootResourceId" + ] + }, + "RestApiId": { + "Ref": "cfapigwlambdaLambdaRestApi775C255B" + }, + "AuthorizationType": "NONE", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "cfapigwlambdaLambdaFunction10C09D31", + "Arn" + ] + }, + "/invocations" + ] + ] + } + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W59", + "reason": "AWS::ApiGateway::Method AuthorizationType is set to 'NONE' because API Gateway behind CloudFront does not support AWS_IAM authentication" + } + ] + } + } + }, + "cfapigwlambdaLambdaRestApiUsagePlan11CE9748": { + "Type": "AWS::ApiGateway::UsagePlan", + "Properties": { + "ApiStages": [ + { + "ApiId": { + "Ref": "cfapigwlambdaLambdaRestApi775C255B" + }, + "Stage": { + "Ref": "cfapigwlambdaLambdaRestApiDeploymentStageprod83104011" + }, + "Throttle": {} + } + ] + } + }, + "cfapigwlambdaLambdaRestApiCloudWatchRole76F5ABDF": { + "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": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy" + } + ] + } + }, + "cfapigwlambdaLambdaRestApiAccountB2390110": { + "Type": "AWS::ApiGateway::Account", + "Properties": { + "CloudWatchRoleArn": { + "Fn::GetAtt": [ + "cfapigwlambdaLambdaRestApiCloudWatchRole76F5ABDF", + "Arn" + ] + } + }, + "DependsOn": [ + "cfapigwlambdaLambdaRestApi775C255B" + ] + }, + "cfapigwlambdaCloudFrontToApiGatewaySetHttpSecurityHeadersE20F2933": { + "Type": "AWS::CloudFront::Function", + "Properties": { + "Name": "SetHttpSecurityHeadersc8273ed23dc12ef2b23814ad425355213a41659e4f", + "AutoPublish": true, + "FunctionCode": "function handler(event) { var response = event.response; var headers = response.headers; headers['strict-transport-security'] = { value: 'max-age=63072000; includeSubdomains; preload'}; headers['content-security-policy'] = { value: \"default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'\"}; headers['x-content-type-options'] = { value: 'nosniff'}; headers['x-frame-options'] = {value: 'DENY'}; headers['x-xss-protection'] = {value: '1; mode=block'}; return response; }", + "FunctionConfig": { + "Comment": "SetHttpSecurityHeadersc8273ed23dc12ef2b23814ad425355213a41659e4f", + "Runtime": "cloudfront-js-1.0" + } + } + }, + "cfapigwlambdaCloudFrontToApiGatewayCloudfrontLoggingBucket2E8E3DC2": { + "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": "Delete", + "DeletionPolicy": "Delete", + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W35", + "reason": "This S3 bucket is used as the access logging bucket for CloudFront Distribution" + } + ] + } + } + }, + "cfapigwlambdaCloudFrontToApiGatewayCloudfrontLoggingBucketPolicy416A95E3": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "cfapigwlambdaCloudFrontToApiGatewayCloudfrontLoggingBucket2E8E3DC2" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "cfapigwlambdaCloudFrontToApiGatewayCloudfrontLoggingBucket2E8E3DC2", + "Arn" + ] + }, + "/*" + ] + ] + }, + { + "Fn::GetAtt": [ + "cfapigwlambdaCloudFrontToApiGatewayCloudfrontLoggingBucket2E8E3DC2", + "Arn" + ] + } + ], + "Sid": "HttpsOnly" + } + ], + "Version": "2012-10-17" + } + } + }, + "cfapigwlambdaCloudFrontToApiGatewayCloudFrontDistributionF8B75200": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "DefaultCacheBehavior": { + "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6", + "Compress": true, + "FunctionAssociations": [ + { + "EventType": "viewer-response", + "FunctionARN": { + "Fn::GetAtt": [ + "cfapigwlambdaCloudFrontToApiGatewaySetHttpSecurityHeadersE20F2933", + "FunctionARN" + ] + } + } + ], + "TargetOriginId": "customCloudfrontLoggingBucketcfapigwlambdaCloudFrontToApiGatewayCloudFrontDistributionOrigin1C90DACBB", + "ViewerProtocolPolicy": "redirect-to-https" + }, + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Logging": { + "Bucket": { + "Fn::GetAtt": [ + "cfapigwlambdaCloudFrontToApiGatewayCloudfrontLoggingBucket2E8E3DC2", + "RegionalDomainName" + ] + } + }, + "Origins": [ + { + "CustomOriginConfig": { + "OriginProtocolPolicy": "https-only", + "OriginSSLProtocols": [ + "TLSv1.2" + ] + }, + "DomainName": { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "/", + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "://", + { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "cfapigwlambdaLambdaRestApi775C255B" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "cfapigwlambdaLambdaRestApiDeploymentStageprod83104011" + }, + "/" + ] + ] + } + ] + } + ] + } + ] + } + ] + }, + "Id": "customCloudfrontLoggingBucketcfapigwlambdaCloudFrontToApiGatewayCloudFrontDistributionOrigin1C90DACBB", + "OriginPath": { + "Fn::Join": [ + "", + [ + "/", + { + "Ref": "cfapigwlambdaLambdaRestApiDeploymentStageprod83104011" + } + ] + ] + } + } + ] + } + }, + "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" + } + ] + } + } + } + }, + "Outputs": { + "cfapigwlambdaLambdaRestApiEndpoint1004A97F": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "cfapigwlambdaLambdaRestApi775C255B" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "cfapigwlambdaLambdaRestApiDeploymentStageprod83104011" + }, + "/" + ] + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store." + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/integ.customCloudfrontLoggingBucket.ts b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/integ.customCloudfrontLoggingBucket.ts new file mode 100644 index 000000000..33379a1be --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/integ.customCloudfrontLoggingBucket.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +/// !cdk-integ * +import { App, Stack, RemovalPolicy } from "@aws-cdk/core"; +import { CloudFrontToApiGatewayToLambda } from "../lib"; +import * as lambda from '@aws-cdk/aws-lambda'; +import { BucketEncryption } from "@aws-cdk/aws-s3"; +import { generateIntegStackName } from '@aws-solutions-constructs/core'; + +// Setup +const app = new App(); +const stack = new Stack(app, generateIntegStackName(__filename)); +stack.templateOptions.description = 'Integration Test for aws-cloudfront-apigateway-lambda custom Cloudfront Logging Bucket'; + +new CloudFrontToApiGatewayToLambda(stack, 'cf-apigw-lambda', { + lambdaFunctionProps: { + code: lambda.Code.fromAsset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler' + }, + cloudFrontLoggingBucketProps: { + removalPolicy: RemovalPolicy.DESTROY, + encryption: BucketEncryption.S3_MANAGED, + versioned: true + } +}); + +// Synth +app.synth(); diff --git a/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/test.cloudfront-apigateway-lambda.test.ts b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/test.cloudfront-apigateway-lambda.test.ts index 30e2de1ff..224451745 100644 --- a/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/test.cloudfront-apigateway-lambda.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/test.cloudfront-apigateway-lambda.test.ts @@ -15,6 +15,7 @@ import { CloudFrontToApiGatewayToLambda, CloudFrontToApiGatewayToLambdaProps } f import * as cdk from "@aws-cdk/core"; import * as lambda from '@aws-cdk/aws-lambda'; import * as api from '@aws-cdk/aws-apigateway'; +import * as s3 from "@aws-cdk/aws-s3"; import '@aws-cdk/assert/jest'; function deployNewFunc(stack: cdk.Stack) { @@ -199,4 +200,75 @@ test('override api gateway properties without existingLambdaObj', () => { }, Name: "LambdaRestApi" }); +}); + +// -------------------------------------------------------------- +// Cloudfront logging bucket with destroy removal policy and auto delete objects +// -------------------------------------------------------------- +test('Cloudfront logging bucket with destroy removal policy and auto delete objects', () => { + const stack = new cdk.Stack(); + + new CloudFrontToApiGatewayToLambda(stack, 'test-cloudfront-apigateway-lambda', { + lambdaFunctionProps: { + code: lambda.Code.fromAsset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler' + }, + apiGatewayProps: { + endpointConfiguration: { + types: [api.EndpointType.PRIVATE], + } + }, + cloudFrontLoggingBucketProps: { + removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true + } + }); + + expect(stack).toHaveResource("AWS::S3::Bucket", { + AccessControl: "LogDeliveryWrite" + }); + + expect(stack).toHaveResource("Custom::S3AutoDeleteObjects", { + ServiceToken: { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F", + "Arn" + ] + }, + BucketName: { + Ref: "testcloudfrontapigatewaylambdaCloudFrontToApiGatewayCloudfrontLoggingBucket7F467421" + } + }); +}); + +// -------------------------------------------------------------- +// Cloudfront logging bucket error providing existing log bucket and logBuckerProps +// -------------------------------------------------------------- +test('Cloudfront logging bucket error when providing existing log bucket and logBuckerProps', () => { + const stack = new cdk.Stack(); + const logBucket = new s3.Bucket(stack, 'cloudfront-log-bucket', {}); + + const app = () => { new CloudFrontToApiGatewayToLambda(stack, 'cloudfront-s3', { + lambdaFunctionProps: { + code: lambda.Code.fromAsset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler' + }, + apiGatewayProps: { + endpointConfiguration: { + types: [api.EndpointType.PRIVATE], + } + }, + cloudFrontLoggingBucketProps: { + removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true + }, + cloudFrontDistributionProps: { + logBucket + }, + }); + }; + + expect(app).toThrowError(); }); \ No newline at end of file