Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
bersanf opened this issue Aug 3, 2024 · 9 comments
Open

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

bersanf opened this issue Aug 3, 2024 · 9 comments
Labels
@aws-cdk/aws-cloudfront Related to Amazon CloudFront bug This issue is a bug. effort/small Small work item – less than a day of effort p3

Comments

@bersanf
Copy link

bersanf commented Aug 3, 2024

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

@bersanf bersanf added bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. labels Aug 3, 2024
@github-actions github-actions bot added the @aws-cdk/aws-cloudfront Related to Amazon CloudFront label Aug 3, 2024
@nmussy
Copy link
Contributor

nmussy commented Aug 5, 2024

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
Copy link
Author

bersanf commented Aug 5, 2024

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
Copy link
Contributor

nmussy commented Aug 5, 2024

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 pahud self-assigned this Aug 5, 2024
@pahud
Copy link
Contributor

pahud commented Aug 5, 2024

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?

@pahud pahud added response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. p3 and removed needs-triage This issue or PR still needs to be triaged. labels Aug 5, 2024
@pahud pahud removed their assignment Aug 5, 2024
@pahud pahud added the effort/small Small work item – less than a day of effort label Aug 5, 2024
@bersanf
Copy link
Author

bersanf commented Aug 7, 2024

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')
    )

@github-actions github-actions bot removed the response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. label Aug 7, 2024
@pahud
Copy link
Contributor

pahud commented Aug 8, 2024

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
Copy link
Contributor

pahud commented Aug 8, 2024

internal tracking: V1479047809

@pahud pahud added the needs-cfn This issue is waiting on changes to CloudFormation before it can be addressed. label Aug 8, 2024
@pahud
Copy link
Contributor

pahud commented Aug 8, 2024

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 pahud added response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. and removed needs-cfn This issue is waiting on changes to CloudFormation before it can be addressed. labels Aug 8, 2024
@pahud
Copy link
Contributor

pahud commented Aug 8, 2024

Let me know if it works for you.

@github-actions github-actions bot removed the response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. label Aug 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@aws-cdk/aws-cloudfront Related to Amazon CloudFront bug This issue is a bug. effort/small Small work item – less than a day of effort p3
Projects
None yet
Development

No branches or pull requests

3 participants