Deploying CloudFormation StackSets with AWS CDK

daknhh

David Krohn

Posted on July 20, 2023

Deploying CloudFormation StackSets with AWS CDK

AWS Cloud Development Kit (CDK) is a powerful framework that allows developers to define cloud infrastructure as code using familiar programming languages. With CDK, you can easily provision and manage AWS resources in a consistent and automated manner. In this blog post, we'll walk you through the process of creating a StackSet using AWS CDK.

Before we dive into the details, let's take a quick look at what a StackSet is and how it can help you manage your AWS infrastructure.

StackSets are containers for CloudFormation stacks that enable simultaneous creation, update, and deletion across multiple AWS accounts and regions. With StacksSets, you can ensure that all environments are consistent and compliant with the policies you have in place.

The native support for StackSet in CDK is somewhat rudimentary, due to the fact that it is more common in CDK to use CDK pipelines to roll out stacks to multiple accounts and regions.

Therefore, in order to use StackSet in CDK, a few things need to be considered. In this blogpost, we will show steps on how to deploy StackSets via CDK.

Let’s see how this works.

Prerequisites

To follow this tutorial, make sure you have the following prerequisites

Bootstrap AWS Account

Bootstrapping is the process of providing resources for the AWS CDK before you can deploy AWS CDK applications in an AWS environment. Normally you could use the default

cdk bootstrap aws://ACCOUNT-NUMBER-1/REGION-1 command. However, we need to customise the template to make the assets available to the entire AWS Organization, as we want to use this CDK environment to deploy StackSets. To get the latest version of the CDK bootstrap template, do the following:

cdk bootstrap --show-template > bootstrap-template.yaml

ℹ️ The CDK boostrap template contains an S3 bucket for files and an ECR repository for container images. It also creates few IAM roles.

Image description

After that, we need to modify two resources in this template. The s3 bucket for Assets and the KMS Key that will be used to encrypt the assets.

Add the following Part to the Parameters Section of the template:

  PrincipalOrgID:
    Description: >-
      The identifier of your AWS organization. Used in the KMS key policy and S3 bucket to
      share the key with all accounts under your organization
    Type: String
Enter fullscreen mode Exit fullscreen mode

We will reference this Parameter in the Resource section.

Add this CodeSnippet to the FileAssetsBucketEncryptionKey Resource in to the Key Policy Section. This will

        KeyPolicy:
        Statement:
          - Action:
          ....
          - Action:
              - kms:Decrypt
              - kms:DescribeKey
            Effect: Allow
            Principal:
              AWS: "*"
            Resource: "*"
            Condition:
              StringEquals:
                kms:ViaService:
                  - Fn::Sub: s3.${AWS::Region}.amazonaws.com
              ForAnyValue:StringLike:
                aws:PrincipalOrgID:
                - !Ref PrincipalOrgID
Enter fullscreen mode Exit fullscreen mode

Extend the PolicyDocument of the StagingBucketPolicy with the following CodeSnippet. This will ensure that all Accounts of the Organization get access to the objects in the Asset Bucket.

      PolicyDocument:
        Id: AccessControl
        Version: "2012-10-17"
        Statement:
        ...
          - Sid: ''
            Effect: Allow
            Principal: '*'
            Action:
            - s3:Get*

            Resource: !Sub '${StagingBucket.Arn}/*'
            Condition:
              ForAnyValue:StringLike:
                aws:PrincipalOrgID:
                - !Ref PrincipalOrgID
          - Sid: ''
            Effect: Allow
            Principal: '*'
            Action: s3:ListBucket
            Resource: !Sub '${StagingBucket.Arn}'
            Condition:
              ForAnyValue:StringLike:
                aws:PrincipalOrgID:
                - !Ref PrincipalOrgID
Enter fullscreen mode Exit fullscreen mode

After all the adjustments we need to deploy the template.

aws cloudformation create-stack \
  --stack-name CDKToolkit \
  --template-body file://bootstrap-template.yaml
Enter fullscreen mode Exit fullscreen mode

Blog Content

Set Up Your CDK Project

After bootstrapping our Account we are ready to Initialize a new CDK project. We will do that in a new directory. The initialisation creates a new CDK project structure with a sample lib/stackset-cdk-demo-stack.ts file, which we will modify to create our StackSet.

#Create new directory
mkdir stackset-cdk-demo
cd stackset-cdk-demo

#Init new CDK Project
cdk init app --language typescript
Enter fullscreen mode Exit fullscreen mode

Define the StackSet

Open lib/stackset-cdk-demo-stack.ts and remove the example stack definition. We'll define our stackset instead:

import * as cdk from 'aws-cdk-lib';
import * as servicecatalog from 'aws-cdk-lib/aws-servicecatalog';
import { StackSetTemplate } from "./stackSetTemplate";
import * as s3 from "aws-cdk-lib/aws-s3";

export class StackSetCdkDemoStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);


    const stackSetTemplateSandbox = new StackSetTemplate(this, "stacksettemplate", {
      Config: props.stacksetProps,
      assetBucket: s3.Bucket.fromBucketName(this, "assetbucket", "myCDKAssetBucket")
    });

    new cdk.CfnStackSet(this, "TESTSTACKSET", {
      permissionModel: "SELF_MANAGED",
      stackSetName: "TEST-STACKSET",
      description:
        "example of StackSet with CDK",
      capabilities: ["CAPABILITY_NAMED_IAM"],
      templateUrl: servicecatalog.CloudFormationTemplate.fromProductStack(stackSetTemplateSandbox).bind(this).httpUrl,
      operationPreferences: {
        failureToleranceCount: 30,
        maxConcurrentCount: 30,
      }
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

⚠️ Ensure to adjust the myCDKAssetBucket to your AWS Account Assets Bucket.

🚨 Using the servicecatalog ProductStack construct we get rid of the PseudoParameter for the assets bucket in lambdas in the template.

❌ Without using the servicatalog ProductStack:

!sub cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}

✅ Using the servicatalog ProductStack:

cdk-hnb659fds-assets-123456789012-eu-central-1
Enter fullscreen mode Exit fullscreen mode

Undefined

This workaround will ensure that all AWS accounts can access the assets in our CDK app.

Create StackSet template

In the lib directory create a new file called stackSetTemplate.ts and add the following code to the file:

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as servicecatalog from "aws-cdk-lib/aws-servicecatalog";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";
import * as lambda from "aws-cdk-lib/aws-lambda";

//Standardstackdefinition
export class StackSetTemplate extends servicecatalog.ProductStack {
// export class CdkStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: cdk.StackProps) {
super(scope, id, props);

/**
 * Dummy Node JS Lambda Function
 */
  const lambdaFunction = new NodejsFunction(this, "testFunction", {
    memorySize: 128,
    timeout: cdk.Duration.seconds(60),
    runtime: lambda.Runtime.NODEJS_18_X,
    handler: "handler",
    entry: path.join(__dirname, "lambda/index.ts"),
    bundling: {
      minify: true,
      externalModules: ["aws-sdk"]
    }
  });
  lambdaFunction.applyRemovalPolicy(cdk.RemovalPolicy.RETAIN);
}
Enter fullscreen mode Exit fullscreen mode

}
}

Enter fullscreen mode Exit fullscreen mode




Example Lambda Code

Create a new directory lambda in the lib directory. In the new lambda directory create a new file called index.ts and add the following code:

import { Handler } from 'aws-lambda';

export const handler: Handler = async (event, context) => {
console.log('EVENT: \n' + JSON.stringify(event, null, 2));
return context.logStreamName;
};

Enter fullscreen mode Exit fullscreen mode




Deploy the StackSet

Run cdk deploy

in a terminal to deploy the StackSet and associated CloudFormation stacks.

ℹ️ CDK will ask you to confirm that you want to deploy the changes. Type y and press Enter to continue.

Conclusion

In this blog post, we've explored how to create a StackSet using AWS CDK. We also learned how to share the CDK Assets to the whole AWS Organization. StackSets are an essential tool for managing infrastructure at scale across multiple AWS accounts and regions. Using CDK, you can easily define and deploy complex cloud infrastructure as code, and leverage the full power of AWS CloudFormation to ensure consistency, compliance, and efficiency across your organisation's cloud resources.

💖 💪 🙅 🚩
daknhh
David Krohn

Posted on July 20, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related