// Copyright Amazon Web Services, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
pub(crate) mod utils;
#[cfg(test)]
mod parse_tree_tests {
use cfn_guard::commands::{PRINT_JSON, PRINT_YAML, RULES};
use cfn_guard::utils::reader::Reader;
use cfn_guard::utils::writer::{WriteBuffer::Vec as WBVec, Writer};
use pretty_assertions::assert_eq;
use crate::utils::{get_full_path_for_resource_file, Command, CommandTestRunner, StatusCode};
use crate::{assert_output_from_file_eq, assert_output_from_str_eq};
#[allow(dead_code)]
#[derive(Default)]
struct ParseTreeTestRunner<'args> {
rules: &'args str,
output: Option<&'args str>,
print_json: bool,
print_yaml: bool,
}
impl<'args> ParseTreeTestRunner<'args> {
fn rules(&'args mut self, arg: &'args str) -> &'args mut ParseTreeTestRunner {
self.rules = arg;
self
}
#[allow(dead_code)]
fn output(&'args mut self, arg: &'args str) -> &'args mut ParseTreeTestRunner {
self.rules = arg;
self
}
#[allow(dead_code)]
fn print_yaml(&'args mut self) -> &'args mut ParseTreeTestRunner {
self.print_yaml = true;
self
}
fn print_json(&'args mut self) -> &'args mut ParseTreeTestRunner {
self.print_json = true;
self
}
}
impl<'args> CommandTestRunner for ParseTreeTestRunner<'args> {
fn build_args(&self) -> Vec<String> {
let mut args = vec![
Command::ParseTree.to_string(),
format!("-{}", RULES.1),
get_path_for_resource_file(self.rules),
];
if self.print_yaml {
args.push(format!("--{}", PRINT_YAML.0));
}
if self.print_json {
args.push(format!("--{}", PRINT_JSON.0));
}
args
}
}
fn get_path_for_resource_file(file: &str) -> String {
get_full_path_for_resource_file(&format!("resources/{}", file))
}
#[test]
fn test_json_output() {
let mut reader = Reader::default();
let mut writer = Writer::new(WBVec(vec![])).expect("Failed to create writer.");
let status_code = ParseTreeTestRunner::default()
.print_json()
.rules("validate/rules-dir/s3_bucket_server_side_encryption_enabled.guard")
.run(&mut writer, &mut reader);
assert_eq!(StatusCode::SUCCESS, status_code);
assert_output_from_file_eq!(
"resources/parse-tree/output-dir/s3_bucket_server_side_encryption_parse_tree.json",
writer
)
}
const YAML_S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED_PARSE_TREE: &str = "assignments:\n- var: s3_buckets_server_side_encryption\n value:\n AccessClause:\n query:\n - Key: Resources\n - AllValues: null\n - Filter:\n - null\n - - - Clause:\n access_clause:\n query:\n query:\n - Key: Type\n match_all: true\n comparator:\n - Eq\n - false\n compare_with:\n Value:\n path: ''\n value: AWS::S3::Bucket\n custom_message: null\n location:\n line: 1\n column: 54\n negation: false\n - - Clause:\n access_clause:\n query:\n query:\n - Key: Metadata\n - Key: guard\n - Key: SuppressedRules\n match_all: true\n comparator:\n - Exists\n - true\n compare_with: null\n custom_message: null\n location:\n line: 2\n column: 3\n negation: false\n - Clause:\n access_clause:\n query:\n query:\n - Key: Metadata\n - Key: guard\n - Key: SuppressedRules\n - AllValues: null\n match_all: true\n comparator:\n - Eq\n - true\n compare_with:\n Value:\n path: ''\n value: S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED\n custom_message: null\n location:\n line: 3\n column: 3\n negation: false\n match_all: true\nguard_rules:\n- rule_name: S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED\n conditions:\n - - Clause:\n access_clause:\n query:\n query:\n - Key: '%s3_buckets_server_side_encryption'\n match_all: true\n comparator:\n - Empty\n - true\n compare_with: null\n custom_message: null\n location:\n line: 6\n column: 52\n negation: false\n block:\n assignments: []\n conjunctions:\n - - Clause:\n Clause:\n access_clause:\n query:\n query:\n - Key: '%s3_buckets_server_side_encryption'\n - AllIndices: null\n - Key: Properties\n - Key: BucketEncryption\n match_all: true\n comparator:\n - Exists\n - false\n compare_with: null\n custom_message: null\n location:\n line: 7\n column: 3\n negation: false\n - - Clause:\n Clause:\n access_clause:\n query:\n query:\n - Key: '%s3_buckets_server_side_encryption'\n - AllIndices: null\n - Key: Properties\n - Key: BucketEncryption\n - Key: ServerSideEncryptionConfiguration\n - AllIndices: null\n - Key: ServerSideEncryptionByDefault\n - Key: SSEAlgorithm\n match_all: true\n comparator:\n - In\n - false\n compare_with:\n Value:\n path: ''\n value:\n - aws:kms\n - AES256\n custom_message: \"\\n Violation: S3 Bucket must enable server-side encryption.\\n Fix: Set the S3 Bucket property BucketEncryption.ServerSideEncryptionConfiguration.ServerSideEncryptionByDefault.SSEAlgorithm to either \\\"aws:kms\\\" or \\\"AES256\\\"\\n \"\n location:\n line: 8\n column: 3\n negation: false\nparameterized_rules: []\n";
const S3_BUCKET_PUBLIC_READ_PROHIBITED_PARSE_TREE: &str = "assignments:\n- var: s3_bucket_public_read_prohibited\n value:\n AccessClause:\n query:\n - Key: Resources\n - AllValues: null\n - Filter:\n - null\n - - - Clause:\n access_clause:\n query:\n query:\n - Key: Type\n match_all: true\n comparator:\n - Eq\n - false\n compare_with:\n Value:\n path: ''\n value: AWS::S3::Bucket\n custom_message: null\n location:\n line: 1\n column: 53\n negation: false\n match_all: true\nguard_rules:\n- rule_name: S3_BUCKET_PUBLIC_READ_PROHIBITED\n conditions:\n - - Clause:\n access_clause:\n query:\n query:\n - Key: '%s3_bucket_public_read_prohibited'\n match_all: true\n comparator:\n - Empty\n - true\n compare_with: null\n custom_message: null\n location:\n line: 3\n column: 44\n negation: false\n block:\n assignments: []\n conjunctions:\n - - Clause:\n Clause:\n access_clause:\n query:\n query:\n - Key: '%s3_bucket_public_read_prohibited'\n - AllIndices: null\n - Key: Properties\n - Key: PublicAccessBlockConfiguration\n match_all: true\n comparator:\n - Exists\n - false\n compare_with: null\n custom_message: null\n location:\n line: 4\n column: 3\n negation: false\n - - Clause:\n Clause:\n access_clause:\n query:\n query:\n - Key: '%s3_bucket_public_read_prohibited'\n - AllIndices: null\n - Key: Properties\n - Key: PublicAccessBlockConfiguration\n - Key: BlockPublicAcls\n match_all: true\n comparator:\n - Eq\n - false\n compare_with:\n Value:\n path: ''\n value: true\n custom_message: null\n location:\n line: 5\n column: 3\n negation: false\n - - Clause:\n Clause:\n access_clause:\n query:\n query:\n - Key: '%s3_bucket_public_read_prohibited'\n - AllIndices: null\n - Key: Properties\n - Key: PublicAccessBlockConfiguration\n - Key: BlockPublicPolicy\n match_all: true\n comparator:\n - Eq\n - false\n compare_with:\n Value:\n path: ''\n value: true\n custom_message: null\n location:\n line: 6\n column: 3\n negation: false\n - - Clause:\n Clause:\n access_clause:\n query:\n query:\n - Key: '%s3_bucket_public_read_prohibited'\n - AllIndices: null\n - Key: Properties\n - Key: PublicAccessBlockConfiguration\n - Key: IgnorePublicAcls\n match_all: true\n comparator:\n - Eq\n - false\n compare_with:\n Value:\n path: ''\n value: true\n custom_message: null\n location:\n line: 7\n column: 3\n negation: false\n - - Clause:\n Clause:\n access_clause:\n query:\n query:\n - Key: '%s3_bucket_public_read_prohibited'\n - AllIndices: null\n - Key: Properties\n - Key: PublicAccessBlockConfiguration\n - Key: RestrictPublicBuckets\n match_all: true\n comparator:\n - Eq\n - false\n compare_with:\n Value:\n path: ''\n value: true\n custom_message: \"\\n Violation: S3 Bucket Public Write Access controls need to be restricted.\\n Fix: Set S3 Bucket PublicAccessBlockConfiguration properties for BlockPublicAcls, BlockPublicPolicy, IgnorePublicAcls, RestrictPublicBuckets parameters to true.\\n \"\n location:\n line: 8\n column: 3\n negation: false\nparameterized_rules: []\n";
const S3_BUCKET_LOGGING_ENABLED_PARSE_TREE: &str = "assignments:\n- var: s3_buckets_bucket_logging_enabled\n value:\n AccessClause:\n query:\n - Key: Resources\n - AllValues: null\n - Filter:\n - null\n - - - Clause:\n access_clause:\n query:\n query:\n - Key: Type\n match_all: true\n comparator:\n - Eq\n - false\n compare_with:\n Value:\n path: ''\n value: AWS::S3::Bucket\n custom_message: null\n location:\n line: 30\n column: 54\n negation: false\n - - Clause:\n access_clause:\n query:\n query:\n - Key: Metadata\n - Key: guard\n - Key: SuppressedRules\n match_all: true\n comparator:\n - Exists\n - true\n compare_with: null\n custom_message: null\n location:\n line: 31\n column: 3\n negation: false\n - Clause:\n access_clause:\n query:\n query:\n - Key: Metadata\n - Key: guard\n - Key: SuppressedRules\n - AllValues: null\n match_all: true\n comparator:\n - Eq\n - true\n compare_with:\n Value:\n path: ''\n value: S3_BUCKET_LOGGING_ENABLED\n custom_message: null\n location:\n line: 32\n column: 3\n negation: false\n match_all: true\nguard_rules:\n- rule_name: S3_BUCKET_LOGGING_ENABLED\n conditions:\n - - Clause:\n access_clause:\n query:\n query:\n - Key: '%s3_buckets_bucket_logging_enabled'\n match_all: true\n comparator:\n - Empty\n - true\n compare_with: null\n custom_message: null\n location:\n line: 35\n column: 37\n negation: false\n block:\n assignments: []\n conjunctions:\n - - Clause:\n Clause:\n access_clause:\n query:\n query:\n - Key: '%s3_buckets_bucket_logging_enabled'\n - AllIndices: null\n - Key: Properties\n - Key: LoggingConfiguration\n match_all: true\n comparator:\n - Exists\n - false\n compare_with: null\n custom_message: \"\\n Violation: S3 Bucket Logging needs to be configured to enable logging.\\n Fix: Set the S3 Bucket property LoggingConfiguration to start logging into S3 bucket.\\n \"\n location:\n line: 36\n column: 3\n negation: false\nparameterized_rules: []\n";
#[rstest::rstest]
#[case(
"validate/rules-dir/s3_bucket_server_side_encryption_enabled.guard",
YAML_S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED_PARSE_TREE,
StatusCode::SUCCESS
)]
#[case(
"validate/rules-dir/s3_bucket_public_read_prohibited.guard",
S3_BUCKET_PUBLIC_READ_PROHIBITED_PARSE_TREE,
StatusCode::SUCCESS
)]
#[case(
"validate/rules-dir/s3_bucket_logging_enabled.guard",
S3_BUCKET_LOGGING_ENABLED_PARSE_TREE,
StatusCode::SUCCESS
)]
fn test_yaml_output(
#[case] rules_arg: &str,
#[case] expected_writer_output: &str,
#[case] expected_status_code: i32,
) {
let mut reader = Reader::default();
let mut writer = Writer::new(WBVec(vec![])).expect("Failed to create writer.");
let status_code = ParseTreeTestRunner::default()
.rules(rules_arg)
.run(&mut writer, &mut reader);
assert_eq!(expected_status_code, status_code);
assert_output_from_str_eq!(expected_writer_output, writer)
}
#[rstest::rstest]
#[case("validate/rules-dir/dne.guard", StatusCode::INTERNAL_FAILURE)]
#[case(
"validate/rules-dir/malformed-rule.guard",
StatusCode::INTERNAL_FAILURE
)]
fn test_yaml_output_with_expected_failures(
#[case] rules_arg: &str,
#[case] expected_status_code: i32,
) {
let mut reader = Reader::default();
let mut writer =
Writer::new_with_err(WBVec(vec![]), WBVec(vec![])).expect("Failed to create writer.");
let status_code = ParseTreeTestRunner::default()
.rules(rules_arg)
.run(&mut writer, &mut reader);
let expected_writer_output = if cfg!(windows) {
"Error occurred I/O error when reading The system cannot find the file specified. (os error 2)\n"
} else {
"Error occurred I/O error when reading No such file or directory (os error 2)\n"
};
assert_eq!(expected_status_code, status_code);
assert_eq!(expected_writer_output, writer.err_to_stripped().unwrap());
}
#[rstest::rstest]
#[case(
"parse-tree/rules-dir/iterate_through_json_list_without_key.guard",
"resources/parse-tree/output-dir/test_rule_iterate_through_json_list_without_key.yaml",
StatusCode::SUCCESS
)]
#[case(
"parse-tree/rules-dir/rule_with_this_keyword.guard",
"resources/parse-tree/output-dir/test_rule_with_this_keyword.yaml",
StatusCode::SUCCESS
)]
#[case(
"validate/functions/rules/string_manipulation.guard",
"resources/parse-tree/output-dir/parse_tree_functions.yaml",
StatusCode::SUCCESS
)]
fn test_yaml_output_compare_buffer_to_file(
#[case] rules_arg: &str,
#[case] expected_writer_output: &str,
#[case] expected_status_code: i32,
) {
let mut reader = Reader::default();
let mut writer = Writer::new(WBVec(vec![])).expect("Failed to create writer.");
let status_code = ParseTreeTestRunner::default()
.rules(rules_arg)
.run(&mut writer, &mut reader);
assert_eq!(expected_status_code, status_code);
assert_output_from_file_eq!(expected_writer_output, writer)
}
}