Skip to main content

cfn_guard_rulegen/
lib.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use log::{self, debug, error, info, trace};
5use serde_json::Value;
6use std::collections::HashMap;
7use std::collections::HashSet;
8use std::error::Error;
9use std::fs;
10
11pub fn run(template_file: &str) -> Result<Vec<String>, Box<dyn Error>> {
12    let template_contents = fs::read_to_string(template_file)?;
13
14    trace!(
15        "Template file is '{}' and its contents are:\n'{}'",
16        template_file,
17        template_contents
18    );
19
20    Ok(run_gen(&template_contents))
21}
22
23pub fn run_gen(template_file_contents: &str) -> Vec<String> {
24    info!("Loading CloudFormation Template");
25    debug!("Entered run_gen");
26
27    debug!("Deserializing CloudFormation template");
28    let cfn_template: HashMap<String, Value> = match serde_json::from_str(template_file_contents) {
29        Ok(s) => s,
30        Err(_) => match serde_yaml::from_str(template_file_contents) {
31            Ok(y) => y,
32            Err(e) => {
33                let msg_string =
34                    format!("Template file format was unreadable as json or yaml: {}", e);
35                error!("{}", &msg_string);
36                return vec![msg_string];
37            }
38        },
39    };
40    trace!("CFN Template is {:#?}", &cfn_template);
41    let cfn_resources_clone = match cfn_template.get("Resources") {
42        Some(y) => y.clone(),
43        None => {
44            let msg_string = format!("Template lacks a Resources section");
45            error!("{}", &msg_string);
46            return vec![msg_string];
47        }
48    };
49    let cfn_resources: HashMap<String, Value> = match serde_json::from_value(cfn_resources_clone) {
50        Ok(y) => y,
51        Err(e) => {
52            let msg_string = format!("Template Resources section has an invalid structure: {}", e);
53            error!("{}", &msg_string);
54            return vec![msg_string];
55        }
56    };
57    trace!("CFN resources are: {:?}", cfn_resources);
58    gen_rules(cfn_resources)
59}
60
61fn gen_rules(cfn_resources: HashMap<String, Value>) -> Vec<String> {
62    let mut rule_set: HashSet<String> = HashSet::new();
63    let mut rule_map: HashMap<String, HashSet<String>> = HashMap::new();
64    for (name, cfn_resource) in cfn_resources {
65        trace!("{} is {:?}", name, &cfn_resource);
66        let props: HashMap<String, Value> =
67            match serde_json::from_value(cfn_resource["Properties"].clone()) {
68                Ok(s) => s,
69                Err(_) => continue,
70            };
71        for (prop_name, prop_val) in props {
72            let stripped_val = match prop_val.as_str() {
73                Some(v) => String::from(v),
74                None => prop_val.to_string(),
75            };
76            let no_newline_stripped_val = stripped_val.trim().replace("\n", "");
77            let key_name = format!("{} {}", &cfn_resource["Type"].as_str().unwrap(), prop_name);
78            // If the key doesn't exist, create it and set its value to a new HashSet with the rule value in it
79            if !rule_map.contains_key(&key_name) {
80                let value_set: HashSet<String> =
81                    vec![no_newline_stripped_val].into_iter().collect();
82                rule_map.insert(key_name, value_set);
83            } else {
84                // If the key does exist, add the item to the HashSet
85                let value_set = rule_map.get_mut(&key_name).unwrap();
86                value_set.insert(no_newline_stripped_val);
87            };
88        }
89    }
90    for (key, val_set) in rule_map {
91        let mut rule_string: String = String::from("");
92        let mut count = 0;
93        let mut sorted_val_set: Vec<String> = val_set.into_iter().collect::<Vec<String>>();
94        sorted_val_set.sort();
95        for r in sorted_val_set {
96            let temp_rule_string = format!("{} == {}", key, r);
97            if count > 0 {
98                rule_string = format!("{} |OR| {}", rule_string, temp_rule_string);
99            } else {
100                rule_string = temp_rule_string;
101            }
102            count += 1;
103        }
104        rule_set.insert(rule_string);
105    }
106    rule_set.into_iter().collect()
107}