use crate::core::compliance_pack::{
evaluate_pack, ComplianceCheck, CompliancePack, ComplianceRule, PackEvalResult,
};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct BoundaryConfig {
pub target_rule_id: String,
pub expected_pass: bool,
pub resources: HashMap<String, HashMap<String, String>>,
pub description: String,
}
#[derive(Debug, Clone)]
pub struct BoundaryTestResult {
pub pack_name: String,
pub rules_tested: usize,
pub rules_with_boundary: usize,
pub outcomes: Vec<BoundaryOutcome>,
}
impl BoundaryTestResult {
pub fn all_passed(&self) -> bool {
self.outcomes.iter().all(|o| o.passed)
}
pub fn failure_count(&self) -> usize {
self.outcomes.iter().filter(|o| !o.passed).count()
}
}
#[derive(Debug, Clone)]
pub struct BoundaryOutcome {
pub rule_id: String,
pub passed: bool,
pub expected: String,
pub actual: String,
pub description: String,
}
pub fn generate_boundary_configs(pack: &CompliancePack) -> Vec<BoundaryConfig> {
let mut configs = Vec::new();
for rule in &pack.rules {
configs.extend(generate_for_rule(rule));
}
configs
}
fn generate_for_rule(rule: &ComplianceRule) -> Vec<BoundaryConfig> {
match &rule.check {
ComplianceCheck::Assert {
resource_type,
field,
expected,
} => generate_assert_boundary(&rule.id, resource_type, field, expected),
ComplianceCheck::Deny {
resource_type,
field,
pattern,
} => generate_deny_boundary(&rule.id, resource_type, field, pattern),
ComplianceCheck::Require {
resource_type,
field,
} => generate_require_boundary(&rule.id, resource_type, field),
ComplianceCheck::RequireTag { tag } => generate_require_tag_boundary(&rule.id, tag),
ComplianceCheck::Script { .. } => {
Vec::new()
}
}
}
fn generate_assert_boundary(
rule_id: &str,
resource_type: &str,
field: &str,
expected: &str,
) -> Vec<BoundaryConfig> {
let resource_name = format!("boundary-{resource_type}");
let mut golden = HashMap::new();
let mut fields = HashMap::new();
fields.insert("type".into(), resource_type.into());
fields.insert(field.into(), expected.into());
golden.insert(resource_name.clone(), fields);
let mut boundary = HashMap::new();
let mut bad_fields = HashMap::new();
bad_fields.insert("type".into(), resource_type.into());
bad_fields.insert(field.into(), format!("NOT_{expected}"));
boundary.insert(resource_name, bad_fields);
vec![
BoundaryConfig {
target_rule_id: rule_id.into(),
expected_pass: true,
resources: golden,
description: format!("golden: {field}={expected}"),
},
BoundaryConfig {
target_rule_id: rule_id.into(),
expected_pass: false,
resources: boundary,
description: format!("boundary: {field}=NOT_{expected}"),
},
]
}
fn generate_deny_boundary(
rule_id: &str,
resource_type: &str,
field: &str,
pattern: &str,
) -> Vec<BoundaryConfig> {
let resource_name = format!("boundary-{resource_type}");
let mut golden = HashMap::new();
let mut fields = HashMap::new();
fields.insert("type".into(), resource_type.into());
fields.insert(field.into(), "safe_value".into());
golden.insert(resource_name.clone(), fields);
let mut boundary = HashMap::new();
let mut bad_fields = HashMap::new();
bad_fields.insert("type".into(), resource_type.into());
bad_fields.insert(field.into(), pattern.into());
boundary.insert(resource_name, bad_fields);
vec![
BoundaryConfig {
target_rule_id: rule_id.into(),
expected_pass: true,
resources: golden,
description: format!("golden: {field}=safe (not {pattern})"),
},
BoundaryConfig {
target_rule_id: rule_id.into(),
expected_pass: false,
resources: boundary,
description: format!("boundary: {field}={pattern} (denied)"),
},
]
}
fn generate_require_boundary(
rule_id: &str,
resource_type: &str,
field: &str,
) -> Vec<BoundaryConfig> {
let resource_name = format!("boundary-{resource_type}");
let mut golden = HashMap::new();
let mut fields = HashMap::new();
fields.insert("type".into(), resource_type.into());
fields.insert(field.into(), "present".into());
golden.insert(resource_name.clone(), fields);
let mut boundary = HashMap::new();
let mut missing_fields = HashMap::new();
missing_fields.insert("type".into(), resource_type.into());
boundary.insert(resource_name, missing_fields);
vec![
BoundaryConfig {
target_rule_id: rule_id.into(),
expected_pass: true,
resources: golden,
description: format!("golden: {field} present"),
},
BoundaryConfig {
target_rule_id: rule_id.into(),
expected_pass: false,
resources: boundary,
description: format!("boundary: {field} missing"),
},
]
}
fn generate_require_tag_boundary(rule_id: &str, tag: &str) -> Vec<BoundaryConfig> {
let mut golden = HashMap::new();
let mut fields = HashMap::new();
fields.insert("tags".into(), tag.into());
golden.insert("boundary-resource".into(), fields);
let mut boundary = HashMap::new();
let no_tags = HashMap::new();
boundary.insert("boundary-resource".into(), no_tags);
vec![
BoundaryConfig {
target_rule_id: rule_id.into(),
expected_pass: true,
resources: golden,
description: format!("golden: tag '{tag}' present"),
},
BoundaryConfig {
target_rule_id: rule_id.into(),
expected_pass: false,
resources: boundary,
description: format!("boundary: no tags (missing '{tag}')"),
},
]
}
pub fn test_boundaries(pack: &CompliancePack) -> BoundaryTestResult {
let configs = generate_boundary_configs(pack);
let mut outcomes = Vec::new();
let mut rules_tested = std::collections::HashSet::new();
for config in &configs {
rules_tested.insert(&config.target_rule_id);
let eval = evaluate_pack(pack, &config.resources);
let rule_result = find_rule_result(&eval, &config.target_rule_id);
let actual_passed = rule_result.is_none_or(|r| r.passed);
let outcome_passed = actual_passed == config.expected_pass;
outcomes.push(BoundaryOutcome {
rule_id: config.target_rule_id.clone(),
passed: outcome_passed,
expected: if config.expected_pass { "pass" } else { "fail" }.into(),
actual: if actual_passed { "pass" } else { "fail" }.into(),
description: config.description.clone(),
});
}
BoundaryTestResult {
pack_name: pack.name.clone(),
rules_tested: pack.rules.len(),
rules_with_boundary: rules_tested.len(),
outcomes,
}
}
fn find_rule_result<'a>(
eval: &'a PackEvalResult,
rule_id: &str,
) -> Option<&'a crate::core::compliance_pack::RuleEvalResult> {
eval.results.iter().find(|r| r.rule_id == rule_id)
}
pub fn format_boundary_results(result: &BoundaryTestResult) -> String {
let mut lines = Vec::new();
lines.push(format!(
"Boundary Testing: {} (rules: {}, boundaries: {})",
result.pack_name,
result.rules_tested,
result.outcomes.len()
));
for outcome in &result.outcomes {
let status = if outcome.passed { "PASS" } else { "FAIL" };
lines.push(format!(
" [{status}] {}: {} (expected={}, actual={})",
outcome.rule_id, outcome.description, outcome.expected, outcome.actual
));
}
let pass_count = result.outcomes.iter().filter(|o| o.passed).count();
lines.push(format!(
"Result: {}/{} boundary tests passed",
pass_count,
result.outcomes.len()
));
lines.join("\n")
}