binxio / cfn-certificate-provider

A custom CloudFormation resource provider for creating DNS validated certificates in AWS
Apache License 2.0
146 stars 59 forks source link

Domain is using validation method EMAIL, not DNS #41

Open RikudouSage opened 1 year ago

RikudouSage commented 1 year ago

Today this error popped up on a code that has been running without a problem for many months: Received response status [FAILED] from custom resource. Message returned: domain is using validation method EMAIL, not DNS.

This is the relevant part of template:

    Certificate:
      Type: Custom::Certificate
      Properties:
        DomainName: ${self:custom.domain}
        SubjectAlternativeNames:
          Fn::If:
            - HasProductionAlias
            - - ${self:custom.ProductionDomainAlias}
            - !Ref AWS::NoValue
        ValidationMethod: DNS
        Region: us-east-1
        ServiceToken: ${self:custom.serviceToken}
    CertificateBlocker:
      Type: Custom::IssuedCertificate
      DependsOn:
        - DnsRecordsCertificateValidation
      Properties:
        CertificateArn: !Ref Certificate
        ServiceToken: ${self:custom.serviceToken}
    CertificateDnsRecord:
      Type: Custom::CertificateDNSRecord
      Properties:
        CertificateArn: !Ref Certificate
        DomainName: ${self:custom.domain}
        ServiceToken: ${self:custom.serviceToken}
    DnsRecordsCertificateValidation:
      Type: AWS::Route53::RecordSetGroup
      Properties:
        HostedZoneId: ${self:custom.hostedZone}
        RecordSets:
          - Name: !GetAtt CertificateDnsRecord.Name
            Type: !GetAtt CertificateDnsRecord.Type
            TTL: 60
            Weight: 1
            SetIdentifier: !Ref Certificate
            ResourceRecords:
              - !GetAtt CertificateDnsRecord.Value
leonardonunespuc commented 1 year ago

I'm also having the same issue. I've being using it for the last 3 years without any problem. This started happening for me yesterday (2023-06-29)

RikudouSage commented 1 year ago

@leonardonunespuc Depending on your use-case, my replacement might work for you: https://github.com/RikudouSage/AwsAcmCloudFormationCustomResources

Though I only reimplemented the parts that are useful to my workflow, so it might not be an exact fit for you. Also note that changing service token is impossible, your service would have to be redeployed.

leonardonunespuc commented 1 year ago

@RikudouSage thanks for sending your repo, I'll have a look and see if it will fit my case. Have you identified the reason it stopped working?

In my case I don't need to create certificates in regions different than my resource, so I will also look at using the AWS::CertificateManager::Certificate (https://aws.amazon.com/blogs/security/how-to-use-aws-certificate-manager-with-aws-cloudformation/).

RikudouSage commented 1 year ago

@leonardonunespuc I haven't, sadly, I've looked through the code, but Python is really unreadable for me. If you don't need cross-region support, the official way works well.

leonardonunespuc commented 1 year ago

@RikudouSage just letting you know I found a workaround that worked for me. I had to make a small change on the lambda code.

I was debugging the code and noticed that right after creating the certificate if you call acm.describe_certificate it will return with ValidationMethod: EMAIL but in AWS it was created correctly as DNS. If I would wait just a bit it and call acm.describe_certificate again, then it returned the correct value.

So for the workaround I added a time.sleep(10) before calling acm.describe_certificate in the certificate property in the certificate_dns_record_provider.py file, and that fixed for me.

@property
def certificate(self):
    result = None
    region = self.certificate_arn.split(":")[3]
    time.sleep(10)
    acm = boto3.client("acm", region_name=region)
    try:
        response = acm.describe_certificate(CertificateArn=self.certificate_arn)
        result = Certificate(response["Certificate"])
        if result.status not in ["PENDING_VALIDATION", "ISSUED"]:
            raise PreConditionFailed(
                "certificate {} is state {}, expected pending validation or issued".format(
                    result.status
                )
            )
    except ClientError as e:
        raise PreConditionFailed("{}".format(e))
    return result
RikudouSage commented 1 year ago

@leonardonunespuc Nice! Though I've already switched to my version as I decided it would be easier to maintain myself given that this project seems not maintained anymore and it's critical part of our workflow.

mvanholsteijn commented 1 year ago

Well, the project is still maintain but there was not much maintenance required in the last 3 years 😃

This bug clearly originates from the aws api. I will provide a workaround.

Cheers, Mark

dmarinuswoodwing commented 1 year ago

I've done some testing and I think I know what's going on.

Right after the certificate has been requested a DescribeCertificate call is made, this returns for the first ~3 seconds ValidationMethod EMAIL and then properly gives ValidationMethod DNS.

This doesn't seem right, I'm going to create a support ticket at AWS.

dmarinuswoodwing commented 1 year ago

AWS Acknowledged this issue and they are working on rolling out a fix.

No ETA's of course :disappointed:

dmarinuswoodwing commented 1 year ago

AWS seems to have rolled out the fix. Now the DomainValidationOptions field is completely missing for the first 3 seconds. I guess #42 will work in that situation as well although it might be better now to poll until the field shows up.