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::Cognito::UserPoolDomain missing return value for the CloudFront target #356

Open
mvanleest opened this issue Jan 22, 2020 · 14 comments
Labels
enhancement New feature or request networking & content deliv VPC, CloudFront, Route 53, API Gateway, Direct Connect, AWS App Mesh, etc.
Milestone

Comments

@mvanleest
Copy link

I am missing the return value for the CloudFront target so that you could create a DNS record in the same template so that you could do this:

  DNS:
    Type: AWS::Route53::RecordSet
    Properties:
      HostedZoneName: mydomain.com.
      Name: myuserpool.mydomain.com.
      Type: A
      AliasTarget:
        HostedZoneId: Z2FDTNDATAQYW2
        DNSName: !GetAtt UserPoolDomain.DomainName

  UserPoolDomain:
    Type: AWS::Cognito::UserPoolDomain
    Properties:
      UserPoolId: !Ref UserPool
      Domain: myuserpool.mydomain.com
      CustomDomainConfig:
        CertificateArn: !Ref CertificateArn

Originally posted by @Nr18 in #58 (comment)

@lkolchin
Copy link

lkolchin commented Feb 5, 2020

Yeah need this one too.

@TheDanBlanco TheDanBlanco added networking & content deliv VPC, CloudFront, Route 53, API Gateway, Direct Connect, AWS App Mesh, etc. enhancement New feature or request labels Feb 10, 2020
@jk2l
Copy link

jk2l commented Feb 25, 2020

i am glad i am not the only need this feature... back to write my Cfn custom resources for now

@lkolchin
Copy link

i am glad i am not the only need this feature... back to write my Cfn custom resources for now

Please share it here....until AWS provides proper resolution :)

@jk2l
Copy link

jk2l commented Feb 26, 2020

sorry, as contractor my works are IP of my client. can't share it out

@0xdevalias
Copy link

0xdevalias commented Mar 19, 2020

Edit 2: See this comment for the modern AWS CDK version of this.


For those that end up here like I did, as a workaround it's possible to get the CloudFrontDistribution using the AWS SDK/CLI:


Edit: My personal workaround using https://github.com/aws/aws-cdk and @aws-cdk/custom-resources.AwsSdkCall:

const cdk = require('@aws-cdk/core')
const cognito = require('@aws-cdk/aws-cognito')
const cr = require('@aws-cdk/custom-resources')
const route53 = require('@aws-cdk/aws-route53')

// The userPool was defined earlier in the code (not shown here)

// This creates the user pool domain resource
const userPoolDomain = new cognito.CfnUserPoolDomain(
  this,
  'UserPoolDomain',
  {
    userPoolId: userPool.userPoolId,
    domain: authDomain,
    customDomainConfig: {
      certificateArn,
    },
  }
)
userPoolDomain.node.addDependency(userPool)

// This allows us to get the cloudfront distribution using a custom resource that calls the AWS SDK
const describeCognitoUserPoolDomain = new cr.AwsCustomResource(
  this,
  'DescribeCognitoUserPoolDomain',
  {
    resourceType: 'Custom::DescribeCognitoUserPoolDomain',
    onCreate: {
      region: 'us-east-1',
      service: 'CognitoIdentityServiceProvider',
      action: 'describeUserPoolDomain',
      parameters: {
        Domain: userPoolDomain.domain,
      },
      physicalResourceId: cr.PhysicalResourceId.of(userPoolDomain.domain),
    },
    // TODO: can we restrict this policy more? Get the ARN for the user pool domain? Or the user pool maybe?
    policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
      resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE,
    }),
  }
)
describeCognitoUserPoolDomain.node.addDependency(userPoolDomain)

const userPoolDomainDistribution = describeCognitoUserPoolDomain.getResponseField(
  'DomainDescription.CloudFrontDistribution'
)

@swoldemi
Copy link

I recently threw together a Custom Resource to do this and released it in AWS SAR: https://serverlessrepo.aws.amazon.com/applications/arn:aws:serverlessrepo:us-east-1:273450712882:applications~amazon-cognito-domain-distribution

Happy to iterate on any improvement ideas

@clorichel
Copy link

Here's how to get around it. Assuming you have a stack.yaml that you deploy with a CI tool, say through bash:

THE_STACK_NAME="my-cognito-stack"
THE_DOMAIN_NAME="auth.yourveryowndomain.org"

# get the alias target
# notice that it will be empty upon first launch (chicken and the egg problem)
ALIAS_TARGET=$(aws cognito-idp describe-user-pool-domain --domain ${THE_DOMAIN_NAME} | grep CloudFrontDistribution | cut -d \" -f4)

# create/update the deployment CloudFormation stack
# notice the AliasTarget parameter (which can be empty, it's okay!)
aws cloudformation deploy --stack-name ${THE_STACK_NAME} --template-file stack.yaml --parameter-overrides AliasTarget=${ALIAS_TARGET} DomainName=${THE_DOMAIN_NAME}

The stack.yaml minimal version (remember to fill the UserPool config):

---
AWSTemplateFormatVersion: 2010-09-09

Parameters:
  DomainName:
    Type: String
    Default: auth.yourveryowndomain.org
    Description: The domain name to use to serve this project.

  ZoneName:
    Type: String
    Default: yourveryowndomain.org
    Description: The hosted zone name coming along with the DomainName used.
  
  AliasTarget: # no default value, can be empty
    Type: String
    Description: The UserPoolDomain alias target.

Conditions: # here's "the trick"
  HasAliasTarget: !Not [!Equals ['', !Ref AliasTarget]]

Resources:
  Certificate:
    Type: "AWS::CertificateManager::Certificate"
    Properties: 
      DomainName: !Ref ZoneName
      DomainValidationOptions:
        - DomainName: !Ref ZoneName
          ValidationDomain: !Ref ZoneName
      SubjectAlternativeNames:
        - !Ref DomainName
  
  UserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      [... fill that with your configuration! ...]

  UserPoolDomain:
    Type: AWS::Cognito::UserPoolDomain
    Properties:
      UserPoolId: !Ref UserPool
      Domain: !Ref DomainName
      CustomDomainConfig:
        CertificateArn: !Ref Certificate

  DnsRecord: # if AliasTarget parameter is empty, well we just can't do that one!
    Condition: HasAliasTarget # and here's how we don't do it when we can't
    Type: AWS::Route53::RecordSet
    Properties:
      HostedZoneName: !Sub "${ZoneName}."
      AliasTarget:
        DNSName: !Ref AliasTarget
        EvaluateTargetHealth: false
        # HostedZoneId value for CloudFront is always this one
        # see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget.html
        HostedZoneId: Z2FDTNDATAQYW2
      Name: !Ref DomainName
      Type: A

Be aware CloudFormation conditions are not "a trick" at all: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html. We simply use it as a trick along with the "first launch won't do it all" to get around our scenario.

Kinda weird, but only for the first run! Launch it again: everything is fine 👌


PS: can't wait to avoid all that by simply having the CloudFrontDistribution alias target directly in the AWS::Cognito::UserPoolDomain return values 🤗

@asterikx
Copy link

asterikx commented May 28, 2020

CDK recently added constructs for this. In CDK, all you need to do is:

const userPoolDomain = new cognito.UserPoolDomain(this, 'UserPoolDomain', {
  userPool,
  customDomain: {
    domainName: `auth.${domainName}`,
    certificate,
  },
});

new route53.ARecord(this, 'UserPoolDomainAliasRecord', {
  zone: hostedZone,
  recordName: `auth.${domainName}`,
  target: route53.RecordTarget.fromAlias(new route53_targets.UserPoolDomainTarget(userPoolDomain)),
});

CDK API reference for UserPoolDomainTarget.
In fact, it was @0xdevalias's proposal that was adopted by the CDK team (see here).

@craigataws craigataws added this to the cov milestone Jul 21, 2020
@henning-krause
Copy link

Nice to see it solved in the CDK. But we need that for simple Cloudformation templates as well.

ankon added a commit to Collaborne/custom-cloudformation-resources that referenced this issue Jul 28, 2021
@michaelwittig
Copy link

duplicate of #241 ?

@jeff1evesque
Copy link

I will probably utilize this custom resource, and forget (next year or after) when the attribute is built in.

@sigpwned
Copy link

sigpwned commented Aug 5, 2022

@swoldemi Just tried your custom resource SAR, and it worked a treat. Thanks for sharing!

@mattyboy
Copy link

mattyboy commented Feb 17, 2023

This may have been fixed recently? I initially hit this and came across this article, then I noticed CloudFrontDistribution is listed as an attribute in the aws-resource-cognito-userpooldomain documentation

I managed to successfully deploy a AWS::Route53::RecordSet along with AWS::Cognito::UserPoolDomain with no workaround. Hopefully this helps someone else who ends up on this path.

Using the original example posted the CloudFormation Template looks like this:

  AuthDomain:
    Type: AWS::Route53::RecordSet
    Properties:
      HostedZoneId: !Ref HostedZoneId
      Domain: !Ref AuthDomainName
      Type: 'A'
      AliasTarget:
        DNSName: !GetAtt UserPoolDomain.CloudFrontDistribution        
        HostedZoneId: Z2FDTNDATAQYW2 # For CloudFront, HostedZoneId is always Z2FDTNDATAQYW2
        EvaluateTargetHealth: false

  UserPoolDomain:
    Type: AWS::Cognito::UserPoolDomain
    Properties:
      UserPoolId: !Ref UserPool
      Domain: !Ref AuthDomainName
      CustomDomainConfig:
        CertificateArn: !Ref DomainCertificateArn

I needed to give my user cognito-idp:DescribeUserPoolDomain permission for it to read this value, just in case you have tight IAM policies.

@hauntingEcho
Copy link

Yup, looks like it was added here in February

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request networking & content deliv VPC, CloudFront, Route 53, API Gateway, Direct Connect, AWS App Mesh, etc.
Projects
None yet
Development

No branches or pull requests