Cloudformation-coverage-roadmap: AWS::Cognito::UserPoolDomain-GetAtt needed for alias target + hosted zone id

Created on 24 Oct 2019  ·  17Comments  ·  Source: aws-cloudformation/cloudformation-coverage-roadmap

2. Scope of request

To take advantage of a Cognito custom user pool domain a DNS A Alias record is needed for the domain name, which points to a Cloudfront alias target (created by the Cognito user pool domain). There is no way to get at the alias target in the current implementation of AWS::Cognito::UserPoolDomain.

3. Expected Behaviour

We need to be able to GetAtt UserPoolDomain.AliasTarget - and ideally also the hosted zone id to avoid hard-coding the zone id (Z2FDTNDATAQYW2).

5. Links to existing API doc

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpooldomain.html

(more information about what is needed here: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-add-custom-domain.html)

6. Category tag

Security

security identity compliance

Most helpful comment

This is preventing me from using Cognito's hosted UI in my templated solution. Cannot set the generated cloudfront alias target to my Route53 template. Would really like a fix for this.

All 17 comments

Hi. Any news on this issue?

FWIW I just tried this to see if this is supported yet (just in case) and it was not. Received error:

[Error] /Resources/XXXXXXXX/Properties/AliasTarget/Fn::GetAtt: Resource type AWS::Cognito::UserPoolDomain does not support attribute {AliasTarget} 

This is preventing me from using Cognito's hosted UI in my templated solution. Cannot set the generated cloudfront alias target to my Route53 template. Would really like a fix for this.

Any updates?

I ran into this recently and worked around it by creating a custom resource to hold the CloudFront Distribution of the UserPoolDomain and used a lambda to do the lookup since the describe-user-pool-domain api does what we want.

Demo stack here: https://gist.github.com/grosscol/3623d2c2affdd3b88ed4538537bb0850

@grosscol Thanks! So do you run this stack to get the alias target and capture that in a shell variable and pass into another stack to create the RecordSet?

@matthewp You could do it like that. Alternatively, you could use Ouput Exports and [Fn::Import](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-importvalue.html) to use the value in other stacks.

I use the gist'd approach it in the same stack because the custom resource, UPDomain, holds the value. The custom resource request object gets it's properties defined by lambda function pointed to by ServiceToken.

I'm using it to get the distribution for a cognito hosted auth page.... which is a bit of a toy example. It does require creating a Route53 record and pointing it to a cloudfront distribution. So in that sense, I expect this to be generally useful. At least it solved an issue I had that required some ugly external scripting or manual intervention.

Below is roughly how I'm using it in a small stack.

  # See gist for definition of lambda that does cloudformation lookup
  #  GetUserPoolClientCFDistribution:
  #    Type: AWS::Lambda::Function

  # Create UserPoolDomain that will need a record pointing at a cloudfront distribution.
  AuthSubDomain:
    Type: AWS::Cognito::UserPoolDomain
    Properties:
      CustomDomainConfig:
        # Cert arn is supplied as parameter to stack
        CertificateArn: !Ref DNSCertArn
      Domain: auth.example.com
      # User pool id is supplied as parameter to stack.
      UserPoolId: !If [ UseInternalPool, !Ref InternalUserPool, !Ref ExtUserPoolId ]

  # Record set that will have an alias target pointing to the cloudfront distribution.
  AuthDomainRecordSet:
    Type: AWS::Route53::RecordSet
    DependsOn: AuthSubDomain
    Properties:
      Name: auth.example.com
      HostedZoneId: !Ref ZoneId
      Comment: Custom auth domain for MICCA
      Type: A
      AliasTarget:
        # Hard coded zone id for cloudformation
        HostedZoneId: Z2FDTNDATAQYW2
        # The UPDomain custom resource holds the cloudfront dist domain target,
        #   which was a property defined by the lambda response 
        DNSName: !GetAtt UPDomain.CloudFrontDistribution
        EvaluateTargetHealth: false

  UPDomain:
    Type: Custom::UserPoolCloudFrontDistribution
    Properties:
      # This is the labmda that will define the properties for custom object
      ServiceToken: !GetAtt GetUserPoolClientCFDistribution.Arn
      # This becomes a ResourceProperty of the event passed to lambda
      UserPoolDomain: auth.example.com

I use !Sub auth.${MainDomainName} instead of auth.example.com. Substitute your own domain variables scheme as necessary to make it generally useful to you.

I also stumbled across this recently. 😔

Infrastructure as code is all well and good, but if there such blatant breaking point it really eats on the Cloudformation credibility.

I'm actually pretty clueless about how one can introduce a AWS::Cognito::UserPoolDomain resource without the ability to wire it up with Route53 🤷‍♂️

@x6j8x see the Domain property of the AWS::Cognito::UserPoolDomain resource in the docs. AWS will host the user pool domain on a subdomain of theirs for you if you don't want to use your own hosted zone. You get something like yourdomain.auth.us-east-1.amazoncognito.com.

One failing, such as not making it convenient to get the DNS Name of the Cloudfront Distribution of a Cognito User Pool, doesn't refute the endeavor.

Creating another stack to extract that value and use Fn::ImportValue sounds to me like a massive overkill, that ends preventing us to use a custom domain 👎

+1 for GetAtt of those needed values

@ayozemr Creating a stack for the sole purpose of extract the cloudfront distribution value does seem like mismatch.

The approach illustrated in the gist above is using a lambda for getting the attribute. In practice, this was done in the same stack where the user pool was set up anyway. If one needed the cloudfront distribution value in multiple stacks, the looked up value could be exported from that stack. Using an entirely separate stack is unnecessary.

In short, it's a lamba that's the substitute for the absent GetAtt; not an entire stack. It would be nice to avoid the 100 lines of workaround to maintain if GetAtt could return the value that the api call cognito.describe_user_pool_domain does.

After I initially raised the ticket I did publish a full stack that addresses the problem with a custom resource lambda. Might be of help to people starting out: https://github.com/virtuability/aws-auth

In API Gateway, the attribute DistributionDomainName is available for this. ("The Amazon CloudFront distribution domain name that's mapped to the custom domain name.")

So if/when AWS decides to implement this for UserPoolDomain, it would be nice if the same naming convention was used.

This seems like a very clear miss to me. Please address this feature...it works in every other part of the system.

It would be very nice to have this in CloudFormation. Right now, we're using the yourdomain.auth.us-east-1.amazoncognito.com solution, but I'm concerned that some users will interpret this change in domain as a phishing attempt. Admittedly though, I haven't gotten any reports from users yet.

@velovix You can use a cloudformation stack to use your custom auth domain (e.g. auth.example.com) for Cognito. See the gist in the comment above for a method of creating a custom resource to hold the cloudfront distribution target of the user pool (UPDomain) that can be used as the AliasTarget in a CloudFront Distribution.

AuthDomainRecordSet:
    Type: AWS::Route53::RecordSet
    # ... 
      AliasTarget:
        # ...
        DNSName: !GetAtt UPDomain.CloudFrontDistribution

The tricky bit is getting the domain of the cognito user pool. This issue boils down to CloudFormation providing GetAtt for the UserPool's domain.

I have a ticket in with support on 6/25/2021 to see if this has been added yet or not. Either way I was able to solve this issue with just a few lines of ansible.

- name: Retrieve Cognito User Pool Custom Domain
  shell: aws --region {{ aws_region }} cognito-idp describe-user-pool-domain --domain {{ yourCustomDomain }} --query 'DomainDescription.CloudFrontDistribution' --output text
  register: custom_domain_alias

- name: Debug stdout
  debug:
    msg: "{{ custom_domain_alias.stdout }}"

- name: Update Route53 Alias for {{ yourCustomDomain }}
  route53:
      state: present
      zone: "{{ r53.domain }}"
      record: "{{ yourCustomDomain }}"
      type: CNAME
      value: "{{ custom_domain_alias.stdout }}"
      overwrite: yes
Was this page helpful?
0 / 5 - 0 ratings