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.72k stars 3.94k forks source link

[aws-certificatemanager] Create certificate in us-east-1 and use it in a different region #9274

Closed dbartholomae closed 4 years ago

dbartholomae commented 4 years ago

:question: General Issue

The Question

I'm currently setting up AWS Cognito with a custom domain via AWS CDK. Our stack lives in eu-central-1, but as I understand the certificate for the custom domain has to live in us-east-1. How can I share the certificate to the AWS Cognito setup?

Environment

Other information

There's a discussion around this already which indicates there might not be a solution to this?

njlynch commented 4 years ago

Hi @dbartholomae,

You generally have two different options for using a certificate cross-region.

  1. Create a dedicated stack for the certificate in us-east-1, and import the certificate in your Cognito stack (in eu-central-1).

  2. Use the DnsValidatedCertificate construct in your Cognito stack, which is a custom resource that can request a certificate cross-region.

Let me know if that helps!

dbartholomae commented 4 years ago

Thanks for the answer! It is good to know that DnsValidatedCertificate is able to request cross-region. Unfortunately the domain in question is not managed by Route53. For 1: How would I do this import in CDK, given that both stacks are defined in CDK? Do I need to change the CI setup to run multiple times for multiple stacks and somehow pass around the result from one call to the other?

njlynch commented 4 years ago

If you already have a Certificate created and defined, you can skip the second stack and just import it directly in your Cognito stack.

const certificate = acm.Certificate.fromCertificateArn(this, 'MyImportedCert', 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123...');

If you are creating the certificate in one stack (and validating via email or manually with another DNS provider), then you can pass a reference from one stack to another. Something like this:

class MyCertStack extends cdk.Stack {
  public readonly certificate: acm.Certificate;

  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    certificate = new acm.Certificate(...);
  }
}

interface MyCognitoStackProps extends cdk.StackProps {
  certificate: acm.Certificate;
}

class MyCognitoStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props: MyCognitoStackProps) {
    const certificate = props.certificate;
    // Define your Cognito pool here, using the above certificate
  }
}

const certStack = new MyCertificateStack(app, 'MyCerts');
new MyCognitoStack(app, 'MyCognito', { certificate: certStack.certificate });
dbartholomae commented 4 years ago

I'm trying the second solution, but so far I always get a cross-stack reference error because the certificate is in us-east-1, but the other stack in eu-central-1.

njlynch commented 4 years ago

Ah, that's correct. Sorry for the misinformation earlier.

Unfortunately, we currently only support cross-environment (region/account) references in instances where we can assign the physical names to the resources; in cases like certificates, where there is only the ARN, we don't currently have a solution.

See https://github.com/aws/aws-cdk/issues/8232#issuecomment-635639322 for one work-around suggestion of creating a custom resource to do the heavy lifting for you. It looks like there are various community-owned solutions to this; I found https://www.npmjs.com/package/@henrist/cdk-cross-region-params as one example. I haven't tested this and can't support it, but it looks like it could be used to pass the certificate ARN cross-stack. YMMV. :)

dbartholomae commented 4 years ago

Thanks! Using SSM is actually quite a smart solution. I'll look deeper into it.

robertofd1995 commented 3 years ago

Did aws cdk added officially support for this? I am also struggling with the same problem.

lenfree commented 3 years ago

@robertofd1995 not sure if are still stuck with this. If you still are, I think you might be looking for create cross region ACM.

A-ndy-git commented 2 years ago

+1 for this feature. I know there are some suggested workaround above, but would be nice to see an easier method to implement this.

My scenario is an Edge API in eu-west-2 with a custom domain. The certificate for the custom domain has to be created in us-east-1. Attempting to import the certificate via ARN outputs the cannot find ARN in eu-west-2 error.

Booligoosh commented 2 years ago

Thank you @lenfree, you just saved me hours of headaches 🤗

jedrekdomanski commented 1 year ago

@robertofd1995 not sure if are still stuck with this. If you still are, I think you might be looking for create cross region ACM.

You saved my day ❤️

Jaftem commented 1 year ago

@robertofd1995 not sure if are still stuck with this. If you still are, I think you might be looking for create cross region ACM.

Using cross-region certificate, HostedZone is a required attribute. For our purposes, we have our HostedZone in another AWS account so using this method will not work. This seems like an odd limitation as I can create a certificate in another region through the AWS console.

salihoglu87 commented 1 year ago

The "acm.DnsValidatedCertificate" is now deprecated. Is there any movement so we can create cross-region certificate using "acm.Certificate"? What is the difference between "acm.DnsValidatedCertificate" and "acm.Certificate" so that we can't set region by "acm.Certificate"?

peterfranzen commented 1 year ago

I'm also struggling with this. I'm trying to deploy a S3/CloudFront stack in us-west-2 with a certificate that was created in another stack in us-east-1. Short of writing a custom resource lambda to go hunt for the certificate and put its info into SSM Parameter Store, what is the best way to accomplish this?

mostafafarzaneh commented 1 year ago

The problem becomes a real nightmare when using a custom domain for Cognito UserPool. The UserPool CustomDomain needs an ICertificate, not a Certificate ARN. So there is no solution besides DnsValidatedCertificate which is deprecated. The CloudFront accepts the Certificate ARN, which you can somehow work around with a custom resource. But not in the Cognito Custom domain case.

This issue should not be closed!

LukaHedt commented 1 year ago

Yeah this is going to become a real problem using certs for Cloudfront too. Requiring downtime for playing with cloudformation stack regions (because of a construct deprecation) is not very fun DevOps. Also makes for some very, very annoying CDK infrastructure dependencies, which drives me insane.

Brian-Azizi commented 1 year ago

Why has DnsValidatedCertificate been deprecated? How is it's usecase solved with Certificate (which is the suggested alternative)?

vincent-milia commented 1 year ago

Why has DnsValidatedCertificate been deprecated? How is it's usecase solved with Certificate (which is the suggested alternative)?

y I just came across the same problem. I believe one option to solve this is to create the certificate in a separate stack (with env.region: us-east-1). Then reference the certificate in the other stack and use lookup. Don't know if there is a better way.

mostafafarzaneh commented 1 year ago

Why has DnsValidatedCertificate been deprecated? How is it's usecase solved with Certificate (which is the suggested alternative)?

y I just came across the same problem. I believe one option to solve this is to create the certificate in a separate stack (with env.region: us-east-1). Then reference the certificate in the other stack and use lookup. Don't know if there is a better way.

Is there a function for lookup in Certificate construct? there is fromCertificateArn, but you cannot use it with cross-region certificates.

JavierMendozaGomez commented 1 year ago

I had to write everything in the same app. Defining the two stacks in the same file.


const app = new App();

// Certificate Stack

// The certificate needs to deployed in us-east-1 and using it in index.ts as it needs to be used in the other stack, this is the reason why we put the two stacks here as its impossible to share references between stacks cross regions
// https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_certificatemanager-readme.html

const certificateStack = new Stack(
  app,
  `CertificateStack`,
  {
    env: {
      account: process.env.AWS_ACCOUNT_ID || process.env.CDK_DEFAULT_ACCOUNT,
      region: 'us-east-1',
    },
    crossRegionReferences: true,
  }
);

const yourHostedZone = HostedZone.fromHostedZoneAttributes(
  CertificateStack,
  'YourHostedZone',
  {
    hostedZoneId: yourHostedZoneId
    zoneName: yourHostedZoneName,
  }
);

// Create a certificate for the custom domain in us-east-1 as it is mandatory for Cognito
const yourCertificate = new Certificate(
  certificateStack,
  'YourCertificateStack',
  {
    domainName: YourAPIDomain,
    validation: CertificateValidation.fromDns(yourHostedZone),
  }
);

/// / USER POOL STACK
const userPoolStack = new Stack(app, `UserPoolStack`, {
  env: {
    account: process.env.AWS_ACCOUNT_ID || process.env.CDK_DEFAULT_ACCOUNT,
    region: process.env.AWS_REGION || process.env.CDK_DEFAULT_REGION,
  },
  crossRegionReferences: true,
});

const yourUserPool = new UserPool(userPoolStack, 'UserPool', {
  userPoolName: `yourUserPoolName`,
});

// Using the UserPoolDomain
const userPoolDomain = new UserPoolDomain(userPoolStack, 'UserPoolDomain', {
  userPool: yourUserPool,
  customDomain: {
    domainName: yourAPIDomainURL,
    certificate: yourCertificate,
  },
});

// Creation of ARecord
new ARecord(userPoolStack, 'UserPoolARecord', {
  zone: yourHostedZone,
  recordName: yourAPIDomainURL,
  target: RecordTarget.fromAlias(new UserPoolDomainTarget(userPoolDomain)),
});

And as everything is in the same App and not separated in different stacks, I unit tested it using the cdk out of executingcdk synth`

sensedata1 commented 1 year ago

We now also have a lot of refactoring to do as it appears that the deprecated construct will no longer create the resources defined in our stacks. If one of the certificates gets deleted, we're currently unable to just re-deploy to replace it. Not best pleased.

mhkafadar commented 1 year ago

Hello,

I've successfully implemented a cross-region deployment, positioning the S3-Cloudfront stack in Europe and the certificate in the us-east-1 region. I've detailed my approach in a blog post. https://medium.com/@mhkafadar/a-practical-aws-cdk-walkthrough-deploying-multiple-websites-to-s3-and-cloudfront-7caaabc9c327

epiphone commented 1 year ago

Why has DnsValidatedCertificate been deprecated? How is it's usecase solved with Certificate (which is the suggested alternative)?

y I just came across the same problem. I believe one option to solve this is to create the certificate in a separate stack (with env.region: us-east-1). Then reference the certificate in the other stack and use lookup. Don't know if there is a better way.

This is now documented in https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_certificatemanager-readme.html#cross-region-certificates.

DanielLaberge commented 10 months ago

This is quite an annoying pitfall that everyone gets into when using CloudFront with ACM and not deploying resources in us-east-1. Shouldn't solving this kind of headache be CDK's raison d'être?

rredpoppy commented 9 months ago

this is just downrigth awful..

case303 commented 8 months ago

Thanks @JavierMendozaGomez and @epiphone This provided good insight around: scope, stacks, and regions. Specify the desired region in a new Stack, then pass that stack into the Construct's scope. As best illustrated in the docs: Cross-region Certificates

const certificateStack = new cdk.Stack(this, CERTIFICATE_STACK_ID,
      {
        env: {
          account: process.env.AWS_ACCOUNT_ID || process.env.CDK_DEFAULT_ACCOUNT,
          region: 'us-east-1'
        },
        crossRegionReferences: true,
      });

const certificate = new acm.Certificate(certificateStack, CERTIFICATE_ID, {
      certificateName: CERTIFICATE_ID,
      domainName: DOMAIN_NAME,
      subjectAlternativeNames: [`*.${DOMAIN_NAME}`],
      validation: acm.CertificateValidation.fromDns(hostedZone),
    });
pqnet commented 8 months ago

Unfortunately having a cross-stack reference has the limitation of having to hard-code the zone for all other stacks in the app because Cross stack/region references are only supported for stacks with an explicit region defined.. It would be great if any of this would be true:

quantfreedom commented 7 months ago

be sure to upvote this or thumbs up it or mark it as an answer so others know it works ... only if it works for you of course ... this way we can save others weeks of trying to figure this out

I have finally figured out how to actually do this ... i am using cdk 2.128.0

This actually works because i am in ca-central-1 and i was able to do it in one stack and created a us-east-1 cert

you can checkout the github code here https://github.com/quantfreedom/aws_cdk_testing/blob/main/amazon_cdk/frontend/www.py

if that doesn't work here is a gist of the current working version https://gist.github.com/quantfreedom/e71267553edc8e0760e88d48ad8b45a7

I watched this youtube video to get an understanding of what is going on and why i am doing the things i am doing https://www.youtube.com/watch?v=p6Os-_t0gEs

and here is their gh code https://github.com/Durgaprasad-Budhwani/hands-on-aws-cdk-lab/blob/main/ts/cloudfront/lib/cloudfront-stack.ts

also here is a link to my github repo where i am just testing out different aws stacks https://github.com/quantfreedom/aws_cdk_testing/tree/main