Skip to content

Commit

Permalink
Add lambda wrapper for cfn guard (#12)
Browse files Browse the repository at this point in the history
* Add lambda wrapper for cfn guard

- Add function to valdiate input json data and rules
- Add function to return result in json format
- Create cfn guard lib
- Create directory for cfn-guard-lambda, call cfn guard lib
  from the lambda wrapper
- Add test directory for functional tests
  • Loading branch information
omkhegde authored and diwakar committed Mar 8, 2021
1 parent b6fc6c9 commit 3189828
Show file tree
Hide file tree
Showing 10 changed files with 261 additions and 5 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[workspace]

members = [
"cfn-guard"
"cfn-guard",
"cfn-guard-lambda"
]
16 changes: 16 additions & 0 deletions cfn-guard-lambda/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "cfn-guard-lambda"
version = "1.0.0"
authors = ["Omkar Hegde <omkhegde@amazon.com>", "aws-cloudformation-developers <aws-cloudformation-developers@amazon.com>"]
description = "Lambda version of cfn-guard. Checks AWS CloudFormation templates for policy compliance using a simple, policy-as-code, declarative syntax"
license = "Apache-2.0"
edition = "2018"

[dependencies]
lambda_runtime = "0.2.1"
serde = "1.0.92"
serde_derive = "1.0.92"
simple-error = "0.2.0"
simple_logger = "1.3.0"
log = "0.4.6"
cfn-guard = { version = "0.10.0", path = "../cfn-guard" }
44 changes: 44 additions & 0 deletions cfn-guard-lambda/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use std::error::Error;

use cfn_guard;
use lambda_runtime::{error::HandlerError, lambda, Context};
use log::{self, info};
use serde_derive::{Deserialize, Serialize};
use simple_logger;

#[derive(Deserialize, Debug)]
struct CustomEvent {
#[serde(rename = "data")]
data: String,
#[serde(rename = "rules")]
rules: String,
}

#[derive(Serialize)]
struct CustomOutput {
message: String,
}

fn main() -> Result<(), Box<dyn Error>> {
simple_logger::init_with_level(log::Level::Info)?;
lambda!(call_cfn_guard);

Ok(())
}

fn call_cfn_guard(e: CustomEvent, _c: Context) -> Result<CustomOutput, HandlerError> {
info!("Template is [{}]", &e.data);
info!("Rule Set is [{}]", &e.rules);
let (result) = match cfn_guard::run_checks(&e.data, &e.rules)
{
Ok(t) => t,
Err(e) => (e.to_string()),
};

Ok(CustomOutput {
message: result,
})
}
3 changes: 3 additions & 0 deletions cfn-guard/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ authors = ["diwakar <diwakar@amazon.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "cfn_guard"
path = "src/lib.rs"

[dependencies]
nom = "5.1.2"
Expand Down
40 changes: 40 additions & 0 deletions cfn-guard/src/commands/helper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::rules::errors::{Error, ErrorKind};
use crate::rules::evaluate::RootScope;
use crate::rules::path_value::PathAwareValue;
use crate::commands::tracker::StackTracker;
use crate::commands::validate::ConsoleReporter;
use crate::rules::{Evaluate, Result};
use std::convert::TryFrom;

pub fn validate_and_return_json(
data: &str,
rules: &str,
) -> Result<String> {
let mut input_data = match serde_json::from_str::<serde_json::Value>(&data) {
Ok(value) => PathAwareValue::try_from(value),
Err(e) => return Err(Error::new(ErrorKind::ParseError(e.to_string()))),
};

let span = crate::rules::parser::Span::new_extra(&rules, "lambda");

match crate::rules::parser::rules_file(span) {

Ok(rules) => {
match input_data {
Ok(root) => {
let root_context = RootScope::new(&rules, &root);
let stacker = StackTracker::new(&root_context);
let reporter = ConsoleReporter::new(stacker, true, true);
rules.evaluate(&root, &reporter)?;
let json_result = reporter.get_result_json();
return Ok((json_result));
}
Err(e) => return Err(e),
}
}
Err(e) => return Err(Error::new(ErrorKind::ParseError(e.to_string()))),
}
}
2 changes: 1 addition & 1 deletion cfn-guard/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
pub(crate) mod files;
pub(crate) mod validate;
pub(crate) mod test;
pub(crate) mod helper;
pub(crate) mod parse_tree;

mod tracker;
mod helper;

//
// Constants for arguments
Expand Down
2 changes: 1 addition & 1 deletion cfn-guard/src/commands/tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ impl StatusContext {
}
}

pub(super) struct StackTracker<'r> {
pub(crate) struct StackTracker<'r> {
root_context: &'r dyn EvaluationContext,
stack: std::cell::RefCell<Vec<StatusContext>>,
}
Expand Down
40 changes: 38 additions & 2 deletions cfn-guard/src/commands/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,38 @@ impl Command for Validate {
}
}

pub fn validate_and_return_json(
data: &str,
rules: &str,
) -> Result<String> {
let mut input_data = match serde_json::from_str::<serde_json::Value>(&data) {
Ok(value) => PathAwareValue::try_from(value),
Err(e) => return Err(Error::new(ErrorKind::ParseError(e.to_string()))),
};

let span = crate::rules::parser::Span::new_extra(&rules, "lambda");

match crate::rules::parser::rules_file(span) {

Ok(rules) => {
match input_data {
Ok(root) => {
let root_context = RootScope::new(&rules, &root);
let stacker = StackTracker::new(&root_context);
let reporter = ConsoleReporter::new(stacker, true, true);
rules.evaluate(&root, &reporter)?;
let json_result = reporter.get_result_json();
return Ok((json_result));
}
Err(e) => return Err(e),
}
}
Err(e) => return Err(Error::new(ErrorKind::ParseError(e.to_string()))),
}
}

#[derive(Debug)]
struct ConsoleReporter<'r> {
pub(crate) struct ConsoleReporter<'r> {
root_context: StackTracker<'r>,
verbose: bool,
print_json: bool
Expand Down Expand Up @@ -159,14 +189,20 @@ pub(super) fn print_context(cxt: &StatusContext, depth: usize) {
}

impl<'r, 'loc> ConsoleReporter<'r> {
fn new(root: StackTracker<'r>, verbose: bool, print_json: bool) -> Self {
pub(crate) fn new(root: StackTracker<'r>, verbose: bool, print_json: bool) -> Self {
ConsoleReporter {
root_context: root,
verbose,
print_json,
}
}

pub fn get_result_json(self) -> String {
let stack = self.root_context.stack();
let top = stack.first().unwrap();
return format!("{}", serde_json::to_string_pretty(&top.children).unwrap());
}

fn report(self) {
let stack = self.root_context.stack();
let top = stack.first().unwrap();
Expand Down
13 changes: 13 additions & 0 deletions cfn-guard/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

mod rules;
mod commands;
mod command;

pub extern "C" fn run_checks(
data: &str,
rules: &str,
) -> crate::rules::Result<String> {
return crate::commands::helper::validate_and_return_json(&data, &rules);
}
103 changes: 103 additions & 0 deletions cfn-guard/tests/functional.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use cfn_guard;

mod tests {
use super::*;
#[test]
fn test_run_check() {
let mut data = String::from(
r#"
{
"Resources": {
"VPC" : {
"Type" : "AWS::ApiGateway::Method",
"Properties" : {
"AuthorizationType" : "10.0.0.0/24"
}
}
}
}
"#,
);
let rule = "AWS::ApiGateway::Method { Properties.AuthorizationType == \"NONE\"}";
let mut expected = String::from(
r#"
[
{
"eval_type": "Rule",
"context": "default",
"from": null,
"to": null,
"status": "FAIL",
"children": [
{
"eval_type": "Type",
"context": "AWS::ApiGateway::Method",
"from": null,
"to": null,
"status": "FAIL",
"children": [
{
"eval_type": "Filter",
"context": "Path=/Resources/VPC,Type=MapElement",
"from": null,
"to": null,
"status": "PASS",
"children": [
{
"eval_type": "Clause",
"context": "GuardAccessClause[ check = Type EQUALS String(\"AWS::ApiGateway::Method\"), loc = Location[file=#1@14] ]",
"from": null,
"to": null,
"status": "PASS",
"children": []
}
]
},
{
"eval_type": "Type",
"context": "AWS::ApiGateway::Method#0(/Resources/VPC)",
"from": null,
"to": null,
"status": "FAIL",
"children": [
{
"eval_type": "Clause",
"context": "GuardAccessClause[ check = Properties.AuthorizationType EQUALS String(\"NONE\"), loc = Location[file=lambda#1@27] ]",
"from": {
"String": [
"/Resources/VPC/Properties/AuthorizationType",
"10.0.0.0/24"
]
},
"to": {
"String": [
"lambda/1/27/Clause/",
"NONE"
]
},
"status": "FAIL",
"children": []
}
]
}
]
}
]
}
]
"#,
);

// Remove white spaces from expected and calculated result for easy comparison.
expected.retain(|c| !c.is_whitespace());

let mut serialized = cfn_guard::run_checks(&data, &rule).unwrap();
serialized.retain(|c| !c.is_whitespace());

assert_eq!(expected, serialized);
}

}

0 comments on commit 3189828

Please sign in to comment.