AWS Security Blog

How to deploy public ACM certificates across multiple AWS accounts and Regions using AWS CloudFormation StackSets

December, 6, 2022: The post had been updated to reflect the updates on Lambda function runtime in the cloudformation template from version 3.6 to 3.9, as 3.6 is deprecated, as well as updates in Lambda deployment package filename in the same template.


In this post, I take you through the steps to deploy a public AWS Certificate Manager (ACM) certificate across multiple accounts and AWS Regions by using the functionality of AWS CloudFormation StackSets and AWS Lambda. ACM is a service offered by Amazon Web Services (AWS) that you can use to obtain x509 v3 SSL/TLS certificates. New certificates can be either requested or—if you’ve already obtained the certificate from a third-party certificate provider—imported into AWS. These certificates can then be used with AWS services to ensure that your content is delivered over HTTPS.

ACM is a regional service. The certificates issued by ACM can be used only with AWS resources in the same Region as your ACM service. Additionally, ACM public certificates cannot be exported for use with external resources, since the private keys aren’t made available to users and are managed solely by AWS. Hence, when your architecture becomes large and complex, involving multiple accounts and resources distributed across various Regions, you must manually request and deploy individual certificates in each Region and account to use the functionalities of ACM. So, the question arises as to how you can simplify the task of obtaining and deploying ACM certificates across multiple accounts.

The proposed solution (illustrated in Figure 1), deploys AWS CloudFormation stack sets to create necessary resources like AWS Identity and Access Management roles and Lambda functions in AWS accounts. The IAM roles provide Lambda functions with the permissions needed. The function can be hosted as a deployment package in an Amazon Simple Storage Service (Amazon S3) bucket of your choice, which then requests ACM certificates on your behalf and ensures they are validated.

Figure 1: Architecture diagram

Figure 1: Architecture diagram

Before I describe the implementation, let’s review the important aspects of an ACM certificate from the time it’s requested to the time it’s available for use.

Important aspects of an ACM certificate

When requesting a new certificate, ACM prompts you to provide one or more domains for the certificate. Before the certificate is issued, ACM must validate the ownership of the domains that the certificate is being requested for. ACM lets you choose either of two options to validate the domain. These options are:

You can choose only one option for validating the domain—this cannot be changed for the entirety of the life of the certificate. ACM uses the same validation option to validate the domain when renewing the certificate.

In this post, I discuss validation through DNS. Validating through DNS can be automated, which helps in achieving the end goal of having public AWS certificates in multiple AWS accounts and Regions. Let’s get started.

Validate DNS by using Lambda

During DNS validation, ACM generates a new CNAME record for the domains the certificate is requested for. ACM then checks if the records are in place.

Note: To achieve the use-case of this post, you need to use Amazon Route 53 as your DNS service provider. This is because the Lambda function has no way to detect and understand third-party DNS servers and cannot populate the records in them. Make sure that the DNS setup for the domain you’re requesting a certificate for is with Route 53.

The Lambda function, which the CloudFormation stack starts, populates the CNAME records from certificates requested in multiple accounts and Regions into a single Route 53 hosted zone. The Lambda function execution role in various accounts assumes the IAM role in the parent account to make changes to the hosted zone and add the required records.

Here are a few things that you need to keep in mind with respect to the Lambda function:

  • All the certificates are issued for all of the domains. There’s no option to deploy the certificates for different domains in different accounts.
  • Route 53 is a global service. Every ACM certificate in an account has the same CNAME record name and value regardless of the Region the certificate is requested from, as CNAME records are all the same for the domain in an account. This means that you need to populate the CNAME record for an account only once, irrespective of the number of Regions for which you are requesting the certificates.

However, you don’t use the Lambda function directly, instead, you use automation through AWS CloudFormation. Using AWS CloudFormation, you can create customized scripts called stacks in JSON or YAML to deploy AWS resources in a specific order. AWS CloudFormation offers another functionality known as StackSets. CloudFormation stacks can only be used within the account and Region they’re launched in. Stack sets give you the ability to deploy the same stack in different accounts and Regions within those accounts automatically. Let’s look at how AWS CloudFormation fits in with everything that I’ve discussed so far.

Deploy resources in multiple accounts and Regions

Let’s look at how AWS CloudFormation can help you extend this solution across multiple accounts and Regions. Using two CloudFormation stacks, you can deploy the following AWS resources:

  • CloudFormation stacks
  • A Lambda function
  • IAM roles for Lambda cross-account access
  • ACM certificates

Note: From this point, I discuss only the prerequisites and steps needed to deploy the solution. You can follow the included hyperlinks to learn more about the services and concepts discussed.

Route 53 and IAM are global services and so you don’t need to create these resources in every Region. The following implementation has been broken into two CloudFormation stacks. One for deploying global resources and the second stack as a stack set to deploy cross-account and cross-Region resources.

Prerequisites before deploying the stacks

It’s important to understand the parent-child relationship between the accounts that are used in the following workflow. The parent account is where the stacks are deployed. The stack set deploys individual stacks in each of the child accounts where the certificate resources are needed. Here are the prerequisites that you must set up before deploying the stack:

  • The DNS of your domain should be set up in a Route 53 hosted zone in the parent account.
  • You must have an Amazon S3 bucket to store the Lambda deployment package. The AWS CloudFormation stack set fetches the deployment package from the bucket, which is added as a parameter when launching the stack set.
  • Since the bucket is in the parent account, you must modify the bucket policy to add the ARN of the cross-account AWS CloudFormation stack set IAM roles, which allows the stack to access the bucket and fetch the Lambda deployment package. For this to work, you must make sure that the bucket policy allows this cross-account access.
  • For stack sets to run, there are a few prerequisites related to cross-account IAM permissions that you must fulfil. Refer to Prerequisites for stack set operations.

Once the prerequisites are met, you can deploy the two CloudFormation stacks. One deploys the Global-resources stack, and the other deploys the Cross-account stack.

Deploy the global resources stack

Let me show you how to deploy the global resources stack. The Global-resources stack creates an IAM role in the parent account and attaches the necessary permissions to it. Please log in to your AWS management console and navigate to the AWS CloudFormation service home page to get started. You can leverage the stack Global resources template given inline directly during the setup.

To deploy the global resources stack

  1. Deploy the stack named Global-resources (the stack can be deployed in any AWS Region). You must deploy this stack in the parent account. This stack consists of a parent account IAM role: This role is assumed by the Lambda execution role from other child accounts to populate the CNAME records of ACM certificates in the hosted zone of the parent account.

    Note: Make sure that the AWS CloudFormation role has enough permissions to perform these actions.

  2. While deploying the stack, you’ll be prompted to supply values for two parameters:
    • TrustedAccounts – The child accounts, which are populated in the trust policy of the role.
    • HostedZoneId – This hosted zone ID is used to create the IAM policy for the parent account role.
  3. When the stack finishes running, go to the Outputs tab, and take note of the RoleARN, which you need for the second part of this implementation.

The following is the Global-resources CloudFormation template:

AWSTemplateFormatVersion: 2010-09-09
Parameters:
  TrustedAccounts:
    Type: List<Number>
    Description: >-
      List of AWS accounts in which the template will be deployed. These
      accounts will form a trust policy of the role that will be used to edit
      the records in the hosted zone.
  HostedZoneId:
    Type: String
    Description: Hosted zone ID for the domain
Resources:
  IamRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: ''
            Effect: Allow
            Principal:
              AWS: !Ref TrustedAccounts
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/AmazonRoute53AutoNamingFullAccess'
      Path: /
      Policies:
        - PolicyName: lambda-policy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'route53:ListResourceRecordSets'
                Resource: !Join 
                  - ''
                  - - 'arn:aws:route53:::hostedzone/'
                    - !Ref HostedZoneId
Outputs:
  RoleARN:
    Description: The role arn to be passed on to the next template
    Value: !GetAtt 
      - IamRole
      - Arn

Deploy the cross-account stack

When the Global-resources stack is in the CREATE_COMPLETE state, you can deploy the second stack. The Cross-account stack deploys the rest of the resources that need to be created in all the Regions and AWS accounts where you want to deploy the certificates.

To deploy the cross-account stack

  1. Before deploying the stack set, download this deployment package and upload it to an Amazon S3 bucket. Don’t create a new folder—object key—in the bucket to store this package. Upload it directly under the root prefix. Make a note of the Region this bucket belongs to.
  2. Navigate to the AWS CloudFormation console to deploy the cross-account stack. You deploy the cross-account stack as a stack set, which can be deployed in any Region. To deploy the stack set, you must provide the following parameters:
    • HostedZone – The hosted zone ID where your domain is hosted.
    • DomainNameParameter – The same parameter as in the previous stack.
    • S3BucketNameParameter – The name of the bucket that hosts the deployment package.
    • SubjectAlternativeNames – These are the additional domain names that you want to create the certificates for. Add only the subdomains of your hosted zone. Route 53 doesn’t allow creation of CNAME records not applicable for the domain.
    • Regions – The different AWS Regions these certificates are deployed in. Note that the certificates are in the same Region in other accounts as well. You can enter multiple Regions as a comma-separated Region code.
    • RoleARN: The IAM role created by the Global-account stack (RoleARN outputs of the previous stack).
  3. Deploy the stack set either in individual accounts (self-service permissions) or in accounts under AWS Organizations (service-managed permissions). You can learn more about the required permissions from Prerequisites for stack set operations.
    • If you choose self-service permissions, be sure to choose the parent account role under the IAM admin role ARN – optional section and the execution role under the IAM execution role name section before moving to the next step.
    • If you choose service-managed permissions, be sure to enable trusted access for AWS CloudFormation stack sets from the AWS Organizations console.
  4. Choose the Region you want to deploy this stack in. In this section, choose the Region in which the Amazon S3 bucket was created. If you deploy this in any other Region, the stack will fail.

    Note: This might not be the same as the Region the certificate is in.

  5. Select Submit to deploy the stack set.

The following is the Cross-account CloudFormation template:

AWSTemplateFormatVersion: 2010-09-09
Parameters:
  DomainNameParameter:
    Type: String
    Description: The domain name for which the certificate will be issued.
  Regions:
    Type: List<CommaDelimitedList>
    Description: >-
      The regions in which this certificate will be deployed in (same across
      multiple accounts).
  HostedZone:
    Type: String
    Description: The hosted zone ID of your domain in Route53.
  RoleArn:
    Type: String
    Description: >-
      The Arn of the role that the lambda's execution role will assume to
      populate the CNAME records.
  S3BucketNameParameter:
    Type: String
    Description: >-
      The S3 bucket name that has the lambda deployment package (should not be
      within an object)
  SubjectAlternativeName:
    Type: List<CommaDelimitedList>
    Description: Alternative sub-domain names that will be covered in your certificate.
Resources:
  CustomResource:
    Type: 'Custom::CustomResource'
    Properties:
      ServiceToken: !GetAtt 
        - LambdaFunction
        - Arn
      HostedZone: !Ref HostedZone
      DomainName: !Ref DomainNameParameter
      SAN: !Ref SubjectAlternativeName
      RoleARN: !Ref RoleArn
      Regions: !Ref Regions
  LambdaFunction:
    Type: 'AWS::Lambda::Function'
    Properties:
      Code:
        S3Bucket: !Ref S3BucketNameParameter
        S3Key: Lambda_Custom_Resource-26196bbb-4eb2-4c23-b1bd-fe7d1c311540.zip

      Handler: index.lambda_handler
      Runtime: python3.9
      Timeout: 900
      Role: !GetAtt 
        - LambdaExecutionRole
        - Arn
  LambdaExecutionRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Path: /
      Policies:
        - PolicyName: lambda-policy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Action:
                  - 'acm:DescribeCertificate'
                  - 'acm:DeleteCertificate'
                  - 'acm:GetCertificate'
                  - 'logs:PutLogEvents'
                  - 'logs:CreateLogGroup'
                  - 'logs:CreateLogStream'
                Resource:
                  - !Sub 'arn:aws:acm:*:${AWS::AccountId}:certificate/*'
                  - !Sub 'arn:aws:logs:*:${AWS::AccountId}:log-group:/aws/lambda/*'
                  - !Sub 'arn:aws:logs:*:${AWS::AccountId}:log-group:/aws/lambda/*:log-stream:*'
                Effect: Allow
              - Action:
                  - 'acm:ListCertificates'
                  - 'acm:RequestCertificate'
                Resource: '*'
                Effect: Allow
              - Effect: Allow
                Action: 'sts:AssumeRole'
                Resource: !Ref RoleArn

This completes the implementation of your cross-account setup. All the CNAMEs of cross-account certificates are now populated in the hosted zone of the parent account, and the certificates are validated after the CNAME records are successfully populated globally, which ideally takes only a few minutes. When set up is complete, you can delete the
CloudFormation stacks.

Note: When you delete the CloudFormation stacks, the ACM certificates and the corresponding Route 53 record sets remain. This is to prevent inconsistency. Other resources such as the Lambda functions and IAM roles are deleted.

Summary

In this post, I’ve shown you how to use Lambda and AWS CloudFormation to automate ACM certificate creation across your AWS environment. The automation simplifies the certificate creation by completing tasks that are normally done manually. The certificates can now be used with other AWS resources to support your use cases. You can learn more about how you can use ACM certificates with integrated services like AWS load balancers and using alternate domain names with Amazon CloudFront distributions.

If you have feedback about this post, submit comments in the Comments section below. If you have questions about this post, start a new thread on the AWS Certificate Manager forum or contact AWS Support.

Want more AWS Security how-to content, news, and feature announcements? Follow us on Twitter.

Author

Prakhar Malik

Prakhar is a Cloud Support Engineer working with the security team in AWS. He has been with Amazon for about two years, working tirelessly to help customers solve technical issues. Outside of work, he is closely connected to music, an avid gamer, and always likes to keep his guitar by his side.