1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use log::{self, debug, error, info, trace};
use serde_json::Value;
use std::collections::HashMap;
use std::collections::HashSet;
use std::error::Error;
use std::fs;

pub fn run(template_file: &str) -> Result<Vec<String>, Box<dyn Error>> {
    let template_contents = fs::read_to_string(template_file)?;

    trace!(
        "Template file is '{}' and its contents are:\n'{}'",
        template_file,
        template_contents
    );

    Ok(run_gen(&template_contents))
}

pub fn run_gen(template_file_contents: &str) -> Vec<String> {
    info!("Loading CloudFormation Template");
    debug!("Entered run_gen");

    debug!("Deserializing CloudFormation template");
    let cfn_template: HashMap<String, Value> = match serde_json::from_str(template_file_contents) {
        Ok(s) => s,
        Err(_) => match serde_yaml::from_str(template_file_contents) {
            Ok(y) => y,
            Err(e) => {
                let msg_string =
                    format!("Template file format was unreadable as json or yaml: {}", e);
                error!("{}", &msg_string);
                return vec![msg_string];
            }
        },
    };
    trace!("CFN Template is {:#?}", &cfn_template);
    let cfn_resources_clone = match cfn_template.get("Resources") {
        Some(y) => y.clone(),
        None => {
            let msg_string = format!("Template lacks a Resources section");
            error!("{}", &msg_string);
            return vec![msg_string];
        }
    };
    let cfn_resources: HashMap<String, Value> = match serde_json::from_value(cfn_resources_clone) {
        Ok(y) => y,
        Err(e) => {
            let msg_string = format!("Template Resources section has an invalid structure: {}", e);
            error!("{}", &msg_string);
            return vec![msg_string];
        }
    };
    trace!("CFN resources are: {:?}", cfn_resources);
    gen_rules(cfn_resources)
}

fn gen_rules(cfn_resources: HashMap<String, Value>) -> Vec<String> {
    let mut rule_set: HashSet<String> = HashSet::new();
    let mut rule_map: HashMap<String, HashSet<String>> = HashMap::new();
    for (name, cfn_resource) in cfn_resources {
        trace!("{} is {:?}", name, &cfn_resource);
        let props: HashMap<String, Value> =
            match serde_json::from_value(cfn_resource["Properties"].clone()) {
                Ok(s) => s,
                Err(_) => continue,
            };
        for (prop_name, prop_val) in props {
            let stripped_val = match prop_val.as_str() {
                Some(v) => String::from(v),
                None => prop_val.to_string(),
            };
            let no_newline_stripped_val = stripped_val.trim().replace("\n", "");
            let key_name = format!("{} {}", &cfn_resource["Type"].as_str().unwrap(), prop_name);
            // If the key doesn't exist, create it and set its value to a new HashSet with the rule value in it
            if !rule_map.contains_key(&key_name) {
                let value_set: HashSet<String> =
                    vec![no_newline_stripped_val].into_iter().collect();
                rule_map.insert(key_name, value_set);
            } else {
                // If the key does exist, add the item to the HashSet
                let value_set = rule_map.get_mut(&key_name).unwrap();
                value_set.insert(no_newline_stripped_val);
            };
        }
    }
    for (key, val_set) in rule_map {
        let mut rule_string: String = String::from("");
        let mut count = 0;
        let mut sorted_val_set: Vec<String> = val_set.into_iter().collect::<Vec<String>>();
        sorted_val_set.sort();
        for r in sorted_val_set {
            let temp_rule_string = format!("{} == {}", key, r);
            if count > 0 {
                rule_string = format!("{} |OR| {}", rule_string, temp_rule_string);
            } else {
                rule_string = temp_rule_string;
            }
            count += 1;
        }
        rule_set.insert(rule_string);
    }
    rule_set.into_iter().collect()
}