Stelligent

Custom Rule Distribution Enhancements for cfn_nag

Introduction

The cfn_nag tool is a static analysis tool for finding obvious security weaknesses in CloudFormation templates.  

The core product includes rules that apply universally across environments and enterprises.  That said, the product supports the development of custom rules to allow enterprise-specific rules for compliance and security controls.

For more information on developing custom rules for cfn_nag, please see: https://youtu.be/JRZct0naFd4

Custom Rule Loading Enhancements

Historically, cfn_nag could only reference custom rule files in a file system directory.  For example, given:

the following cfn_nag command line applies the custom rule to the template: 

See the gist on github.

With the release of cfn_nag 0.5.x, the internal plumbing for how rules are loaded has been generalized to also allow for loading rules from arbitrary persistent stores including:

As far as the best practice for distributing rules via a Ruby gem, the content of this article supersedes Stelligent’s previous article: 

https://stelligent.com/2019/05/07/extending-cfn_nag-with-custom-rules/

Gem-based Rules

A bunch of “rule files” sitting around on a filesystem isn’t great from a traditional software development perspective.  The rule files are code, so versioning and traceability immediately become an important requirement. Unfortunately, the rule files by themselves have no obvious versioning or traceability.

Instead, these rule files can now be distributed via a Ruby gem, which in turn allows for proper versioning, traceability, and release.  Beyond versioning, distributing rules as a gem allows for organizing rules by a theme or principle. For example, if there are some rules specific to HIPAA compliance or another standard, they can be bundled as a gem and selectively installed by developers or auditors.

Gem Creation

For demonstration purposes, consider an imaginary standard “Fake HIPAA” for which there is one compliance rule that EBS volumes must have SSE enabled.  To create a gem to distribute the cfn_nag rules for “Fake HIPAA”:

  1. Create a directory for the gem: cfn-nag-fake-hipaa-rules
  2. Create a directory underneath: lib/cfn-nag-fake-hipaa-rules
  3. Create an empty file for the entry point of the gem: lib/cfn-nag-fake-hipaa-rules.rb
  4. Define the file: cfn-nag-fake-hipaa-rules.gemspec in the root

See the gist on github.

In the gemspec, the most important item to observe is the metadata.  The gemspec MUST include {‘cfn_nag_rules’ => ‘true’} in order for cfn_nag to load the custom rules from this gem.

Next, create a rule file: lib/cfn-nag-fake-hipaa-rules/CustomSseRule.rb

See the gist on github.

In the custom rule code, the most important item to observe is the base class for the rule.  Any custom rules should derive from CfnNag::BaseRule in cfn-nag/base_rule (not cfn-nag/custom-rules/base).  If the rule must derive from something else, define a method cfn_nag_rule? that returns true.  If neither of these two criteria is met, cfn_nag will not consider the class a rule so in turn it will not be used for evaluation of CloudFormation templates.

Finally, build the gem.

See the gist on github.

For more information on gems, see: https://guides.rubygems.org/specification-reference/

The example code for this article is available at https://github.com/stelligent/cfn-nag-fake-hipaa-rules.

Gem Usage

Presuming the rule gem has been properly created, usage is almost trivial.  First, install cfn_nag and the rule gem:

See the gist on github.

Or alternatively, if the gem is published to rubygems.org or a local gem repository:

See the gist on github.

Finally, run a “red” template with the violation through cfn_nag that will exercise the rule in the gem.  Save this template as unencrypted_ebs_volume.yml

See the gist on github.

and run cfn_nag without any extra rule directory arguments.  The rule from the installed gem will emit a violation from the “Fake HIPAA” standard:

See the gist on github.

S3 Buckets

An S3 bucket can serve as a “rule repository” for developers on the security team to publish their custom cfn_nag rules for the whole of an enterprise to use.  Previously, to access custom rules stored in an S3 bucket, the objects had to be copied down to the local filesystem on a regular basis.  

This is inconvenient for developers, but more importantly, it’s awkward for when cfn_nag is running in an AWS Lambda function without much of a filesystem.  For example, the “cfn-nag-pipeline” published in AWS Serverless Application Repository, is an action for AWS CodePipeline that invokes cfn_nag against CloudFormation templates.  Recent versions of cfn-nag-pipeline can now access custom rules directly from a “rule repository” in a specified S3 bucket.

To load custom rules from an S3 bucket on the command line, first, capture the coordinates of the S3 bucket in a “rule repository” definition file.  Copy the following text into security-controls.yml on the local filesystem:

See the gist on github.

Edit the YAML to match the local situation.  To elaborate:

Next, upload a custom rule file to the referenced S3 bucket with the referenced prefix/key.  For example, copy the following code to the local file CustomSseRule.rb:

See the gist on github.

And then upload as the object cfn_nag_rules/CustomSseRule.rb in bucket my-enterprise-security-controls-code.

See the gist on github.

The name of the class and the name of the file holding the class should agree and end with the suffix: Rule.

Finally, run a “red” template with a violation through cfn_nag that will exercise the rule in the S3 bucket.  Save this template as unencrypted_ebs_volume.yml

See the gist on github.

and run cfn_nag with the specification for the S3 bucket “coordinates”.  The rule from the bucket will emit a violation around SSE:

See the gist on github.

Conclusion

The cfn_nag tool has long provided the means to develop custom rules, but the distribution mechanism has been limited to referencing rule files in a file system.  With the release of cfn_nag 0.5.x, cfn_nag can load rules implicitly from installed “rule gems” and directly from S3 buckets in AWS.