Extending cfn_nag with custom rules
Update: March 5, 2020
Please see Custom Rule Distribution Enhancements for cfn_nag for our latest best practices for cfn_nag custom rules.
Stelligent cfn_nag is an open source command-line tool that performs static analysis of AWS CloudFormation templates. The tool runs as a part of your pre-flight checks in your automated delivery pipeline and can be used to prevent a CloudFormation update from occurring that would put you in a compromised state. The core gem provides over 50 custom rules at the time of writing this blog post. These rules cover a wide range of AWS resources and are geared towards keeping your AWS account and resources secure.
The typical open source contribution model allows the community to propose additions to the core gem. This tends to be the most desirable outcome. Chances are that if you find something useful that someone else would find it useful too. However, there are instances where custom rules have company or project specific logic that may not make sense to put into the cfn_nag core gem. To accomplish this we recommend wrapping the cfn_nag core gem with a wrapper gem that contains your custom rules.
This article will walk you through the process necessary to create a wrapper gem. We have published an example wrapper gem which is a great starting point.
Adding custom rules with a gem wrapper
The following file structure is the bare minimum required for a cfn_nag wrapper gem. In this example the name of the gem is cfn-nag-custom-rules-example and it provides one custom rule called ExampleCustomRule. You will execute cfn_nag (or cfn_nag_scan) with your wrapper’s executable bin/cfn_nag_custom.
. |- Gemfile |- bin | \- cfn_nag_custom |- cfn-nag-custom-rules-example.gemspec |- lib | \- cfn-nag-custom-rules-example.rb \- rules \- ExampleCustomRule.rb
The first of the important files is the Gemfile which is boilerplate.
Gemfile:
# frozen_string_literal: true source 'https://rubygems.org' gemspec
After that is the executable ruby script wrapper used to load up your custom rules on top of the core rules. It will pass through all arguments to the underlying cfn_nag (or cfn_nag_scan) command as you see fit.
bin/cfn_nag_custom:
#!/usr/bin/env ruby args = *ARGV path = Gem.loaded_specs['cfn-nag-custom-rules-example'].full_gem_path command = "cfn_nag -r #{path}/lib/rules #{args.join(" ")}" system(command)
Up next is the gemspec. There is nothing to note here outside of requiring the core gem as a dependency. Feel free to pin it any way you would like but we would recommend not always grabbing the latest version.
cfn-nag-custom-rules-example.gemspec:
# frozen_string_literal: true Gem::Specification.new do |s| s.name = 'cfn-nag-custom-rules-example' s.license = 'MIT' s.version = '0.0.1' s.bindir = 'bin' s.executables = %w[cfn_nag_custom] s.authors = ['Eric Kascic'] s.summary = 'Example CFN Nag Wrapper' s.description = 'Wrapper to show how to define custom rules with cfn_nag' s.homepage = 'https://github.com/stelligent/cfn_nag' s.files = Dir.glob('lib/**/*.rb') s.require_paths << 'lib' s.required_ruby_version = '>= 2.2' s.add_development_dependency('rspec', '~> 3.4') s.add_development_dependency('rubocop') s.add_runtime_dependency('cfn-nag', '>= 0.3.73') end
The lib/cfn-nag-custom-rules-example.rb is just a blank ruby file required as part of how gems work and are loaded. Finally, we have our example custom rule. Any file in lib/rules that ends in Rule.rb will be loaded as a custom rule in cfn_nag. The example rule here enforces all S3 buckets should be named ‘foo.’ Please note that custom rules have a rule id that starts with C for custom rule. Rule types can be one of the following.
- Violation::FAILING_VIOLATION – Will result in a failure
- Violation::WARNING – Informational message. Only causes a failure if –file_on_warnings is set to true
lib/rules/ExampleCustomRule.rb:
# frozen_string_literal: true require 'cfn-nag/custom_rules/base' class ExampleCustomRule < BaseRule def rule_text 'S3 buckets should always be named "foo"' end def rule_type Violation::FAILING_VIOLATION end def rule_id 'C1' # Custom Rule #1 end def audit_impl(cfn_model) resources = cfn_model.resources_by_type('AWS::S3::Bucket') violating_buckets = resources.select do |bucket| bucket.bucketName != 'foo' end violating_buckets.map(&:logical_resource_id) end end
At this point, you can build, install, and execute your custom rules.
gem build cfn-nag-custom-rules-example.gemspec gem install cfn-nag-custom-rules-example-0.0.1.gem cfn_nag_custom buckets_with_insecure_acl.json
This results in:
{ "failure_count": 3, "violations": [ { "id": "C1", "type": "FAIL", "message": "S3 buckets should always be named \"foo\"", "logical_resource_ids": [ "S3BucketRead", "S3BucketReadWrite" ] }, { "id": "W31", "type": "WARN", "message": "S3 Bucket likely should not have a public read acl", "logical_resource_ids": [ "S3BucketRead" ] }, { "id": "F14", "type": "FAIL", "message": "S3 Bucket should not have a public read-write acl", "logical_resource_ids": [ "S3BucketReadWrite" ] } ] }
As you can see, it evaluated core cfn_nag rules as well as your custom rule.
Additional Resources
- cfn_nag – Open source tool for validating CloudFormation templates
- cfn_nag_custom_rules_example – Example gem wrapper for extending cfn_nag with custom rules
- cfn_nag_examples – A few examples of CloudFormation templates used to demonstrate the capabilities of cfn_nag
- Finding Security Problems Early in the Development Process of a CloudFormation Template with “cfn-nag” – Blog post describing cfn_nag from its lead engineer, Eric Kascic
- Validating AWS CloudFormation templates with cfn_nag and mu – Blog post illustrating mu and cfn_nag integration
- https://www.youtube.com/watch?v=JRZct0naFd4&t=1603s – Video on how to develop custom rules
Stelligent Amazon Pollycast
|