Giter Site home page Giter Site logo

dflook / cloudformation-dns-certificate Goto Github PK

View Code? Open in Web Editor NEW
48.0 8.0 13.0 201 KB

Cloudformation DNS Validated Certificate Resource

License: MIT License

Python 98.14% Shell 1.86%
aws-cloudformation cloudformation aws-acm aws-route53 route53 troposphere certificate

cloudformation-dns-certificate's Introduction

Cloudformation DNS Validated Certificate Resource

This is a cloudformation custom resource which is an enhancement of the AWS::CertificateManager::Certificate resource.

It allows creating a certificate in a region different from the stack's region (e.g. us-east-1 for cloudfront), and allows for creating a certificate for a Route 53 hosted zone in another AWS account. It also allows for setting the key algorithm.

Usage

To use this custom resource, copy the CustomAcmCertificateLambda and CustomAcmCertificateLambdaExecutionRole resources into your template. You can then create certificate resources of Type: Custom::DNSCertificate.

This resource is also available as troposphere extension, in the troposphere-dns-certificate package

Remember to add a ServiceToken property to the resource which references the CustomAcmCertificateLambda arn. Certificates may take up to 30 minutes to be issued, but typically takes ~3 minutes. The Certificate resource remains as CREATE_IN_PROGRESS until the certificate is issued.

Differences from AWS::CertificateManager::Certificate

It should behave similarly to AWS::CertificateManager::Certificate, except for the differences described here.

The additional Region property can be used to set the region to create the certificate in.

The DomainValidationOption has a additional properties Route53RoleArn and Route53RoleExternalId which allow assuming a role before creating DNS validation records. This lets you create a certificate for a hosted zone in another account.

The additional KeyAlgorithm property allows setting the key algorithm used to generate the key pair used by the certificate.

Certificate Resource

Syntax

Type: Custom::DNSCertificate
Properties: 
  DomainName: String
  DomainValidationOptions:
    - DomainValidationOption
  SubjectAlternativeNames:
    - String
  Tags:
    - Resource Tag
  ValidationMethod: String
  Region: String
  CertificateTransparencyLoggingPreference: String
  KeyAlgorithm: String
  ServiceToken: !GetAtt 'CustomAcmCertificateLambda.Arn'  

Properties

  • DomainName

    Fully qualified domain name (FQDN) to issue the certificate for. Use an asterisk as a wildcard.

    • Required: Yes
    • Type: String
    • Update requires: Replacement
  • DomainValidationOptions

    Information for validating domain ownership. A DomainValidationOption should be present for the DomainName and all SubjectAlternativeNames to create validation records. A DomainValidationOption for a parent domain can be used for names that have the same HostedZoneId.

    If a DomainValidationOption is not present for a domain, validation records for that domain will not be created. They must be created through other means. The resource will remain in the CREATE_IN_PROGRESS state until the records can be validated.

    • Required: Yes
    • Type: List of DomainValidationOption
    • Update requires: Replacement if a HostedZoneId changes
  • SubjectAlternativeNames

    FQDNs to include in the Subject Alternative Name of the certificate.

    • Required: No
    • Type: List of String values
    • Update requires: Replacement
  • Tags

    Tags for this certificate

  • ValidationMethod

    Method to use to validate domain ownership. This should be DNS.

    • Required: No
    • Default: EMAIL
    • Type: String
    • Update requires: Replacement
  • Region

    The region to create the certificate in.

    • Required: No
    • Default: The Stack's region
    • Type: String
    • Update requires: Replacement
  • CertificateTransparencyLoggingPreference

    Certificate Transparency Logging Preference. This may be 'ENABLED' or 'DISABLED'.

    • Required: No
    • Default: ENABLED
    • Type: String
    • Update requires: No interruption
  • KeyAlgorithm

    The algorithm that will be used to generate the key pair used by the certificate. Currently, this may be RSA_2048, EC_prime256v1, or EC_secp384r1 for new certificates.

    โš ๏ธ Not all algorithms are supported by all clients, AWS services or regions.

    • Required: No
    • Default: RSA_2048
    • Type: String
    • Update requires: Replacement

Return value

  • Ref

    When the Ref function is used on the logical ID of a Certificate resource the certificate ARN is returned.

DomainValidationOption

Syntax

DomainName: String
HostedZoneId: String
Route53RoleArn: String
Route53RoleExternalId: String

Properties

  • DomainName

    Fully qualified domain name of the validation request.

    • Required: Yes
    • Type: String
    • Update requires: Replacement
  • HostedZoneId

    The Route53 Hosted Zone to create validation records in.

    • Required: Yes
    • Type: String
    • Update requires: Replacement
  • Route53RoleArn

    The arn of an IAM Role to assume when creating DNS validation records. This can be used to create the records for a Hosted Zone in another AWS account.

  • Route53RoleExternalId

    An External ID to use when assuming the Route53RoleArn. This can be set if required by the trust policy of the role. See https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html for details of using ExternalIds.

Troposphere

If you are using troposphere you can install this resource as an extension using pip:

$ pip install troposphere_dns_certificate

You can then import the Certificate resource from troposphere_dns_certificate.certificatemanager instead of troposphere.certificatemanager.

cloudformation.py is an example of using troposphere to create a template with a Certificate resource.

If you are not using troposphere, you can simply copy the CustomAcmCertificateLambda and CustomAcmCertificateLambdaExecutionRole resources from the cloudformation.json or cloudformation.yaml files.

Examples

The certificate resource looks like:

ExampleCertificate:
  Properties:
    DomainName: test.example.com
    ValidationMethod: DNS
    DomainValidationOptions:
      - DomainName: test.example.com
        HostedZoneId: Z2KZ5YTUFZNC7H
    Tags:
      - Key: Name
        Value: Example Certificate
    ServiceToken: !GetAtt 'CustomAcmCertificateLambda.Arn'
  Type: Custom::DNSCertificate

As with AWS::CertificateManager::Certificate providing the logical ID of the resource to the Ref function returns the certificate ARN.

For example (in yaml): !Ref 'ExampleCertificate'

SubjectAlternativeNames

Additional names can be added to the certificate using the SubjectAlternativeNames property.

ExampleCertificate:
  Properties:
    DomainName: example.com
    SubjectAlternativeNames:
      - additional.example.com
      - another.example.com
    ValidationMethod: DNS
    DomainValidationOptions:
      - DomainName: example.com
        HostedZoneId: Z2KZ5YTUFZNC7H
    Tags:
      - Key: Name
        Value: Example Certificate
    ServiceToken: !GetAtt 'CustomAcmCertificateLambda.Arn'
Type: Custom::DNSCertificate

Multiple Hosted Zones

Names from multiple hosted zones can be used by adding DomainValidationOptions for each of the hosted zones. For example:

ExampleCertificate:
  Properties:
    DomainName: example.com
    SubjectAlternativeNames:
      - additional.example.org
    ValidationMethod: DNS
    DomainValidationOptions:
      - DomainName: example.com
        HostedZoneId: Z2KZ5YTUFZNC7H
      - DomainName: example.org
        HostedZoneId: ZEJZ9DIN47IQN
    Tags:
      - Key: Name
        Value: Example Certificate
    ServiceToken: !GetAtt 'CustomAcmCertificateLambda.Arn'
Type: Custom::DNSCertificate

Wildcards

Wildcards can be used normally. A certificate for a name and all subdomains for example:

ExampleCertificate:
  Properties:
    DomainName: example.com
    SubjectAlternativeNames:
      - *.example.com
    ValidationMethod: DNS
    DomainValidationOptions:
      - DomainName: example.com
        HostedZoneId: Z2KZ5YTUFZNC7H
    Tags:
      - Key: Name
        Value: Example Certificate
    ServiceToken: !GetAtt 'CustomAcmCertificateLambda.Arn'
  Type: Custom::DNSCertificate

Specifying a region

This example uses the Region property to create the certificate in us-east-1, for use with cloudfront:

ExampleCertificate:
  Properties:
    DomainName: example.com
    ValidationMethod: DNS
    DomainValidationOptions:
      - DomainName: example.com
        HostedZoneId: Z2KZ5YTUFZNC7H
    Region: us-east-1
    Tags:
      - Key: Name
        Value: Example Certificate
    ServiceToken: !GetAtt 'CustomAcmCertificateLambda.Arn'
  Type: Custom::DNSCertificate

Assuming a role for Route 53 record creation

In some cases the account owning the hosted zone might be a different one than the one you are generating the certificate in. To support this you can specify the domain validation option property Route53RoleArn with a role-ARN that should be assumed before creating the records required for certificate validation.

Optionally, you can also specify a Route53RoleExternalId that will be used when assuming the role specified by Route53RoleArn. This would be required if the trust policy of the role requires an external ID.

If a top-level Route53RoleArn property is specified it will be assumed when validating domains that don't contain a Route53RoleArn domain validation option property.

ExampleCertificate:
  Properties:
    DomainName: test.example.com
    ValidationMethod: DNS
    DomainValidationOptions:
      - DomainName: test.example.com
        HostedZoneId: Z2KZ5YTUFZNC7H
        Route53RoleArn: arn:aws:iam::TRUSTING-ACCOUNT-ID:role/ACMRecordCreationRole
        Route53RoleExternalId: EXTERNAL-ID
    Tags:
      - Key: Name
        Value: Example Certificate
    ServiceToken: !GetAtt 'CustomAcmCertificateLambda.Arn'
  Type: Custom::DNSCertificate

Additionally you have to allow the assumption of this role by adding this statement to the CustomAcmCertificateLambdaExecutionRole:

- Action:
    - sts:AssumeRole
  Resource:
    - arn:aws:iam::TRUSTING-ACCOUNT-ID:role/ACMRecordCreationRole
  Effect: Allow

If you are using the troposphere extension, this statement is added automatically. The full CustomAcmCertificateLambdaExecutionRole for this example would look like:

CustomAcmCertificateLambdaExecutionRole:
  Properties:
    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
                - acm:UpdateCertificateOptions
              Effect: Allow
              Resource:
                - !Sub 'arn:${AWS::Partition}: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
              Effect: Allow
              Resource:
                - arn:aws:iam::TRUSTING-ACCOUNT-ID:role/ACMRecordCreationRole
          Version: '2012-10-17'
        PolicyName: !Sub '${AWS::StackName}CustomAcmCertificateLambdaExecutionPolicy'

The IAM role in the account with the hosted zone would look something like:

ACMRecordCreationRole:
  Type: AWS::IAM::Role
  Properties:
    AssumeRolePolicyDocument:
      Statement:
        - Action:
            - sts:AssumeRole
          Principal:
            AWS:
              - arn:aws:iam::TRUSTED-ACCOUNT-ID:root
          Effect: Allow
          Condition:
            StringEquals:
              'sts:ExternalId': EXTERNAL-ID
      Version: '2012-10-17'
    Policies:
      - PolicyName: 'ACMRecordCreation'
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Action:
                - route53:ChangeResourceRecordSets
              Resource:
                - arn:aws:route53:::hostedzone/Z2KZ5YTUFZNC7H
              Effect: Allow
    RoleName: ACMRecordCreationRole

cloudformation-dns-certificate's People

Contributors

curryeleison avatar dflook avatar mseiwald avatar pitkley avatar pritamrungta avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cloudformation-dns-certificate's Issues

SubjectAlternativeNames not working properly

Hi,

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

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

DeprecationWarning botocore.vendored.requests

Hi,

from botocore.vendored import requests
...
response = requests.put(event['ResponseURL'], json=event, headers={'content-type': ''})

raises this warning in the logs:

/var/runtime/botocore/vendored/requests/api.py:67: DeprecationWarning: You are using the put() function from 'botocore.vendored.requests'. This is not a public API in botocore and will be removed in the future. Additionally, this version of requests is out of date. We recommend you install the requests package, 'import requests' directly, and use the requests.put() function instead.

Given the short release cycles of boto3/botocore, it is possible that this will break in a not too distant future. It is possible to fix this before then?

Thanks in advance,
BR.

Throttling exception when a large number of certificates exists

When a large number of certificates exists, this requests to fetch the certificate's tags:

https://github.com/dflook/cloudformation-dns-certificate/blob/master/src/troposphere_dns_certificate/certificate.py#L124

causes a throttling exception. I think that a nice solution would be to check if p['DomainName'] == certificate['DomainName'] (Not sure if we need case sensitivity and/or a trailing .) before attempting to fetch the tags. Is this a good idea?

First describe_certificate call does not return DomainValidationOptions

Since yesterday we are running into the issue that the Lambda fails with the following error:

Traceback (most recent call last):
File "/var/task/index.py", line 74, in handler
j()
File "/var/task/index.py", line 57, in j
for F in K[Z]:
KeyError: 'DomainValidationOptions'

Apparently this is because the first call to describe_certificate here does not return DomainValidationOptions. After adding a sleep(10) directly in front of the describe_certificate it starts working again.

Using SubjectAlternativeNames causes unexpected behaviour

Take the following example..

The lambda is unhappy because it appears to be looking for another HostedZoneId for *.mywebsite.com, however there isn't a documented location to put the next HostedZoneId or why it is needed since they would likely share the same zone.

"DomainValidationOptions":[
{
"DomainName":"mywebsite.com",
"HostedZoneId":"123456789"
}
],
"DomainName":"mywebsite.com",
"ValidationMethod":"DNS",
"SubjectAlternativeNames":[
"*.mywebsite.com"
],
"Tags":[
{
"Value":"Example Certificate",
"Key":"Name"
}
]

Warnings from cfn-lint about AWS partitions

cfn-lint issues a warning I3042 on the provided templates. I made a PR with a fix and provide more detail in the PR description: #18 . Feel free to close this without merging if you think it isn't necessary.

Cloudfront: Route53 cname record not removed on stack deletion

Hi,

using the sample code from cloudformation.yaml (version 1.7.1), generating a domain validated cert in ACM with an Route53 zone works fine. However, when the CF stack is removed, the ACM certificate is removed as expected, but the CNAME record used for initial domain validation remains in the Route53 hosted zone. Is this an intentional behavior or it that use case not covered as of yet? Or should that work, and I somehow missed something?
I could provide an CF stack and/or logfiles, if required.

Thanks in advance, and thanks providing this very useful module sample.

BR.

Ability to skip recordset creation for some domains

Hi,

I have a use case whereby the certificate contains some domains that are managed externally (outside of Route53). For these domains it would be useful to have an option to skip the attempt to create the validation records (otherwise the certificate creation fails).

Best regards,
RJ

Cross-Account Usage Failing

Hi Daniel-

First and foremost - thanks for this excellent repo, which I've used with great success to automate the creation and provisioning of ACM certificates with Route53 HostedZone DNS verification. Super-slick with the inline Lambda to create the required validation records!

I'm not trying to leverage in a more complex environment, where the Route53 HostedZone is in another account than where I'm trying to generate / issue the certs. I've created the role in the account with the Route53 Hosted Zone:

arn:aws:iam::############:role/RoleAllowedToEditHostedZone

which has the appropriate permissions to manage the hosted zone.

However, when the template runs (from the 'target' account, where I'm creating the certificate) - it fails, every time, when it gets to the certificate resource. It throws the error:

Failed to create resource. Parameter validation failed: Unknown parameter in input: "Route53RoleArn", must be one of: DomainName, ValidationMethod, SubjectAlternativeNames, IdempotencyToken, DomainValidationOptions, Options, CertificateAuthorityArn

This makes sense to me, since the Route53RoleArn doesn't appear in the CloudFormation docs for that resource type:

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-certificatemanager-certificate.html

Am I reading your docs wrong? Ahh - I see that it's not actually a resource of Type: AWS::CertificateManager::Certificate - but rather, Custom::DNSCertificate - so perhaps this IS a valid attribute / parameter for this object type?

Any guidance / assistance is much appreciated: Thank You!

Timeout Issues

Hey first off, many thanks for the awesome custom resource. It's super helpful!

It appears that everything is working correctly but I'm running into a timeout issue where the ACM cert gets stuck in a "pending validation" state.

Have you ever seen this before?

image

The CNAME was successfully created in Route53

image

And the custom resource lambda is doing its job (per its logs)

image

However, when the max execution time hits, the stack creation fails ๐Ÿ˜…

Any ideas on how I might be able to resolve this?

Many thanks!

New CF CertificateManager updates

Shouldn't we get the README updated and add a new section for the CF CertificateManage updates?
Since, June 2020 CloudFormation AWS::CertificateManager::Certificate allows you to specify the Route53 hosted zone, in which to insert the validation records.

You can automate the provisioning of ACM certificates with DNS with a single resource. Below you see the required AWS::CertificateManager::Certificate resource:

 Certificate:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref DomainName
      ValidationMethod: DNS
      DomainValidationOptions:
        - DomainName: !Ref DomainName
          HostedZoneId: !Ref HostedZoneId

This will create the required DNS validation records for the domain in the specified route53 hosted zone.

Race condition when creating/updating a large number of certificates

I've hit a race condition when creating/updating a large number of certificates.

While in find_certificate, and checking whether the certificate domain name matches:

https://github.com/dflook/cloudformation-dns-certificate/blob/master/src/troposphere_dns_certificate/certificate.py#L126

I occasionally encounter a certificate object which does not contain a DomainName, leading to a KeyError.

Is it a good idea to change the conditional to something like:

domain_name = certificate.get('DomainName')
if domain_name and p['DomainName'].lower() == domain_name:
    ...

So that if the certificate is 'being created', and does not contain a domain name, then it checks the tags.

Errors while provisioning certificate with SAN values

First and foremost: Thanks for this outstanding tool, which we have used extensively to really streamline and enhance our interaction with ACM across a large collection of accounts. Great stuff!

I've long since avoided the use of SANs - in part, because it's so easy to crank out whatever vanity FQDN a team may require / desire. However, I find myself with a use case requiring a SAN - and for whatever reason, I can't seem to get the DomainValidation records created. I've experimented a bit with where I place the SubjectAlternativeNames: property, and can get some different results based on that placement - but can't seem to get the validation records to be created.

I'm even starting with the SIMPLE scenario where I'm trying to include a SAN from the same sub-domain / Route53 Hosted Zone. Ultimately, I need to include a SAN from a 'foreign' (non-Route53 hosted) domain - but I'll cross that bridge when I come to it.

I'm using syntax that looks like this, for my custom resource:

 GeneratedCertificate:
    Properties:
      DomainName: !Ref pCertificateSubject
      SubjectAlternativeNames:
        - !Ref pSubjectAlternativeName
      DomainValidationOptions:
        - DomainName: !Ref pCertificateSubject
          HostedZoneId: !Ref pHostedZoneId
      ServiceToken: !GetAtt 'CustomAcmCertificateLambda.Arn'
      Route53RoleArn: !Ref pRoute53AssumedRoleArn
      Tags:
        - Key: Name
          Value: !Ref pCertificateSubject
        - Key: Owner
          Value: !Ref pCFNOwnerTag
        - Key: App
          Value: !Ref pAppName
        - Key: Env
          Value: !Ref pEnv
      ValidationMethod: DNS
    Type: Custom::DNSCertificate

and when it executes, I'm getting a "CREATE_FAILED" with an error:

Received response status [FAILED] from custom resource. Message returned: DomainValidationOptions missing for mysanvalue.example.com (RequestId: 24307790-d42e-4608-94b9-c0d4b1a89631)

(SAN value changed to protect the innocent. Both the Subject, and the SAN, are both within the same Route53 Hosted Zone).

What am I doing wrong? At first I thought perhaps the SAN required it's own DomainValidationOptions - experimented with that, but then looking at the README - that doens't seem to be the case. I'm sure it's something obvious, whatever it is... :)

Thanks again for this super helpful tool!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.