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
Full Changelog: https://github.com/aws-cloudformation/cloudformation-guard/compare/3.1.0...3.1.1
Full Changelog: https://github.com/aws-cloudformation/cloudformation-guard/compare/3.0.3...3.1.0
-o
or --output-format
flag to request a JUnit report -o junit
-o
or --output-format
flag to request a Sarif report -o sarif
NOTE: If either junit, or sarif output-format is set, this requires the user to also pass --structured
, and -S none
otherwise cfn-guard will return an error
NOTE: This feature was previously introduced in version 3.0.1, it is now stabilized as of version 3.1.0 To improve the user experience for validating templates when schemas use types that might be easier evaluated as a different type (i.e. a string thats actually a number) the 3.0.1 release adds support to convert between specific types.
The conversions allowed are the following strings/floats-> ints strings/ints -> floats strings -> bools bools/floats/ints -> strings
The following is an example of parsing a string into an int.
Given the following template:
Resources:
asg:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
MinSize: "1"
We can write the following rule
let asg = Resources.*[ Type == 'AWS::AutoScaling::AutoScalingGroup' ]
rule test_parse_int when %asg !empty {
let min = parse_int(%asg.Properties.MinSize)
%min == 1
}
Full Changelog: https://github.com/aws-cloudformation/cloudformation-guard/compare/3.0.3...3.1.0-beta
-o
or --output-format
flag to request a JUnit report -o junit
-o
or --output-format
flag to request a Sarif report -o sarif
NOTE: If either junit, or sarif output-format is set, this requires the user to also pass --structured
, and -S none
otherwise cfn-guard will return an error
Full Changelog: https://github.com/aws-cloudformation/cloudformation-guard/compare/3.0.2...3.0.3
Full Changelog: https://github.com/aws-cloudformation/cloudformation-guard/compare/3.0.1...3.0.2
To improve the user experience for validating templates when schemas use types that might be easier evaluated as a different type (i.e. a string thats actually a number) the 3.0.1 release adds support to convert between specific types.
The conversions allowed are the following strings/floats-> ints strings/ints -> floats strings -> bools bools/floats/ints -> strings
The following is an example of parsing a string into an int.
Given the following template:
Resources:
asg:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
MinSize: "1"
We can write the following rule
let asg = Resources.*[ Type == 'AWS::AutoScaling::AutoScalingGroup' ]
rule test_parse_int when %asg !empty {
let min = parse_int(%asg.Properties.MinSize)
%min == 1
}
Full Changelog: https://github.com/aws-cloudformation/cloudformation-guard/compare/3.0.0...3.0.1
README.md
for 3.0.0
cfn-guard-lambda
through AWS SAM-CLI-j
as short flag for print-json
v3
in themrogue_one
branch to docker workflow, this will publish an ECR image for all updates to this remote branch as wellFn::ImportValue
, Fn::Sub
, etc in unit tests in an improved way. Read more about this change here.cfn-guard-lambda
--structured
flag to validate command to emit JSON/YAML parseable output
As of version 3.0.0 guard now supplies some builtin functions, allowing for stateful rules.
Built-in functions are supported only through assignment to a variable at the moment, and not inline.
NOTE: all examples are operating off the following YAML template
Resources:
newServer:
Type: AWS::New::Service
Properties:
Policy: |
{
"Principal": "*",
"Actions": ["s3*", "ec2*"]
}
Arn: arn:aws:newservice:us-west-2:123456789012:Table/extracted
Encoded: This%20string%20will%20be%20URL%20encoded
Collection:
- a
- b
- c
BucketPolicy:
PolicyText: '{"Version":"2012-10-17","Statement":[{"Sid":"DenyReducedReliabilityStorage","Effect":"Deny","Principal":"*","Action":"s3:*","Resource":"arn:aws:s3:::s3-test-123/*","Condition":{"StringEquals":{"s3:x-amz-storage-class-123":["ONEZONE_IA","REDUCED_REDUNDANCY"]}}}]}'
s3:
Type: AWS::S3::Bucket
Properties:
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
bucket:
Type: AWS::S3::Bucket
Properties:
PublicAccessBlockConfiguration:
BlockPublicAcls: false
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
The following functions all operate on queries that resolve to string values
json_parse()
The json_parse
function adds support for parsing inline JSON strings from a given template. After parsing the string into an object,
you can now evaluate certain properties of this struct just like with a normal JSON/YAML object
let template = Resources.*[ Type == 'AWS::New::Service']
let expected = {
"Principal": "*",
"Actions": ["s3*", "ec2*"]
}
rule TEST_JSON_PARSE when %template !empty {
let policy = %template.Properties.Policy
let res = json_parse(%policy)
%res !empty
%res == %expected
<<
Violation: the IAM policy does not match with the recommended policy
>>
}
regex_replace()
The regex_replace
function adds support for replacing one regular expression with another
In this simple example, we will re-format an ARN by moving around some sections in it.
We will start with a normal ARN that has the following pattern: arn:<Partition>:<Service>:<Region>:<AccountID>:<ResourceType>/<ResourceID>
and we will try to convert it to: <Partition>/<AccountID>/<Region>/<Service>-<ResourceType>/<ResourceID>
let template = Resources.*[ Type == 'AWS::New::Service']
rule TEST_REGEX_REPLACE when %template !empty {
%template.Properties.Arn exists
let arn = %template.Properties.Arn
let arn_partition_regex = "^arn:(\w+):(\w+):([\w0-9-]+):(\d+):(.+)$"
let capture_group_reordering = "${1}/${4}/${3}/${2}-${5}"
let res = regex_replace(%arn, %arn_partition_regex, %capture_group_reordering)
%res == "aws/123456789012/us-west-2/newservice-Table/extracted"
<< Violation: Resulting reformatted ARN does not match the expected format >>
}
join()
The join
function adds support to collect a query, and then join their values using the provided delimiter.
let template = Resources.*[ Type == 'AWS::New::Service']
rule TEST_COLLECTION when %template !empty {
let collection = %template.Collection.*
let res = join(%collection, ",")
%res == "a,b,c"
<< Violation: The joined value does not match the expected result >>
}
to_lower()
This function can be used to change the casing of the all characters in the string passed to all lowercase.
let type = Resources.newServer.Type
rule STRING_MANIPULATION when %type !empty {
let lower = to_lower(%type)
%lower == /aws::new::service/
<< Violation: expected a value to be all lowercase >>
}
to_upper()
This function can be used to change the casing of the all characters in the string passed to all uppercase.
let type = Resources.newServer.Type
rule STRING_MANIPULATION when %type !empty {
let upper = to_upper(%type)
%upper == "AWS::NEW::SERVICE"
<< Violation: expected a value to be all uppercase >>
}
substring()
The substring
function allows to extract a part of string(s) resolved from a query
let template = Resources.*[ Type == 'AWS::New::Service']
rule TEST_SUBSTRING when %template !empty {
%template.Properties.Arn exists
let arn = %template.Properties.Arn
let res = substring(%arn, 0, 3)
%res == "arn"
<< Violation: Substring extracted does not match with the expected outcome >>
}
url_decode()
This function can be used to transform URL encoded strings into their decoded versions
let template = Resources.*[ Type == 'AWS::New::Service']
rule SOME_RULE when %template !empty {
%template.Properties.Encoded exists
let encoded = %template.Properties.Encoded
let res = url_decode(%encoded)
%res == "This string will be URL encoded"
<<
Violation: The result of URL decoding does not
match with the expected outcome
>>
}
count()
This function can be used to count the number of items that a query resolves to
let template = Resources.*[ Type == 'AWS::New::Service' ]
rule SOME_RULE when %template !empty {
let collection = %template.Collection.*
let res2 = count(%collection)
%res2 >= 3
<< Violation: Collection should contain at least 3 items >>
}
cfn-guard-lambda
cfn-guard-lambda
can now also be deployed using this new method, through AWS SAM CLI.
{
"Version": "2012-10-17",
"Statement":
[
{
"Effect": "Allow",
"Action":
[
"cloudformation:CreateChangeSet",
"cloudformation:CreateStack",
"cloudformation:DeleteChangeSet",
"cloudformation:DeleteStack",
"cloudformation:DescribeChangeSet",
"cloudformation:DescribeStackEvents",
"cloudformation:DescribeStackResource",
"cloudformation:DescribeStackResources",
"cloudformation:DescribeStacks",
"cloudformation:ExecuteChangeSet",
"cloudformation:GetTemplate",
"cloudformation:GetTemplateSummary",
"cloudformation:ListStackResources",
"cloudformation:SetStackPolicy",
"cloudformation:UpdateStack",
"cloudformation:UpdateTerminationProtection",
"iam:AttachRolePolicy",
"iam:CreateRole",
"iam:DeleteRole",
"iam:DetachRolePolicy",
"iam:GetRole",
"iam:PassRole",
"lambda:CreateFunction",
"lambda:DeleteFunction",
"lambda:GetFunction",
"lambda:TagResource",
"s3:GetObject",
"s3:PutObject"
],
"Resource": "*"
}
]
}
guard-lambda
directory and run sam build --use-container
to build the code for the Lambda functionsam deploy --guided
and complete the interactive workflow. This workflow will create a CloudFormation changeset and deploy itCloudFormationGuardLambdaFunctionName
outputsam deploy
(without --guided
)Output now shows the data template filename field populated under the field name
.
let s3_buckets_server_side_encryption = Resources.*[ Type == 'AWS::S3::Bucket']
rule S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED when %s3_buckets_server_side_encryption !empty {
%s3_buckets_server_side_encryption.Properties.BucketEncryption exists
%s3_buckets_server_side_encryption.Properties.BucketEncryption.ServerSideEncryptionConfiguration[*].ServerSideEncryptionByDefault.SSEAlgorithm in ["aws:kms","AES256"]
<<
Violation: S3 Bucket must enable server-side encryption.
Fix: Set the S3 Bucket property BucketEncryption.ServerSideEncryptionConfiguration.ServerSideEncryptionByDefault.SSEAlgorithm to either "aws:kms" or "AES256"
>>
}
Resources:
MyBucket1:
Type: AWS::S3::Bucket
Properties:
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: aws:kms
cfn-guard validate \
-d s3-server-side-encryption-template.yaml \
-r s3_bucket_server_side_encryption_enabled.guard \
--show-summary none -o json
{
"name": "s3-server-side-encryption-template.yaml",
"metadata": {},
"status": "PASS",
"not_compliant": [],
"not_applicable": [],
"compliant": [
"S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED"
]
}
The output using the --structured
flag with R
rule files against D
data files now gives a merged array of size D
, all the related rules show up as R
children of a single parent object per data template passed as the input. (see example 1 below). An anonymous rule will be fully-qualified and will contain the filename as well. (see example 2 below)
cfn-guard validate --structured --show-summary none --output-format json --payload
Note the rules only have conditions and no name.
{
"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\""]
}
Press <Ctrl+D> to register input
[
{
"name": "DATA_STDIN[1]",
"metadata": {},
"status": "PASS",
"not_compliant": [],
"not_applicable": [],
"compliant": [
"RULES_STDIN[1]/default",
"RULES_STDIN[2]/default"
]
},
{
"name": "DATA_STDIN[2]",
"metadata": {},
"status": "PASS",
"not_compliant": [],
"not_applicable": [],
"compliant": [
"RULES_STDIN[1]/default",
"RULES_STDIN[2]/default"
]
}
]
cfn-guard validate \
--show-summary all \
--rules guard/resources/validate/rules-dir/advanced_regex_negative_lookbehind_rule.guard \
--data guard/resources/validate/data-dir/advanced_regex_negative_lookbehind_non_compliant.yaml
NotAwsAccessKey != /(?<![A-Z0-9])[A-Z0-9]{20}(?![A-Z0-9])/
NotSecretAccessKey != /(?<![A-Za-z0-9\\/+=])[A-Za-z0-9\\/+=]{40}(?![A-Za-z0-9\\/+=])/
NotAwsAccessKey: AKIAIOSFODNN7EXAMPLE
NotSecretAccessKey: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
advanced_regex_negative_lookbehind_non_compliant.yaml Status = FAIL
FAILED rules
advanced_regex_negative_lookbehind_rule.guard/default FAIL
---
Evaluation of rules advanced_regex_negative_lookbehind_rule.guard against data advanced_regex_negative_lookbehind_non_compliant.yaml
--
Property [/NotAwsAccessKey] in data [advanced_regex_negative_lookbehind_non_compliant.yaml] is not compliant with [advanced_regex_negative_lookbehind_rule.guard/default] because provided value ["AKIAIOSFODNN7EXAMPLE"] did match expected value ["/(?<![A-Z0-9])[A-Z0-9]{20}(?![A-Z0-9])/"]. Error Message []
Property [/NotSecretAccessKey] in data [advanced_regex_negative_lookbehind_non_compliant.yaml] is not compliant with [advanced_regex_negative_lookbehind_rule.guard/default] because provided value ["wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"] did match expected value ["/(?<![A-Za-z0-9\\/+=])[A-Za-z0-9\\/+=]{40}(?![A-Za-z0-9\\/+=])/"]. Error Message []
--
We now have auto-completions for all our commands in shell.
To setup auto-completions you will need to follow instructions for the specific shell your are running.
Currently guard only supports auto-completions for zsh, bash, and fish shells. If you would like autocompletions for a specific shell feel free to open up a new github issue.
Auto-completions are only something available for version >= 3.0
cfn-guard completions --shell='zsh' > /usr/local/share/zsh/site-functions/_cfn-guard && compinit
cfn-guard completions --shell='bash' > ~/cfn-guard.bash && source ~/cfn-guard.bash
cfn-guard completions --shell='fish' > ~/cfn-guard.fish
cd ~
./ ./cfn-guard.fish
NOTE: for both bash and fish shells you are able to output the completions script to any file in any location you would like, just make sure the file you output it to and the file you source are the same. For bash shells if you dont want to do this everytime you open up a new terminal, once you have the script you can add source ~/cfn-guard.bash to your .bashrc
Supports usage of advanced regular expressions such as lookaround and backreferences.
NotAwsAccessKey != /(?<![A-Z0-9])[A-Z0-9]{20}(?![A-Z0-9])/
NotSecretAccessKey != /(?<![A-Za-z0-9\\/+=])[A-Za-z0-9\\/+=]{40}(?![A-Za-z0-9\\/+=])/
NotAwsAccessKey: AKIAIOSFODNN7EXAMPLE
NotSecretAccessKey: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
cfn-guard validate \
-d guard/resources/validate/data-dir/advanced_regex_negative_lookbehind_non_compliant.yaml \
-r guard/resources/validate/rules-dir/advanced_regex_negative_lookbehind_rule.guard \
--show-summary all
advanced_regex_negative_lookbehind_non_compliant.yaml Status = FAIL
FAILED rules
advanced_regex_negative_lookbehind_rule.guard/default FAIL
---
Evaluation of rules advanced_regex_negative_lookbehind_rule.guard against data advanced_regex_negative_lookbehind_non_compliant.yaml
--
Property [/NotAwsAccessKey] in data [advanced_regex_negative_lookbehind_non_compliant.yaml] is not compliant with [advanced_regex_negative_lookbehind_rule.guard/default] because provided value ["AKIAIOSFODNN7EXAMPLE"] did match expected value ["/(?<![A-Z0-9])[A-Z0-9]{20}(?![A-Z0-9])/"]. Error Message []
Property [/NotSecretAccessKey] in data [advanced_regex_negative_lookbehind_non_compliant.yaml] is not compliant with [advanced_regex_negative_lookbehind_rule.guard/default] because provided value ["wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"] did match expected value ["/(?<![A-Za-z0-9\\/+=])[A-Za-z0-9\\/+=]{40}(?![A-Za-z0-9\\/+=])/"]. Error Message []
With the test command, the intrinsic functions now get resolved to their equivalent JSON syntax.
- name: a redshift cluster with short hand functions
input:
Resources:
myCluster:
Type: "AWS::Redshift::Cluster"
Properties:
DBName: "mydb"
KmsKeyId:
Fn::ImportValue:
!Sub "${pSecretKmsKey}"
expectations:
rules:
REDSHIFT_ENCRYPTED_CMK: PASS
let redshift_clusters = Resources.*[ Type == 'AWS::Redshift::Cluster']
rule REDSHIFT_ENCRYPTED_CMK when %redshift_clusters !empty {
%redshift_clusters.Properties.KmsKeyId exists
%redshift_clusters.Properties.KmsKeyId == {"Fn::ImportValue": {"Fn::Sub":"${pSecretKmsKey}"}}
}
cfn-guard test \
-t intrinsic_fn_tests.yaml \
-r intrinsic_fn_rule.guard
Test Case #1
Name: a redshift cluster with short hand functions
PASS Rules:
REDSHIFT_ENCRYPTED_CMK: Expected = PASS
--structured
flag to validate command to emit JSON/YAML parseable outputEmits an output that could be directly parsed using native JSON and YAML parsers, in case of multiple files or directories passed as input with the new flag.
cfn-guard validate \
-d guard/resources/validate/data-dir/s3-public-read-prohibited-template-non-compliant.yaml \
-d guard/resources/validate/data-dir/s3-public-read-prohibited-template-compliant.yaml \
-r guard/resources/validate/rules-dir/s3_bucket_public_read_prohibited.guard \
--structured -o json --show-summary none
[
{
"name": "",
"metadata": {},
"status": "FAIL",
"not_compliant": [
{
"Rule": {
"name": "S3_BUCKET_PUBLIC_READ_PROHIBITED",
"metadata": {},
"messages": {
"custom_message": null,
"error_message": null
},
"checks": [
{
"Clause": {
"Unary": {
"context": " %s3_bucket_public_read_prohibited[*].Properties.PublicAccessBlockConfiguration EXISTS ",
"messages": {
"custom_message": "",
"error_message": "Check was not compliant as property [PublicAccessBlockConfiguration] is missing. Value traversed to [Path=/Resources/MyBucket/Properties[L:13,C:6] Value={\"BucketEncryption\":{\"ServerSideEncryptionConfiguration\":[{\"ServerSideEncryptionByDefault\":{\"SSEAlgorithm\":\"AES256\"}}]},\"VersioningConfiguration\":{\"Status\":\"Enabled\"}}]."
},
"check": {
"UnResolved": {
"value": {
"traversed_to": {
"path": "/Resources/MyBucket/Properties",
"value": {
"BucketEncryption": {
"ServerSideEncryptionConfiguration": [
{
"ServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}
]
},
"VersioningConfiguration": {
"Status": "Enabled"
}
}
},
"remaining_query": "PublicAccessBlockConfiguration",
"reason": "Could not find key PublicAccessBlockConfiguration inside struct at path /Resources/MyBucket/Properties[L:13,C:6]"
},
"comparison": [
"Exists",
false
]
}
}
}
}
},
{
"Clause": {
"Binary": {
"context": " %s3_bucket_public_read_prohibited[*].Properties.PublicAccessBlockConfiguration.BlockPublicAcls EQUALS true",
"messages": {
"custom_message": "",
"error_message": "Check was not compliant as property [PublicAccessBlockConfiguration.BlockPublicAcls] to compare from is missing. Value traversed to [Path=/Resources/MyBucket/Properties[L:13,C:6] Value={\"BucketEncryption\":{\"ServerSideEncryptionConfiguration\":[{\"ServerSideEncryptionByDefault\":{\"SSEAlgorithm\":\"AES256\"}}]},\"VersioningConfiguration\":{\"Status\":\"Enabled\"}}]."
},
"check": {
"UnResolved": {
"value": {
"traversed_to": {
"path": "/Resources/MyBucket/Properties",
"value": {
"BucketEncryption": {
"ServerSideEncryptionConfiguration": [
{
"ServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}
]
},
"VersioningConfiguration": {
"Status": "Enabled"
}
}
},
"remaining_query": "PublicAccessBlockConfiguration.BlockPublicAcls",
"reason": "Could not find key PublicAccessBlockConfiguration inside struct at path /Resources/MyBucket/Properties[L:13,C:6]"
},
"comparison": [
"Eq",
false
]
}
}
}
}
},
{
"Clause": {
"Binary": {
"context": " %s3_bucket_public_read_prohibited[*].Properties.PublicAccessBlockConfiguration.BlockPublicPolicy EQUALS true",
"messages": {
"custom_message": "",
"error_message": "Check was not compliant as property [PublicAccessBlockConfiguration.BlockPublicPolicy] to compare from is missing. Value traversed to [Path=/Resources/MyBucket/Properties[L:13,C:6] Value={\"BucketEncryption\":{\"ServerSideEncryptionConfiguration\":[{\"ServerSideEncryptionByDefault\":{\"SSEAlgorithm\":\"AES256\"}}]},\"VersioningConfiguration\":{\"Status\":\"Enabled\"}}]."
},
"check": {
"UnResolved": {
"value": {
"traversed_to": {
"path": "/Resources/MyBucket/Properties",
"value": {
"BucketEncryption": {
"ServerSideEncryptionConfiguration": [
{
"ServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}
]
},
"VersioningConfiguration": {
"Status": "Enabled"
}
}
},
"remaining_query": "PublicAccessBlockConfiguration.BlockPublicPolicy",
"reason": "Could not find key PublicAccessBlockConfiguration inside struct at path /Resources/MyBucket/Properties[L:13,C:6]"
},
"comparison": [
"Eq",
false
]
}
}
}
}
},
{
"Clause": {
"Binary": {
"context": " %s3_bucket_public_read_prohibited[*].Properties.PublicAccessBlockConfiguration.IgnorePublicAcls EQUALS true",
"messages": {
"custom_message": "",
"error_message": "Check was not compliant as property [PublicAccessBlockConfiguration.IgnorePublicAcls] to compare from is missing. Value traversed to [Path=/Resources/MyBucket/Properties[L:13,C:6] Value={\"BucketEncryption\":{\"ServerSideEncryptionConfiguration\":[{\"ServerSideEncryptionByDefault\":{\"SSEAlgorithm\":\"AES256\"}}]},\"VersioningConfiguration\":{\"Status\":\"Enabled\"}}]."
},
"check": {
"UnResolved": {
"value": {
"traversed_to": {
"path": "/Resources/MyBucket/Properties",
"value": {
"BucketEncryption": {
"ServerSideEncryptionConfiguration": [
{
"ServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}
]
},
"VersioningConfiguration": {
"Status": "Enabled"
}
}
},
"remaining_query": "PublicAccessBlockConfiguration.IgnorePublicAcls",
"reason": "Could not find key PublicAccessBlockConfiguration inside struct at path /Resources/MyBucket/Properties[L:13,C:6]"
},
"comparison": [
"Eq",
false
]
}
}
}
}
},
{
"Clause": {
"Binary": {
"context": " %s3_bucket_public_read_prohibited[*].Properties.PublicAccessBlockConfiguration.RestrictPublicBuckets EQUALS true",
"messages": {
"custom_message": "; Violation: S3 Bucket Public Write Access controls need to be restricted.; Fix: Set S3 Bucket PublicAccessBlockConfiguration properties for BlockPublicAcls, BlockPublicPolicy, IgnorePublicAcls, RestrictPublicBuckets parameters to true.; ",
"error_message": "Check was not compliant as property [PublicAccessBlockConfiguration.RestrictPublicBuckets] to compare from is missing. Value traversed to [Path=/Resources/MyBucket/Properties[L:13,C:6] Value={\"BucketEncryption\":{\"ServerSideEncryptionConfiguration\":[{\"ServerSideEncryptionByDefault\":{\"SSEAlgorithm\":\"AES256\"}}]},\"VersioningConfiguration\":{\"Status\":\"Enabled\"}}]."
},
"check": {
"UnResolved": {
"value": {
"traversed_to": {
"path": "/Resources/MyBucket/Properties",
"value": {
"BucketEncryption": {
"ServerSideEncryptionConfiguration": [
{
"ServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}
]
},
"VersioningConfiguration": {
"Status": "Enabled"
}
}
},
"remaining_query": "PublicAccessBlockConfiguration.RestrictPublicBuckets",
"reason": "Could not find key PublicAccessBlockConfiguration inside struct at path /Resources/MyBucket/Properties[L:13,C:6]"
},
"comparison": [
"Eq",
false
]
}
}
}
}
}
]
}
}
],
"not_applicable": [],
"compliant": []
},
{
"name": "",
"metadata": {},
"status": "PASS",
"not_compliant": [],
"not_applicable": [],
"compliant": [
"S3_BUCKET_PUBLIC_READ_PROHIBITED"
]
}
]
Full Changelog: https://github.com/aws-cloudformation/cloudformation-guard/compare/2.1.4...3.0.0
3.0.0-beta
release changes by @akshayrane in https://github.com/aws-cloudformation/cloudformation-guard/pull/366
3.0.0-beta
README.md
for 3.0.0
cfn-guard-lambda
through AWS SAM-CLI-j
as short flag for print-json
v3
in themrogue_one
branch to docker workflow, this will publish an ECR image for all updates to this remote branch as wellFull Changelog: https://github.com/aws-cloudformation/cloudformation-guard/compare/3.0.0-alpha...3.0.0-beta
cfn-guard-lambda
As of version 3.0.0 guard now supplies some builtin functions, allowing for stateful rules
NOTE: all examples are operating off the following yaml template
Resources:
newServer:
Type: AWS::New::Service
Properties:
Policy: |
{
"Principal": "*",
"Actions": ["s3*", "ec2*"]
}
Arn: arn:aws:newservice:us-west-2:123456789012:Table/extracted
Encoded: This%20string%20will%20be%20URL%20encoded
Collection:
- a
- b
- c
BucketPolicy:
PolicyText: '{"Version":"2012-10-17","Statement":[{"Sid":"DenyReducedReliabilityStorage","Effect":"Deny","Principal":"*","Action":"s3:*","Resource":"arn:aws:s3:::s3-test-123/*","Condition":{"StringEquals":{"s3:x-amz-storage-class-123":["ONEZONE_IA","REDUCED_REDUNDANCY"]}}}]}'
s3:
Type: AWS::S3::Bucket
Properties:
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
bucket:
Type: AWS::S3::Bucket
Properties:
PublicAccessBlockConfiguration:
BlockPublicAcls: false
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
The following functions all operate on queries that resolve to string values
json_parse()
The json_parse function adds support for parsing inline json strings from a given template. After parsing the string into an object, you can now evaluate certain properties of this struct just like with a normal json/yaml object
This function accepts a single argument:
The return value for this function is a query where each string that was resolved from the input is parsed into its json value
The following example shows how you could parse 2 fields on the above template and then write clauses on the results
let template = Resources.*[ Type == 'AWS::New::Service']
rule TEST_JSON_PARSE when %template !empty {
let policy = %template.Properties.Policy
let res = json_parse(%policy)
%res !empty
%res == %expected
let policy_text = %template.BucketPolicy.PolicyText
let res2 = json_parse(%policy_text)
%res2.Statement[*]
{
Effect == "Deny"
Resource == "arn:aws:s3:::s3-test-123/*"
}
}
regex_replace()
The regex_replace function adds support for replacing one regular expression with another
This function accepts 3 arguments:
The return value for this function is a query where each string that was resolved from the input that contains the the regex from our 2nd argument is replaced with the regex in the 3rd argument
In this simple example, we will re-format an ARN by moving around some sections in it.
We will start with a normal ARN that has the following pattern: arn:<Partition>:<Service>:<Region>:<AccountID>:<ResourceType>/<ResourceID>
and we will try to convert it to: <Partition>/<AccountID>/<Region>/<Service>-<ResourceType>/<ResourceID>
let template = Resources.*[ Type == 'AWS::New::Service']
rule TEST_REGEX_REPLACE when %template !empty {
%template.Properties.Arn exists
let arn = %template.Properties.Arn
let arn_partition_regex = "^arn:(\w+):(\w+):([\w0-9-]+):(\d+):(.+)$"
let capture_group_reordering = "${1}/${4}/${3}/${2}-${5}"
let res = regex_replace(%arn, %arn_partition_regex, %capture_group_reordering)
%res == "aws/123456789012/us-west-2/newservice-Table/extracted"
}
join()
The join function adds support to collect a query, and then join their values using the provided delimiter.
This function accepts 2 arguments:
The return value for this function is query where each string that was resolved from the input is joined with the provided delimiter
The following example queries the template for a Collection field on a given resource, it then provides a join on ONLY the string values that this query resolves to with a ,
delimiter
let template = Resources.*[ Type == 'AWS::New::Service']
rule TEST_COLLECTION when %template !empty {
let collection = %template.Collection.*
let res = join(%collection, ",")
%res == "a,b,c"
}
to_lower()
& 5. to_upper()
Both functions accept a single argument:
Both these functions are very similar, one manipulates all resolved strings from a query to lower case, and the other to upper case
let type = Resources.newServer.Type
rule STRING_MANIPULATION when %type !empty {
let lower = to_lower(%type)
%lower == "aws::new::service"
%lower == /aws::new::service/
let upper = to_upper(%type)
%upper == "AWS::NEW::SERVICE"
%upper == /AWS::NEW::SERVICE/
}
substring()
The substring function adds support to collect a part of all strings resolved from a query
This function accepts 3 arguments:
The return value for this function takes the strings resolved from the first argument, and returns a result of substrings for each one of them: Note: Any string that would result in an index out of bounds from the 2nd or 3rd argument is skipped
let template = Resources.*[ Type == 'AWS::New::Service']
rule TEST_SUBSTRING when %template !empty {
%template.Properties.Arn exists
let arn = %template.Properties.Arn
let res = substring(%arn, 0, 3)
%res == "arn"
}
url_decode()
This function accepts a single argument:
The return value for this function is a query that contains each url decoded version of every string value from the input
The following rule shows how you could url_decode the string This%20string%20will%20be%20URL%20encoded
let template = Resources.*[ Type == 'AWS::New::Service']
rule SOME_RULE when %template !empty {
%template.Properties.Encoded exists
let encoded = %template.Properties.Encoded
let res = url_decode(%encoded)
%res == "This string will be URL encoded"
}
count()
The count function adds support to count the number of items that a query resolves to
This function accepts a single argument:
The following rules show different ways we can use the count function.
let template = Resources.*[ Type == 'AWS::New::Service' ]
rule SOME_RULE when %template !empty {
let props = %template.Properties.*
let res = count(%props)
%res == 3
let collection = %template.Collection.*
let res2 = count(%collection)
%res2 == 3
let buckets = Resources.*[ Type == 'AWS::S3::Bucket' ]
let b = %buckets[ Properties.PublicAccessBlockConfiguration exists ]
let res3 = count(%b)
%res3 == 2
}
cfn-guard-lambda
cfn-guard-lambda
can now also be deployed using this new method, through AWS SAM CLI.
{
"Version": "2012-10-17",
"Statement":
[
{
"Effect": "Allow",
"Action":
[
"cloudformation:CreateChangeSet",
"cloudformation:CreateStack",
"cloudformation:DeleteChangeSet",
"cloudformation:DeleteStack",
"cloudformation:DescribeChangeSet",
"cloudformation:DescribeStackEvents",
"cloudformation:DescribeStackResource",
"cloudformation:DescribeStackResources",
"cloudformation:DescribeStacks",
"cloudformation:ExecuteChangeSet",
"cloudformation:GetTemplate",
"cloudformation:GetTemplateSummary",
"cloudformation:ListStackResources",
"cloudformation:SetStackPolicy",
"cloudformation:UpdateStack",
"cloudformation:UpdateTerminationProtection",
"iam:AttachRolePolicy",
"iam:CreateRole",
"iam:DeleteRole",
"iam:DetachRolePolicy",
"iam:GetRole",
"iam:PassRole",
"lambda:CreateFunction",
"lambda:DeleteFunction",
"lambda:GetFunction",
"lambda:TagResource",
"s3:GetObject",
"s3:PutObject"
],
"Resource": "*"
}
]
}
guard-lambda
directory and run sam build --use-container
to build the code for the Lambda functionsam deploy --guided
and complete the interactive workflow. This workflow will create a CloudFormation changeset and deploy itCloudFormationGuardLambdaFunctionName
outputsam deploy
(without --guided
)Output now shows the data template filename field populated under the field name
.
let s3_buckets_server_side_encryption = Resources.*[ Type == 'AWS::S3::Bucket']
rule S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED when %s3_buckets_server_side_encryption !empty {
%s3_buckets_server_side_encryption.Properties.BucketEncryption exists
%s3_buckets_server_side_encryption.Properties.BucketEncryption.ServerSideEncryptionConfiguration[*].ServerSideEncryptionByDefault.SSEAlgorithm in ["aws:kms","AES256"]
<<
Violation: S3 Bucket must enable server-side encryption.
Fix: Set the S3 Bucket property BucketEncryption.ServerSideEncryptionConfiguration.ServerSideEncryptionByDefault.SSEAlgorithm to either "aws:kms" or "AES256"
>>
}
Resources:
MyBucket1:
Type: AWS::S3::Bucket
Properties:
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: aws:kms
cfn-guard validate \
-d s3-server-side-encryption-template.yaml \
-r s3_bucket_server_side_encryption_enabled.guard \
--show-summary none -o json
{
"name": "s3-server-side-encryption-template.yaml",
"metadata": {},
"status": "PASS",
"not_compliant": [],
"not_applicable": [],
"compliant": [
"S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED"
]
}
The output using the --structured
flag with R
rule files against D
data files now gives a merged array of size D
, all the related rules show up as R
children of a single parent object per data template passed as the input. (see example 1 below). An anonymous rule will be fully-qualified and will contain the filename as well. (see example 2 below)
cfn-guard validate --structured --show-summary none --output-format json --payload
Note the rules only have conditions and no name.
{
"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\""]
}
Press <Ctrl+D> to register input
[
{
"name": "DATA_STDIN[1]",
"metadata": {},
"status": "PASS",
"not_compliant": [],
"not_applicable": [],
"compliant": [
"RULES_STDIN[1]/default",
"RULES_STDIN[2]/default"
]
},
{
"name": "DATA_STDIN[2]",
"metadata": {},
"status": "PASS",
"not_compliant": [],
"not_applicable": [],
"compliant": [
"RULES_STDIN[1]/default",
"RULES_STDIN[2]/default"
]
}
]
cfn-guard validate \
--show-summary all \
--rules guard/resources/validate/rules-dir/advanced_regex_negative_lookbehind_rule.guard \
--data guard/resources/validate/data-dir/advanced_regex_negative_lookbehind_non_compliant.yaml
NotAwsAccessKey != /(?<![A-Z0-9])[A-Z0-9]{20}(?![A-Z0-9])/
NotSecretAccessKey != /(?<![A-Za-z0-9\\/+=])[A-Za-z0-9\\/+=]{40}(?![A-Za-z0-9\\/+=])/
NotAwsAccessKey: AKIAIOSFODNN7EXAMPLE
NotSecretAccessKey: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
advanced_regex_negative_lookbehind_non_compliant.yaml Status = FAIL
FAILED rules
advanced_regex_negative_lookbehind_rule.guard/default FAIL
---
Evaluation of rules advanced_regex_negative_lookbehind_rule.guard against data advanced_regex_negative_lookbehind_non_compliant.yaml
--
Property [/NotAwsAccessKey] in data [advanced_regex_negative_lookbehind_non_compliant.yaml] is not compliant with [advanced_regex_negative_lookbehind_rule.guard/default] because provided value ["AKIAIOSFODNN7EXAMPLE"] did match expected value ["/(?<![A-Z0-9])[A-Z0-9]{20}(?![A-Z0-9])/"]. Error Message []
Property [/NotSecretAccessKey] in data [advanced_regex_negative_lookbehind_non_compliant.yaml] is not compliant with [advanced_regex_negative_lookbehind_rule.guard/default] because provided value ["wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"] did match expected value ["/(?<![A-Za-z0-9\\/+=])[A-Za-z0-9\\/+=]{40}(?![A-Za-z0-9\\/+=])/"]. Error Message []
--
We now have auto-completions for all our commands in shell.
To setup auto-completions you will need to follow instructions for the specific shell your are running.
Currently guard only supports auto-completions for zsh, bash, and fish shells. If you would like autocompletions for a specific shell feel free to open up a new github issue.
Auto-completions are only something available for version >= 3.0
cfn-guard completions --shell='zsh' > /usr/local/share/zsh/site-functions/_cfn-guard && compinit
cfn-guard completions --shell='bash' > ~/cfn-guard.bash && source ~/cfn-guard.bash
cfn-guard completions --shell='fish' > ~/cfn-guard.fish
cd ~
./ ./cfn-guard.fish
NOTE: for both bash and fish shells you are able to output the completions script to any file in any location you would like, just make sure the file you output it to and the file you source are the same. For bash shells if you dont want to do this everytime you open up a new terminal, once you have the script you can add source ~/cfn-guard.bash to your .bashrc
Full Changelog: https://github.com/aws-cloudformation/cloudformation-guard/compare/2.1.3...2.1.4
Supports usage of advanced regular expressions such as lookaround and backreferences.
NotAwsAccessKey != /(?<![A-Z0-9])[A-Z0-9]{20}(?![A-Z0-9])/
NotSecretAccessKey != /(?<![A-Za-z0-9\\/+=])[A-Za-z0-9\\/+=]{40}(?![A-Za-z0-9\\/+=])/
NotAwsAccessKey: AKIAIOSFODNN7EXAMPLE
NotSecretAccessKey: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
cfn-guard validate \
-d guard/resources/validate/data-dir/advanced_regex_negative_lookbehind_non_compliant.yaml \
-r guard/resources/validate/rules-dir/advanced_regex_negative_lookbehind_rule.guard \
--show-summary all
advanced_regex_negative_lookbehind_non_compliant.yaml Status = FAIL
FAILED rules
advanced_regex_negative_lookbehind_rule.guard/default FAIL
---
Evaluation of rules advanced_regex_negative_lookbehind_rule.guard against data advanced_regex_negative_lookbehind_non_compliant.yaml
--
Property [/NotAwsAccessKey] in data [advanced_regex_negative_lookbehind_non_compliant.yaml] is not compliant with [advanced_regex_negative_lookbehind_rule.guard/default] because provided value ["AKIAIOSFODNN7EXAMPLE"] did match expected value ["/(?<![A-Z0-9])[A-Z0-9]{20}(?![A-Z0-9])/"]. Error Message []
Property [/NotSecretAccessKey] in data [advanced_regex_negative_lookbehind_non_compliant.yaml] is not compliant with [advanced_regex_negative_lookbehind_rule.guard/default] because provided value ["wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"] did match expected value ["/(?<![A-Za-z0-9\\/+=])[A-Za-z0-9\\/+=]{40}(?![A-Za-z0-9\\/+=])/"]. Error Message []
--structured
flag to validate command to emit JSON/YAML parseable outputFull Changelog: https://github.com/aws-cloudformation/cloudformation-guard/compare/2.1.3...3.0.0-alpha
Supports usage of advanced regular expressions such as lookaround and backreferences.
NotAwsAccessKey != /(?<![A-Z0-9])[A-Z0-9]{20}(?![A-Z0-9])/
NotSecretAccessKey != /(?<![A-Za-z0-9\\/+=])[A-Za-z0-9\\/+=]{40}(?![A-Za-z0-9\\/+=])/
NotAwsAccessKey: AKIAIOSFODNN7EXAMPLE
NotSecretAccessKey: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
cfn-guard validate \
-d guard/resources/validate/data-dir/advanced_regex_negative_lookbehind_non_compliant.yaml \
-r guard/resources/validate/rules-dir/advanced_regex_negative_lookbehind_rule.guard \
--show-summary all
advanced_regex_negative_lookbehind_non_compliant.yaml Status = FAIL
FAILED rules
advanced_regex_negative_lookbehind_rule.guard/default FAIL
---
Evaluation of rules advanced_regex_negative_lookbehind_rule.guard against data advanced_regex_negative_lookbehind_non_compliant.yaml
--
Property [/NotAwsAccessKey] in data [advanced_regex_negative_lookbehind_non_compliant.yaml] is not compliant with [advanced_regex_negative_lookbehind_rule.guard/default] because provided value ["AKIAIOSFODNN7EXAMPLE"] did match expected value ["/(?<![A-Z0-9])[A-Z0-9]{20}(?![A-Z0-9])/"]. Error Message []
Property [/NotSecretAccessKey] in data [advanced_regex_negative_lookbehind_non_compliant.yaml] is not compliant with [advanced_regex_negative_lookbehind_rule.guard/default] because provided value ["wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"] did match expected value ["/(?<![A-Za-z0-9\\/+=])[A-Za-z0-9\\/+=]{40}(?![A-Za-z0-9\\/+=])/"]. Error Message []
With the test command, the intrinsic functions now get resolved to their equivalent JSON syntax.
- name: a redshift cluster with short hand functions
input:
Resources:
myCluster:
Type: "AWS::Redshift::Cluster"
Properties:
DBName: "mydb"
KmsKeyId:
Fn::ImportValue:
!Sub "${pSecretKmsKey}"
expectations:
rules:
REDSHIFT_ENCRYPTED_CMK: PASS
let redshift_clusters = Resources.*[ Type == 'AWS::Redshift::Cluster']
rule REDSHIFT_ENCRYPTED_CMK when %redshift_clusters !empty {
%redshift_clusters.Properties.KmsKeyId exists
%redshift_clusters.Properties.KmsKeyId == {"Fn::ImportValue": {"Fn::Sub":"${pSecretKmsKey}"}}
}
cfn-guard test \
-t intrinsic_fn_tests.yaml \
-r intrinsic_fn_rule.guard
Test Case #1
Name: a redshift cluster with short hand functions
PASS Rules:
REDSHIFT_ENCRYPTED_CMK: Expected = PASS
--structured
flag to validate command to emit JSON/YAML parseable outputEmits an output that could be directly parsed using native JSON and YAML parsers, in case of multiple files or directories passed as input with the new flag.
cfn-guard validate \
-d guard/resources/validate/data-dir/s3-public-read-prohibited-template-non-compliant.yaml \
-d guard/resources/validate/data-dir/s3-public-read-prohibited-template-compliant.yaml \
-r guard/resources/validate/rules-dir/s3_bucket_public_read_prohibited.guard \
--structured -o json --show-summary none
[
{
"name": "",
"metadata": {},
"status": "FAIL",
"not_compliant": [
{
"Rule": {
"name": "S3_BUCKET_PUBLIC_READ_PROHIBITED",
"metadata": {},
"messages": {
"custom_message": null,
"error_message": null
},
"checks": [
{
"Clause": {
"Unary": {
"context": " %s3_bucket_public_read_prohibited[*].Properties.PublicAccessBlockConfiguration EXISTS ",
"messages": {
"custom_message": "",
"error_message": "Check was not compliant as property [PublicAccessBlockConfiguration] is missing. Value traversed to [Path=/Resources/MyBucket/Properties[L:13,C:6] Value={\"BucketEncryption\":{\"ServerSideEncryptionConfiguration\":[{\"ServerSideEncryptionByDefault\":{\"SSEAlgorithm\":\"AES256\"}}]},\"VersioningConfiguration\":{\"Status\":\"Enabled\"}}]."
},
"check": {
"UnResolved": {
"value": {
"traversed_to": {
"path": "/Resources/MyBucket/Properties",
"value": {
"BucketEncryption": {
"ServerSideEncryptionConfiguration": [
{
"ServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}
]
},
"VersioningConfiguration": {
"Status": "Enabled"
}
}
},
"remaining_query": "PublicAccessBlockConfiguration",
"reason": "Could not find key PublicAccessBlockConfiguration inside struct at path /Resources/MyBucket/Properties[L:13,C:6]"
},
"comparison": [
"Exists",
false
]
}
}
}
}
},
{
"Clause": {
"Binary": {
"context": " %s3_bucket_public_read_prohibited[*].Properties.PublicAccessBlockConfiguration.BlockPublicAcls EQUALS true",
"messages": {
"custom_message": "",
"error_message": "Check was not compliant as property [PublicAccessBlockConfiguration.BlockPublicAcls] to compare from is missing. Value traversed to [Path=/Resources/MyBucket/Properties[L:13,C:6] Value={\"BucketEncryption\":{\"ServerSideEncryptionConfiguration\":[{\"ServerSideEncryptionByDefault\":{\"SSEAlgorithm\":\"AES256\"}}]},\"VersioningConfiguration\":{\"Status\":\"Enabled\"}}]."
},
"check": {
"UnResolved": {
"value": {
"traversed_to": {
"path": "/Resources/MyBucket/Properties",
"value": {
"BucketEncryption": {
"ServerSideEncryptionConfiguration": [
{
"ServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}
]
},
"VersioningConfiguration": {
"Status": "Enabled"
}
}
},
"remaining_query": "PublicAccessBlockConfiguration.BlockPublicAcls",
"reason": "Could not find key PublicAccessBlockConfiguration inside struct at path /Resources/MyBucket/Properties[L:13,C:6]"
},
"comparison": [
"Eq",
false
]
}
}
}
}
},
{
"Clause": {
"Binary": {
"context": " %s3_bucket_public_read_prohibited[*].Properties.PublicAccessBlockConfiguration.BlockPublicPolicy EQUALS true",
"messages": {
"custom_message": "",
"error_message": "Check was not compliant as property [PublicAccessBlockConfiguration.BlockPublicPolicy] to compare from is missing. Value traversed to [Path=/Resources/MyBucket/Properties[L:13,C:6] Value={\"BucketEncryption\":{\"ServerSideEncryptionConfiguration\":[{\"ServerSideEncryptionByDefault\":{\"SSEAlgorithm\":\"AES256\"}}]},\"VersioningConfiguration\":{\"Status\":\"Enabled\"}}]."
},
"check": {
"UnResolved": {
"value": {
"traversed_to": {
"path": "/Resources/MyBucket/Properties",
"value": {
"BucketEncryption": {
"ServerSideEncryptionConfiguration": [
{
"ServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}
]
},
"VersioningConfiguration": {
"Status": "Enabled"
}
}
},
"remaining_query": "PublicAccessBlockConfiguration.BlockPublicPolicy",
"reason": "Could not find key PublicAccessBlockConfiguration inside struct at path /Resources/MyBucket/Properties[L:13,C:6]"
},
"comparison": [
"Eq",
false
]
}
}
}
}
},
{
"Clause": {
"Binary": {
"context": " %s3_bucket_public_read_prohibited[*].Properties.PublicAccessBlockConfiguration.IgnorePublicAcls EQUALS true",
"messages": {
"custom_message": "",
"error_message": "Check was not compliant as property [PublicAccessBlockConfiguration.IgnorePublicAcls] to compare from is missing. Value traversed to [Path=/Resources/MyBucket/Properties[L:13,C:6] Value={\"BucketEncryption\":{\"ServerSideEncryptionConfiguration\":[{\"ServerSideEncryptionByDefault\":{\"SSEAlgorithm\":\"AES256\"}}]},\"VersioningConfiguration\":{\"Status\":\"Enabled\"}}]."
},
"check": {
"UnResolved": {
"value": {
"traversed_to": {
"path": "/Resources/MyBucket/Properties",
"value": {
"BucketEncryption": {
"ServerSideEncryptionConfiguration": [
{
"ServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}
]
},
"VersioningConfiguration": {
"Status": "Enabled"
}
}
},
"remaining_query": "PublicAccessBlockConfiguration.IgnorePublicAcls",
"reason": "Could not find key PublicAccessBlockConfiguration inside struct at path /Resources/MyBucket/Properties[L:13,C:6]"
},
"comparison": [
"Eq",
false
]
}
}
}
}
},
{
"Clause": {
"Binary": {
"context": " %s3_bucket_public_read_prohibited[*].Properties.PublicAccessBlockConfiguration.RestrictPublicBuckets EQUALS true",
"messages": {
"custom_message": "; Violation: S3 Bucket Public Write Access controls need to be restricted.; Fix: Set S3 Bucket PublicAccessBlockConfiguration properties for BlockPublicAcls, BlockPublicPolicy, IgnorePublicAcls, RestrictPublicBuckets parameters to true.; ",
"error_message": "Check was not compliant as property [PublicAccessBlockConfiguration.RestrictPublicBuckets] to compare from is missing. Value traversed to [Path=/Resources/MyBucket/Properties[L:13,C:6] Value={\"BucketEncryption\":{\"ServerSideEncryptionConfiguration\":[{\"ServerSideEncryptionByDefault\":{\"SSEAlgorithm\":\"AES256\"}}]},\"VersioningConfiguration\":{\"Status\":\"Enabled\"}}]."
},
"check": {
"UnResolved": {
"value": {
"traversed_to": {
"path": "/Resources/MyBucket/Properties",
"value": {
"BucketEncryption": {
"ServerSideEncryptionConfiguration": [
{
"ServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}
]
},
"VersioningConfiguration": {
"Status": "Enabled"
}
}
},
"remaining_query": "PublicAccessBlockConfiguration.RestrictPublicBuckets",
"reason": "Could not find key PublicAccessBlockConfiguration inside struct at path /Resources/MyBucket/Properties[L:13,C:6]"
},
"comparison": [
"Eq",
false
]
}
}
}
}
}
]
}
}
],
"not_applicable": [],
"compliant": []
},
{
"name": "",
"metadata": {},
"status": "PASS",
"not_compliant": [],
"not_applicable": [],
"compliant": [
"S3_BUCKET_PUBLIC_READ_PROHIBITED"
]
}
]