DEV Community

Cover image for Enforcing compliance with AWS CDK Aspects

Enforcing compliance with AWS CDK Aspects

Getting the security right in applications is tricky. Most developers did not undergo professional security training and are not adept in such topics. At least I know I'm not.

Luckily, multiple tools can help us achieve a relatively good security posture. Snyk, CodeQL and GitGuardian are good examples.

In some cases, even a deployment framework can be helpful. This blog post will expand on this topic, looking at how a particular feature of AWS CDK can help us enforce compliance of various resources we deploy to AWS.

Enter AWS CDK Aspects.

The code this blog post is based on lives in this GitHub repository

What are AWS CDK Aspects

AWS CDK Aspects is a feature of the AWS CDK framework that allows you to perform various operations on each node of the AWS CDK Construct tree.

Note that the Aspects code is invoked at the prepare phase of AWS CDK application lifecycle.

AWS CDK Aspects and the Construct tree

Imagine a scenario where you have to ensure that every AWS S3 bucket in the AWS CDK application you maintain has the BucketEncryption property specified according to your organization's needs.

Instead of going bucket by bucket and applying the correct value for the BucketEncryption property, one might use AWS CDK Aspects to programmatically set this property for all buckets deployed by the application.

With a basic overview of Aspects behind us, let us look at examples next.

Practical examples

What follows is a series of examples showcasing the usefulness of AWS CDK Aspects.

Enforcing AWS S3 bucket encryption

AWS S3 is an excellent service. Sadly, when misconfigured, it can be a source of a data breach. There are many stories out there regarding publicly accessible AWS S3 buckets with plain-text files that hold important data.

Thankfully, with AWS CDK Aspects, it is possible to reduce the human error factor regarding AWS S3 bucket configuration. The following is an example of ensuring that all the buckets within a Stack have BucketEncryption specified.

import { Aspects, aws_s3, IAspect, Stack, StackProps, App } from "aws-cdk-lib";
import { Construct, IConstruct } from "constructs";

export class UnencryptedBucket extends Construct {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    const unencryptedBucket = new aws_s3.Bucket(this, "UnencryptedBucket", {
      /**
       * Per documentation: `Kms` if `encryptionKey` is specified, or `Unencrypted` otherwise.
       * The `undefined` is added for the sake of the example.
       */
      encryption: undefined
    });
  }
}

class BucketEncryptionChecker implements IAspect {
  public visit(node: IConstruct): void {
    if (node instanceof aws_s3.CfnBucket) {
      /**
       * Feel free to be more specific in terms of conditions in your code.
       */
      if (!node.bucketEncryption) {
        throw new Error("`bucketEncryption` must be specified.");
      }
    }
  }
}

class BucketStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    new UnencryptedBucket(this, "UnencryptedBucket");
  }
}

const app = new App();
const bucketStack = new BucketStack(app, "BucketStack");

Aspects.of(bucketStack).add(new BucketEncryptionChecker());
Enter fullscreen mode Exit fullscreen mode

Variable DeletionPolicy

The DeletionPolicy attribute instructs AWS CloudFormation what should happen to the resource when AWS CloudFormation applies the "DELETE" action to that resource.

Imagine a scenario where someone removes the definition of a database from the template and pushes that Change Set to AWS CloudFormation – without the DeletionPolicy specified to Retain, AWS CloudFormation delete the resource and all the data along with it!

Conversely, imagine deploying emphermal stacks onto your development-only AWS account. You, most likely, would want all the resources gone whenever the stack is deleted – in such scenarios having the DeletionPolicy as Delete might be a wiser choice.

The following is an example of using AWS CDK Aspects to specify the DeletionPolicy to Delete for all resources when deploying the stack in a dev-only environment and to Retain when deploying to a production environment.

import {
  App,
  Aspects,
  CfnResource,
  IAspect,
  RemovalPolicy,
  Stack,
  StackProps
} from "aws-cdk-lib";
import { Construct, IConstruct } from "constructs";

export class DeletionPolicySetter implements IAspect {
  constructor(private readonly policy: RemovalPolicy) {}
  visit(node: IConstruct): void {
    /**
     * Nothing stops you from adding more conditions here.
     */
    if (node instanceof CfnResource) {
      node.applyRemovalPolicy(this.policy);
    }
  }
}

class MyStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);
    /**
     * Your code...
     */
  }
}

const app = new App();
const myStack = new MyStack(app, "MyStack");

const removalPolicy =
  process.env.DEPLOYMENT_ENV == "DEV"
    ? RemovalPolicy.DESTROY
    : RemovalPolicy.RETAIN;

Aspects.of(myStack).add(new DeletionPolicySetter(removalPolicy));
Enter fullscreen mode Exit fullscreen mode

Please be extra careful here. While, to my best knowledge, the change in the DeletionPolicy is an "Update with No Interruption" the worst thing to do is to forget about setting the DeletionPolicy to Delete on resources holding production data.

Closing words

I hope that you found the examples helpful. I've been using Aspects in my personal projects for a while now, and I found them very useful.
Found some neat use-case for Aspects? Please share it! I'm always keen on learning new things from the community.

For more AWS CDK and serverless content, consider following me on Twitter - @wm_matuszewski

Thank you for your valuable time.

Top comments (1)

Collapse
 
jonlauridsen profile image
Jon Lauridsen

Clear and to the point, thanks!