AWS CDK Deep Dive: Exploring CloudFormation StackSets and Custom Resources

Technical Deep Dive
August 16, 2024
by
Protagona Team

Ever grappled with the intricacies of managing multiple AWS accounts, or found orchestrating cross-organizational deployments using CloudFormation more complex than it needs to be? Or maybe you’ve pondered over how to extend CloudFormation’s capabilities to manage resources that it doesn’t support natively? If any of these resonate with you, then you’re about to discover a whole new world of possibilities.

Welcome to our deep dive into the AWS Cloud Development Kit (CDK). In this exploration, we will unveil its remarkable prowess in handling CloudFormation StackSets and creating custom resources, providing you with practical solutions that can be applied to everyday challenges. Our goal? To arm AWS developers like you with an expanded arsenal of tools and clearer insights, guided by real-life examples.

We will delve into specific areas of AWS CDK, starting with a unique feature: direct synthesis of an assembly template of one CDK stack within another. By circumventing traditional steps, this method enhances workflow efficiency. We’ll navigate through the mechanics and implications of this approach, giving you a clear roadmap.

Then, we’ll turn our attention to how we can instruct CDK not to include certain metadata in the synthesized template. By passing synthesizer=DefaultStackSynthesizer(generate_bootstrap_version_rule=False) to a Stack, we can mitigate deployment failure when a new account is added to an organizational unit. The importance of this function in boosting operational efficiency is not to be underestimated, and we'll examine the reasons why.

Lastly, we’ll look at the creation of CloudFormation custom resources using AWS CDK. Even though some resources aren’t natively supported by AWS CloudFormation, it doesn’t mean they’re beyond our reach. Through the creation of a custom resource using a Lambda function, we can manage otherwise unsupported resources. We’ll explore this topic in detail, providing practical examples.

Given the scarcity of resources discussing these particular CDK nuances, this blog aims to fill that gap. The goal is not to dictate, but to enlighten. Thus, presenting AWS developers with more options, clarity, and practical examples. So, let’s embark on this journey of understanding the CDK together.

Part 1: Diving into AWS CloudFormation StackSets

In this section, we will explore the powerful capabilities of AWS CloudFormation StackSets. They offer a convenient way to manage and deploy AWS resources across multiple accounts and regions. Let’s dive into their key aspects and benefits.

What are Stack Sets in CloudFormation?

AWS CloudFormation StackSets are a feature within AWS CloudFormation that enables you to manage and deploy CloudFormation stacks across multiple AWS accounts and regions simultaneously. You can centrally manage and apply changes to resources across your entire organization or specific subsets of accounts.

Stack sets utilize a centralized administrator account, known as the “stack set administrator,” to define and manage the stack set. The stack set administrator specifies a CloudFormation template and deploys it to target accounts or organizational units within AWS Organizations. This approach simplifies the management of infrastructure and ensures consistent resource provisioning across your AWS environment.

Use cases of stack sets

Stack sets offer several valuable use cases that streamline the deployment and management of AWS resources:

  1. Standardized deployments: Stack sets enable you to enforce consistent resource configurations and best practices across multiple accounts and regions. By defining a single CloudFormation template and deploying it through stack sets, you can ensure that resources are provisioned uniformly across your organization.
  2. Governance and compliance: Stack sets provide a centralized mechanism for implementing governance policies and enforcing compliance controls. By managing deployments through stack sets, you can enforce policies, such as specific resource configurations, security settings, or tagging standards, to ensure compliance across all accounts and regions.
  3. Efficient updates and rollbacks: With stack sets, you can easily update or roll back changes across multiple accounts and regions in a controlled manner. This simplifies the process of making updates to resources, ensuring consistent configuration changes and minimizing potential errors or downtime.

Managing stack sets and simplifying multi-account and multi-region deployments

Managing stack sets is made easier through the use of AWS CloudFormation and AWS Organizations. Here’s an overview of the steps involved in managing stack sets and their deployment:

  1. Define the stack set: As the stack set administrator, you create a CloudFormation template that defines the desired AWS resources and configurations. This template serves as the blueprint for deploying resources to target accounts or organizational units.
  2. Specify deployment targets: Using AWS Organizations, you can specify the target accounts or organizational units where the resources will be deployed. This allows you to easily manage deployments across multiple accounts or specific subsets of your organization.
  3. Configure deployment options: Stack sets provide options to control the deployment behavior, such as failure tolerance and concurrency settings. You can specify the number of accounts or regions that must successfully deploy the stack before considering it a success, and the maximum number of accounts or regions to deploy concurrently.
  4. Deploy and manage resources: Once the stack set is defined and the deployment targets are specified, you can deploy the stack set. AWS CloudFormation takes care of orchestrating the deployments to the specified accounts or organizational units, ensuring consistent resource provisioning.
  5. Update and rollback: Stack sets make it straightforward to apply updates or roll back changes across multiple accounts and regions. You can make modifications to the stack set template and initiate updates, which AWS CloudFormation handles by applying the changes to the appropriate targets.

By utilizing AWS CloudFormation StackSets, you can efficiently manage and deploy resources at scale, ensuring consistency and simplifying the management of multi-account and multi-region AWS environments.

Part 2: The AWS Cloud Development Kit (CDK)

As we embark on this exploration of the CDK, let’s delve deeper into some of its powerful features and see how we can leverage them for multi-account setups and stack sets.

CDK and CloudFormation StackSets

When we’re orchestrating resources across AWS Organizations and multiple accounts, CloudFormation StackSets emerge as a powerful tool. StackSets extend the functionality of ordinary stacks by enabling us to create, update, or delete stacks across multiple accounts and regions with a single CloudFormation template.

In our setup, we have leveraged CDK to construct these stack sets, which allows us to orchestrate complex organizational structures with relative ease. You may be accustomed to a traditional deployment method; where an artifact is deployed to S3, and the S3 URL is then passed to the stack set. However, our approach offers an alternative:

account_alias_app = App()

AliasStack(

   account_alias_app,

   "AliasStack",

   ...

)

assembly = account_alias_app.synth().get_stack_by_name("AliasStack")

template = json.dumps(assembly.template)

cfn.CfnStackSet(

   self,

   "AliasStackSet",

   stack_set_name="CreateAccountAlias",

   ...

   template_body=template,

   ...

)

Here, we’re taking our AWS CDK Stack, “AliasStack”, and transforming it into an assembly template. Think of it as repackaging our high-level stack into a practical blueprint that AWS CloudFormation knows how to build from.

Next, we create a new AWS CloudFormation StackSet, which we name “CreateAccountAlias”. This stack set uses our freshly minted assembly template as its groundwork. The beauty of StackSets is that they empower us to manage resources across multiple AWS accounts and regions from a single control point; a capability that truly amplifies our reach.

What’s particularly elegant about this approach is how it simplifies and automates our workflow. By directly using the synthesized assembly template in our stack set, we sidestep some traditionally manual tasks like setting up an S3 bucket and uploading the template file. This not only streamlines our deployment process, but it also keeps our focus where it should be — on delivering impactful solutions.

Handling Metadata in the CDK

Another important aspect of working with CDK is understanding how to manage metadata in your synthesized templates. By default, CDK includes metadata about the stack in the output template, which is typically used to support features like resource tagging and deployment operations.

For instance, a typical CDK-synthesized CloudFormation template might include a Metadata section like this:

Conditions:

 CDKMetadataAvailable:

   Fn::Or:

     - Fn::Equals: [Ref: AWS::Region, us-east-1]

     - Fn::Equals: [Ref: AWS::Region, us-west-2]

Parameters:

 BootstrapVersion:

   Type: AWS::SSM::Parameter::Value<String>

   Default: /cdk-bootstrap/hnb659fds/version

   Description: Version of CDK Bootstrap resources (cdk:skip)

Rules:

 CheckBootstrapVersion:

   Assertions:

     - Assert:

         Fn::Not:

           Fn::Contains: [["1", "2", "3", "4", "5"], Ref: BootstrapVersion]

       AssertDescription: CDK bootstrap stack version 6 required

In this example, the Conditions section is checking if the AWS region where the stack is being deployed is either us-east-1 or us-west-2. The Parameters section has a parameter for the Bootstrap version retrieved from the SSM Parameter Store, and finally, the Rules section asserts that the bootstrap version should be 6.

Excluding or customizing metadata in your synthesized templates allows you to tailor the template to specific deployment requirements and avoid any potential conflicts or failures during stack set deployments. This flexibility ensures the stability and success of stack set deployments, particularly when new accounts are added to an organizational unit (OU), as it prevents issues that may arise from including metadata referencing resources not available to the newly added account.

account_alias_app = App()

AliasStack(

   account_alias_app,

   "AliasStack",

   ...

   synthesizer=DefaultStackSynthesizer(

       generate_bootstrap_version_rule=False

   ),

)

Continuing on the previous example, we set a value for the synthesizer parameter of the AliasStack class. To achieve this, we import DefaultStackSynthesizer from the aws_cdk package. This crucial step ensures stability and avoids circular dependencies in our stack set deployments. By setting generate_bootstrap_version_rule to False, we prevent the inclusion of CDK-specific metadata related to the bootstrap version in the synthesized template.

Including this configuration in our CDK code removes the need for the new accounts added to the organizational unit to have access to the CDK bootstrap resources, such as the bootstrap version stored in the AWS Systems Manager (SSM) Parameter Store. By excluding this metadata, we eliminate any potential conflicts or failures during stack set deployments, providing a smoother deployment experience.

In the next section, we will explore the creation and utilization of custom resources using AWS CDK, demonstrating how we can extend the capabilities of AWS CloudFormation to handle scenarios that are not natively supported.

Part 3: Custom Resources in AWS CDK

Custom resources in AWS provide a way to extend the capabilities of AWS CloudFormation by allowing you to define and manage resources that are not natively supported. They enable you to incorporate custom logic and workflows into your CloudFormation templates.

When to Use a Custom Resource

  • Custom resources are particularly useful when you need to interact with services or APIs that are not directly supported by CloudFormation.
  • They can be used to provision or manage resources that require complex configuration or setup beyond what is available through CloudFormation.

Creating Custom Resources with AWS CDK

Let’s walk through the process of creating a custom resource using AWS CDK. In this example, we’ll demonstrate how to create an account alias in AWS Identity and Access Management (IAM) using a Lambda function as the custom resource.

For our specific use case, we encounter a scenario where there is no existing CloudFormation resource available for creating an account alias. In this situation, we need an account alias to be created for every account that is added to specific governed Organizational Units (OUs).

To address this requirement, we will leverage AWS CDK and create a custom resource using a Lambda function. This custom resource will interact with IAM and handle the creation and deletion of the account aliases.

First, let’s define the Lambda function that will handle the custom resource operations:

import boto3

import cfnresponse

import botocore.exceptions

iam_client = boto3.client("iam")

def create(event, context):

   account_alias = event["ResourceProperties"]["AccountAlias"]

   try:

       iam_client.create_account_alias(AccountAlias=account_alias)

       cfnresponse.send(

           event,

           context,

           cfnresponse.SUCCESS,

           {"Alias": account_alias},

       )

   except botocore.exceptions.ClientError as err:

       cfnresponse.send(event, context, cfnresponse.FAILED, {}, reason=str(err))

def delete(event, context):

   try:

       response = iam_client.list_account_aliases()

       if response["AccountAliases"]:

           iam_client.delete_account_alias(AccountAlias=response["AccountAliases"][0])

       cfnresponse.send(event, context, cfnresponse.SUCCESS, {})

   except botocore.exceptions.ClientError as err:

       cfnresponse.send(event, context, cfnresponse.FAILED, {}, reason=str(err))

def handler(event, context):

   request_type = event["RequestType"]

   if request_type == "Create":

       create(event, context)

   elif request_type == "Delete":

       delete(event, context)

   elif request_type == "Update":

       # We don't need to handle updates for this custom resource

       cfnresponse.send(event, context, cfnresponse.SUCCESS,

This Lambda function includes two main functions: create and delete. The create function retrieves the desired account alias from the CloudFormation event and uses the AWS SDK to create the account alias in IAM. If successful, it sends a success response back to CloudFormation. In case of any errors, it sends a failure response with the appropriate error message.

The delete function handles the deletion of the account alias. It retrieves the current account aliases, checks if any exist, and deletes the first one found. Similar to the create function, it sends the appropriate response back to CloudFormation.

Now that we have the Lambda function defined, we can proceed with integrating it as a custom resource:

from aws_cdk import (

   Stack,

   Duration,

   aws_cloudformation as cfn,

)

class AliasStack(Stack):

   def __init__(

       self,

       scope: Construct,

       construct_id: str,

       **kwargs,

   ) -> None:

       super().__init__(scope, construct_id, **kwargs)

       create_alias_fn = _lambda.Function(

           self,

           "CreateAliasFunction",

           runtime=_lambda.Runtime.PYTHON_3_9,

           handler="index.handler",

           code=_lambda.Code.from_inline(

               """

               <Lambda function code goes here>

               """

           ),

           function_name="CreateAccountAlias",

           timeout=Duration.seconds(30),

       )

       # Add necessary IAM permissions to the Lambda function

       create_alias_fn.add_to_role_policy(

           <IAM policy statement goes here>

       )

       resource = cfn.CfnCustomResource(

           self,

           "CreateAliasResource",

           service_token=create_alias_fn.function_arn,

       )

Part 4: Working with AWS CDK, CFN StackSets and Custom Resources Together

In this section, we’ll explore how AWS CDK, CloudFormation StackSets, and Custom Resources can be integrated and work together to manage resources across multiple AWS accounts.

AWS CDK provides a high-level programming model for defining cloud infrastructure using familiar programming languages. It allows you to define and deploy reusable constructs called stacks. These stacks can include Custom Resources, which enable you to define and manage AWS resources that are not natively supported by CloudFormation.

CloudFormation StackSets allow you to create, update, or delete stacks across multiple AWS accounts and regions with a single CloudFormation template. This is particularly useful when you have a multi-account setup and want to manage resources consistently across all accounts.

By combining AWS CDK and CloudFormation StackSets, you can create a stack set that deploys a common set of resources to multiple AWS accounts. This provides a centralized and scalable way to manage infrastructure and enforce configurations across the entire organization.

Custom Resources can be integrated into the stack set to extend CloudFormation’s capabilities. You can use Custom Resources to automate tasks or provision resources that are not directly supported by CloudFormation. In our use case, we demonstrated how to create an account alias in AWS Identity and Access Management (IAM) using a Lambda function as the Custom Resource.

The Custom Resource interacts with IAM to create or delete the account alias based on the CloudFormation events received. By integrating this Custom Resource into the stack set, you can ensure that an account alias is created for every account added to specific governed Organizational Units (OUs). This allows you to enforce naming conventions and manage accounts consistently.

By leveraging the power of AWS CDK, CloudFormation StackSets, and Custom Resources, you can automate the deployment and management of resources across multiple AWS accounts, ensuring consistency and reducing operational overhead.

To access the complete code used in these examples, please refer to the following code snippet

Conclusion

As we reach the end of our deep-dive exploration into the CDK, let’s take a moment to revisit what we’ve covered. We’ve explored the method of synthesizing an assembly template directly with CDK and incorporating it into another stack, allowing us to bypass conventional deployment steps and enhance our operational efficiency. We’ve highlighted the importance of carefully managing CDK metadata in our synthesized templates and demonstrated how to do this, thereby removing the need to bootstrap every account & reducing potential deployment failures.

Moreover, our journey led us to investigate how to build CloudFormation custom resources with the aid of the AWS CDK. We’ve seen how, with the right knowledge and tools, we can extend beyond native AWS CloudFormation support and manage otherwise unsupported resources through custom Lambda functions.

Through it all, our aim has been to shed light on these under explained aspects of the CDK, and I hope this blog post has done just that. By sharing this information, the goal is to empower you with a broader toolset, clearer insights, and practical examples. I hope that you will find these concepts useful in your journey and that they’ll serve as stepping stones to more efficient and effective solutions.

Remember, the landscape of AWS development is constantly evolving. So, keep exploring, keep learning, and most importantly, keep innovating.

test
Got Questions? Contact us

Your data is trying to tell you something

Contact us

... are you listening?