Check and secure CDK code with cdk-nag
Tags: cdk, aws, security, python
Published: 2022-03-14 20:51:32

## Background
In my previous blog, [CDK Pipelines and CloudFormation linting](https://yvovanzee.nl/cdk-pipelines-and-cloudformation-linting) I've wrote about how you can use cfn_nag in CDK pipelines to check your CloudFormation templates on security best practises. With an end goal to make your code more secure and enterprise ready. 
Since 2 December last year, AWS [announced](https://aws.amazon.com/blogs/aws/announcing-general-availability-of-construct-hub-and-aws-cloud-development-kit-version-2/) the general availability of the [Construct Hub](https://constructs.dev/). 
> The Construct Hub is a single home where the open-source community, AWS, and cloud technology providers can discover and share construct libraries for all CDKs.

In the Construct Hub the [construct cdk-nag](https://constructs.dev/packages/cdk-nag/v/1.10.1?lang=python) has been released. The cdk-nag construct allows you to check your CDK applications for best practices using a combination of available [rule packs](https://github.com/cdklabs/cdk-nag/blob/HEAD/RULES.md).
Using this cdk-nag construct and use it inside your code to check upon security best practises, will eventually make your application more secure. In this article I will describe how to use the cdk-nag construct in combination with CDK. 

## Prerequisites
As this blog describes the use of CDK, knowledge of CDK is required. Luckily AWS created workshops on this topic. Please check them out if you want to try out CDK.

  * https://cdkworkshop.com
  * https://cdk-advanced.workshop.aws

Furthermore access to an AWS Account with proper rights to deploy resources is needed.

## Installation
The idea here is to create a basic S3 bucket, and let cdk-nag check the code by using the Aspect function in CDK. The outcome will be checked with the normal cfn_nag cli tool to see if this match.
After that we will import the secure bucket construct used in my other blog to check if this will make the S3 bucket secure. 

So start with creating a CDK project and work from there:

```bash
➜  mkdir s3bucket_with_cdk_nag_construct && cd s3bucket_with_cdk_nag_construct 
➜  cdk init app --language python
``` 
I'm using Python here, as the previous blogs were also using Python CDK and I find personally that there are more Typescript examples then Python

## Real World Scenario
Within enterprises, security frameworks are often used to make sure that resources are securely deployed in the cloud.  AWS has the service Security Hub which can deploy rulesets to support such security frameworks. 

When developing a CDK application, it is sometimes hard to track if your created code is compliant or not before actually deploying your CDK stacks. 

Using tools like cfn_nag will allow you to check your CloudFormation templates on security best practices. Now with the construct cdk_nag from the [Construct Hub](https://constructs.dev/packages/cdk-nag/v/1.10.4?lang=python) it is possible to implement cfn_nag rules and checks directly into your code set during synthesizing. 

## Go Build
As we are using CDK version 2, a lot of dependencies are taken away from us. We only need to add cdk-nag. In the CDK application edit the `requirements.txt` file and add cdk-nag. I'm using cdk-nag above version 2.10 for this example, so that I'm using the latest one.
```bash
aws-cdk-lib==2.16.0
constructs>=10.0.0,<11.0.0
cdk-nag>=2.10.0
```
Now install everything. First switch to the virtual environment created by the `cdk init app` command:
```bash
➜  cdkpipeline_with_cfn_nag_construct git:(main) ✗ source .venv/bin/activate   
(.venv) ➜  cdkpipeline_with_cfn_nag_construct git:(main) ✗ pip install -r requirements.txt
Collecting aws-cdk-lib==2.16.0
  Downloading aws_cdk_lib-2.16.0-py3-none-any.whl (64.1 MB)
     |████████████████████████████████| 64.1 MB 311 kB/s             
Collecting constructs<11.0.0,>=10.0.0
  Downloading constructs-10.0.87-py3-none-any.whl (54 kB)
     |████████████████████████████████| 54 kB 5.9 MB/s             
Collecting cdk-nag>=2.10.0
  Downloading cdk_nag-2.10.4-py3-none-any.whl (661 kB)
     |████████████████████████████████| 661 kB 8.0 MB/s            
Collecting jsii<2.0.0,>=1.54.0
  Using cached jsii-1.55.0-py3-none-any.whl (383 kB)
Collecting publication>=0.0.3
  Using cached publication-0.0.3-py2.py3-none-any.whl (7.7 kB)
Collecting typing-extensions<5.0,>=3.7
  Using cached typing_extensions-4.1.1-py3-none-any.whl (26 kB)
Collecting cattrs<1.11,>=1.8
  Using cached cattrs-1.10.0-py3-none-any.whl (29 kB)
Collecting python-dateutil
  Using cached python_dateutil-2.8.2-py2.py3-none-any.whl (247 kB)
Collecting attrs~=21.2
  Using cached attrs-21.4.0-py2.py3-none-any.whl (60 kB)
Collecting six>=1.5
  Using cached six-1.16.0-py2.py3-none-any.whl (11 kB)
Installing collected packages: six, attrs, typing-extensions, python-dateutil, cattrs, publication, jsii, constructs, aws-cdk-lib, cdk-nag
Successfully installed attrs-21.4.0 aws-cdk-lib-2.16.0 cattrs-1.10.0 cdk-nag-2.10.4 constructs-10.0.87 jsii-1.55.0 publication-0.0.3 python-dateutil-2.8.2 six-1.16.0 typing-extensions-4.1.1
```
Now we have installed all dependencies, it is time to add the code. 

### Create S3 bucket
As we are using CDK version 2 now, it is not needed to install the bucket construct as a separate pip installation. This has already been taken care of with the installation of CDK version 2 libraries.

Open the project inside your preferred editor, mine is [Visual Studio Code](https://code.visualstudio.com/).

Inside the the project open the file `s3bucket_with_cdk_nag_construct_stack.py` inside the `s3bucket_with_cdk_nag_construct` folder. This file will be used to create the S3 bucket.

Add to the import `aws_s3 as s3` to import the S3 construct.
```python
from aws_cdk import (
    aws_s3 as s3,
    Stack,
)
from constructs import Construct

class S3BucketWithCfnNagConstructStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        s3.Bucket(self, 'S3Bucket')
```
What is different between this stack and the one created in the [CDK Pipelines and CloudFormation linting](https://yvovanzee.nl/cdk-pipelines-and-cloudformation-linting) which uses CDK version 1 are the import statements. 

Now synthesize the CDK project. This will show the CloudFormation template. Here is the snippet of mine:
```Yaml
Resources:
  S3Bucket07682993:
    Type: AWS::S3::Bucket
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain
```
This is what a plain S3 bucket construct will produce as CloudFormation json output.
If you want to run cfn_nag_scan locally, you need to install the package `cfn-nag` as a ruby gem:
```bash
(.venv) ➜  s3bucket_with_cdk_nag_construct git:(main) ✗ sudo gem install cfn-nag
```
This allows you to run `cfn_nag_scan` on the synthesized template in the `cdk.out` directory:
```text
(.venv) ➜  s3bucket_with_cdk_nag_construct git:(main) ✗ cfn_nag_scan -i cdk.out/S3BucketWithCfnNagConstructStack.template.json
------------------------------------------------------------
cdk.out/S3BucketWithCfnNagConstructStack.template.json
------------------------------------------------------------------------------------------------------------------------
| WARN W51
|
| Resource: ["S3Bucket07682993"]
| Line Numbers: [4]
|
| S3 bucket should likely have a bucket policy
------------------------------------------------------------
| WARN W35
|
| Resource: ["S3Bucket07682993"]
| Line Numbers: [4]
|
| S3 Bucket should have access logging configured
------------------------------------------------------------
| WARN W41
|
| Resource: ["S3Bucket07682993"]
| Line Numbers: [4]
|
| S3 Bucket should have encryption option set

Failures count: 0
Warnings count: 3
```
As you can see the simple created bucket via the S3 bucket construct isn't that securely configured accordingly to `cfn_nag`. This is cool and all, but what if we can use an Aspect for `cfn_nag_scan`. Let start with configuring `cdk_nag`. 
Update the `s3bucket_with_cdk_nag_construct_stack.py` file to use the `cdk_nag` construct:
```python
from aws_cdk import (
    Aspects,
    Stack,
    aws_s3 as s3,
)
from constructs import Construct
import cdk_nag

class S3BucketWithCfnNagConstructStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        s3.Bucket(self, 'S3Bucket')
        Aspects.of(self).add(cdk_nag.AwsSolutionsChecks())
```
As you can see, in the import statement, the `Aspects` and `cdk_nag` are imported. Take in account that the package name to import is with lower case instead of hyphen. 
The last line in the code is the actual piece which calls the cdk_nag construct. Here it is used as an Aspect on the constructs inside this stack, but you can also use cdk_nag on a stack level. Then you need to import the `cdk_nag` construct in the `app.py` file and use `Aspect.of(App).add(cdk_nag.AwsSolutions(Checks())` instead. More on the options on `cdk_nag` can be found in the documentation on the [Construct Hub](https://constructs.dev/packages/cdk-nag/v/1.10.4?lang=python)

So now everything is in place, synthesize the CDK app:
```bash
(.venv) ➜  s3bucket_with_cdk_nag_construct git:(main) ✗ cdk synth
[Error at /S3BucketWithCfnNagConstructStack/S3Bucket/Resource] AwsSolutions-S1: The S3 Bucket has server access logs disabled.

[Error at /S3BucketWithCfnNagConstructStack/S3Bucket/Resource] AwsSolutions-S2: The S3 Bucket does not have public access restricted and blocked.

[Error at /S3BucketWithCfnNagConstructStack/S3Bucket/Resource] AwsSolutions-S3: The S3 Bucket does not default encryption enabled.

[Error at /S3BucketWithCfnNagConstructStack/S3Bucket/Resource] AwsSolutions-S10: The S3 Bucket does not require requests to use SSL.


Found errors
``` 
It doesn't synthesize due to errors raised by `cdk_nag`. If we compare the outcome with the normal `cfn_nag_scan` you can see that there is an extra error raised with the `cdk_nag` construct.

```bash
[Error at /S3BucketWithCfnNagConstructStack/S3Bucket/Resource] AwsSolutions-S10: The S3 Bucket does not require requests to use SSL.
```

### Make the bucket secure
In my previous post [Secure S3 Bucket construct with CDK version 2](https://yvovanzee.nl/secure-s3-bucket-construct-with-cdk-version-2) I do write about how to create a secure bucket construct. Let us try that one and see how secure it really is.

Import the secure bucket construct from pypi.org. Add the package `secure-bucket-construct==2.1.0` to the `requirements.txt` file and run `pip install -r requirements.txt` again.

Now add it to the `s3bucket_with_cdk_nag_construct_stack.py` file so we can use it instead of the normal S3 bucket import. The `s3bucket_with_cdk_nag_construct_stack.py` should look like this:

```python
from aws_cdk import (
    Aspects,
    Stack,
    aws_s3 as s3,
)
from constructs import Construct
from secure_bucket_construct import SecureBucket
import cdk_nag

class S3BucketWithCfnNagConstructStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        SecureBucket(self, 'SecureS3Bucket')
        Aspects.of(self).add(cdk_nag.AwsSolutionsChecks())
```
When synthesizing the template you can now see that there is more in the CloudFormation template added, such as an AWS KMS key, S3 Bucket and a S3 Bucket Policy. No errors are raised by the `cdk_nag` construct. Does it still work? We can double check with `cfn_nag_scan`.

Now when running `cfn_nag_scan` on the synthesized template, the outcome is 0 Failures and 0 Warnings:
```bash
(.venv) ➜  s3bucket_with_cdk_nag_construct git:(main) ✗ cfn_nag_scan -i cdk.out/S3BucketWithCfnNagConstructStack.template.json
------------------------------------------------------------
cdk.out/S3BucketWithCfnNagConstructStack.template.json
------------------------------------------------------------
Failures count: 0
Warnings count: 0
``` 
Happy days!

Recap: with `cdk-nag` from the [Construct Hub](https://constructs.dev/packages/cdk-nag/v/1.10.4?lang=python) we are able to see if resources are being deployed according to best practices during synthesize. It also showed how to use the secure-bucket-construct from pypi.org created in my previous blog.

## Try Yourself
The complete code used above can be found in my GitHub: [S3 Bucket checked with cdk-nag construct](https://github.com/yvthepief/s3bucket_with_cdk_nag_construct)