Exploring CDK and Policy as Code with CDK-Nag and Python
Dr. Malte Polley
Posted on September 11, 2024
Infrastructure as Code (IaC) has become a standard in cloud development, allowing for quick environment setups and compliance through versioning. Tools like Terraform and the Cloud Development Kit (CDK) simplify the process compared to traditional CloudFormation.
In a previous blog post, I discussed using a custom solution for pull request reporting with cfn-lint
and cfn_nag
. While cfn-lint
is still valuable for creating your own compliance rules, keeping up with CDK and AWS recommendations can be challenging. Fortunately, cdk-nag
can serve as a substitute for cfn-lint
.
CDK-Nag: Making Infrastructure Decisions Visible
Most tools I've used have a common issue: they can check your infrastructure but often fall short on providing actionable insights. cdk-nag
enhances visibility into the compliant aspects of your IaC deployment. It comes with prebuilt rule packs for various standards:
- AWS Solutions
- HIPAA Security
- NIST 800-53 Rev 4
- NIST 800-53 Rev 5
- PCI DSS 3.2.1
These rules are mapped to specific controls, making compliance evaluations straightforward, even for non-AWS exployees.
Sample Implementation of CDK-Nag in Python
For demonstration, we'll use a sample CDK application created with the command cdk init app --language python
. If you're new to CDK, check out the official tutorial.
Here's a minimal example of creating an S3 bucket in your CDK stack:
from aws_cdk import Stack, aws_s3 as s3
from constructs import Construct
from src.load_env.config import CDKConfig
class CdkSampleRepoStack(Stack):
"""Create the actual deployment in each AWS account."""
def __init__(self, scope: Construct, construct_id: str, stage: str, config: CDKConfig, **kwargs) -> None:
"""Initialize CDK stack class."""
super().__init__(scope, construct_id, **kwargs)
# Create the S3 bucket
bucket = s3.Bucket(self, id="MyBucket")
After running cdk synth, you'll generate a CloudFormation template in the cdk.out folder. However, this bucket configuration may have implicit or missing settings, such as encryption or TLS policies.
To leverage AWS best practices, add cdk-nag to your app.py:
#!/usr/bin/env python3
import os
import cdk_nag
import aws_cdk as cdk
from hello_cdk.hello_cdk_stack import CdkSampleRepoStack
app = cdk.App()
CdkSampleRepoStack(app, "HelloCdkStack", env=cdk.Environment(account='123456789012', region='us-east-1'))
cdk_nag.Aspects.of(app).add(cdk_nag.AwsSolutionsChecks(verbose=True))
app.synth()
Running cdk synth now will display error messages in your terminal, such as:
[Error at /CdkSampleRepoStack/MyBucket/Resource] AwsSolutions-S1: The S3 Bucket has server access logs disabled.
[Error at /CdkSampleRepoStack/MyBucket/Resource] AwsSolutions-S10: The S3 Bucket or bucket policy does not require requests to use SSL.
Handling Best Practice Violations
Sometimes, you may need to explicitly violate a best practice, like disabling access logging for a non-critical bucket. You can suppress specific rules as follows:
# hello_cdk.hello_cdk_stack.py
bucket = s3.Bucket(
self,
id="MyBucket",
block_public_access=s3.BlockPublicAccess(
block_public_acls=True,
block_public_policy=True,
ignore_public_acls=True,
restrict_public_buckets=True,
),
versioned=True,
enforce_ssl=True,
encryption=s3.BucketEncryption.KMS_MANAGED,
)
nag_suppression.add_resource_suppressions(
construct=bucket,
suppressions=[
{
"id": "AwsSolutions-S1",
"reason": "This bucket does not hold customer data",
}
],
)
With this suppression, the CSV report will now indicate that the rule has been suppressed. But, cdk-nag not supports suppressions on constructs but also on resource paths and many more
Integrating Pull Request Reporting with cdk-nag
Integrating cdk-nag into your CI/CD pipeline can enhance your deployment process. For instance, in Azure DevOps, you can automate pull request comments based on the cdk synth results.
Here's a brief overview of how to set this up:
- Create a message class to handle comments.
- Use pandas to read the generated CSV files.
- Translate the CSV table to Markdown.
- Post comments to your pull requests.
import pull_request_comment
import logging
import pandas as pd
import os
logging.basicConfig(
level=logging.INFO, format="[%(levelname)s] - %(message)s", force=True
)
def main():
"""Create a Pull Request comment within azure-pipelines-pr.yml"."""
for filename in os.listdir("./synth/templates/"):
if filename.endswith(".csv"):
try:
df = pd.read_csv(f"./cdk.out/templates/{filename}")
except FileNotFoundError as e:
logging.exception(e)
raise
if df.empty is False:
logging.info("Adding CDK Validation report")
result = msg.add(comment=df.to_markdown())
if __name__ == "__main__":
main()
In your Azure DevOps pipeline, add a task to run cdk synth and then execute the script to post comments:
- task: AWSShellScript@1
inputs:
awsCredentials: ${{ parameters.ServiceConnection }}
regionName: ${{ parameters.AwsRegion }}
scriptType: 'inline'
inlineScript: |
cdk synth
python3 src/pull_requests/comment.py
Final Thoughts
Implementing cdk-nag in your workflow may require some effort, but it provides an easier way to ensure compliance before deploying to AWS. Shifting left in your development process is always a good idea, especially for beginners looking to ensure correct deployments.
Happy and save coding! :-)
Posted on September 11, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.