It’s a little too easy to make non-secure configurations of resources in CloudFormation when you are focused on getting the entire stack to render correctly. By the time you are done building and testing a template, you must take extra time to revisit all your resources to make sure you are following good security and IaaS practices.

Enter cfn_nag, a handy little Ruby gem created by Stelligent that can help identify problems in your CloudFormation templates before you publish them. According to the README for the repo on Github, Stelligent says this about cfn_nag:

The cfn-nag tool looks for patterns in CloudFormation templates that may indicate insecure infrastructure. Roughly speaking it will look for:

IAM rules that are too permissive (wildcards)

Security group rules that are too permissive (wildcards)

Access logs that aren’t enabled

Encryption that isn’t enabled

Under the covers, cfn_nag is using jq to parse the JSON input files you provide to it for inspection. In my case, I simply installed jq first using homebrew:

[rcrelia@fuji vpc-scenario-2-reference (master=)]$ brew install jq ==> Installing dependencies for jq: oniguruma ==> Installing jq dependency: oniguruma ==> Downloading https://homebrew.bintray.com/bottles/oniguruma-6.0.0.yosemite.bottle.tar.gz ######################################################################## 100.0% ==> Pouring oniguruma-6.0.0.yosemite.bottle.tar.gz 🍺 /usr/local/Cellar/oniguruma/6.0.0: 16 files, 1.3M ==> Installing jq ==> Downloading https://homebrew.bintray.com/bottles/jq-1.5_1.yosemite.bottle.tar.gz ######################################################################## 100.0% ==> Pouring jq-1.5_1.yosemite.bottle.tar.gz 🍺 /usr/local/Cellar/jq/1.5_1: 18 files, 958.5K

Once I had jq, I installed cfn_nag:

[rcrelia@fuji vpc-scenario-2-reference (master=)]$ gem install cfn-nag Fetching: trollop-2.1.2.gem (100%) Successfully installed trollop-2.1.2 Fetching: multi_json-1.12.1.gem (100%) Successfully installed multi_json-1.12.1 Fetching: little-plugger-1.1.4.gem (100%) Successfully installed little-plugger-1.1.4 Fetching: logging-2.0.0.gem (100%) Successfully installed logging-2.0.0 Fetching: cfn-nag-0.0.19.gem (100%) Successfully installed cfn-nag-0.0.19 Parsing documentation for trollop-2.1.2 Installing ri documentation for trollop-2.1.2 Parsing documentation for multi_json-1.12.1 Installing ri documentation for multi_json-1.12.1 Parsing documentation for little-plugger-1.1.4 Installing ri documentation for little-plugger-1.1.4 Parsing documentation for logging-2.0.0 Installing ri documentation for logging-2.0.0 Parsing documentation for cfn-nag-0.0.19 Installing ri documentation for cfn-nag-0.0.19 Done installing documentation for trollop, multi_json, little-plugger, logging, cfn-nag after 1 seconds 5 gems installed

At this point, I had a working version of cfn_nag and immediately checked some recent templates. Here is output from running against one of my aws-mojo “Scenario 2” templates I recently posted about:

[rcrelia@fuji vpc-scenario-2-reference (master=)]$ cfn_nag --input-json-path ./aws-vpc-instance-securitygroups.json ------------------------------------------------------------ ./aws-vpc-instance-securitygroups.json ------------------------------------------------------------------------------------------------------------------------ | WARN | | Resources: ["PubInstSGIngressHttp", "PubInstSGIngressHttps"] | | Security Group Standalone Ingress found with cidr open to world. This should never be true on instance. Permissible on ELB ------------------------------------------------------------ | WARN | | Resources: ["PrivInstSGEgressGlobalHttp", "PrivInstSGEgressGlobalHttps", "PubInstSGEgressGlobalHttp", "PubInstSGEgressGlobalHttps"] | | Security Group Standalone Egress found with cidr open to world. Failures count: 0 Warnings count: 6

Pretty neat! In this case, these warnings are anticipated due to how I designed the VPC security groups to make use of network routing through NAT instances as well as the public NAT instances themselves being able to receive traffic globally in the public zones.

Obviously, you may want to consider adding your own cfn_nag rules to the stock set it ships with, to reflect your own specific security and configuration concerns.

To see a list of all the rules that come pre-configured in cfn_nag, simply run cfn_nag_rules:

[rcrelia@fuji vpc-scenario-2-reference (master=)]$ cfn_nag_rules WARNING VIOLATIONS: CloudFront Distribution should enable access logging Elastic Load Balancer should have access logging configured Elastic Load Balancer should have access logging enabled IAM managed policy should not allow * resource IAM managed policy should not allow Allow+NotAction IAM managed policy should not allow Allow+NotResource IAM policy should not allow * resource IAM policy should not allow Allow+NotAction IAM policy should not allow Allow+NotResource IAM role should not allow * resource on its permissions policy IAM role should not allow Allow+NotAction IAM role should not allow Allow+NotAction on trust permissinos IAM role should not allow Allow+NotResource Lambda permission beside InvokeFunction might not be what you want? Not sure!? S3 Bucket likely should not have a public read acl S3 Bucket policy should not allow Allow+NotAction SNS Topic policy should not allow Allow+NotAction SQS Queue policy should not allow Allow+NotAction Security Group Standalone Egress found with cidr open to world. Security Group Standalone Ingress cidr found that is not /32 Security Group Standalone Ingress found with cidr open to world. This should never be true on instance. Permissible on ELB Security Group egress with port range instead of just a single port Security Group ingress with port range instead of just a single port Security Groups found egress with port range instead of just a single port Security Groups found ingress with port range instead of just a single port Security Groups found with cidr open to world on egress Security Groups found with cidr open to world on egress array Security Groups found with cidr open to world on ingress array. This should never be true on instance. Permissible on ELB Security Groups found with cidr open to world on ingress. This should never be true on instance. Permissible on ELB Security Groups found with cidr that is not /32 Specifying credentials in the template itself is probably not the safest thing FAILING VIOLATIONS: A Cloudformation template must have at least 1 resource AWS::EC2::SecurityGroup must have Properties AWS::EC2::SecurityGroupEgress must have Properties AWS::EC2::SecurityGroupEgress must not have GroupName - EC2 classic is a no-go! AWS::EC2::SecurityGroupIngress must have Properties AWS::EC2::SecurityGroupIngress must not have GroupName - EC2 classic is a no-go! AWS::IAM::ManagedPolicy must have Properties ...snip...

There are two classes of notifications, warning violations and failing violations. There is good guidance in each set, but again, you may find that you want to edit/add your own rules to increase the value of cfn_nag for your infrastructure.