Design, build, innovate & share cloud.

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.

Screen-Shot-2020-11-16-at-3.19.07-pm

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.

talkncloud/aws
all things cloud from IaC to full apps. Contribute to talkncloud/aws development by creating an account on GitHub.
You've successfully subscribed to talkncloud
Welcome back! You've successfully signed in.
Great! You've successfully signed up.
Success! Your account is fully activated, you now have access to all content.