Cloudformation-coverage-roadmap: AWS::Cognito::UserPoolDomain missing return value for the CloudFront target

Created on 22 Jan 2020  路  9Comments  路  Source: aws-cloudformation/cloudformation-coverage-roadmap

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 https://github.com/aws-cloudformation/aws-cloudformation-coverage-roadmap/issues/58#issuecomment-539652016_

enhancement networking & content deliv

Most helpful comment

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

All 9 comments

Yeah need this one too.

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

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

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

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

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

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 馃

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

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

Was this page helpful?
0 / 5 - 0 ratings