cfn-guard 3.2.0

AWS CloudFormation Guard is an open-source general-purpose policy-as-code evaluation tool. It provides developers with a simple-to-use, yet powerful and expressive domain-specific language (DSL) to define policies and enables developers to validate JSON- or YAML- formatted structured data with those policies.
Documentation
use std::{convert::TryFrom, rc::Rc};

use crate::rules::{
    eval_context::eval_context_tests::BasicQueryTesting,
    exprs::AccessQuery,
    functions::date_time::{now, parse_epoch},
    path_value::PathAwareValue,
    EvalContext, QueryResult,
};
use chrono::Utc;
use pretty_assertions::assert_eq;
use rstest::rstest;

const VALUE_STR: &str = r#"
{
    "Resources": {
        "LambdaFunction": {
            "Type": "AWS::Lambda::Function",
            "Properties": {
                "UpdatedAt": "2024-08-21T00:00:00Z",
                "CreatedAt": "2024-08-13T00:00:00Z",
                "BadValue": "not-a-date"
            }
        }
    }
}
    "#;

#[rstest]
#[case(r#"UpdatedAt"#, 1724198400, false)]
#[case(r#"CreatedAt"#, 1723507200, false)]
#[case(r#"BadValue"#, 1723507200, true)]
fn test_parse_epoch(
    #[case] query_str: &str,
    #[case] _expected_epoch: i64,
    #[case] should_error: bool,
) -> crate::rules::Result<()> {
    let value = PathAwareValue::try_from(serde_json::from_str::<serde_json::Value>(VALUE_STR)?)?;

    let mut eval = BasicQueryTesting {
        root: Rc::new(value),
        recorder: None,
    };
    let root_query =
        format!(r#"Resources[ Type == 'AWS::Lambda::Function' ].Properties.{query_str}"#,);
    let query = AccessQuery::try_from(root_query.as_str())?;
    let results = eval.query(&query.query)?;
    match results[0].clone() {
        QueryResult::Literal(val) | QueryResult::Resolved(val) => {
            assert!(matches!(&*val, PathAwareValue::String(_)));
        }
        _ => unreachable!(),
    }

    let epoch_values = parse_epoch(&results);
    if should_error {
        assert!(epoch_values.is_err());
    } else {
        assert!(epoch_values.is_ok());
        let epoch_values = epoch_values.unwrap();
        assert_eq!(epoch_values.len(), 1);
        assert!(matches!(
            epoch_values[0].as_ref().unwrap(),
            PathAwareValue::Int((_, _expected_epoch))
        ));
    }

    Ok(())
}

#[rstest]
fn test_now() {
    let now_result = now();
    assert!(now_result.is_ok());

    let now_vec = now_result.unwrap();
    assert_eq!(now_vec.len(), 1);

    let now_option = now_vec.first().unwrap();
    assert!(now_option.is_some());

    let now_value = now_option.as_ref().unwrap();

    let timestamp = match now_value {
        PathAwareValue::Int((_, timestamp)) => *timestamp,
        _ => unreachable!(),
    };

    let now = Utc::now().timestamp();

    assert!((now - timestamp).abs() <= 1);
}