Thursday, July 29, 2021

Using cfn_nag with the CDK

Nota bene: As I was writing this post, I noticed that CDK Labs has started work on cdk-nag which will let you analyze your work directly from within your code.

We've been using Stelligent's excellent cfn_nag for over a year to ensure that our homemade CloudFormation templates are following minimal security best practices. As we've begun transitioning to the CDK, we now need to do similar analysis on synthesized templates generated by the CDK.

First of all, up until now, it seems that cfn_nag is perfectly capable of analyzing templates synthesized from the CDK. The templates themselves are harder to read due to their nature, but the tool still works.


Running cfn_nag on synthesized templates


Running cfn_nag in a CDK workflow is fairly simple, just synthesize the template with "cdk synth" then run cfn_nag_scan on your template as you would do if creating it by hand, i.e. "cfn_nag_scan -i cdk.out/mytemplate.json". There is no more to it.


Adding exceptions in your CDK code

A small challenge for me was to find a way to embed the cfn_nag exceptions in the synthesized output. Thanks to Yan Xiao's stackoverflow post, I've been able to do this quite easily.

Please note that I'm not a typescript expert (or javascript, for the matter) so I'll show you how I've done it, but keep in mind that it might not be the best way.


Using an array to hold the exceptions

First, define empty arrays that will contain the exceptions for each of the resources in your code, for example:

    // Array containing cfn_nag exceptions for the Lambda Function
    var cfnnag_LambdaFunctionRulesToSupress = [];
    // Array containing cfn_nag exceptions for the Bucket
    var cfnnag_BucketRulesToSupress = [];

As your code flows, add the exceptions that you need in the appropriate array, for example, if you need to add an exception to your bucket, do this:

    cfnnag_BucketRulesToSupress.push({
      "id": "W35",   
      "reason": "No need for logging on this Bucket"
    });

Note here that I'm appending new exceptions to the array using push. So just push as many of these exceptions as you need.


Applying the exceptions as CloudFormation Metadata

Once you've finished defining the resources, you'll need to add the exceptions as CloudFormation metadata using the Level 1 properties. Here are a two examples.

1. This adds the exceptions as metadata to your bucket, assuming that you've pushed some exceptions in the array beforehand:

   const S3CfnBucket = this.S3Bucket.node.defaultChild as s3.CfnBucket;
    if (cfnnag_BucketRulesToSupress.length > 0 ) {
      this.S3CfnBucket.cfnOptions.metadata = { 
        "cfn_nag": {
          "rules_to_suppress" : cfnnag_BucketRulesToSupress
        }
      }
    }

2. When adding policies directly to a bucket using the addToResourcePolicy method, things get more tricky as the CDK will not embed these policies in your bucket, but will create a separate AWS::S3::BucketPolicy resource. So if you have specific cfn_nag exceptions to apply to your policies, do this to add them as metadata to the resource :

    const S3CfnbucketPolicy = this.S3Bucket.policy?.node.defaultChild as s3.CfnBucketPolicy;
    if (cfnnag_BucketPolicyRulesToSupress.length > 0 ) {
      S3CfnbucketPolicy.cfnOptions.metadata = { 
        "cfn_nag": {
          "rules_to_suppress" : cfnnag_BucketPolicyRulesToSupress
        }
      }
    }

So here it goes. Hope this helps.