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:
- a Cloudformation template to scan ebs_volume_without_encryption.json
- a custom rule file EbsVolumeHasSseRule.rb residing in a directory /var/tmp/rules
the following cfn_nag command line applies the custom rule to the template:
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:
- An installed “rule gem”
- An S3 bucket
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”:
- Create a directory for the gem: cfn-nag-fake-hipaa-rules
- Create a directory underneath: lib/cfn-nag-fake-hipaa-rules
- Create an empty file for the entry point of the gem: lib/cfn-nag-fake-hipaa-rules.rb
- Define the file: cfn-nag-fake-hipaa-rules.gemspec in the root
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
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.
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:
Or alternatively, if the gem is published to rubygems.org or a local gem repository:
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
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:
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:
Edit the YAML to match the local situation. To elaborate:
- repo_class_name
- This value should not be changed (for an S3 rule repository). As a point of interest, this is exposed in order to allow writing custom rule repositories.
- s3_bucket_name
- This is the bucket from where the rules are to be loaded.
- prefix
- This is the S3 key prefix for where to discover custom rules. For example, cfn_nag_rules would allow a rule/object with the key cfn_nag_rules/EncryptionRule.rb to be discovered and evaluated.
- aws_profile
- This governs the credentials that are used to access this bucket. This field is optional, and if left out, the ambient environment credentials are used. For more information see Named Profiles.
- index_lifetime
- This controls the local file system caching expiration for rules. This argument is optional and defaults to one hour. If new rules are added often, this setting can be dropped to minutes (e.g. 10m). If the lifetime is too short, then performance may decrease and charges for accessing S3 API may start to accrue.
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:
And then upload as the object cfn_nag_rules/CustomSseRule.rb in bucket my-enterprise-security-controls-code.
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
and run cfn_nag with the specification for the S3 bucket “coordinates”. The rule from the bucket will emit a violation around SSE:
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.