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

CloudFrontToS3: ssl certificate not added #1147

Open
mattiLeBlanc opened this issue Jul 13, 2024 · 17 comments
Open

CloudFrontToS3: ssl certificate not added #1147

mattiLeBlanc opened this issue Jul 13, 2024 · 17 comments
Labels
bug Something isn't working needs-triage The issue or PR still needs to be triaged

Comments

@mattiLeBlanc
Copy link

mattiLeBlanc commented Jul 13, 2024

I am adding an existing ACM certificate to my deployment and it is not reflected in Cloudfront. Manually adding it to cloudfront works, so I believe the certificate domains are valid.

Reproduction Steps

import { Construct } from 'constructs';
import { CloudFrontToS3, CloudFrontToS3Props } from '@aws-solutions-constructs/aws-cloudfront-s3';
import { Bucket, BucketProps } from 'aws-cdk-lib/aws-s3';
import { Certificate } from 'aws-cdk-lib/aws-certificatemanager';
import { Distribution, SSLMethod, SecurityPolicyProtocol } from 'aws-cdk-lib/aws-cloudfront';
import { BucketDeployment, Source } from 'aws-cdk-lib/aws-s3-deployment';
import { Duration } from 'aws-cdk-lib';
import { Dictionary } from '../../../libs/cdk/model/base.model';
import { awsConfig } from '../../includes/aws-export';
import { ARecord, HostedZone, IHostedZone, RecordTarget } from 'aws-cdk-lib/aws-route53';
import { CloudFrontTarget } from 'aws-cdk-lib/aws-route53-targets';

interface HostingConstructProps {
  environmentName: string;
  applicationName: string;
  rootName: string;
  s3BucketName: string;
  dist: string;
  rootDomainName: string;
  subDomain: string;
  envDomains: Dictionary<string>;
}

export class HostingConstruct extends Construct {
  appBucket: Bucket;
  distribution: Distribution;
  props: HostingConstructProps;

  constructor(scope: Construct, readonly id: string, props: HostingConstructProps) {
    super(scope, id);
    this.props = props;
    const { rootDomainName, subDomain, envDomains } = this.props;

    this.createDeploymentBucket();

    let distributionUrl = '';
    let hostedZoneUrl = '';
    // local user deployed env
    if (!['production', 'development', 'staging'].includes(this.props.environmentName)) {
      hostedZoneUrl = `dev.${rootDomainName}`;
      distributionUrl = `${this.props.environmentName}.dev.${this.props.rootDomainName}`;
    // cloud deployed environment
    } else {
      // production gets deployed to the root domain
      if (process.env.ENV === 'production') {
        hostedZoneUrl = `${rootDomainName}`;
        distributionUrl = `${subDomain}.${rootDomainName}`;
        //  dev get an `app`subdomain on their env sub domain
      } else {
        hostedZoneUrl = `${envDomains[process.env.ENV!]}.${rootDomainName}`;
        distributionUrl = `${subDomain}.${envDomains[process.env.ENV!]}.${rootDomainName}`;
      }
    }

    console.log(`[Creating distribution] distributionUrl ${distributionUrl}`);
    const cfTos3Props: CloudFrontToS3Props = {
      existingBucketObj: this.appBucket,
      insertHttpSecurityHeaders: false, // This header resolves issues with angular inlining CSS etc
      cloudFrontDistributionProps: {
        domainNames: [distributionUrl],
        enableLogging: false,
        errorResponses: [
          { httpStatus: 403, responseHttpStatus: 200, responsePagePath: '/index.html', ttl: Duration.seconds(0) },
          { httpStatus: 404, responseHttpStatus: 200, responsePagePath: '/index.html', ttl: Duration.seconds(0) }
        ]
      }
    }
    if (awsConfig.certificates && awsConfig.certificates[subDomain]) {
      console.log(`[Using certificate] ${awsConfig.certificates[subDomain]}`);
      cfTos3Props.cloudFrontDistributionProps.certificate = Certificate.fromCertificateArn(this, 'certificate',awsConfig.certificates[subDomain]);
      cfTos3Props.cloudFrontDistributionProps.minimumProtocolVersion = SecurityPolicyProtocol.TLS_V1_2_2021;
      cfTos3Props.cloudFrontDistributionProps.sslSupportMethod = SSLMethod.SNI;
    }

    // https://docs.aws.amazon.com/solutions/latest/constructs/aws-cloudfront-s3.html
    const instance = new CloudFrontToS3(this, `${this.props.applicationName}CfDistribution`, cfTos3Props);
    const { hostedZone } = this.getHostedZone(hostedZoneUrl);

    this.distribution = instance.cloudFrontWebDistribution;
    this.createRoute53Records(hostedZone, distributionUrl)
    this.appDeployment();
  }

  private appDeployment() {
    new BucketDeployment(this, `${this.props.applicationName}Source`, {
      sources: [Source.asset(this.props.dist)],
      destinationBucket: this.appBucket,
      distribution: this.distribution || undefined,
      distributionPaths: ['/*'],

    });
  }

  private getHostedZone(hostedZoneUrl: string) {
    console.log(`Find hosted zone for ${hostedZoneUrl}`);
    const hostedZone = HostedZone.fromLookup(this, `${this.props.rootName}HostedZone`, {
      domainName: hostedZoneUrl
    });
    return { hostedZone };
  }

  private createRoute53Records(
    hostedZone: IHostedZone,
    url: string
  ) {
    new ARecord(this, 'Alias', {
      zone: hostedZone,
      recordName: url,
      target: RecordTarget.fromAlias(new CloudFrontTarget(this.distribution)),
    });
  }

  private createDeploymentBucket() {
    const appBucketProps: BucketProps = {
      bucketName: `${this.props.applicationName.replace(/_/g, '-')}`,
    };
    this.appBucket = new Bucket(this, `${this.props.applicationName}Bucket`, appBucketProps);
  }
}

This code deploys my angular app to an s3 bucket, creates the distribution and adds a record to the hosted zone.
That all works well, but the certificate is not displayed in the distribution when I have a look at it:
image

The Arn of the certificate is correct and I can manually select it. So for some reason the certificate reference I get with Certificate.fromCertificateArn is not accepted.

Error Log

Certificate is not added to distribution:
image
No error is happening during deploy

Environment

  • **CDK CLI Version : ^2.143.0
  • **CDK Framework Version:*^2.143.0
  • **AWS Solutions Constructs Version : 2.58.0
  • **OS : Macos
  • **Language : typescript

Other


This is 🐛 Bug Report

@mattiLeBlanc mattiLeBlanc added bug Something isn't working needs-triage The issue or PR still needs to be triaged labels Jul 13, 2024
@mattiLeBlanc
Copy link
Author

I am also getting this when I try to deploy:

 hosting/dashboardCfDistribution/CloudFrontDistribution (hostingdashboardCfDistributionCloudFrontDistribution21525E03) Resource handler returned message: "Invalid request provided: To add an alternate domain name (CNAME) to a CloudFront distribution, you must attach a trusted certificate that validates your authorization to use the domain name. For more details, see: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/CNAMEs.html#alternate-domain-names-requirements (Service: CloudFront, Status Code: 400, Request ID: 8751bbd4-e39a-49ce-8819-19e814f38019)" (RequestToken: 1a0d152c-2686-e0c1-0335-bace8b52fc75, HandlerErrorCode: InvalidRequest)

But again, I can manually add the domain, and after doing that, the deployment will succeed, but it will remove the certicate from the distribution. Like it s not compatible or something.

@mattiLeBlanc
Copy link
Author

mattiLeBlanc commented Aug 9, 2024

Any news on this?
I just did a deploy and I got this error:

Invalid request provided: To add an alternate domain name (CNAME) to a CloudFront distribution, you must attach a trusted certificate that validates your authorization to use the domain name.

The domain name I am using is covered in the certificate as an alternate domain with a wildcard, like *.domain.com.
I am trying to deploy foo.domain.com as the alternate domain name.

When I deploy without these settings I can then add them in manually via Cloudfront and route53 and I am getting no errors.

My code looks like this:

import { Construct } from 'constructs';
import { CloudFrontToS3, CloudFrontToS3Props } from '@aws-solutions-constructs/aws-cloudfront-s3';
import { Bucket, BucketProps } from 'aws-cdk-lib/aws-s3';
import { Certificate } from 'aws-cdk-lib/aws-certificatemanager';
import { Distribution, SSLMethod, SecurityPolicyProtocol } from 'aws-cdk-lib/aws-cloudfront';
import { BucketDeployment, Source } from 'aws-cdk-lib/aws-s3-deployment';
import { Duration } from 'aws-cdk-lib';
import { Dictionary } from '../../../libs/cdk/model/base.model';
import { awsConfig } from '../../includes/aws-export';
import { ARecord, HostedZone, IHostedZone, RecordTarget } from 'aws-cdk-lib/aws-route53';
import { CloudFrontTarget } from 'aws-cdk-lib/aws-route53-targets';

interface HostingConstructProps {
  environmentName: string;
  applicationName: string;
  rootName: string;
  s3BucketName: string;
  dist: string;
  rootDomainName: string;
  subDomain: string;
  envDomains: Dictionary<string>;
}

export class HostingConstruct extends Construct {
  appBucket: Bucket;
  distribution: Distribution;
  props: HostingConstructProps;

  constructor(scope: Construct, readonly id: string, props: HostingConstructProps) {
    super(scope, id);
    this.props = props;
    const { rootDomainName, subDomain, envDomains } = this.props;

    this.createDeploymentBucket();

    let distributionUrl = '';
    let hostedZoneUrl = '';
    // local user deployed env
    if (!['production', 'development', 'staging'].includes(this.props.environmentName)) {
      hostedZoneUrl = `dev.${rootDomainName}`;
      distributionUrl = `${this.props.environmentName}.dev.${this.props.rootDomainName}`;
    // cloud deployed environment
    } else {
      // production gets deployed to the root domain
      if (process.env.ENV === 'production') {
        hostedZoneUrl = `${rootDomainName}`;
        distributionUrl = `${subDomain}.${rootDomainName}`;
        //  dev get an `app`subdomain on their env sub domain
      } else {
        hostedZoneUrl = `${envDomains[process.env.ENV!]}.${rootDomainName}`;
        distributionUrl = `${subDomain}.${envDomains[process.env.ENV!]}.${rootDomainName}`;
      }
    }

    console.log(`[Creating distribution] distributionUrl ${distributionUrl}`);
    const cfTos3Props: CloudFrontToS3Props = {
      existingBucketObj: this.appBucket,
      insertHttpSecurityHeaders: false, // This header resolves issues with angular inlining CSS etc
      cloudFrontDistributionProps: {
        // domainNames: [distributionUrl],
        enableLogging: false,
        errorResponses: [
          { httpStatus: 403, responseHttpStatus: 200, responsePagePath: '/index.html', ttl: Duration.seconds(0) },
          { httpStatus: 404, responseHttpStatus: 200, responsePagePath: '/index.html', ttl: Duration.seconds(0) }
        ]
      }
    }
    if (awsConfig.certificates && awsConfig.certificates[subDomain]) {
      console.log(`[Using certificate] ${awsConfig.certificates[subDomain]}`);
//currently throwing an error
      // cfTos3Props.cloudFrontDistributionProps.certificate = Certificate.fromCertificateArn(this, 'certificate',awsConfig.certificates[subDomain]);
      // cfTos3Props.cloudFrontDistributionProps.minimumProtocolVersion = SecurityPolicyProtocol.TLS_V1_2_2021;
      // cfTos3Props.cloudFrontDistributionProps.sslSupportMethod = SSLMethod.SNI;
    }
    console.log(cfTos3Props.cloudFrontDistributionProps);
    // https://docs.aws.amazon.com/solutions/latest/constructs/aws-cloudfront-s3.html
    const instance = new CloudFrontToS3(this, `${this.props.applicationName}CfDistribution`, cfTos3Props);
    const { hostedZone } = this.getHostedZone(hostedZoneUrl);

    this.distribution = instance.cloudFrontWebDistribution;
    // CURRENTLY NOT WORKING :(
    // this.createRoute53Records(hostedZone, distributionUrl)
    this.appDeployment();
  }

  private appDeployment() {
    new BucketDeployment(this, `${this.props.applicationName}Source`, {
      sources: [Source.asset(this.props.dist)],
      destinationBucket: this.appBucket,
      distribution: this.distribution || undefined,
      distributionPaths: ['/*'],

    });
  }

  private getHostedZone(hostedZoneUrl: string) {
    console.log(`Find hosted zone for ${hostedZoneUrl}`);
    const hostedZone = HostedZone.fromLookup(this, `${this.props.rootName}HostedZone`, {
      domainName: hostedZoneUrl
    });
    return { hostedZone };
  }

  private createRoute53Records(
    hostedZone: IHostedZone,
    url: string
  ) {
    new ARecord(this, 'Alias', {
      zone: hostedZone,
      recordName: url,
      target: RecordTarget.fromAlias(new CloudFrontTarget(this.distribution)),
    });
  }

  private createDeploymentBucket() {
    const appBucketProps: BucketProps = {
      bucketName: `${this.props.applicationName.replace(/_/g, '-')}`,
    };
    this.appBucket = new Bucket(this, `${this.props.applicationName}Bucket`, appBucketProps);
  }
}

Can I get some feedback on this?
In my other apps I still use the OAI setup and that has no problem with the certificates and creating a A record with alias).

@mattiLeBlanc
Copy link
Author

Can someone confirm this is an issue or am I doing something wrong?

@biffgaut
Copy link
Contributor

Sorry, this slipped by us - we'll take a look.

@mattiLeBlanc
Copy link
Author

@biffgaut Thank you.
I dont really want to go back to using an OAI identity for cloudfront s3 deployments.
But I am currently deploying and I can't assign SSL or custom domain name programatically but I can do it manually after the deploy.

@biffgaut
Copy link
Contributor

Does the ACM code work when you use an older version of aws-cloudfront-s3 that is still based upon OAI? Is this a change when we rolled out OAC?

@mattiLeBlanc
Copy link
Author

@biffgaut no before I would not use the AWS-cloudfront-s3 function.
I use the old way:


  private createCloudFrontDistribution(url: string) {
    console.log(`Create distribution for ${url}`);

    // create the cloudFront function
    const documentReWriteFunction = this.createCloudFrontFunction();


    const aliases = [url];


    const appIdentity = new OriginAccessIdentity(this, `${this.props.applicationName}SourceOAIdentity`, {
      comment: `${this.props.applicationName}-source`
    });
    this.appBucket.grantRead(appIdentity);

    const s3DocumentIdentity = new OriginAccessIdentity(this, `${this.props.applicationName}DocumentOAIdentity`, {
      comment: `${this.props.applicationName}-documents`
    });

    // currently you can use this control with the distribution origin L2 constructs.
    // const s3DocumentControl = new CfnOriginAccessControl(this, `${this.props.applicationName}DocumentControl`, {
    //   originAccessControlConfig: {
    //     name: `${this.props.applicationName}-documents`,
    //     originAccessControlOriginType: 's3',
    //     signingBehavior: 'no-override',
    //     signingProtocol: 'sigv4'
    //   }
    // })

    // NOTE: Update bucket policy for s3DocumentBucket OAI manually in the origin
    const s3DocumentBucket = Bucket.fromBucketName(this, `${this.props.applicationName}}Documents`, `${this.props.s3BucketName}-documents`);


    this.distribution = new CloudFrontWebDistribution(this, `${this.props.applicationName}Distribution`, {
      originConfigs: [
        {
          s3OriginSource: {
            s3BucketSource: this.appBucket,
            originAccessIdentity: appIdentity
          },
          behaviors: [{
            isDefaultBehavior: true
          }],
        },
        {
          s3OriginSource: {
            s3BucketSource:  s3DocumentBucket,
            originAccessIdentity: s3DocumentIdentity,
          },
          behaviors: [{
            pathPattern: '/assets/public/*',
            functionAssociations: [
              {
                function: documentReWriteFunction,
                eventType: FunctionEventType.VIEWER_REQUEST,
              }
            ]
          }]
        }
      ],
      viewerCertificate: {
        aliases: aliases,
        props: {
          acmCertificateArn: awsConfig.certificates.app,
          sslSupportMethod: 'sni-only',
          minimumProtocolVersion: 'TLSv1.2_2021',
        }
      },
      errorConfigurations: [
        {
          errorCode: 404,
          responseCode: 200,
          errorCachingMinTtl: 0,
          responsePagePath: '/index.html'
        }
      ]
    });
  }

@mattiLeBlanc
Copy link
Author

@biffgaut any update on this? Do you need something from me to expedite this?

@biffgaut
Copy link
Contributor

We had some issues with our publication pipeline that sidetracked us for a while, but hope to look at this this week.

@biffgaut
Copy link
Contributor

The code below works for me, I need to manually change the Alias record in the Route53 Hosted Zone, but the certificate is registered with the CloudFront distribution:

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

    // Get the certificate registered with biffstestdomain.net (details changed)
    const myCert = acm.Certificate.fromCertificateArn(
      this,
      "cert1147",
      "arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012"
    )
    const constructProps: CloudFrontToS3Props = {
      cloudFrontDistributionProps: {
        domainNames: ["biffstestdomain.net"],
        certificate: myCert,
      }
    };

    const target = new CloudFrontToS3(this, 'issue1147', constructProps);

    // Just loading a Hello, World web page to retrieve
    new s3deploy.BucketDeployment(this, 'DeployWebsite', {
      sources: [s3deploy.Source.asset('./content')],
      destinationBucket: target.s3Bucket!,
    });
  }
}

@biffgaut
Copy link
Contributor

Can you see any differences between this and what you're trying to accomplish? Did I interpret the question correctly?

@biffgaut
Copy link
Contributor

FWIW - I'll leave biffstestdomain.net running for a little while.

@mattiLeBlanc
Copy link
Author

mattiLeBlanc commented Aug 31, 2024

@biffgaut
I did the same thing plus some extra settings:

 if (awsConfig.certificates && awsConfig.certificates[subDomain]) {
      console.log(`[Using certificate] ${awsConfig.certificates[subDomain]}`);
      cfTos3Props.cloudFrontDistributionProps.certifacte = Certificate.fromCertificateArn(this, 'certificate',awsConfig.certificates[subDomain]);
      cfTos3Props.cloudFrontDistributionProps.minimumProtocolVersion = SecurityPolicyProtocol.TLS_V1_2_2021;
      cfTos3Props.cloudFrontDistributionProps.sslSupportMethod = SSLMethod.SNI;
    }

I also had distrubution settings in the BucketDeployment. This is a leftover from the old way I am doing it.
But you are not using it. Is it required? I read in the code comments that adding the distribution will help invalidate the cache in edge locations.

   new BucketDeployment(this, `${this.props.applicationName}Source`, {
      sources: [Source.asset(this.props.dist)],
      destinationBucket: this.appBucket,
      distribution: this.distribution || undefined,
      distributionPaths: ['/*'],

    });

Then creating the ARecord didn't work because the certificate wasn't accepted.

...
new ARecord(this, 'Alias', {
      zone: hostedZone,
      recordName: url,
      target: RecordTarget.fromAlias(new CloudFrontTarget(this.distribution)),
    });

I will try to create a fresh deployment and see if it throws the same error and then maybe remove the minimumProtocolVersion and sslSupportMethod and try again.
I will report back

@mattiLeBlanc
Copy link
Author

mattiLeBlanc commented Sep 1, 2024

@biffgaut
Okay, I got it almost working now.
In the cloudFrontDistributionProps I had domainNames commented out, so that didn't help with installing the certificate.
The ARecord also get's created.
The only issue I am now resolving is that my bucket had not permissions setup for Cloudfront to access it.
So I am looking into that now.\

I also noticed I had a typo in the original code under cloudFrontDistributionProps.certificate, it was spelled cloudFrontDistributionProps.certifacte so that doesn't help, ouch.

The permission error I am getting is:

<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>NAQBDP1V0G1738E3</RequestId>
<HostId>vJgfdszC7swYpaOvY4OxB5ayAgyhY7vb2zj2UmYGwMi8NFy8ZtuUGPh+e/5Lz+hk4RZ9IYtlEjY=</HostId>
</Error>

I see no permissions in my s3 bucket's policy.
When I check the cloudfront origin, I see:
image

Then when I follow the link to the s3 bucket, I see a new s3 bucket has been created.
I now see that I am mixing things:
I was creating my own deployment bucket (to control the name a bit better) and then I removed existingBucketObj in my latest test, so CloudFrontToS3 was creating its own bucket, but I was doing an app deployment to my bucket.

I see that I can also change the name of the bucket using bucketProps in the CloudFrontToS3 properties.

Edit:
image

After using this:

 const instance = new CloudFrontToS3(this, `${this.props.applicationName}CfDistribution`, {
      // existingBucketObj: this.appBucket,
      bucketProps: {
        bucketName: `${this.props.applicationName.replace(/_/g, '-')}`,
      },
      insertHttpSecurityHeaders: false, // This header resolves issues with angular inlining CSS etc
      cloudFrontDistributionProps: {
        domainNames: [distributionUrl],
        enableLogging: false,
        certificate: myCert,
        minimumProtocolVersion: SecurityPolicyProtocol.TLS_V1_2_2021,
        sslSupportMethod: SSLMethod.SNI,
        errorResponses: [
          { httpStatus: 403, responseHttpStatus: 200, responsePagePath: '/index.html', ttl: Duration.seconds(0) },
          { httpStatus: 404, responseHttpStatus: 200, responsePagePath: '/index.html', ttl: Duration.seconds(0) }
        ]
      }
    });
new BucketDeployment(this, `${this.props.applicationName}Source`, {
      sources: [Source.asset(this.props.dist)],
      destinationBucket: instance.s3Bucket!
    });

I ended up with 2 buckets, as you can see in screenshot, and they where both empty.
I guess if you use bucketProps with bucketName it is not creating a new bucket?
When I ran the deploy again without deleting my stack, I saw files added to the tiffany-mattijs-dashboard.

@mattiLeBlanc
Copy link
Author

@biffgaut Good news, it works now. I do have to create my own bucket so I can control the bucketname.
It seems I can't create the bucket via the bucketProps in the CloudfrontToS3 properties.

My full code is now:

    ....
    this.createDeploymentBucket();

    let distributionUrl = '';
    let hostedZoneUrl = '';

    // local user deployed env
    if (!['production', 'development', 'staging'].includes(this.props.environmentName)) {
      hostedZoneUrl = `dev.${rootDomainName}`;
      distributionUrl = `${this.props.environmentName}.dev.${this.props.rootDomainName}`;
    // cloud deployed environment
    } else {
      // production gets deployed to the root domain
      if (process.env.ENV === 'production') {
        hostedZoneUrl = `${rootDomainName}`;
        distributionUrl = `${subDomain}.${rootDomainName}`;
        //  dev get an `app`subdomain on their env sub domain
      } else {
        hostedZoneUrl = `${envDomains[process.env.ENV!]}.${rootDomainName}`;
        distributionUrl = `${subDomain}.${envDomains[process.env.ENV!]}.${rootDomainName}`;
      }
    }

    console.log(`[Creating distribution] distributionUrl ${distributionUrl}`);
    console.log(`[Using certificate] ${awsConfig.certificates![subDomain]}`);

    const myCert = awsConfig.certificates && awsConfig.certificates[subDomain] ?
      Certificate.fromCertificateArn(this, 'certificate',awsConfig.certificates![subDomain]) : null;

    // https://docs.aws.amazon.com/solutions/latest/constructs/aws-cloudfront-s3.html
    const instance = new CloudFrontToS3(this, `${this.props.applicationName}CfDistribution`, {
      existingBucketObj: this.appBucket,
      insertHttpSecurityHeaders: false, // This header resolves issues with angular inlining CSS etc
      cloudFrontDistributionProps: {
        domainNames: [distributionUrl],
        enableLogging: false,
        certificate: myCert,
        minimumProtocolVersion: SecurityPolicyProtocol.TLS_V1_2_2021,
        sslSupportMethod: SSLMethod.SNI,
        errorResponses: [
          { httpStatus: 403, responseHttpStatus: 200, responsePagePath: '/index.html', ttl: Duration.seconds(0) },
          { httpStatus: 404, responseHttpStatus: 200, responsePagePath: '/index.html', ttl: Duration.seconds(0) }
        ]
      }
    });
    const { hostedZone } = this.getHostedZone(hostedZoneUrl);
    this.distribution = instance.cloudFrontWebDistribution;
    this.createRoute53Records(hostedZone, distributionUrl)
    this.appDeployment();
  }

  private appDeployment() {
    new BucketDeployment(this, `${this.props.applicationName}Source`, {
      sources: [Source.asset(this.props.dist)],
      destinationBucket: this.appBucket,
    });
  }

  private getHostedZone(hostedZoneUrl: string) {
    console.log(`Find hosted zone for ${hostedZoneUrl}`);
    const hostedZone = HostedZone.fromLookup(this, `${this.props.rootName}HostedZone`, {
      domainName: hostedZoneUrl
    });
    return { hostedZone };
  }

  private createRoute53Records(
    hostedZone: IHostedZone,
    url: string
  ) {
    new ARecord(this, 'Alias', {
      zone: hostedZone,
      recordName: url,
      target: RecordTarget.fromAlias(new CloudFrontTarget(this.distribution)),
    });
  }

  private createDeploymentBucket() {
    const appBucketProps: BucketProps = {
      bucketName: `${this.props.applicationName.replace(/_/g, '-')}`,
    };
    this.appBucket = new Bucket(this, `${this.props.applicationName}Bucket`, appBucketProps);
  }

@biffgaut
Copy link
Contributor

biffgaut commented Sep 3, 2024

Glad you got it working. A couple things to think about:

  • I'm not sure why you couldn't control the name through bucketProps in the CloudfrontToS3Props - I was able to do so (see the code below). With your current code you are missing out on best practices that come by default from the CloudFrontToS3 construct, such as: versioning, S3 access logs, SSL access required, etc. (OTOH - the code you show may be reduced for clarity and you are already doing all this)
  • You mentioned seeing 2 buckets - be aware that by default the construct will create 4 buckets: The Content Bucket and a bucket to hold the S3 access logs of the content bucket, and a bucket to hold CloudFront logs and a bucket to hold the S3 access logs for the CloudFront logs bucket. There are props to turn off the 2 S3 Access Logging buckets, or each bucket can be customized. Our docs haven't been refreshed recently, but the details can be found at the README.md page for the Construct.
  • Unless an external dependency out of your control requires a specific resource name, we find it better to let CloudFormation create a unique name that is constant for the lifetime of the stack to ensure a) that it can be destroyed cleanly; b) there are not name collisions when the app is launched twice in an account (or any account when we're talking about S3 bucket names). If you need to specify a name, consider making use of cdk.Aws.STACK_ID as part of the name. This will ensure the name is unique to the specific stack.
    const constructProps: CloudFrontToS3Props = {
      bucketProps: {
        bucketName: "myname-issue1147",
      },
      cloudFrontDistributionProps: {
        domainNames: ["biffstestdomain.net"],
        certificate: myCert,
      }
    };

    const target = new CloudFrontToS3(this, 'issue1147', constructProps);

@mattiLeBlanc
Copy link
Author

@biffgaut Thanks for the clarification.
I will try again if the construct will create the bucket with my name or if that doesn't work.

I understand prefer using my own naming convention which is linked to stack and environment and has some semantic meaning to me.
I think we can close this issue now. If I have trouble with the bucket creation, I can report that in a separate issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working needs-triage The issue or PR still needs to be triaged
Projects
None yet
Development

No branches or pull requests

2 participants