aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.5k stars 3.85k forks source link

aws-cloudfront: unable to set origin group as default behavior origin #31016

Open bersanf opened 1 month ago

bersanf commented 1 month ago

Describe the bug

When deploying a CloudFront distribution with an origin group as default behavior origin, you get the following CloudFormation error: Resource handler returned message: "Invalid request provided: Exactly one of CustomOriginConfig and S3OriginConfig must be specified"

When setting the origin group as default behavior origin in the AWS Console everything works.

Expected Behavior

Set the origin group as the origin in the default behavior of the cloudfront distribution.

Current Behavior

An error occurs when the CloudFormation stack gets deployed:

Resource handler returned message: "Invalid request provided: Exactly one of CustomOriginConfig and S3OriginConfig must be specified"

Reproduction Steps

Here a sample setup:

    const cloudfrontDistribution = new cloudfront.Distribution(this, 'CloudFrontDistribution', {
      comment: 'Test CloudFront Distribution',
      httpVersion: cloudfront.HttpVersion.HTTP2_AND_3,
      enableIpv6: true,
      certificate: certificate,
      domainNames: [domainName],
      defaultRootObject: 'index.html',
      defaultBehavior: {
        origin: myOriginGroup,
      },
    });

Possible Solution

No response

Additional Information/Context

No response

CDK CLI Version

2.151.0 (build b8289e2)

Framework Version

No response

Node.js Version

v20.16.0

OS

macos

Language

TypeScript

Language Version

Typescript 5.5.4

Other information

No response

nmussy commented 1 month ago

Would you be able to provide a minimal and complete reproduction stack? I'm assuming you're using an S3Origin judging by the CloudFormation error, but myOriginGroup is not defined in your sample. The following example deploys without error:

import * as cdk from "aws-cdk-lib";
import type { Construct } from "constructs";
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
import * as origins from "aws-cdk-lib/aws-cloudfront-origins";
import * as s3 from "aws-cdk-lib/aws-s3";

export class TestStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const bucket = new s3.Bucket(this, "Bucket", {
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    const origin = new origins.OriginGroup({
      primaryOrigin: new origins.S3Origin(bucket),
      fallbackOrigin: new origins.HttpOrigin("www.example.com"),
    });

    new cloudfront.Distribution(this, "Distribution", {
      defaultBehavior: { origin },
    });
  }
}
bersanf commented 1 month ago

Hello, i have done some tests and the issue occurs only when the Cloudfront distribution is created with an origin group that contains an ALB origin created with ECS pattern (ApplicationLoadBalancedFargateService).

Here the code to reproduce:

import * as cdk from "aws-cdk-lib";
import type { Construct } from "constructs";
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
import * as origins from "aws-cdk-lib/aws-cloudfront-origins";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as path from 'path';
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as nodejs_lambda from "aws-cdk-lib/aws-lambda-nodejs";
import *  as ec2 from "aws-cdk-lib/aws-ec2";
import * as elbv2 from "aws-cdk-lib/aws-elasticloadbalancingv2";
import * as targets from "aws-cdk-lib/aws-elasticloadbalancingv2-targets";

export class AwsCdk31016Stack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const vpc = new cdk.aws_ec2.Vpc(this, "sap-cs-ecs-vpc", {
      maxAzs: 2// Default is all AZs in region
    });

    const cluster = new cdk.aws_ecs.Cluster(this, "sap-cs-ecs-cluster", {
      vpc: vpc
    });

    const ecs_pattern = new cdk.aws_ecs_patterns.ApplicationLoadBalancedFargateService(this, 'sap-cs-ecs-pattern', {
      cluster: cluster,
      desiredCount: 1,
      cpu: 1024,
      runtimePlatform: {
        operatingSystemFamily: cdk.aws_ecs.OperatingSystemFamily.LINUX,
        cpuArchitecture: cdk.aws_ecs.CpuArchitecture.ARM64
      },
      memoryLimitMiB: 2048,
      taskImageOptions: {
        // image: cdk.aws_ecs.ContainerImage.fromEcrRepository(ecr, dockerImageTagParam.valueAsString),
        image: cdk.aws_ecs.ContainerImage.fromRegistry('nginx:latest'),
        containerPort: 4000
      },
      serviceName: 'sap-cs-on-aws-svc',
      deploymentController: {
        // type: cdk.aws_ecs.DeploymentControllerType.EXTERNAL
        type: cdk.aws_ecs.DeploymentControllerType.ECS
      },
      loadBalancerName: 'sap-cs-on-aws-alb',
      assignPublicIp: true,
      // internal: true,
      publicLoadBalancer: true,
      listenerPort: 80,
      protocol: cdk.aws_elasticloadbalancingv2.ApplicationProtocol.HTTP,
      // sslPolicy: cdk.aws_elasticloadbalancingv2.SslPolicy.TLS12,

      // certificate: certificate,
      // sslPolicy: SslPolicy.FORWARD_SECRECY_TLS12      

      healthCheck: {
        command: [ "CMD-SHELL", "curl -f http://localhost:4000/ || exit 1" ],
        interval: cdk.Duration.seconds(300),
        retries: 10,
        startPeriod: cdk.Duration.seconds(30),
        timeout: cdk.Duration.seconds(30),
      }
    });    

    // Output
    new cdk.CfnOutput(this, 'AwsCdk31016LoadBalancerALBUrl', {
      value: `http://${ecs_pattern.loadBalancer.loadBalancerDnsName}`
    });

    const bucket = new s3.Bucket(this, "Bucket", {
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    const origin = new origins.OriginGroup({
      primaryOrigin: new origins.LoadBalancerV2Origin(ecs_pattern.loadBalancer, {
        protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY,
      }),
      fallbackOrigin: new origins.S3Origin(bucket),

    });

    const distribution = new cloudfront.Distribution(this, "Distribution", {
      defaultBehavior: { origin },
    });

    // Output
    new cdk.CfnOutput(this, 'Cloudfrontdistributionurl', {
      value: distribution.distributionDomainName
    });
  }
}
nmussy commented 1 month ago

I was unable to deploy your example, your task definition never passes its health check as nginx is running on port 80. I needed to update the following:

    const ecs_pattern = new cdk.aws_ecs_patterns.ApplicationLoadBalancedFargateService(this, 'sap-cs-ecs-pattern', {
      // ...
      taskImageOptions: {
        image: cdk.aws_ecs.ContainerImage.fromRegistry('nginx:latest'),
-       containerPort: 4000
      },
      // ...
      healthCheck: {
-       command: [ "CMD-SHELL", "curl -f http://localhost:4000/ || exit 1" ],
+       command: [ "CMD-SHELL", "curl -f http://localhost/ || exit 1" ],

After that, I was able to successfully deploy the stack, I'm not getting the CloudFormation error mentioned in the bug report. Could you double check your repro stack?

pahud commented 1 month ago

Hi @bersanf

I was able to deploy this simplified code using CDK 2.148.0.

export class AwsCdk31016Stack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const vpc = new ec2.Vpc(this, "sap-cs-ecs-vpc", {
       maxAzs: 2// Default is all AZs in region
    });

    const cluster = new ecs.Cluster(this, "sap-cs-ecs-cluster", {
      vpc,
    });

    const ecs_pattern = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'sap-cs-ecs-pattern', {
      cluster,
      desiredCount: 1,
      cpu: 1024,
      memoryLimitMiB: 2048,
      taskImageOptions: {
        image: ecs.ContainerImage.fromRegistry('nginx:latest'),
        containerPort: 80
      },
      listenerPort: 80,
    });    

    // Output
    new CfnOutput(this, 'AwsCdk31016LoadBalancerALBUrl', {
      value: `http://${ecs_pattern.loadBalancer.loadBalancerDnsName}`
    });

    const bucket = new s3.Bucket(this, "Bucket", {
      removalPolicy: RemovalPolicy.DESTROY,
    });

    const origin = new cfo.OriginGroup({
      primaryOrigin: new cfo.LoadBalancerV2Origin(ecs_pattern.loadBalancer, {
        protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY,
      }),
      fallbackOrigin: new cfo.S3Origin(bucket),

    });

    const distribution = new cloudfront.Distribution(this, "Distribution", {
      defaultBehavior: { origin },
    });

    // Output
    new CfnOutput(this, 'Cloudfrontdistributionurl', {
      value: distribution.distributionDomainName
    });
  }
}

Can you first simplify your ECS and make sure ECS service is deployed and can be access through the ALB and then deploy the CloudFront distribution?

bersanf commented 1 month ago

Hello @pahud, @nmussy I have been finally able to isolate a use case when this issue occurs. With the simplified version, everything works like a charm.

But, when the origin group contains a S3Origin with an S3 bucket configured with OAC to enforce security, then the CloudFormation stack fails.

Here the code i am using:

import * as cdk from "aws-cdk-lib";
import type { Construct } from "constructs";
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
import * as origins from "aws-cdk-lib/aws-cloudfront-origins";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as path from 'path';
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as nodejs_lambda from "aws-cdk-lib/aws-lambda-nodejs";
import *  as ec2 from "aws-cdk-lib/aws-ec2";
import * as elbv2 from "aws-cdk-lib/aws-elasticloadbalancingv2";
import * as targets from "aws-cdk-lib/aws-elasticloadbalancingv2-targets";
import * as iam from 'aws-cdk-lib/aws-iam';

export class AwsCdk31016Stack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const vpc = new cdk.aws_ec2.Vpc(this, "sap-cs-ecs-vpc", {
      maxAzs: 2// Default is all AZs in region
    });

    const cluster = new cdk.aws_ecs.Cluster(this, "sap-cs-ecs-cluster", {
      vpc: vpc
    });

    const ecs_pattern = new cdk.aws_ecs_patterns.ApplicationLoadBalancedFargateService(this, 'sap-cs-ecs-pattern', {
      cluster: cluster,
      desiredCount: 1,
      cpu: 1024,
      runtimePlatform: {
        operatingSystemFamily: cdk.aws_ecs.OperatingSystemFamily.LINUX,
        cpuArchitecture: cdk.aws_ecs.CpuArchitecture.ARM64
      },
      memoryLimitMiB: 2048,
      taskImageOptions: {
        // image: cdk.aws_ecs.ContainerImage.fromEcrRepository(ecr, dockerImageTagParam.valueAsString),
        image: cdk.aws_ecs.ContainerImage.fromRegistry('nginx:latest'),
        containerPort: 80
      },
      serviceName: 'sap-cs-on-aws-svc',
      deploymentController: {
        // type: cdk.aws_ecs.DeploymentControllerType.EXTERNAL
        type: cdk.aws_ecs.DeploymentControllerType.ECS
      },
      loadBalancerName: 'sap-cs-on-aws-alb',
      assignPublicIp: true,
      // internal: true,
      publicLoadBalancer: true,
      listenerPort: 80,
      protocol: cdk.aws_elasticloadbalancingv2.ApplicationProtocol.HTTP,
      // sslPolicy: cdk.aws_elasticloadbalancingv2.SslPolicy.TLS12,

      // certificate: certificate,
      // sslPolicy: SslPolicy.FORWARD_SECRECY_TLS12      

      healthCheck: {
        command: [ "CMD-SHELL", "curl -f http://localhost/ || exit 1" ],
        interval: cdk.Duration.seconds(300),
        retries: 10,
        startPeriod: cdk.Duration.seconds(30),
        timeout: cdk.Duration.seconds(30),
      }
    });    

    // Output
    new cdk.CfnOutput(this, 'AwsCdk31016LoadBalancerALBUrl', {
      value: `http://${ecs_pattern.loadBalancer.loadBalancerDnsName}`
    });

    const bucket = new s3.Bucket(this, "Bucket", {
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    const origin = new origins.OriginGroup({
      primaryOrigin: new origins.LoadBalancerV2Origin(ecs_pattern.loadBalancer, {
        protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY,
      }),
      fallbackOrigin: new origins.S3Origin(bucket),

    });

    const distribution = new cloudfront.Distribution(this, "Distribution", {
      defaultBehavior: { origin },
    });

    // //// configure OAC for S3 bucket and CloudFront
    const oac = new cloudfront.CfnOriginAccessControl(this, 'ComposableStorefrontOriginAccessControl', {
      originAccessControlConfig: {
        name: 'ComposableStorefrontOriginAccessControl',
        originAccessControlOriginType: 's3',
        signingBehavior: 'always',
        signingProtocol: 'sigv4'
      }
    });

    //configure S3 Bucket to be accessed by Cloudfront only
    const allowCloudFrontReadOnlyPolicy = new iam.PolicyStatement({
      sid: 'allowCloudFrontReadOnlyPolicy',
      actions: ['s3:GetObject'],
      principals: [new iam.ServicePrincipal('cloudfront.amazonaws.com')],
      effect: iam.Effect.ALLOW,
      conditions: {
        'StringEquals': {
          "AWS:SourceArn": "arn:aws:cloudfront::" + this.account + ":distribution/" + distribution.distributionId
        }
      },
      resources: [bucket.bucketArn, bucket.bucketArn.concat('/').concat('*')]
    });
    bucket.addToResourcePolicy(allowCloudFrontReadOnlyPolicy)

    // OAC is not supported by CDK L2 construct yet. L1 construct is required
    const cfnDistribution = distribution.node.defaultChild as cloudfront.CfnDistribution
    //enable OAC
    cfnDistribution.addPropertyOverride(
      'DistributionConfig.Origins.0.OriginAccessControlId',
      oac.getAtt('Id')
    )
    //disable OAI
    cfnDistribution.addPropertyOverride(
      'DistributionConfig.Origins.0.S3OriginConfig.OriginAccessIdentity',
      '',
    )    

    // Output
    new cdk.CfnOutput(this, 'Cloudfrontdistributionurl', {
      value: distribution.distributionDomainName
    });
  }
}

Please note that the code that breaks the cloud formation stack is the following:

    // OAC is not supported by CDK L2 construct yet. L1 construct is required
    const cfnDistribution = distribution.node.defaultChild as cloudfront.CfnDistribution
    //enable OAC
    cfnDistribution.addPropertyOverride(
      'DistributionConfig.Origins.0.OriginAccessControlId',
      oac.getAtt('Id')
    )
pahud commented 1 month ago

Thank you. I can reproduce it.

I tried to simplify the code:

    const vpc = ec2.Vpc.fromLookup(this, 'VPC', { isDefault: true })

    const cluster = new cdk.aws_ecs.Cluster(this, "sap-cs-ecs-cluster", {
      vpc
    });

    const ecs_pattern = new cdk.aws_ecs_patterns.ApplicationLoadBalancedFargateService(this, 'sap-cs-ecs-pattern', {
      cluster: cluster,
      desiredCount: 1,
      cpu: 1024,
      runtimePlatform: {
        operatingSystemFamily: cdk.aws_ecs.OperatingSystemFamily.LINUX,
        cpuArchitecture: cdk.aws_ecs.CpuArchitecture.ARM64
      },
      memoryLimitMiB: 2048,
      taskImageOptions: {
        // image: cdk.aws_ecs.ContainerImage.fromEcrRepository(ecr, dockerImageTagParam.valueAsString),
        image: cdk.aws_ecs.ContainerImage.fromRegistry('nginx:latest'),
        containerPort: 80
      },
      serviceName: 'sap-cs-on-aws-svc',
      deploymentController: {
        // type: cdk.aws_ecs.DeploymentControllerType.EXTERNAL
        type: cdk.aws_ecs.DeploymentControllerType.ECS
      },
      loadBalancerName: 'sap-cs-on-aws-alb',
      assignPublicIp: true,
      // internal: true,
      publicLoadBalancer: true,
      listenerPort: 80,
      protocol: cdk.aws_elasticloadbalancingv2.ApplicationProtocol.HTTP,
      // sslPolicy: cdk.aws_elasticloadbalancingv2.SslPolicy.TLS12,

      // certificate: certificate,
      // sslPolicy: SslPolicy.FORWARD_SECRECY_TLS12      

      healthCheck: {
        command: [ "CMD-SHELL", "curl -f http://localhost/ || exit 1" ],
        interval: cdk.Duration.seconds(300),
        retries: 10,
        startPeriod: cdk.Duration.seconds(30),
        timeout: cdk.Duration.seconds(30),
      }
    });    

    // Output
    new cdk.CfnOutput(this, 'AwsCdk31016LoadBalancerALBUrl', {
      value: `http://${ecs_pattern.loadBalancer.loadBalancerDnsName}`
    });

    const bucket = new s3.Bucket(this, "Bucket", {
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    const originGroup = new origins.OriginGroup({
      primaryOrigin: new origins.LoadBalancerV2Origin(ecs_pattern.loadBalancer, {
        protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY,
      }),
      fallbackOrigin: new origins.S3Origin(bucket),

    });

    const distribution = new cloudfront.Distribution(this, "Distribution", {
      defaultBehavior: { origin: originGroup },
    });

    // configure OAC for S3 bucket and CloudFront
    const oac = new cloudfront.CfnOriginAccessControl(this, 'ComposableStorefrontOriginAccessControl', {
      originAccessControlConfig: {
        name: 'ComposableStorefrontOriginAccessControl',
        originAccessControlOriginType: 's3',
        signingBehavior: 'always',
        signingProtocol: 'sigv4'
      }
    });

    //configure S3 Bucket to be accessed by Cloudfront only
    const allowCloudFrontReadOnlyPolicy = new iam.PolicyStatement({
      sid: 'allowCloudFrontReadOnlyPolicy',
      actions: ['s3:GetObject'],
      principals: [new iam.ServicePrincipal('cloudfront.amazonaws.com')],
      effect: iam.Effect.ALLOW,
      conditions: {
        'StringEquals': {
          "AWS:SourceArn": "arn:aws:cloudfront::" + this.account + ":distribution/" + distribution.distributionId
        }
      },
      resources: [bucket.bucketArn, bucket.bucketArn.concat('/').concat('*')]
    });
    bucket.addToResourcePolicy(allowCloudFrontReadOnlyPolicy)

This works for me to have a CF distribution and OriginGroup with one custom origin of ALB and S3 origin with OAI.

It deploys!

Now if I add this that remove OriginAccessIdentity and add OriginAccessControlId for the S3 origin:

    // OAC is not supported by CDK L2 construct yet. L1 construct is required
    const cfnDistribution = distribution.node.defaultChild as cloudfront.CfnDistribution

    //enable OAC for S3(the 2nd Origin)
    cfnDistribution.addPropertyOverride(
      'DistributionConfig.Origins.1.OriginAccessControlId',
      oac.getAtt('Id')
    )
    //disable OAI
    cfnDistribution.addPropertyDeletionOverride(
      'DistributionConfig.Origins.1.S3OriginConfig.OriginAccessIdentity',
    )    

cdk diff LGTM

image

But cdk deploy failed

11:20:32 AM | UPDATE_FAILED        | AWS::CloudFront::Distribution                   | Distribution
Resource handler returned message: "Invalid request provided: Exactly one of CustomOriginConfig and S3OriginConfig must be specified" (RequestToken: 2
e03549e-a613-7cce-8a54-4616447bc482, HandlerErrorCode: InvalidRequest)

CFN template for the distribution

 "Distribution830FAC52": {
   "Type": "AWS::CloudFront::Distribution",
   "Properties": {
    "DistributionConfig": {
     "DefaultCacheBehavior": {
      "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
      "Compress": true,
      "TargetOriginId": "AwsCdk31016StackDistributionOriginGroup1079853D0",
      "ViewerProtocolPolicy": "allow-all"
     },
     "Enabled": true,
     "HttpVersion": "http2",
     "IPV6Enabled": true,
     "OriginGroups": {
      "Items": [
       {
        "FailoverCriteria": {
         "StatusCodes": {
          "Items": [
           500,
           502,
           503,
           504
          ],
          "Quantity": 4
         }
        },
        "Id": "AwsCdk31016StackDistributionOriginGroup1079853D0",
        "Members": {
         "Items": [
          {
           "OriginId": "AwsCdk31016StackDistributionOrigin1417AA29A"
          },
          {
           "OriginId": "AwsCdk31016StackDistributionOrigin263B82466"
          }
         ],
         "Quantity": 2
        }
       }
      ],
      "Quantity": 1
     },
     "Origins": [
      {
       "CustomOriginConfig": {
        "OriginProtocolPolicy": "http-only",
        "OriginSSLProtocols": [
         "TLSv1.2"
        ]
       },
       "DomainName": {
        "Fn::GetAtt": [
         "sapcsecspatternLB2DB79BF9",
         "DNSName"
        ]
       },
       "Id": "AwsCdk31016StackDistributionOrigin1417AA29A"
      },
      {
       "DomainName": {
        "Fn::GetAtt": [
         "Bucket83908E77",
         "RegionalDomainName"
        ]
       },
       "Id": "AwsCdk31016StackDistributionOrigin263B82466",
       "S3OriginConfig": {
        "OriginAccessIdentity": {
         "Fn::Join": [
          "",
          [
           "origin-access-identity/cloudfront/",
           {
            "Ref": "DistributionOrigin2S3OriginE484D4BF"
           }
          ]
         ]
        }
       }
      }
     ]
    }
   },

This doesn't seem to be an issue of CDK but something we need to clarify with the CFN team internally.

I will cut an internal ticket for that.

pahud commented 1 month ago

internal tracking: V1479047809

pahud commented 1 month ago

OK this works for me. Check my full sample below:

export class AwsCdk31016Stack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // const vpc = new cdk.aws_ec2.Vpc(this, "sap-cs-ecs-vpc", {
    //   maxAzs: 2// Default is all AZs in region
    // });
    const vpc = ec2.Vpc.fromLookup(this, 'VPC', { isDefault: true })

    const cluster = new cdk.aws_ecs.Cluster(this, "sap-cs-ecs-cluster", {
      vpc
    });

    const ecs_pattern = new cdk.aws_ecs_patterns.ApplicationLoadBalancedFargateService(this, 'sap-cs-ecs-pattern', {
      cluster: cluster,
      desiredCount: 1,
      cpu: 1024,
      runtimePlatform: {
        operatingSystemFamily: cdk.aws_ecs.OperatingSystemFamily.LINUX,
        cpuArchitecture: cdk.aws_ecs.CpuArchitecture.ARM64
      },
      memoryLimitMiB: 2048,
      taskImageOptions: {
        // image: cdk.aws_ecs.ContainerImage.fromEcrRepository(ecr, dockerImageTagParam.valueAsString),
        image: cdk.aws_ecs.ContainerImage.fromRegistry('nginx:latest'),
        containerPort: 80
      },
      serviceName: 'sap-cs-on-aws-svc',
      deploymentController: {
        type: cdk.aws_ecs.DeploymentControllerType.ECS
      },
      loadBalancerName: 'sap-cs-on-aws-alb',
      assignPublicIp: true,
      publicLoadBalancer: true,
      listenerPort: 80,
      protocol: cdk.aws_elasticloadbalancingv2.ApplicationProtocol.HTTP,    
      healthCheck: {
        command: [ "CMD-SHELL", "curl -f http://localhost/ || exit 1" ],
        interval: cdk.Duration.seconds(300),
        retries: 10,
        startPeriod: cdk.Duration.seconds(30),
        timeout: cdk.Duration.seconds(30),
      }
    });    

    // Output
    new cdk.CfnOutput(this, 'AwsCdk31016LoadBalancerALBUrl', {
      value: `http://${ecs_pattern.loadBalancer.loadBalancerDnsName}`
    });

    const bucket = new s3.Bucket(this, "Bucket", {
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    const originGroup = new origins.OriginGroup({
      primaryOrigin: new origins.LoadBalancerV2Origin(ecs_pattern.loadBalancer, {
        protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY,
      }),
      fallbackOrigin: new origins.S3Origin(bucket),

    });

    const distribution = new cloudfront.Distribution(this, "Distribution", {
      defaultBehavior: { origin: originGroup },
    });

    // configure OAC for S3 bucket and CloudFront
    const oac = new cloudfront.CfnOriginAccessControl(this, 'ComposableStorefrontOriginAccessControl', {
      originAccessControlConfig: {
        name: 'ComposableStorefrontOriginAccessControl',
        originAccessControlOriginType: 's3',
        signingBehavior: 'always',
        signingProtocol: 'sigv4'
      }
    });

    //configure S3 Bucket to be accessed by Cloudfront only
    const allowCloudFrontReadOnlyPolicy = new iam.PolicyStatement({
      sid: 'allowCloudFrontReadOnlyPolicy',
      actions: ['s3:GetObject'],
      principals: [new iam.ServicePrincipal('cloudfront.amazonaws.com')],
      effect: iam.Effect.ALLOW,
      conditions: {
        'StringEquals': {
          "AWS:SourceArn": "arn:aws:cloudfront::" + this.account + ":distribution/" + distribution.distributionId
        }
      },
      resources: [bucket.bucketArn, bucket.bucketArn.concat('/').concat('*')]
    });
    bucket.addToResourcePolicy(allowCloudFrontReadOnlyPolicy)

    // OAC is not supported by CDK L2 construct yet. L1 construct is required
    const cfnDistribution = distribution.node.defaultChild as cloudfront.CfnDistribution

    //enable OAC for S3(the 2nd Origin)
    cfnDistribution.addPropertyOverride(
      'DistributionConfig.Origins.1.OriginAccessControlId',
      oac.getAtt('Id')
    )
    // disable OAI by setting an empty string for it
    // see - https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-s3originconfig.html
    cfnDistribution.addOverride('Properties.DistributionConfig.Origins.1.S3OriginConfig.OriginAccessIdentity', "");

    // Output
    new cdk.CfnOutput(this, 'Cloudfrontdistributionurl', {
      value: distribution.distributionDomainName
    });
  }
}
pahud commented 1 month ago

Let me know if it works for you.