AWS CloudFormation cfn-guard is now GA - hello policy as code
Last month AWS Cloudformation Guard (cfn-guard) became generally available (GA) and it’s a pretty cool addition by AWS which gets us started with policy as code. You can check out the release announcement by AWS for more details. I missed the preview in June and first heard about the GA on the official AWS podcast run by Simon and Nicki, if you don’t already, be sure to check out the podcast it’s a good source of info, guest speakers etc. The cfn-guard release was discussed in episode #397 October 2020 update and you can check out more details on the AWS podcast over at AWS.
In this post i’ll step through getting setup with cfn-guard and we’ll use one of my previous CDK stacks as an example to apply cfn-guard to existing project by generating a ruleset configuration file.
What is cloudformation?
AWS explains this better than I do, but in general you define your AWS Infrastructure as Code (IaC). Using this method your AWS config can live in a code repo along side your project and is developed in JSON or YAML. It’s a neat way to provision infrastructure in a nice repeatable fashion and gets you started with automation. Check it out if haven’t already.
Policy as code
Once you start developing in the cloud with IaC and you get familiar with stacks, edits, etc you might find that certain deployments or updates shouldn’t have gone through. Or, perhaps you have some organizational level guidelines that specify certain design standards e.g. deploying EC2 instances in a certain region or availability zone. These types of guide lines can be turned into guard rails inside AWS through policies but can you stop them before they are deployed? This is where policy as code comes in.
We can use policy-as-code to again, live in our code repositories, these can be defined by champions in certain areas e.g. DevSecOps engineers who are security focused and aligned with corporate policy. Now before the cloudformation stack is deployed you can simply check the stack against the policy.
Installation
Cloudformation guard is a cli tool and needs to be installed, thankfully AWS have us covered with popular package managers. I’ll run through installation on MacOS but Linux and Windows are also supported.
You’ll need to use Homebrew, I assume you’ve already installed Homebrew and you’re familiar with what it does.
Simply run:
brew install cloudformation-guard
This will install the cli tool which can be run by calling cfn-guard
# cfn-guard
CloudFormation Guard 1.0.0
USAGE:
cfn-guard [SUBCOMMAND]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
SUBCOMMANDS:
check Check CloudFormation templates against rules
help Prints this message or the help of the given subcommand(s)
rulegen Autogenerate rules from an existing CloudFormation template
Note: If you’re on Linux or Windows checkout the cloudformation guard github repo for instructions: link
An existing CDK project
We are going to run cfn-guard against an existing project, I am using one my previous CDK posts about AppSync and WAF. It’s a small project that shows you how to use AppSync with web application firewalls and it’s developed in CDK. You can pull the code from github if you want to check it out.
I’m using a CDK project because i’m a big fan of CDK and all of my projects are developed in CDK. When you synthesize CDK it generates the cloudformation file that we’ll be using to run cfn-guard to compliance against our rules.
Synth CDK to Cloudformation
Once we have the project we’ll run the CDK synth command to generate the file needed so that we can generate a cfn-guard ruleset.
cdk synth
Now look in the CDK.out directory and we’ll see the cloudformation json template generated.
Note: If you want to follow along with your own cf template skip the CDK parts
Generating the cfn-guard ruleset
As discussed earlier, I wanted to try out generating ruleset so that we can start with the correct policy syntax required and I’m interested to see the output so lets run cfn-guard against the template.
cfn-guard rulegen cdk.out/AppSyncWafStack.template.json
You should get a busy looking output in the terminal:
AWS::AppSync::DataSource ApiId == {"Fn::GetAtt":["api","ApiId"]}
AWS::AppSync::DataSource Description == talkncloud dynamo datasource
AWS::AppSync::DataSource DynamoDBConfig == {"AwsRegion":{"Ref":"AWS::Region"},"TableName":{"Ref":"tncdb38B2E622"}}
AWS::AppSync::DataSource Name == dynamodb
AWS::AppSync::DataSource ServiceRoleArn == {"Fn::GetAtt":["tncdbRoleBA7ABA7B","Arn"]}
AWS::AppSync::DataSource Type == AMAZON_DYNAMODB
AWS::AppSync::GraphQLApi AuthenticationType == AMAZON_COGNITO_USER_POOLS
AWS::AppSync::GraphQLApi Name == talkncloud
AWS::AppSync::GraphQLApi UserPoolConfig == {"AwsRegion":{"Ref":"AWS::Region"},"DefaultAction":"ALLOW","UserPoolId":{"Ref":"tncup"}}
AWS::AppSync::GraphQLSchema ApiId == {"Fn::GetAtt":["api","ApiId"]}
AWS::AppSync::GraphQLSchema Definition == type Todo { id: ID! name: String! description: String}enum ModelSortDirection
{ ASC DESC}type ModelTodoConnection { items: [Todo] nextToken: String}input ModelStringInput { ne: String eq: String
le: String lt: String ge: String gt: String contains: String notContains: String between: [String] beginsWith:
String attributeExists: Boolean attributeType: ModelAttributeTypes size
Let’s go ahead and save the output
cfn-guard rulegen cdk.out/AppSyncWafStack.template.json > cfn.ruleset
It’s a simple as that, I really like cli tools that use simple redirection to save valid output used for configs. Nice and clean.
Why rulegen?
If we review the output of cfn-guard rulegen we can see that the output isn’t as useful as we were hoping. It’s pretty clear the tool parses the template and makes everything a rule. Might be nice to split out certain streams e.g. –security to output only security related items which strips out names, descriptions etc.
What do you think?
What policy should we define
When I think about AppSync which is a GraphQL API from AWS we need to think about what policy might be required for that service. Let’s keep it simple but practical with the following:
- Cognito authentication
- Web application firewall (WAF) rules
By default AppSync uses API keys but we want to make sure users are authenticated with a username and password instead of just a key, commonly federated with social logins. AppSync also doesn’t use a WAF by default but we want to make sure one is configured with the correct rules. We’ll write a policy that ensures this.
Our AppSync Policy
We’ve removed quite a bit from the generated policy and he’s what we ended up with:
AWS::AppSync::GraphQLApi AuthenticationType == AMAZON_COGNITO_USER_POOLS
AWS::WAFv2::WebACL Rules == [{"Action":{"Count":{}},"Name":"GeoMatch",
"Priority":0,"Statement":{"NotStatement":{"Statement":{"GeoMatchStatement":
{"CountryCodes":["AU"]}}}},"VisibilityConfig":{"CloudWatchMetricsEnabled":
true,"MetricName":"GeoMatch","SampledRequestsEnabled":true}},
{"Name":"AWS-AWSManagedRulesCommonRuleSet","OverrideAction":{"None":{}},"Priority":
1,"Statement":{"ManagedRuleGroupStatement":{"ExcludedRules":[{"Name":
"NoUserAgent_HEADER"}],"Name":"AWSManagedRulesCommonRuleSet","VendorName":"AWS"}
},"VisibilityConfig":{"CloudWatchMetricsEnabled":true,"MetricName":
"AWS-AWSManagedRulesCommonRuleSet","SampledRequestsEnabled":true}},
{"Action":{"Block":{}},"Name":"LimitRequests100","Priority":2,
"Statement":{"RateBasedStatement":{"AggregateKeyType":"IP","Limit":100}},
"VisibilityConfig":{"CloudWatchMetricsEnabled":true,"MetricName":
"LimitRequests100","SampledRequestsEnabled":true}}]
This is two lines, one for cogntio authentication and one specifying the WAF rules we want applied.
Are we compliant?
Let’s check out cloudformation for our rules and make sure everything is on the up and up.
cfn-guard check --rule_set cfn.ruleset --template cdk.out/AppsyncWafStack.template.json
You should receive no output (0 exit status) which indicates everything is OK. Now we’ll make some changes, synth and re-check. I’ve made the following changes to the CDK project:
- Changed AppSync auth to API Key
- Remove the geo location firewall rule
Now if we run:
cfn-guard check --rule_set cfn.ruleset --template cdk.out/AppsyncWafStack.template.json
We get the following with exit code 2:
[api] failed because [AuthenticationType] is [API_KEY] and the permitted value is [AMAZON_COGNITO_USER_POOLS]
[waf] failed because [Rules] is [[{"Name":"AWS-AWSManagedRulesCommonRuleSet","OverrideAction":{"None":{}},"Priority":1,
"Statement":{"ManagedRuleGroupStatement":{"ExcludedRules":[{"Name":"NoUserAgent_HEADER"}],"Name":"AWSManagedRulesCommonRuleSet",
"VendorName":"AWS"}},"VisibilityConfig":{"CloudWatchMetricsEnabled":true,"MetricName":"AWS-AWSManagedRulesCommonRuleSet",
"SampledRequestsEnabled":true}},{"Action":{"Block":{}},"Name":"LimitRequests100","Priority":2,"Statement":
{"RateBasedStatement":{"AggregateKeyType":"IP","Limit":100}},"VisibilityConfig":{"CloudWatchMetricsEnabled":true,
"MetricName":"LimitRequests100","SampledRequestsEnabled":true}}]] and the permitted value is [[{"Action":{"Count":{}},
"Name":"GeoMatch","Priority":0,"Statement":{"NotStatement":{"Statement":{"GeoMatchStatement":{"CountryCodes":["AU"]}}}},
"VisibilityConfig":{"CloudWatchMetricsEnabled":true,"MetricName":"GeoMatch","SampledRequestsEnabled":true}},
{"Name":"AWS-AWSManagedRulesCommonRuleSet","OverrideAction":{"None":{}},"Priority":1,"Statement":
{"ManagedRuleGroupStatement":{"ExcludedRules":[{"Name":"NoUserAgent_HEADER"}],"Name":"AWSManagedRulesCommonRuleSet",
"VendorName":"AWS"}},"VisibilityConfig":{"CloudWatchMetricsEnabled":true,"MetricName":"AWS-AWSManagedRulesCommonRuleSet",
"SampledRequestsEnabled":true}},{"Action":{"Block":{}},"Name":"LimitRequests100","Priority":2,"Statement":
{"RateBasedStatement":{"AggregateKeyType":"IP","Limit":100}},"VisibilityConfig":{"CloudWatchMetricsEnabled":true,
"MetricName":"LimitRequests100","SampledRequestsEnabled":true}}]]
Number of failures: 2
We can see at the very bottom, Number of failures: 2 success the policy works!!
Key take aways
I think that this is a really cool addition to the cloudformation toolkit, the team have done a great job at creating what they set out to do which was create a firewall like syntax for policy as code with cf. The team have also thought about how this might fit into the dev workflow and this clearly slots nicely into CI/CD pipelines with correct error codes and overrides to make sure things run smoothly. Now if you have a CDK project and are using something like AWS CodePipeline you can see how this would be a step after the synth to check your policy and pass/fail/warn based on the result.
As discussed earlier, the rulegen isn’t the best as it outputs everything but it’s also not too difficult to modify that starter into something more useful. This probably becomes less important the more you build out your templates and adopt cfn-guard.
It’s going to be interesting to see community uptake on this one and see if policies emerge aligned to best practices and other frameworks like CIS.
Access the project
This is simply a clone of the my previous AppSync WAF CDK project with small modifications to make the rule check fail. This is available on github but just know that this isn’t working project, please refer to the original project for a working AppSync project.
Related Posts
AWS Global EC2 runner w/ CDK + CF
I tend to check out various sites here and there to see what people are talking about with AWS and Cloud Computing and a user had a interesting request.
Read moreEnter the bots…AWS Budget with Chatbot
I’ve been talkn about AWS Budgets recently and would like to dive a little further into this neat service to integrate more services that are supported by AWS Budgets.
Read more