dflook / cloudformation-dns-certificate

Cloudformation DNS Validated Certificate Resource
MIT License
48 stars 13 forks source link

SubjectAlternativeNames not working properly #16

Closed RekhaPGE closed 3 years ago

RekhaPGE commented 3 years ago

Hi,

I am trying to add SubjectAlternativeNames to add additional domain (e.g..additional.example.com) I am following this syntax: Properties: SubjectAlternativeNames:

But CFT throwing the below error: Received response status [FAILED] from custom resource. Message returned: DomainValidationOptions missing for additional.example.com (RequestId: 9758388c-7dba-4c2a-a15b-bfa8f06a0931)

Can you please let me know why Lambda is sending FAILED status to CFT and kindly provide the update.

Regards, Rekha

dflook commented 3 years ago

Hello @RekhaRavula, you need to provide a DomainValidationOptions for your domain. See the example here. Can you post the full resource from the template?

RekhaPGE commented 3 years ago

@dflook,

Please find the below resource from our template, we are trying to use secondary domain name xyz.nonprod.pqr.com (e.x) using

generatedCertificate:
    Properties:
      DomainName: !Ref pWebsiteFQDN
      DomainValidationOptions:
        - DomainName: !Ref pWebsiteFQDN
          HostedZoneId: !Ref pHostedZoneId
          Route53RoleArn: !Ref pRoute53AssumedRoleArn
          SubjectAlternativeNames: 
            - xyz.nonprod.pqr.com
      Tags:
        - Key: AppID
          Value: !Ref pAppID
        - Key: Environment
          Value: !Ref pEnv
        - Key: Notify
          Value: !Ref pNotify
        - Key: Order
          Value: !Ref pOrderNumber
        - Key: Org
          Value: !Ref pOrg
        - Key: AppName
          Value: !Ref pAppName
        - Key: Owner
          Value: !Ref pCFNOwnerTag
        - Key: Compliance
          Value: None
        - Key: DataClassification
          Value: Confidential
        - Key: CRIS
          Value: N/A
        - Key: MCP
          Value: "noMcp"
      ValidationMethod: DNS
      Region: us-east-1
      ServiceToken: !GetAtt 'CustomAcmCertificateLambda.Arn'
    Type: Custom::DNSCertificate

But Lambda throwing the below error:

[ERROR] 2021-07-09T02:10:27.28Z 40256572-6e9e-4696-9d58-a7ad516354df 
Traceback (most recent call last): 
File "/var/task/index.py", line 93, in handler 
    if K not in e:g() 
File "/var/task/index.py", line 23, in g 
    for H in set([A[M]]+A.get('SubjectAlternativeNames',[])):k(H) 
File "/var/task/index.py", line 70, in k 
    raise U(N+' missing for '+n) 
RuntimeError: DomainValidationOptions missing for xyz.nonprod.pqr.com

Note: What I'm suspecting is Lambda is unable to read DomainValidationOptions for secondary domain which we are trying to add through SubjectAlternativeNames

dflook commented 3 years ago

SubjectAlternativeNames names should not be inside DomainValidationOptions - is that a copy paste error?

You will need a DomainValidationOptions zone for all zones needed - both the DomainName zone and the zone for any SubjectAlternativeNames (if different).

So it would look something like:

generatedCertificate:
    Properties:
      DomainName: !Ref pWebsiteFQDN
      SubjectAlternativeNames: 
        - xyz.nonprod.pqr.com
      DomainValidationOptions:
        - DomainName: !Ref pWebsiteFQDN
          HostedZoneId: !Ref pHostedZoneId
          Route53RoleArn: !Ref pRoute53AssumedRoleArn
        - DomainName: nonprod.pqr.com
          HostedZoneId: !Ref NonprodZoneId
          Route53RoleArn: !Ref pRoute53AssumedRoleArn
      ValidationMethod: DNS
      Region: us-east-1
      ServiceToken: !GetAtt 'CustomAcmCertificateLambda.Arn'
    Type: Custom::DNSCertificate
RekhaPGE commented 3 years ago

As per your suggestion, I used the same code you mentioned above but still getting the same error, can you please confirm in your test environment were you able to add the secondary domain (SubjectAlternativeNames) to your certificate. I think Lambda code is not able to validate the secondary domain and going to exception in the below code:

def k(n):
    C='.';n=n.rstrip(C);D={A[p].rstrip(C):A for A in B[N]};A=n.split(C)
    while len(A):
        if C.join(A)in D:return D[C.join(A)]
        A=A[1:]
    raise T(N+' missing'+' for '+n)
dflook commented 3 years ago

The Exception is telling you something. Please examine and check your resource is correct.

RekhaPGE commented 3 years ago

Two domains are in the same AWS account, one is getting updated correctly but secondary domain is failing at validation level. Our resource is correct.

dflook commented 3 years ago

@RekhaRavula Can you post the actual resource you are using

RekhaPGE commented 3 years ago

Please find below:

Resources:

  CustomAcmCertificateLambda:
    Metadata:
      Source: https://github.com/dflook/cloudformation-dns-certificate
      Version: 1.7.3
    Properties:
      Code:
        ZipFile: "x=Exception\nV=True\nU=RuntimeError\nimport copy,hashlib as s,json,logging\
          \ as B,time\nfrom boto3 import client as J\nfrom botocore.exceptions import\
          \ ClientError as t,ParamValidationError as u\nfrom urllib.request import\
          \ Request as v,urlopen as w\nA=B.getLogger()\nA.setLevel(B.INFO)\nC=A.info\n\
          R=A.exception\nL=copy.copy\nS=time.sleep\nT=lambda j:json.dumps(j,sort_keys=V).encode()\n\
          K='R'\ndef handler(e,c):\n\tAA='OldResourceProperties';A9='Update';A8='Delete';A7='None';A6='acm';A5='FAILED';A4='properties';A3='stack-id';A2='logical-id';A1='DNS';r='Old';p='Certificate';q='LogicalResourceId';o='ValidationMethod';n='Route53RoleArn';m='Region';d='RequestType';b='StackId';a=None;Q='Status';P='Key';O='';N='DomainValidationOptions';M='DomainName';I='ResourceProperties';H='cloudformation:';G='Value';F='CertificateArn';E='Tags';B='PhysicalResourceId';f=c.get_remaining_time_in_millis;C(e)\n\
          \tdef g():\n\t\tC=L(A)\n\t\tfor G in ['ServiceToken',m,E,n]:C.pop(G,a)\n\
          \t\tif o in A:\n\t\t\tif A[o]==A1:\n\t\t\t\tfor H in set([A[M]]+A.get('SubjectAlternativeNames',[])):k(H)\n\
          \t\t\t\tdel C[N]\n\t\te[B]=D.request_certificate(IdempotencyToken=z,**C)[F];l()\n\
          \tdef W(a):\n\t\twhile V:\n\t\t\ttry:D.delete_certificate(**{F:a});return\n\
          \t\t\texcept t as B:\n\t\t\t\tR(O);A=B.response['Error']['Code']\n\t\t\t\
          \tif A=='ResourceInUseException':\n\t\t\t\t\tif f()/1000<30:raise\n\t\t\t\
          \t\tS(5);continue\n\t\t\t\tif A in['ResourceNotFoundException','ValidationException']:return\n\
          \t\t\t\traise\n\t\t\texcept u:return\n\tdef X(p):\n\t\tfor I in D.get_paginator('list_certificates').paginate():\n\
          \t\t\tfor A in I['CertificateSummaryList']:\n\t\t\t\tC(A)\n\t\t\t\tif p[M].lower()==A[M]:\n\
          \t\t\t\t\tB={B[P]:B[G]for B in D.list_tags_for_certificate(**{F:A[F]})[E]}\n\
          \t\t\t\t\tif B.get(H+A2)==e[q]and B.get(H+A3)==e[b]and B.get(H+A4)==Y(p):return\
          \ A[F]\n\tdef h():\n\t\tif K in e:raise U('Certificate not issued in time')\n\
          \t\te[K]=K;C(e);J('lambda').invoke(FunctionName=c.invoked_function_arn,InvocationType='Event',Payload=T(e))\n\
          \tdef i():\n\t\twhile f()/1000>30:\n\t\t\tA=D.describe_certificate(**{F:e[B]})[p];C(A)\n\
          \t\t\tif A[Q]=='ISSUED':return V\n\t\t\telif A[Q]==A5:raise U(A.get('FailureReason',O))\n\
          \t\t\tS(5)\n\t\treturn False\n\tdef y():A=L(e[r+I]);A.pop(E,a);B=L(e[I]);B.pop(E,a);return\
          \ A!=B\n\tdef j():\n\t\tX='Type';W='Name';U='HostedZoneId';T='ValidationStatus';R='PENDING_VALIDATION';K='ResourceRecord'\n\
          \t\tif A.get(o)!=A1:return\n\t\twhile V:\n\t\t\tH=D.describe_certificate(**{F:e[B]})[p];C(H)\n\
          \t\t\tif H[Q]!=R:return\n\t\t\tif not[A for A in H.get(N,[{}])if T not in\
          \ A or K not in A]:break\n\t\t\tS(1)\n\t\tfor E in H[N]:\n\t\t\tif E[T]==R:L=k(E[M]);O=L.get(n,A.get(n));I=J('sts').assume_role(RoleArn=O,RoleSessionName=(p+e[q])[:64],DurationSeconds=900)['Credentials']if\
          \ O is not a else{};P=J('route53',aws_access_key_id=I.get('AccessKeyId'),aws_secret_access_key=I.get('SecretAccessKey'),aws_session_token=I.get('SessionToken')).change_resource_record_sets(**{U:L[U],'ChangeBatch':{'Comment':'Domain\
          \ validation for '+e[B],'Changes':[{'Action':'UPSERT','ResourceRecordSet':{W:E[K][W],X:E[K][X],'TTL':60,'ResourceRecords':[{G:E[K][G]}]}}]}});C(P)\n\
          \tdef k(n):\n\t\tC='.';n=n.rstrip(C);D={B[M].rstrip(C):B for B in A[N]};B=n.split(C)\n\
          \t\twhile len(B):\n\t\t\tif C.join(B)in D:return D[C.join(B)]\n\t\t\tB=B[1:]\n\
          \t\traise U(N+' missing for '+n)\n\tY=lambda v:s.new('md5',T(v)).hexdigest()\n\
          \tdef l():A=L(e[I].get(E,[]));A+=[{P:H+A2,G:e[q]},{P:H+A3,G:e[b]},{P:H+'stack-name',G:e[b].split('/')[1]},{P:H+A4,G:Y(e[I])}];D.add_tags_to_certificate(**{F:e[B],E:A})\n\
          \tdef Z():\n\t\tC(e);A=w(v(e['ResponseURL'],T(e),{'content-type':O},method='PUT'))\n\
          \t\tif A.status!=200:raise x(A)\n\ttry:\n\t\tz=Y(e['RequestId']+e[b]);A=e[I];D=J(A6,region_name=A.get(m));e[Q]='SUCCESS'\n\
          \t\tif e[d]=='Create':\n\t\t\tif K not in e:e[B]=A7;g()\n\t\t\tj()\n\t\t\
          \tif not i():return h()\n\t\telif e[d]==A8:\n\t\t\tif e[B]!=A7:\n\t\t\t\t\
          if e[B].startswith('arn:'):W(e[B])\n\t\t\t\telse:W(X(A))\n\t\telif e[d]==A9:\n\
          \t\t\tif y():\n\t\t\t\tC(A9)\n\t\t\t\tif X(A)==e[B]:\n\t\t\t\t\ttry:D=J(A6,region_name=e[AA].get(m));C(A8);W(X(e[AA]))\n\
          \t\t\t\t\texcept:R(O)\n\t\t\t\t\treturn Z()\n\t\t\t\tif K not in e:g()\n\
          \t\t\t\tj()\n\t\t\t\tif not i():return h()\n\t\t\telse:\n\t\t\t\tif E in\
          \ e[r+I]:D.remove_tags_from_certificate(**{F:e[B],E:e[r+I][E]})\n\t\t\t\t\
          l()\n\t\telse:raise U(e[d])\n\t\treturn Z()\n\texcept x as A0:R(O);e[Q]=A5;e['Reason']=str(A0);return\
          \ Z()"
      Description: Cloudformation custom resource for DNS validated certificates
      Handler: index.handler
      Role: !GetAtt 'CustomAcmCertificateLambdaExecutionRole.Arn'
      Runtime: python3.6
      Timeout: 900
      Tags:
        - Key: AppID
          Value: !Ref pAppID
        - Key: Environment
          Value: !Ref pEnv
        - Key: Notify
          Value: !Ref pNotify
        - Key: Order
          Value: !Ref pOrderNumber
        - Key: Org
          Value: !Ref pOrg
        - Key: AppName
          Value: !Ref pAppName
        - Key: Owner
          Value: !Ref pCFNOwnerTag
        - Key: Compliance
          Value: None
        - Key: DataClassification
          Value: Confidential
        - Key: CRIS
          Value: N/A
        - Key: MCP
          Value: "noMcp"
    Type: AWS::Lambda::Function

  CustomAcmCertificateLambdaExecutionRole:
    Properties:
      Tags:
        - Key: AppID
          Value: !Ref pAppID
        - Key: Environment
          Value: !Ref pEnv
        - Key: Notify
          Value: !Ref pNotify
        - Key: Order
          Value: !Ref pOrderNumber
        - Key: Org
          Value: !Ref pOrg
        - Key: AppName
          Value: !Ref pAppName
        - Key: Owner
          Value: !Ref pCFNOwnerTag
        - Key: Compliance
          Value: None
        - Key: DataClassification
          Value: Confidential
        - Key: CRIS
          Value: N/A
        - Key: MCP
          Value: "noMcp"
      AssumeRolePolicyDocument:
        Statement:
          - Action:
              - sts:AssumeRole
            Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
        Version: '2012-10-17'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        - arn:aws:iam::aws:policy/service-role/AWSLambdaRole
      Policies:
        - PolicyDocument:
            Statement:
              - Action:
                  - acm:AddTagsToCertificate
                  - acm:DeleteCertificate
                  - acm:DescribeCertificate
                  - acm:RemoveTagsFromCertificate
                Effect: Allow
                Resource:
                  - !Sub 'arn:aws:acm:*:${AWS::AccountId}:certificate/*'
              - Action:
                  - acm:RequestCertificate
                  - acm:ListTagsForCertificate
                  - acm:ListCertificates
                Effect: Allow
                Resource:
                  - '*'
              - Action:
                  - route53:ChangeResourceRecordSets
                Effect: Allow
                Resource:
                  - arn:aws:route53:::hostedzone/*
              - Action:
                  - sts:AssumeRole
                Resource:
                  - !Ref pRoute53AssumedRoleArn
                Effect: Allow    
            Version: '2012-10-17'
          PolicyName: !Sub '${AWS::StackName}CustomAcmCertificateLambdaExecutionPolicy'
    Type: AWS::IAM::Role

  generatedCertificate:
    Properties:
      DomainName: !Ref pWebsiteFQDN
      SubjectAlternativeNames: 
        - geomartcloud-qa.nonprod.pge.com
      DomainValidationOptions:
        - DomainName: !Ref pWebsiteFQDN
          HostedZoneId: !Ref pHostedZoneId
          Route53RoleArn: !Ref pRoute53AssumedRoleArn
        - DomainName: !Ref pWebsiteFQDN1
          HostedZoneId: !Ref pHostedZoneId
          Route53RoleArn: !Ref pRoute53AssumedRoleArn
      Tags:
        - Key: AppID
          Value: !Ref pAppID
        - Key: Environment
          Value: !Ref pEnv
        - Key: Notify
          Value: !Ref pNotify
        - Key: Order
          Value: !Ref pOrderNumber
        - Key: Org
          Value: !Ref pOrg
        - Key: AppName
          Value: !Ref pAppName
        - Key: Owner
          Value: !Ref pCFNOwnerTag
        - Key: Compliance
          Value: None
        - Key: DataClassification
          Value: Confidential
        - Key: CRIS
          Value: N/A
        - Key: MCP
          Value: "noMcp"
      ValidationMethod: DNS
      Region: us-east-1
      ServiceToken: !GetAtt 'CustomAcmCertificateLambda.Arn'
    Type: Custom::DNSCertificate
dflook commented 3 years ago

What is pWebsiteFQDN and pWebsiteFQDN1 set to?

RekhaPGE commented 3 years ago

pWebsiteFQDN and pWebsiteFQDN1 are set to hawctst.nonprod.pge.com and geomartcloud-tst.nonprod.pge.com respectively.

dflook commented 3 years ago

You have DomainValidationOptions for zones hawctst.nonprod.pge.com and geomartcloud-tst.nonprod.pge.com, but not one for geomartcloud-qa.nonprod.pge.com. The error is telling you that there should be a DomainValidationOptions for geomartcloud-qa.nonprod.pge.com, but it is missing.

RekhaPGE commented 3 years ago

@dflook , looks like there is typo. I think it would be better if I can give more information during troubleshooting session. Can you please kindly let me know which time works for you. Thank you.

dflook commented 3 years ago

@RekhaRavula for commercial support queries please contact me via email at daniel@flook.org