Julian Michel
Posted on September 23, 2024
cdk-nag is a small tool for checking AWS CDK applications for (security) best practices. It provides rules and rule packs that can be applied to a CDK application. The rules are evaluated during cdk synth
, which has the benefit of providing feedback to developers early in the development cycle. Similar capabilities can be implemented using the AWS Security Hub. However, cdk-nag has the advantage of finding issues before deployment.
cdk-nag provides five rules packs that can be used right out of the box:
- AWS Solutions
- HIPAA Security
- NIST 800-53 rev 4
- NIST 800-53 rev 5
- PCI DSS 3.2.1
If one of these rule packs meets your organization's needs, you can directly use it. If you have custom rules, you can define your own rules and rule packs to make them available to all your projects. The documentation shows how to create them, but there is no information on how to publish them. So this blog post shows how to publish custom cdk-nag rules and rule packs.
Preconditions
You must have access to GitHub in order to create and use GitHub repositories. The Projen features used in this blog post are only available on GitHub.
To publish the cdk-nag ruleset, you must use an npm registry such as npmjs.com. In this blog post, the rule pack is published as a private npm package, which requires a paid npmjs account. If you are using a self-hosted npm registry, this is not a strict requirement.
Create a new Projen project
Projen is another open source project - it manages project configurations. Similar to AWS CDK, which creates CloudFormation templates, a Projen project is synthesized into project configurations, including GitHub actions, linter configuration, and so on.
cdk-nag rules and rule packs are published as a software library, so Projen provides everything needed to publish them. Also, cdk-nag itself uses Projen to publish new releases. The Projen type awscdk-construct
contains everything needed to manage the project, including the GitHub workflows to publish new releases. With jsii it is also possible to publish releases in other programming languages like Java or Python. To create the new project, run this command:
npx projen new awscdk-construct
Add cdk-nag dependency
First, add cdk-nag as new dependency in file .projenrc.ts
. Insert the following line:
peerDeps: ['cdk-nag'],
Then run npx projen
to install it.
Implement a custom rule and rule pack
Now add one or more custom rules and a rule pack. See the documentation and the cdk-nag repository on GitHub for more information. As an example, use the following rule and rule pack.
Implement a rule that checks if SSE is enabled in S3 buckets. This is just an example - it probably makes more sense to use KMS encryption instead. Add a new file src/rules/S3DefaultEncryptionSSE.ts
and paste the content:
import { parse } from 'path';
import { CfnResource, Stack } from 'aws-cdk-lib';
import { CfnBucket } from 'aws-cdk-lib/aws-s3';
import { NagRuleCompliance, NagRules } from 'cdk-nag/lib';
/**
* S3 Buckets are encrypted S3 SSE encryption
* @param node the CfnResource to check
*/
export default Object.defineProperty(
(node: CfnResource): NagRuleCompliance => {
if (node instanceof CfnBucket) {
if (node.bucketEncryption == undefined) {
return NagRuleCompliance.NON_COMPLIANT;
}
const encryption = Stack.of(node).resolve(node.bucketEncryption);
if (encryption.serverSideEncryptionConfiguration == undefined) {
return NagRuleCompliance.NON_COMPLIANT;
}
const sse = Stack.of(node).resolve(
encryption.serverSideEncryptionConfiguration,
);
for (const rule of sse) {
const defaultEncryption = Stack.of(node).resolve(
rule.serverSideEncryptionByDefault,
);
if (defaultEncryption == undefined) {
return NagRuleCompliance.NON_COMPLIANT;
}
const sseAlgorithm = NagRules.resolveIfPrimitive(
node,
defaultEncryption.sseAlgorithm,
);
if (sseAlgorithm != 'AES256') {
return NagRuleCompliance.NON_COMPLIANT;
}
}
return NagRuleCompliance.COMPLIANT;
} else {
return NagRuleCompliance.NOT_APPLICABLE;
}
},
'name',
{ value: parse(__filename).name },
);
Also add a custom rule pack. This one contains a reference to the new rule and an existing rule from cdk-nag. Paste the contents into file src/MyCustomChecks.ts
:
import { CfnResource } from 'aws-cdk-lib';
import { NagMessageLevel, NagPack, NagPackProps, rules } from 'cdk-nag';
import { IConstruct } from 'constructs';
import S3DefaultEncryptionSSE from './rules/S3DefaultEncryptionSSE';
export class MyCustomChecks extends NagPack {
constructor(props?: NagPackProps) {
super(props);
this.packName = 'MyCustom';
}
public visit(node: IConstruct): void {
if (node instanceof CfnResource) {
this.applyRule({
info: 'SSL not enabled in this bucket.',
explanation: 'SSL must be enabled to encrypt traffic.',
level: NagMessageLevel.ERROR,
rule: rules.s3.S3BucketSSLRequestsOnly,
node: node,
});
this.applyRule({
info: 'SSE encryption not set for this bucket.',
explanation: 'SSE encryption must be enabled to encrypt files in this bucket.',
level: NagMessageLevel.ERROR,
rule: S3DefaultEncryptionSSE,
node: node,
});
}
}
}
Replace the existing content in src/index.ts
and export the two files created before.
export * from './MyCustomChecks';
export * from './rules/S3DefaultEncryptionSSE';
It is also recommended to add some test cases. First remove the generated default example test case by deleting file test/hello.test.ts
. For the rule, add a new test case in file test/S3DefaultEncryptionSSE.test.ts
import { App, Stack } from 'aws-cdk-lib';
import { CfnBucket } from 'aws-cdk-lib/aws-s3';
import S3DefaultEncryptionSSE from '../src/rules/S3DefaultEncryptionSSE';
test('Non-Complient Bucket', () => {
const app = new App();
const stack = new Stack(app, 'Stack');
const bucket = new CfnBucket(stack, 'Bucket', {
});
expect(S3DefaultEncryptionSSE(bucket)).toBe('Non-Compliant');
});
test('Complient Bucket', () => {
const app = new App();
const stack = new Stack(app, 'Stack');
const bucket = new CfnBucket(stack, 'Bucket', {
bucketEncryption: {
serverSideEncryptionConfiguration: [
{
serverSideEncryptionByDefault: {
sseAlgorithm: 'AES256',
},
},
],
},
});
expect(S3DefaultEncryptionSSE(bucket)).toBe('Compliant');
});
To complete this step, run npx projen build
to build and test the code. If successful, proceed to the next step.
Release to npmjs
To import the rule pack into another project, it must be published to npmjs. This will be done by Github actions generated by Projen. To release an internal library, add a scope to property name
in .projenrc.ts
. In my case, I added @jumic
.
name: '@jumic/cdk-nag-custom-rules',
Also, enable npm release and set access level to RESTRICTED
.
releaseToNpm: true,
npmAccess: NpmAccess.RESTRICTED,
At npmjs.com, open Access Tokens in the navigation and generate a new access token.
Enter a token name such as GitHub
.
As a result, npmjs will display the generated access token. This needs to be copied to GitHub.
On GitHub, go to the repository settings for your repository. Create a new secret.
Enter the name NPM_TOKEN
, which is the default name Projen expects. Then paste the access token.
Once you have completed these configuration steps, commit your source code to the repository. The GitHub actions will start to build the project and publish it to npmjs.
Check the status on GitHub and make sure that the workflow is running successfully.
Also, go to the npmjs packages and check if the release is available.
If both checks pass, the custom cdk-nag rule package has been successfully released.
Adding your rule pack to a new CDK project
It's time to test the new rule pack. Create a new CDK project or use an existing CDK project. As this library was published as a private package, login to npmjs to access private packages:
npm login
Now you can install the dependency to your project:
npm install @jumic/cdk-nag-custom-rules
In the bin
folder, add the new rule pack to the CDK application.
cdk.Aspects.of(app).add(new MyCustomChecks())
Then execute cdk synth
and it will show all invalid resources.
In case that all checks are passed, you will see the standard CDK output.
Summary
Publishing cdk-nag rule packs with Projen works great. It is a very useful combination of tools for publishing custom cdk-nag rule packs. Once configured, changes are continuously published to the registry.
Using CDK in multiple programming languages? Just add more jsii targets to .projenrc.ts
to publish packages for Python, Java, ... (see Projen documentation).
Posted on September 23, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.