Guard offers a policy-as-code domain-specific language (DSL) to write rules and validate JSON- and YAML-formatted data such as CloudFormation Templates, K8s configurations, and Terraform JSON plans/configurations against those rules. Take this survey to provide feedback about cfn-guard: https://amazonmr.au1.qualtrics.com/jfe/form/SV_bpyzpfoYGGuuUl0
AWS CloudFormation Guard 2.1.3 is a patch release that resolves 2 bugs from 2.1.2 release.
cfn-guard-lambda
for timestamp offsetFull change log: https://github.com/aws-cloudformation/cloudformation-guard/compare/2.1.2...2.1.3
AWS CloudFormation Guard 2.1.2 is a patch release that resolves a bug from 2.1.1 release.
test
commands for input templates that had shorthand syntax in YAML, added unit test coverageFull change log: https://github.com/aws-cloudformation/cloudformation-guard/compare/2.1.1...2.1.2
AWS CloudFormation Guard 2.1.1 is a patch release that includes new features, resolves bugs, and addresses feedback from the open source community.
Full change log: https://github.com/aws-cloudformation/cloudformation-guard/compare/2.1.0...2.1.1
cfn-guard
. We'll refer this directory to be called guard-files
in the rest of this guideTo use the binary, we should pull the latest docker image, we may do so using the following command:
docker pull public.ecr.aws/aws-cloudformation/cloudformation-guard:latest
Now go ahead and run the docker image, using the files from directory we have our templates and rules file in, using:
docker run \
--mount src=/path/to/guard-files,target=/container/guard-files,type=bind \
-it public.ecr.aws/aws-cloudformation/cloudformation-guard:latest \
./cfn-guard validate -d /container/guard-files/template.yml -r /container/guard-files/rule.guard
We should see the evaluation result emitted out on the console.
latest
for the most recent docker image that gets published in sync with main
branch of the cloudformation-guard
GitHub repository.<branch_name>.<github_shorthand_commit_hash>
for tags of historical docker imagesAWS CloudFormation Guard 2.1.0 is a major release that includes new features, resolves bugs, and addresses feedback from the open source community.
cfn-guard
2.x.xcfn-guard validate —print-json
does not workIN
Statement prints out extraneous error infoinput-parameters
argument for shared context from parameter files against data templates by @akshayranerules
, data
and input-parameters
arguments to support multiple mixed-type values of files and directories by @akshayranerules
, data
and input-parameters
by @akshayraneFull Changelog: https://github.com/aws-cloudformation/cloudformation-guard/compare/2.0.4...2.1.0
A user can leverage parameterized rules to write re-usable checks. User can use these checks to write Guard rules that works across several types of payloads such as AWS CloudFormation Templates, Terraform plans that use AWS CC, and AWS Config for asserting conditions.
Example of re-usable checks:
#
# Top level doc type checks
#
let cfn_resources = Resources.*
let aws_config = configuration.*
rule is_cfn_doc_type when %cfn_resources !empty {
Resources exists
}
rule is_aws_config_doc_type when %aws_config !empty {
configuration exists
}
#
# ECS Service
#
rule deny_ecs_services_invalid_configuration when is_cfn_doc_type {
check_ecs_services_cfgs(Resources[ Type == 'AWS::ECS::Service' ].Properties)
}
rule deny_ecs_services_invalid_configuration when is_aws_config_doc_type
resourceType == 'AWS::ECS::Service'
{
check_ecs_services_cfgs(configuration)
}
#
# Example of network configuration checks across ECS TaskSets and ECS Service using a common rule:
#
# ECS TaskSet
#
rule deny_ecs_task_set_invalid_configuration when is_cfn_doc_type {
#
# For TaskSet, the property is NetworkConfiguration.Aws[V]pcConfiguration
#
check_ecs_network_config(
Resources[ Type == 'AWS::ECS::TaskSet' ]
.Properties
.NetworkConfiguration
.AwsVpcConfiguration)
}
#
# ECS Service check, common across AWS Config and CloudFormation
#
rule check_ecs_services_cfgs(ecs_service_cfgs) {
%ecs_service_cfgs {
EnableExecuteCommand not exists or
EnableExecuteCommand == false
<<Disallowed command executions for ECS services>>
#
# For ECS Service, the property is NetworkConfiguration.Aws[V]pcConfiguration
#
check_ecs_network_config(NetworkConfiguration.AwsVpcConfiguration)
}
}
#
# Check ECS network configuration common to TaskSet and Service
#
rule check_ecs_network_config(network_cfgs) {
%network_cfgs {
AssignPublicIp == 'DISABLED' or
AssignPublicIp == 'disabled'
<<Prevent assignment of public IP address to ECS services. AssignPublicIp must be DISABLE>>
}
}
Please note that some properties are intentionally omitted for brevity.
Resources:
Cluster:
Type: AWS::ECS::Cluster
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: test
ContainerDefinitions:
- Name: test
Image: amazon/amazon-ecs-sample
Essential: true
Cpu: 256
Memory: 512
Service:
Type: AWS::ECS::Service
Properties:
Cluster:
Ref: Cluster
DeploymentController:
Type: EXTERNAL
DesiredCount: 0
NetworkConfiguration:
AwsVpcConfiguration:
AssignPublicIp: DISABLED
TaskSet1:
Type: AWS::ECS::TaskSet
Properties:
Service:
Ref: Service
Cluster:
Ref: Cluster
TaskDefinition:
Ref: TaskDefinition
Scale:
Unit: PERCENT
Value: 100
LaunchType: EC2
ExternalId: task-set-001
NetworkConfiguration:
AwsVpcConfiguration:
AssignPublicIp: DISABLED
Users can now evaluate all rules at once for both testing and validating. Users can run all tests for all rules by pointing to the top-level directory. Testing follows a simple naming convention to run the appropriate tests against the rules.
guard-test-root
. Create a Guard rule and name it rule_01.guard
.guard-test-root
called tests
directory (name has to be verbatim tests
for this to work). Create rule_01_*.yaml
for success and failures. This names will very as per different rule names.Directory structure
guard-test-root/
├── rule_01.guard
└── tests/
├── rule_01_cfn_fail.yaml
├── rule_01_cfn_success.yaml
├── rule_01_config_fail.yaml
└── rule_01_config_success.yaml
Command
Run the following command in the parent directory ofguard-test-root
.
cfn-guard test -d guard-test-root
For validate, we have overloaded all 3 arguments to support directory as input --data
, --rules
as well as --input-parameters
. Let us demonstrate an example with using a directory to pass rules, but know that it works the same way with others.
guard-validate-root
. Create a sub-directory called rules
.rule_01.guard
, rule_02.guard
, rule_03.guard
. Please note we should use one of the supported extensions here. As of now, .guard
and .ruleset
are good for rules.guard-validate-root
. Create an infrastructure as code template to be validated as template.yaml
.Directory structure
guard-validate-root/
├── template.yaml
└── rules/
├── rule_01.guard
├── rule_02.guard
└── rule_03.guard
Command
Navigate to guard-validate-root
and run the following command.
cfn-guard validate -r rules/ -d template.yaml
The validate
command will pick all rule file names with the following extensions and execute the checks:
*.guard
*.ruleset
More scenarios for validate
In a similar scenario, where we have multiple template data files in a directory named data
to be validated against all files in a rules
directory, we can run the following command.
cfn-guard validate -r rules/ -d data/
Where directory structure looks like the following:
guard-validate-root/
├── data/
| ├── template_01.yaml
| ├── template_02.yaml
| └── template_03.yaml
└── rules/
├── rule_01.guard
├── rule_02.guard
└── rule_03.guard
For a data directory passed as input, the validate
command will pick all rule file names with the following extensions and execute the checks:
*.yaml
*.yml
*.json
*.jsn
*.template
Thus, we have extended support for validating...
Apart from support for directory we also support multiple usages of the arguments with values of mixed nature (directory/file). For example, the following command is a valid command.
cfn-guard validate -r rules/ -r foo/rule_99.guard -d data/ -d bar/template_99.yaml
Users can now specify multiple data files for dynamic look ups using --input-parameters
argument, along with the independent context of a data file passed as --data
for actual validation inspection (e.g., the template that is the validation target).
All files passed as --input-parameters
are combined to form a common context. This common context is then combined with every file passed as --data
independently.
For example, network related data can be stored in a network.yaml
file. rule:
NETWORK:
allowed_security_groups: ["sg-282850", "sg-292040"]
allowed_prefix_lists: ["pl-63a5400a", "pl-02cd2c6b"]
This data will be used dynamically to look up as valid values in our Guard rule defined in a different security_groups.guard
file:
let groups = Resources.*[ Type == 'AWS::EC2::SecurityGroup' ]
let permitted_sgs = NETWORK.allowed_security_groups
let permitted_pls = NETWORK.allowed_prefix_lists
rule check_permitted_security_groups_or_prefix_lists(groups) {
%groups {
this in %permitted_sgs or
this in %permitted_pls
}
}
rule CHECK_PERMITTED_GROUPS when %groups !empty {
check_permitted_security_groups_or_prefix_lists(
%groups.Properties.GroupName
)
}
The above two will be used to validate a data template security_groups_fail.yaml
:
# ---
# AWSTemplateFormatVersion: 2010-09-09
# Description: CloudFormation - EC2 Security Group
Resources:
mySecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupName: "wrong"
cfn-guard validate -r security_groups.guard -i network.yaml -d security_groups_fail.yaml
The above command will validate the security_groups_fail.yaml
template against the rules in security-groups.guard
which use dynamic data from network.yaml
Full Changelog: https://github.com/aws-cloudformation/cloudformation-guard/compare/2.0.4...2.1.0
AWS CloudFormation Guard 2.1.0 is a major release that includes new features, resolves bugs, and addresses feedback from the open source community.
cfn-guard
2.x.xcfn-guard validate —print-json
does not workIN
Statement prints out extraneous error infoFull Changelog: https://github.com/aws-cloudformation/cloudformation-guard/compare/2.0.3...v2.1.0-pre-rc1
A user can leverage parameterized rules to write re-usable checks. User can use these checks to write Guard rules that works across several types of payloads such as AWS CloudFormation Templates, Terraform plans that use AWS CC, and AWS Config for asserting conditions.
Example of re-usable checks:
#
# Top level doc type checks
#
let cfn_resources = Resources.*
let aws_config = configuration.*
rule is_cfn_doc_type when %cfn_resources !empty {
Resources exists
}
rule is_aws_config_doc_type when %aws_config !empty {
configuration exists
}
#
# ECS Service
#
rule deny_ecs_services_invalid_configuration when is_cfn_doc_type {
check_ecs_services_cfgs(Resources[ Type == 'AWS::ECS::Service' ].Properties)
}
rule deny_ecs_services_invalid_configuration when is_aws_config_doc_type
resourceType == 'AWS::ECS::Service'
{
check_ecs_services_cfgs(configuration)
}
#
# Example of network configuration checks across ECS TaskSets and ECS Service using a common rule:
#
# ECS TaskSet
#
rule deny_ecs_task_set_invalid_configuration when is_cfn_doc_type {
#
# For TaskSet, the property is NetworkConfiguration.Aws[V]pcConfiguration
#
check_ecs_network_config(
Resources[ Type == 'AWS::ECS::TaskSet' ]
.Properties
.NetworkConfiguration
.AwsVpcConfiguration)
}
#
# ECS Service check, common across AWS Config and CloudFormation
#
rule check_ecs_services_cfgs(ecs_service_cfgs) {
%ecs_service_cfgs {
EnableExecuteCommand not exists or
EnableExecuteCommand == false
<<Disallowed command executions for ECS services>>
#
# For ECS Service, the property is NetworkConfiguration.Aws[V]pcConfiguration
#
check_ecs_network_config(NetworkConfiguration.AwsVpcConfiguration)
}
}
#
# Check ECS network configuration common to TaskSet and Service
#
rule check_ecs_network_config(network_cfgs) {
%network_cfgs {
AssignPublicIp == 'DISABLED' or
AssignPublicIp == 'disabled'
<<Prevent assignment of public IP address to ECS services. AssignPublicIp must be DISABLE>>
}
}
Please note that some properties are intentionally omitted for brevity.
Resources:
Cluster:
Type: AWS::ECS::Cluster
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: test
ContainerDefinitions:
- Name: test
Image: amazon/amazon-ecs-sample
Essential: true
Cpu: 256
Memory: 512
Service:
Type: AWS::ECS::Service
Properties:
Cluster:
Ref: Cluster
DeploymentController:
Type: EXTERNAL
DesiredCount: 0
NetworkConfiguration:
AwsVpcConfiguration:
AssignPublicIp: DISABLED
TaskSet1:
Type: AWS::ECS::TaskSet
Properties:
Service:
Ref: Service
Cluster:
Ref: Cluster
TaskDefinition:
Ref: TaskDefinition
Scale:
Unit: PERCENT
Value: 100
LaunchType: EC2
ExternalId: task-set-001
NetworkConfiguration:
AwsVpcConfiguration:
AssignPublicIp: DISABLED
Users can now evaluate all rules at once for both testing and validating. Users can run all tests for all rules by pointing to the top-level directory. Testing follows a simple naming convention to run the appropriate tests against the rules.
guard-test-root
. Create a Guard rule and name it rule_01.guard
.guard-test-root
called tests
directory (name has to be verbatim tests
for this to work). Create rule_01_*.yaml
for success and failures. This names will very as per different rule names.Directory structure
guard-test-root/
├── rule_01.guard
└── tests/
├── rule_01_cfn_fail.yaml
├── rule_01_cfn_success.yaml
├── rule_01_config_fail.yaml
└── rule_01_config_success.yaml
Command
Run the following command in the parent directory ofguard-test-root
.
cfn-guard test -d guard-test-root
For validate, we have overloaded all 3 arguments to support directory as input --data
, --rules
as well as --input-parameters
. Let us demonstrate an example with using a directory to pass rules, but know that it works the same way with others.
guard-validate-root
. Create a sub-directory called rules
.rule_01.guard
, rule_02.guard
, rule_03.guard
. Please note we should use one of the supported extensions here. As of now, .guard
and .ruleset
are good for rules.guard-validate-root
. Create an infrastructure as code template to be validated as template.yaml
.Directory structure
guard-validate-root/
├── template.yaml
└── rules/
├── rule_01.guard
├── rule_02.guard
└── rule_03.guard
Command
Navigate to guard-validate-root
and run the following command.
cfn-guard validate -r rules/ -d template.yaml
The validate
command will pick all rule file names with the following extensions and execute the checks:
*.guard
*.ruleset
More scenarios for validate
In a similar scenario, where we have multiple template data files in a directory named data
to be validated against all files in a rules
directory, we can run the following command.
cfn-guard validate -r rules/ -d data/
Where directory structure looks like the following:
guard-validate-root/
├── data/
| ├── template_01.yaml
| ├── template_02.yaml
| └── template_03.yaml
└── rules/
├── rule_01.guard
├── rule_02.guard
└── rule_03.guard
For a data directory passed as input, the validate
command will pick all rule file names with the following extensions and execute the checks:
*.yaml
*.yml
*.json
*.jsn
*.template
Thus, we have extended support for validating...
Apart from support for directory we also support multiple usages of the arguments with values of mixed nature (directory/file). For example, the following command is a valid command.
cfn-guard validate -r rules/ -r foo/rule_99.guard -d data/ -d bar/template_99.yaml
Users can now specify multiple data files for dynamic look ups using --input-parameters
argument, along with the independent context of a data file passed as --data
for actual validation inspection (e.g., the template that is the validation target).
All files passed as --input-parameters
are combined to form a common context. This common context is then combined with every file passed as --data
independently.
For example, network related data can be stored in a network.yaml
file. rule:
NETWORK:
allowed_security_groups: ["sg-282850", "sg-292040"]
allowed_prefix_lists: ["pl-63a5400a", "pl-02cd2c6b"]
This data will be used dynamically to look up as valid values in our Guard rule defined in a different security_groups.guard
file:
let groups = Resources.*[ Type == 'AWS::EC2::SecurityGroup' ]
let permitted_sgs = NETWORK.allowed_security_groups
let permitted_pls = NETWORK.allowed_prefix_lists
rule check_permitted_security_groups_or_prefix_lists(groups) {
%groups {
this in %permitted_sgs or
this in %permitted_pls
}
}
rule CHECK_PERMITTED_GROUPS when %groups !empty {
check_permitted_security_groups_or_prefix_lists(
%groups.Properties.GroupName
)
}
The above two will be used to validate a data template security_groups_fail.yaml
:
# ---
# AWSTemplateFormatVersion: 2010-09-09
# Description: CloudFormation - EC2 Security Group
Resources:
mySecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupName: "wrong"
cfn-guard validate -r security_groups.guard -i network.yaml -d security_groups_fail.yaml
The above command will validate the security_groups_fail.yaml
template against the rules in security-groups.guard
which use dynamic data from network.yaml
Description of improvements released in version v2.0.4:
#201
You can now use a payload
flag that will allow to pass a JSON with data and rules as strings to validate
command.
{"data": [<data1 as string>, <data2 as string>,....], "rules" : [ <rule1 as string>, <rule2 as string>,....]}
eg.
{"data": ["{\"Resources\":{\"NewVolume\":{\"Type\":\"AWS::EC2::Volume\",\"Properties\":{\"Size\":500,\"Encrypted\":false,\"AvailabilityZone\":\"us-west-2b\"}},\"NewVolume2\":{\"Type\":\"AWS::EC2::Volume\",\"Properties\":{\"Size\":50,\"Encrypted\":false,\"AvailabilityZone\":\"us-west-2c\"}}},\"Parameters\":{\"InstanceName\":\"TestInstance\"}}","{\"Resources\":{\"NewVolume\":{\"Type\":\"AWS::EC2::Volume\",\"Properties\":{\"Size\":500,\"Encrypted\":false,\"AvailabilityZone\":\"us-west-2b\"}},\"NewVolume2\":{\"Type\":\"AWS::EC2::Volume\",\"Properties\":{\"Size\":50,\"Encrypted\":false,\"AvailabilityZone\":\"us-west-2c\"}}},\"Parameters\":{\"InstanceName\":\"TestInstance\"}}"], "rules" : [ "Parameters.InstanceName == \"TestInstance\"","Parameters.InstanceName == \"TestInstance\"" ]}
Sample run:
$ cfn-guard validate --payload
{"data": ["{\"Resources\":{\"NewVolume\":{\"Type\":\"AWS::EC2::Volume\",\"Properties\":{\"Size\":500,\"Encrypted\":false,\"AvailabilityZone\":\"us-west-2b\"}},\"NewVolume2\":{\"Type\":\"AWS::EC2::Volume\",\"Properties\":{\"Size\":50,\"Encrypted\":false,\"AvailabilityZone\":\"us-west-2c\"}}},\"Parameters\":{\"InstanceName\":\"TestInstance\"}}","{\"Resources\":{\"NewVolume\":{\"Type\":\"AWS::EC2::Volume\",\"Properties\":{\"Size\":500,\"Encrypted\":false,\"AvailabilityZone\":\"us-west-2b\"}},\"NewVolume2\":{\"Type\":\"AWS::EC2::Volume\",\"Properties\":{\"Size\":50,\"Encrypted\":false,\"AvailabilityZone\":\"us-west-2c\"}}},\"Parameters\":{\"InstanceName\":\"TestInstance\"}}"], "rules" : [ "Parameters.InstanceName == \"TestInstance\"","Parameters.InstanceName == \"TestInstance\"" ]}
DATA_STDIN[1] Status = PASS
PASS rules
RULES_STDIN[1]/default PASS
---
Evaluation of rules RULES_STDIN[1] against data DATA_STDIN[1]
--
Rule [RULES_STDIN[1]/default] is compliant for template [DATA_STDIN[1]]
--
DATA_STDIN[2] Status = PASS
PASS rules
RULES_STDIN[1]/default PASS
---
Evaluation of rules RULES_STDIN[1] against data DATA_STDIN[2]
--
Rule [RULES_STDIN[1]/default] is compliant for template [DATA_STDIN[2]]
--
DATA_STDIN[1] Status = PASS
PASS rules
RULES_STDIN[2]/default PASS
---
Evaluation of rules RULES_STDIN[2] against data DATA_STDIN[1]
--
Rule [RULES_STDIN[2]/default] is compliant for template [DATA_STDIN[1]]
--
DATA_STDIN[2] Status = PASS
PASS rules
RULES_STDIN[2]/default PASS
---
Evaluation of rules RULES_STDIN[2] against data DATA_STDIN[2]
--
Rule [RULES_STDIN[2]/default] is compliant for template [DATA_STDIN[2]]
#179 Retrieve version number dynamically from environment variable in code
Description of improvements released in version 2.0.3.
#158 You can now provide test names to each unit test in your test file. The test names will be displayed in the test execution report together with the unit test execution status. This enhances the readability of test file execution reports.
#159
Guard will now continue evaluation of a clause for all values produced by its query even after encountering a failed evaluation. You will be able to see details of failed values by using the --show-clause-failures
flag with the validate
command.
#154
validate
command now supports JSON, YAML and single-line output formats; you can now use the json
, yaml
and single-line-summary
values, respectively, for the -o
or --output-format
options of the validate
command. Example:cfn-guard validate -r rules/ -d data/ --show-summary none --type CFNTemplate —output-format yaml
Output:
---
data_from: sample-template.yaml
rules_from: cluster.guard
not_compliant: {}
not_applicable:
- test
compliant: []
---
data_from: sample-template.yaml
rules_from: migrated-3.guard
not_compliant:
vol2:
- rule: aws_ec2_volume_checks
path: Properties.Encrypted
provided: false
expected: true
comparison:
operator: Eq
not_operator_exists: false
message: ""
not_applicable:
- aws_apigateway_deployment_checks
- aws_apigateway_stage_checks
- aws_dynamodb_table_checks
compliant:
- aws_events_rule_checks
- aws_iam_role_checks
-t
or --type
option for the validate
command to specify the type of the data file against which you are evaluating your rules. CFNTemplate
is the only value supported today. When you now specify, for example, --type CFNTemplate
as an option to the validate
command, Guard will output logical name of resources and relevant properties (e.g., Resource [vol2] property [Properties.Encrypted] in template [sample-template.yaml]
), as opposed to property paths and values (e.g., Property [/Resources/vol2/Properties/Encrypted] in data [sample-template.yaml]
). Example:cfn-guard validate -r /tmp/rules/ -d /tmp/data/ --show-summary none —type CFNTemplate
Output:
Evaluation of rules cluster.guard for template sample-template.yaml, number of resource failures = 0
--
Rule [cluster.guard/test] is not applicable for template [sample-template.yaml]
--
Evaluation of rules migrated-3.guard for template sample-template.yaml, number of resource failures = 1
--
Resource [vol2] property [Properties.Encrypted] in template [sample-template.yaml] is not compliant with [migrated-3.guard/aws_ec2_volume_checks] because provided value [false] did not match with expected value [true]. Error message []
Resource [vol2] traversed until [Properties] for template [sample-template.yaml] wasn't compliant with [migrated-3.guard/aws_ec2_volume_checks] due to retrieval error. Error Message [Attempting to retrieve array index or key from map at path = /Resources/vol2/Properties , Type was not an array/object map, Remaining Query = Size]
Resource [vol2] property [Properties.Encrypted] in template [sample-template.yaml] is not compliant with [migrated-3.guard/mixed_types_checks] because provided value [false] did not match with expected value [true]. Error message []
--
Rule [migrated-3.guard/aws_iam_role_checks] is compliant for template [sample-template.yaml]
Rule [migrated-3.guard/aws_events_rule_checks] is compliant for template [sample-template.yaml]
--
Rule [migrated-3.guard/aws_apigateway_deployment_checks] is not applicable for template [sample-template.yaml]
Rule [migrated-3.guard/aws_apigateway_stage_checks] is not applicable for template [sample-template.yaml]
Rule [migrated-3.guard/aws_dynamodb_table_checks] is not applicable for template
validate
command; by default, summary is displayed (--show-summary all
); alternatively, you can specify --show-summary pass,fail
to only summarize rules that did pass/fail), and with --show-summary none
you turn off the visualization of the summary.Documentation update over v2.0.0.
This release makes Guard a general-purpose policy-as-code evaluation tool. With Guard 2.0, developers can write policy rules for any JSON- and YAML-formatted file such as Kubernetes configurations and Terraform JSON configurations, in addition to already supported CloudFormation templates. This release also enhances Guard’s DSL, making your rule writing experience simple and unambiguous. It also enables you to create advanced rules as your use cases and cloud environments get more complex. For example, named rules feature enables you to define a set of rules that you can reference in another set of rules.