Giter Club home page Giter Club logo

aws-cloudformation / cloudformation-guard Goto Github PK

View Code? Open in Web Editor NEW
1.2K 39.0 176.0 9.19 MB

Guard offers a policy-as-code domain-specific language (DSL) to write rules and validate JSON- and YAML-formatted data such as CloudFormation Templates, K8s configurations, and Terraform JSON plans/configurations against those rules. Take this survey to provide feedback about cfn-guard: https://amazonmr.au1.qualtrics.com/jfe/form/SV_bpyzpfoYGGuuUl0

License: Apache License 2.0

Rust 98.77% Shell 0.70% Makefile 0.06% C 0.07% Dockerfile 0.09% PowerShell 0.31%
policy-as-code cloudformation terraform k8s policy-rule-evaluation governance security compliance cfn-guard

cloudformation-guard's Introduction

AWS CloudFormation Guard

Validate Cloud Environments with Policy-as-Code

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.

The Guard 3.0 release introduces support for stateful rules through built-in functions, SAM CLI as an alternative deployment method for cfn-guard-lambda, command auto-completions for shell, advanced regular expressions, improved handling of intrinsic functions for the test command, as well as the --structured flag to the validate command to emit JSON/YAML parseable output. Note that previously written tests may have to be reviewed due to the corrected behavior of intrinsic function handling. Please see the release notes for full details and examples.

Guard can be used for the following domains:

  1. Preventative Governance and Compliance (shift left): validate Infrastructure-as-code (IaC) or infrastructure/service compositions such as CloudFormation Templates, CloudFormation ChangeSets, Terraform JSON configuration files, Kubernetes configurations, and more against Guard policies representing your organizational best practices for security, compliance, and more. For example, developers can use Guard policies with
    1. Terraform plan (in JSON format) for deployment safety assessment checks or Terraform state files to detect live state deviations.
    2. Static assessment of IaC templates to determine network reachability like Amazon Redshift cluster deployed inside a VPC and prevent the provision of such stacks.
  2. Detective Governance and Compliance: validate conformity of Configuration Management Database (CMDB) resources such as AWS Config-based configuration items (CIs). For example, developers can use Guard policies against AWS Config CIs to continuously monitor state of deployed AWS and non-AWS resources, detect violations from policies, and trigger remediation.
  3. Deployment Safety: validate CloudFormation ChangeSets to ensure changes are safe before deployment. For example, renaming an Amazon DynamoDB Table will cause a replacement of the Table. With Guard 3.0, you can prevent such changes in your CI/CD pipelines.

NOTE: If you are using Guard 1.0 rules, we highly recommend adopting the latest version of Guard to simplify your current policy-as-code experience. Guard 2.0 and higher versions are backward incompatible with your Guard 1.0 rules and can result in breaking changes. The Guard 2.0 release was a complete re-write of the earlier 1.0 version to make the tool general-purpose.

To migrate from Guard 1.0 rules to use the updated grammar, follow the steps given below.

  1. Pull the release artifacts for the latest 2.x.x release from the corresponding release page listed here.
  2. Use migrate command to transition your existing 1.0 rules to use the updated grammar
  3. Read about all new Guard features from the latest release, and modify your rules for enhanced experience

Guard In Action

Guard In Action

Customer Feedback Take this survey to provide feedback about cfn-guard

Table of Contents

FAQs

1) What is Guard?

Guard is an open-source command line interface (CLI) that provides developers a general purpose domain-specific language (DSL) to express policy-as-code and then validate their JSON- and YAML-formatted data against that code. Guard’s DSL is a simple, powerful, and expressive declarative language to define policies. It is built on the foundation of clauses, which are assertions that evaluate to true or false. Examples clauses can include simple validations like all Amazon Simple Storage Service (S3) buckets must have versioning enabled, or combined to express complex validations like preventing public network reachability of Amazon Redshift clusters placed in a subnet. Guard has support for looping, queries with filtering, cross query joins, single shot variable assignments, conditional executions, and composable rules. These features help developers to express simple and advanced policies for various domains.

2) What Guard is not?

Guard is not a general-purpose programming language. It is a purpose-built DSL that is designed for policy definition and evaluation. Both non-technical people and developers can easily pick up Guard. Guard is human-readable and machine enforceable.

3) Where can I use Guard?

You can use Guard to define any type of policy for evaluation. You can apply Guard in the context of multiple domains: a) validating IaC/service compositions such as CloudFormation Templates, Terraform JSON configuration files, and Kubernetes configurations, b) verifying conformity of CMDB resources such as AWS Config-based CIs, and c) assessing security postures across resources like AWS Security Hub. The policy language and expression is common to all of them, based on simple Guard clauses.

3) What is a clause in Guard?

Clause is an assertion that evaluates to true or false. Clauses can either use binary operations to compare two values (e.g ==, > and in), or unary operations that takes only one value (e.g. exists, empty, and is_list). Here is a sample clause that compares Type to be a AWS::S3::Bucket :

Type == /AWS::S3::Bucket/

4) What are the supported types that can I use to define clauses?

Guard supports all primitives string, integer (64), float (64), bool, char, regex and specialized range expression like r(10, 200), for specifying ranges of values. It supports general key value pair maps (a.k.a associative arrays/struct) like { "my-map": { "nested-maps": [ { "key": 10, "value": 20 } ] } }, and arrays of primitives or key-value pair maps like [10, 20, 30] or [{ Key: "MyApp", Value: "PROD}, ..].

5) What binary and unary comparison operators can I use?

Unary Operators: exists, empty, is_string, is_list, is_struct, is_bool, is_int, is_float, not(!) Binary Operators: ==, !=, >, >=, <, <=, IN

Most operators are self-explanatory. A few important points:

  1. Refer Guard: Clauses to understand the usage of exists and empty operators
  2. Clause ports >= [10, 20, 30] implies that every element for ports is >= 30. If your intention is range, then express it as r[10, 30] .
  3. Clause ports >= 100 can have ports resolve to an array [121, 200, 443]. This check ensures that every element returned was >= 100, and in the example shown this evaluates to true.
  4. IN operator for collections (does not work for string type) to check if any value matches. For example:
Properties.SslPolicy IN ["ELBSecurityPolicy-TLS-1-2-2017-01", "ELBSecurityPolicy-TLS-1-2-Ext-2018-06"]

6) How can I define advanced policy rules?

You can define advanced policy rules using Conjunctive normal form. For example, here is a clause that asserts that all S3 buckets have a) names that start with a common prefix, b) encryption turned on, and c) only KMS-based algorithm is used (to know more about the query part read Guard: Query and Filtering) for IaC template.

let s3_buckets = Resources.*[ Type == /S3::Bucket/ ]

# Skip the checks if there are no S3 buckets present
rule s3_bucket_name_encryption_check when %s3_buckets !empty {
    %s3_buckets {
        Properties {
             # common prefix
             BucketName == /^MyCompanyPrefix/

             # encryption MUST BE on
             BucketEncryption.ServerSideEncryptionConfiguration[*] {
                # only KMS
                ServerSideEncryptionByDefault.SSEAlgorithm IN
                    ["aws:KMS"]
             }
        }
    }
}

7) Can I easily test policy rules?

Yes. Guard supports a built-in unit testing framework to test policy rules and clauses. This gives customers confidence that their guard policy rules work as intended. You can learn more about this unit testing framework in this doc Guard: Unit Testing

8) Does Guard support rule categories?

Yes. Guard supports running several rule-sets together for validating policies. You can create multiple rule files, each with its own intended purpose. For example, you can create one rules file for S3, second one for Dynamo DB, third one for access management, and so on. Alternatively, you can create a rules file for all your security related rules, second one for cost compliance, and so on. You can run Guard against all these rule files at once for evaluation. Refer example rules file Guard: Clauses, Guard: Complex Composition.

9) Where can I evaluate Guard policies?

Guard supports the entire spectrum of end-to-end evaluation of policy checks. The tool supports bringing in shift-left practices as close as running it directly at development time, integrated into code repositories via hooks like GitHub Actions for pull requests, and into CI/CD pipelines such as AWS CodePipeline pipelines and Jenkins (just exec process).

10) What are you not telling me? This sounds too good to be true.

Guard is a DSL and an accompanying CLI tool that allows easy-to-use definitions for declaring and enforcing policies. Today the tool supports local file-based execution of a category of policies. Guard doesn’t support the following things today, along with workarounds for some:

  1. Sourcing of rules from external locations such as GitHub Release and S3 bucket. If you want this feature natively in Guard, please raise an issue or +1 an existing issue.
  2. Ability to import Guard policy file by reference (local file or GitHub, S3, etc.). It currently only supports a directory on disk of policy files, that it would execute.
  3. Parameter/Vault resolution for IaC tools such as CloudFormation or Terraform. Before you ask, the answer is NO. We will not add native support in Guard as the engine is general-purpose. If you need CloudFormation resolution support, raise an issue and we might have a solution for you. We do not support HCL natively. We do, however, support Terraform Plan in JSON to run policies against for deployment safety. If you need HCL support, raise an issue as well.
  4. Ability to reference variables like %s3_buckets, inside error messages. Both JSON/Console output for evaluation results contain some of this information for inference. We also do not support using variable references to create dynamic regex expressions. However, we support variable references inside queries for cross join support, like Resources.%references.Properties.Tags.
  5. Support for specifying variable names when accessing map or list elements to cature these values. For example, consider this check Resources[resource_name].Properties.Tags not empty, here resource_name captures the key or index value. The information is tracked as a part of the evaluation context today and present in both console/JSON outputs. This support will be extended to regex expression variable captures as well.
  6. There are known issues with potential workarounds that we are tracking towards resolution

11) What are we really thankful about?

Where do we start? Hmm.... we want to thank Rust language’s forums, build management, and amazing ecosystem without which none of this would have been possible. We are not the greatest Rust practitioners, so if we did something that is not idiomatic Rust, please raise a PR.

We want to make a special mention to nom combinator parser framework to write our language parser in. This was an excellent decision that improved readability, testability, and composition. We highly recommend it. There are some rough edges, but it’s just a wonderful, awesome library. Thank you. Apart from that, we are consumers of many crates including hyper for HTTP handling, simple logger, and many more. We also want to thank the open-source community for sharing their feedback with us through GitHub issues/PRs.

And of course AWS for supporting the development and commitment to this project. Now read the docs and take it for a ride and tell us anything and everything.

Guard DSL

Tenets

(Unless you know better ones)

These tenets help guide the development of the Guard DSL:

  • Simple: The language must be simple for customers to author policy rules, simple to integrate with an integrated development environment (IDE), readable for human comprehension, and machine enforceable.

  • Unambiguous: The language must not allow for ambiguous interpretations that make it hard for customers to comprehend the policy evaluation. The tool is targeted for security and compliance related attestations that need the auditor to consistently and unambiguously understand rules and their evaluations.

  • Deterministic: The language design must allow language implementations to have deterministic, consistent, and isolated evaluations. Results for repeated evaluations for the same context and rules must evaluate to the same result every time. Time to evaluate results inside near-identical environments must be within acceptable tolerance limits.

  • Composable: The language must support composition to help build higher order functionality such as checks for PCI compliance, by easily combining building blocks together. Composition should not increase the complexity for interpreting outcomes, syntax, or navigation.

Features of Guard DSL

  • Clauses: Provides the foundational underpinning for Guard. They are assertions that evaluate to true or false. You can combine clauses using Conjunctive Normal Form. You can use them for direct assertions, as part of filters to select values, or for conditional evaluations. To learn more read Guard: Clauses

  • Context-Aware Evaluations, this binding and Loops: Automatic binding for context values when traversing hierarchical data with support for implicit looping over collections with an easy-to-use syntax. Collections can arise from accessing an array of elements, values for a map along with a filter, or from a query. To learn more read Guard: Context-Aware Evaluations, this and Loops

  • Query & Filtering: Queries support simple decimal dotted format syntax to access properties in the hierarchical data. Arrays/Collections are accessed using [] . Map or Struct’s values can use * for accessing values for all keys. All collections can be further narrowed to target specific instances inside the collection using filtering. To learn more read Guard: Query and Filtering

  • Variables, Projections, and Query Interpolation: Guard supports single shot assignment to variables using a let keyword for assignment. All variable assignments resulting from a query is a list (result set). One can also assign static literals to variables. Variables are assessed using a prefix % and can be used inside the Query for interpolation. To learn more read Guard: Query, Projection and Interpolation

  • Complex Composition: As stated earlier, clauses can be expressed in Conjunctive Normal Form. Clauses on separates lines are ANDs. Disjunctions are expressed using the or|OR keyword. You can group clauses in a named rule. You can then use named rules in other rules to create more advanced compositions. Furthermore, you can have multiple files containing named rules that together form a category of checks for a specific compliance like “ensure encryption at rest”. To learn more read Guard: Complex Composition

Guard CLI

Installation

Installation from Pre-Built Release Binaries

MacOS

By default this is built for macOS-12 (Monterey). See OS Matrix

  1. Open terminal of your choice. Default Cmd+Space, type terminal
  2. Cut-n-paste the commands below (change version=X for other versions)
$ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/aws-cloudformation/cloudformation-guard/main/install-guard.sh | sh

Remember to add ~/.guard/bin/ to your $PATH.

Alternatively, you can install the latest version with Homebrew.

$ brew install cloudformation-guard

You would not need to modify $PATH this way.

Ubuntu
  1. Open any terminal of your choice
  2. Cut-n-paste the commands below (change version=X for other versions)
$ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/aws-cloudformation/cloudformation-guard/main/install-guard.sh | sh

Remember to add ~/.guard/bin/ to your $PATH.

Windows
  1. Open PowerShell as Administrator
  2. Cut-n-paste the command below
$GuardWindowsInstallScript = Invoke-WebRequest https://raw.githubusercontent.com/aws-cloudformation/cloudformation-guard/main/install-guard.ps1; Invoke-Expression $($GuardWindowsInstallScript.Content)

If you get an error regarding authorization to execute the script run the follow before retrying step 2:

Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass

Installation of Rust and Cargo

Ubuntu/MacOS: Install Rust and Cargo
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

If you have not already, run source $HOME/.cargo/env as recommended by the rust installer. Read here for more information.

If building on Ubuntu, it is recommended to run sudo apt-get update; sudo apt install build-essential.

Windows 10: Install Rust and Cargo
  1. Create a Windows 10 workspace.
  2. Install the version of Microsoft Visual C++ Build Tools 2019 which provides just the Visual C++ build tools: https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019.
  3. Download the installer and run it.
  4. Select the "Individual Components" tab and check "Windows 10 SDK".
  5. Select the "Language Packs" tab and make sure that at least "English" is selected.
  6. Click "Install".
  7. Let it download and reboot if asked.
  8. Install Rust.
  9. Download rust-init.exe.
  10. Run it and accept the defaults.

Cargo-based Installation

Now that you have rust and cargo installed, installation of cfn-guard is easy:

$ cargo install cfn-guard

Check help to see if it is working.

$ cfn-guard help
cfn-guard 3.1.1

  Guard is a general-purpose tool that provides a simple declarative syntax to define
  policy-as-code as rules to validate against any structured hierarchical data (like JSON/YAML).
  Rules are composed of clauses expressed using Conjunctive Normal Form
  (fancy way of saying it is a logical AND of OR clauses). Guard has deep
  integration with CloudFormation templates for evaluation but is a general tool
  that equally works for any JSON- and YAML- data.

Usage: cfn-guard [COMMAND]

Commands:
  parse-tree   Prints out the parse tree for the rules defined in the file.
  test         Built in unit testing capability to validate a Guard rules file against
               unit tests specified in YAML format to determine each individual rule's success
               or failure testing.

  validate     Evaluates rules against the data files to determine success or failure.
               You can point rules flag to a rules directory and point data flag to a data directory.
               When pointed to a directory it will read all rules in the directory file and evaluate
               them against the data files found in the directory. The command can also point to a
               single file and it would work as well.
               Note - When pointing the command to a directory, the directory may not contain a mix of
               rules and data files. The directory being pointed to must contain only data files,
               or rules files.

  rulegen      Autogenerate rules from an existing JSON- or YAML- formatted data. (Currently works with only CloudFormation templates)
  completions  Generate auto-completions for all the sub-commands in shell.
  help         Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version

How does Guard CLI work?

The two common Guard CLI commands are validate and test.

Validate

Validate command is used when you need to assess the compliance or security posture as defined by a set of policy files against incoming JSON/YAML data. Common data payloads used are CloudFormation Templates, CloudFormation ChangeSets, Kubernetes Pod policies, Terraform Plan/Configuration in JSON format, and more. Here is an example run of the validate command for assessing Kubernetes Pod Container configurations

  1. Save the sample policy rules file below as k8s-pod-containers-limits.guard:
#
# Kubernetes container based limit checks
#

#
# These set of rules primarily apply to the version 1 of the API spec (including v1Beta) and
# the kind of document is a 'Pod'
#
rule version_and_kind_match
{
    apiVersion == /v1/
    kind == 'Pod'
}

#
# For the 'Pod' document ensure that containers have resource limits set
# for memory
#
rule ensure_container_has_memory_limits when version_and_kind_match
{
    spec.containers[*]
    {
       resources.limits
       {
            #
            # Ensure that memory attribute is set
            #
            memory exists
            <<
                Id: K8S_REC_22
                Description: Memory limit must be set for the container
            >>
        }
   }

}

#
# For the 'Pod' document ensure that containers have resource limits set
# for cpu
#
rule ensure_container_has_cpu_limits when version_and_kind_match
{
    spec.containers[*]
    {
       resources.limits
       {
            #
            # Ensure that cpu attribute is set
            #
            cpu exists
            <<
                Id: K8S_REC_24
                Description: Cpu limit must be set for the container
            >>
       }
   }
}
  1. Paste the command below and hit enter
cfn-guard validate -r k8s-pod-containers-limits.guard
  1. Cut-n-paste the sample configuration below for k8s pods on STDIN and then hit CTRL+D:
apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
    - name: app
      image: 'images.my-company.example/app:v4'
      resources:
        requests:
          memory: 64Mi
          cpu: 0.25
        limits:
          memory: 128Mi
    - name: log-aggregator
      image: 'images.my-company.example/log-aggregator:v6'
      resources:
        requests:
          memory: 64Mi
          cpu: 0.25
        limits:
          memory: 128Mi
          cpu: 0.75

Execution of validate

The container app does not contain CPU limits specified, which fails the overall evaluation as shown in the screenshot.

Test

Test command is used during the development of guard policy rules files. Test provides a simple integrated unit-test frameworks that allows authors to individually test each policy file for different types of inputs. Unit testing helps authors gain confidence that the rule does indeed conform to expectations. It can also be used as regression tests for rules. Here is example run for test command

  1. Save the sample policy rules file below as api_gateway_private_access.guard:
#
# Select from Resources section of the template all ApiGateway resources
# present in the template.
#
let api_gws = Resources.*[ Type == 'AWS::ApiGateway::RestApi' ]

#
# Rule intent
# a) All ApiGateway instances deployed must be private
# b) All ApiGateway instances must have atleast one IAM policy condition key to allow access from a VPC
#
# Expectations:
# 1) SKIP when there are not API Gateway instances in the template
# 2) PASS when ALL ApiGateway instances MUST be "PRIVATE" and
#              ALL ApiGateway instances MUST have one IAM Condition key with aws:sourceVpc or aws:SourceVpc
# 3) FAIL otherwise
#
#

rule check_rest_api_is_private when %api_gws !empty {
  %api_gws {
    Properties.EndpointConfiguration.Types[*] == "PRIVATE"
  }
}

rule check_rest_api_has_vpc_access when check_rest_api_is_private {
  %api_gws {
    Properties {
      #
      # ALL ApiGateways must have atleast one IAM statement that has Condition keys with
      #     aws:sourceVpc
      #
      some Policy.Statement[*] {
        Condition.*[ keys == /aws:[sS]ource(Vpc|VPC|Vpce|VPCE)/ ] !empty
      }
    }
  }
}
  1. Save the sample test file below as api_gateway_private_access_tests.yaml:
---
- input: {}
  expectations:
    rules:
      check_rest_api_is_private: SKIP
      check_rest_api_has_vpc_access: SKIP
- input:
     Resources: {}
  expectations:
    rules:
      check_rest_api_is_private: SKIP
      check_rest_api_has_vpc_access: SKIP
- input:
    Resources:
      apiGw:
        Type: AWS::ApiGateway::RestApi
  expectations:
    rules:
      check_rest_api_is_private: FAIL
      check_rest_api_has_vpc_access: SKIP
- input:
    Resources:
      apiGw:
        Type: AWS::ApiGateway::RestApi
        Properties:
          EndpointConfiguration:
            Types: PRIVATE
  expectations:
    rules:
      check_rest_api_is_private: PASS
      check_rest_api_has_vpc_access: FAIL
- input:
    Resources:
      apiGw:
        Type: AWS::ApiGateway::RestApi
        Properties:
          EndpointConfiguration:
            Types: [PRIVATE, REGIONAL]
  expectations:
    rules:
      check_rest_api_is_private: FAIL
      check_rest_api_has_vpc_access: SKIP
- input:
    Resources:
      apiGw:
        Type: AWS::ApiGateway::RestApi
        Properties:
          EndpointConfiguration:
            Types: PRIVATE
          Policy:
            Statement:
              - Action: Allow
                Resource: '*'
                Condition:
                  StringLike:
                    'aws:sourceVPC': vpc-12345678
  expectations:
    rules:
      check_rest_api_is_private: PASS
      check_rest_api_has_vpc_access: PASS
  1. Run the test command
cfn-guard test -r api_gateway_private_access.guard -t api_gateway_private_access_tests.yaml

Execution of test

Read Guard: Unit Testing for more information on unit testing. To know about other commands read the Readme in the guard directory.

Rule authoring references

As a starting point for writing Guard rules for yourself or your organisation we recommend following this official guide

Quick links:

Writing AWS CloudFormation Guard rules

  1. Clauses
  2. Using queries in clauses
  3. Using operators in clauses
  4. Using custom messages in clauses
  5. Combining clauses
  6. Using blocks with Guard rules
  7. Defining queries and filtering
  8. Assigning and referencing variables in AWS CloudFormation Guard rules
  9. Composing named-rule blocks in AWS CloudFormation Guard
  10. Writing clauses to perform context-aware evaluations

Built-in functions & stateful rules

Guard 3.0 introduces support for functions, allowing for stateful rules that can run on a value that's evaluated based on some properties extracted out of a data template.

Sample template

Imagine we have a property in our template which consists of a list called as Collection and we need to ensure it has at least 3 items in it.

Resources:
  newServer:
    Type: AWS::New::Service
    Collection:
      - a
      - b

Sample rule

We can write a rule to check this condition as follows:

let server = Resources.*[ Type == 'AWS::New::Service' ]
rule COUNT_CHECK when %server !empty {
    let collection = %server.Collection.*
    let count_of_items = count(%collection)
    %count_of_items >= 3
    <<
      Violation: Collection should contain at least 3 items
    >>
}

Expected outcome is that rule fails showing us the violation message since our template is non-compliant.

For detailed documentation regarding all supported functions, please follow this link. For limitations of functions usage, please read this note.

AWS Rule Registry

As a reference for Guard rules and rule-sets that contain (on a best-effort basis) the compliance policies that adhere to the industry best practices around usages across AWS resources, we have recently launched AWS Guard Rules Registry.

Use Guard as a Docker Image

Guard is also published as an ECR image in ECR public gallery and can be used as an image in a docker container.

Prerequisites

  1. Install docker. Follow this guide.
  2. Have a directory ready on the host you are downloading the docker image to that contains data templates and Guard rules you are planning to use, we may mount this directory and use the files as input to cfn-guard. We'll refer this directory to be called guard-files in the rest of this guide

Usage Guide

To use the binary, we should pull the latest docker image, we may do so using the following command:

docker pull public.ecr.aws/aws-cloudformation/cloudformation-guard:latest

Now go ahead and run the docker image, using the files from directory we have our templates and rules file in, using:

docker run \
  --mount src=/path/to/guard-files,target=/container/guard-files,type=bind \
  -it public.ecr.aws/aws-cloudformation/cloudformation-guard:latest \
  ./cfn-guard validate -d /container/guard-files/template.yml -r /container/guard-files/rule.guard

We should see the evaluation result emitted out on the console.

Tagging convention

  • We use the tag latest for the most recent docker image that gets published in sync with main branch of the cloudformation-guard GitHub repository.
  • We use the convention <branch_name>.<github_shorthand_commit_hash> for tags of historical docker images

Use Guard as a CI tool

Guard is great for CI checks with the Junit output format, making the process of validating or testing your templates seamless and simple. Check out the examples below.

GitHub Actions

Junit

GitHub Actions

Get the template here!

SARIF

GitHub Actions

Get the template here!

CircleCI

Junit

CircleCI

Get the template here!

Contribute using the DevContainer in VSCode

Setup

NOTE: The devcontainer supports zsh and bash with theming and aliases. You can choose which shell you like in the terminal options in your VSCode session.

  1. Follow the prerequisite instructions here.
  2. In VSCode following the install, open the command palette.
  3. Search "Dev Containers: Build Container"
  4. Once done, open the project inside the container.

Aliases

  • gval: cargo run --bin cfn-guard validate
  • gtest: cargo run --bin cfn-guard test
  • gparse: cargo run --bin cfn-guard parse-tree
  • cb: cargo build
  • ct: cargo nextest run
  • cn: cargo +nightly

License

This project is licensed under the Apache-2.0 License.

cloudformation-guard's People

Contributors

0xjjoyy avatar 4naesthetic avatar aishwarya4400 avatar akshayrane avatar amazon-auto avatar beaknit avatar dannyvassallo avatar dchakrav-github avatar dependabot[bot] avatar gandhek avatar gliptak avatar grusy avatar iann0036 avatar jbleduigou avatar john-reyes-eroad avatar joshfried-aws avatar matthewdfuller avatar nathanataws avatar omkhegde avatar patmyron avatar priyap286 avatar rachfop avatar sejimhp avatar sepehrdaddev avatar shreyasdamle avatar smaly-amazon avatar tylersouthwick avatar umireon avatar volovikariel avatar wkexiang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cloudformation-guard's Issues

Add the ability to do conditional checks based on properties

Customers may need to check things conditionally, based on the values of other properties.

Here's a basic form that could suffice:

<AWS RESOURCE TYPE> IF <address> ==|=!|IN|NOT_IN THEN <address> ==|=!|IN|NOT_IN <VALUE> ... AND|OR ...

Where address is a dotted notation that starts from the root of the template (or whatever form the structured data is in) and can be absolute:

Template.Resources.<RESOURCE NAME>|*.Properties.<PROP NAME>.<PROP NAME>|*... == <VALUE>

or relative to the root of the type under inspection by prefacing the address with a ".":

.Properties.<PROP NAME>.<PROP NAME>|*... == <VALUE>

Looking for help: S3 Bucket Policy

Hi,

Looking for some help.
Trying to setup cfn-guard to parse S3 BucketPolicy to disallow public write. Is cfn-guard able to parse the S3 BucketPolicy, looking for s3:pubobject or s3:* on principal: "*" ?

I tried playing with regex and json in the ruleset file, but I can't get it to work. Any help is appreciated.

Thank you.

JSON/YAML discrepancy when parsing functions

Hi,

I have a policy to check for specific tags like:

AWS::ElasticLoadBalancingV2::LoadBalancer Tags.0 == {"Key":"EnvironmentType","Value":"EnvironmentType"}
AWS::ElasticLoadBalancingV2::LoadBalancer Tags.1 == {"Key":"OwnerContact","Value":"OwnerContact"}

They work fine on YAMLs in format:

        - Key: EnvironmentType
          Value: !Ref EnvironmentType
        - Key: OwnerContact
          Value: !Ref OwnerContact

However they fail on JSON:

          { "Key" : "EnvironmentType", "Value" : { "Ref" : "EnvironmentType" } },
          { "Key" : "OwnerContact", "Value" : { "Ref" : "OwnerContact" } },

with the error:

"[Sg] failed because [Tags.0] is [{\"Key\":\"EnvironmentType\",\"Value\":{\"Ref\":\"EnvironmentType\"}}] and the permitted value is [{\"Key\":\"EnvironmentType\",\"Value\":\"EnvironmentType\"}]"
"[Sg] failed because [Tags.1] is [{\"Key\":\"OwnerContact\",\"Value\":{\"Ref\":\"OwnerContact\"}}] and the permitted value is [{\"Key\":\"OwnerContact\",\"Value\":\"OwnerContact\"}]"

What would be the proper way of matching both formats?

//Edit

Using IN [{"Key":"OwnerContact","Value":"OwnerContact"},{"Key":"OwnerContact","Value":{"Ref":"OwnerContact"}}] doesnt work either with the error:

"[Alb] failed because [{\"Key\":\"OwnerContact\",\"Value\":\"OwnerContact\"}] is not in [{\"Key\":\"OwnerContact\",\"Value\":\"OwnerContact\"},{\"Key\":\"OwnerContact\",\"Value\":{\"Ref\":\"OwnerContact\"}}] for [Tags.1]"

However I believe this should be working as the matching object is in the list. (I tried with [[{...}],[{...}]] same result)

//Edit2:

== /^\{"Key":"OwnerContact","Value":"OwnerContact"\}$|^\{"Key":"OwnerContact","Value":\{"Ref":"OwnerContact"\}\}$/
This seems to work, but I highly doubt this is how it's intended to work

[Enhancement] Recursive scanning of templates in a folder

I am working on an usecase where I need to scan multiple cft files (yaml or json) against the rules.
But after reading the documentation on the cfn-guard I came to know that its an 1:1 validation, is there any way that i can do it recursively?

[BUG]

Describe the bug
A clear and concise description of what the bug is.

To Reproduce
Please supply:

  1. An example rule set and template that results in the error
  2. The commands you used to invoke the tool
  3. The output of a -vvv log level if it's not related to cfn-guard-lambda, or the relevant CloudWatch log messages if it is related to the lambda

NOTE: Please be sure that the templates, rule sets and logs you provide as part of your bug report do not contain any sensitive information.

Expected behavior
A clear and concise description of what you expected to happen.

Screenshots
If applicable, add screenshots to help explain your problem.

Operating System:
[eg, MacOS, Windows, Ubuntu, etc]

OS Version
[eg Catalina, 10, 18.04, etc]

Additional context
Add any other context about the problem here.

[Enhancement] Support different output formats (e.g. JSON)

Is your feature request related to a problem? Please describe.
Currently, it is hard to parse the output of cloudformation-guard using other scripts since the output is in text only, having different output formats would help ingest that output to different tools.

Describe the solution you'd like
It will be nice to have an option like cfn-lint/cfn_nag/cfripper/tflint/tfsec/terrascan/checkov that allows the user to specify different output options

Describe alternatives you've considered
Current solution is to parse the text output which is not ideal

Support numeric comparison operators

I'd like to test "less than or equals to" for numeric values. This allows us to test that an ECS task is compliant to resource budgets.

e.g.

let allowed_max_memory = 512

AWS::ECS::TaskDefinition Memory <= %allowed_max_memory

license conflicts with license header

Describe the bug
The repo has an apache 2.0 license, but the file headers has content that is not standard apache pre-amble and its unclear if its even related to opensource vs some contractual agreement executed on a party to party basis.

"This AWS Content is provided subject to the terms of the AWS Customer Agreement available at http://aws.amazon.com/agreement or other written agreement between Customer and either Amazon Web Services, Inc. or Amazon Web Services EMEA SARL or both."

[Enhancement] cfn-guard-rulegen for AWS::IAM::Role without a name is missing a rule

Is your feature request related to a problem? Please describe.
I have existing templates were the AWS::IAM::Role Name attribute is NOT present, this is because we do not want roles to be named inside the template. We prefer that CFN generate the name of the role automatically based on the stack AND making it unique, etc.

Describe the solution you'd like
I would like to see cfn-guard-rulegen detect that there is no name attribute present and make a rule to NOT allow the name to be defined.

AWS::IAM::Role Name == ''

or some such. Sorry, I'm a bit of a newbie using the tool, and this looked like the only way to express the 'NOT present'

Describe alternatives you've considered
Obviously hand coding the rule AFTER cfn-guard-rulegen is executed. However, that is a bit of a pain on 100+ CFN templates.

Add inline "AND" form

Customers may need to have explicit in-line "AND's" (as opposed to the implicit AND's that are already part of a ruleset) for clarity:

AWS::EC2::SecurityGroup SecurityGroupIngress.*.ToPort != 3306 |AND| SecurityGroupIngress.*.CidrIp != 0.0.0.0/0

[Enhancement] Add rules to block entire Resource/Service.

Is your feature request related to a problem? Please describe.
In our team we manage our AWS implementation and we don't want our developers to deploy certain services/resources. We want to use cfn-guard for this, to give quick feedback to our devs. However, we don't have the possibility to block an entire resource in Cloudformation (I'll use EC2 as an example) through cfn-guard.

Describe the solution you'd like
I want to be able to have a rule like AWS::EC2 == false.

Describe alternatives you've considered
As an alternative, we've worked with rules like AWS:: EC2::Instance InstanceType == false. This works, but does not give the proper feedback, and is more of a workaround.

Add a Comment Form

Customers may want to add documentation to their rule sets in the form of comments within the rule set itself.

Proposed form:

# This is a comment

[Enhancement] Publish to crates.io via travisci

Uploading to crates.io from desktop is error prone. We need a step in our deployment script on travis to do this for each package automatically. Since there are multiple crates in this repo, we need something a bit more sophisticated than just running from the root. Additionally, it would nice to keep a common versioning for all the tools as cfn-guard depends on rulegen and the lambdas depend on their respective tools as well.

[BUG] Cannot properly check a nested field in strict mode

Describe the bug

When running in strict mode, the presence of a nested field is checked only if there is more than one resource in the template.

To Reproduce

I'm running cfn-guard 1.0.0:

$cfn-guard --version
CloudFormation Guard 1.0.0

I have a rule file with this unique rule:

AWS::S3::Bucket BucketEncryption.ServerSideEncryptionConfiguration.*.ServerSideEncryptionByDefault.SSEAlgorithm == AES256

And I have this template:

---
AWSTemplateFormatVersion: 2010-09-09
Resources:
  S3Bucket1:
    Type: 'AWS::S3::Bucket'
    Properties:
      AccessControl: Private

  S3Bucket2:
    Type: 'AWS::S3::Bucket'
    Properties:
      AccessControl: Private
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256

If I run cfn-guard in strict mode, it works as intended, with S3Bucket1 failing the check:

$ cfn-guard check -s --rule_set ../templates_cloudformation/rulesets/general.rules --template ../templates_cloudformation/template-s3a.yml -v
2020-10-15 10:37:21,720 INFO  [cfn_guard] CloudFormation Guard is checking the template '../templates_cloudformation/template-s3a.yml' against the rules in '../templates_cloudformation/rulesets/general.rules'
2020-10-15 10:37:21,722 INFO  [cfn_guard] Loading CloudFormation Template and Rule Set
2020-10-15 10:37:21,723 INFO  [cfn_guard] Parsing rule set
2020-10-15 10:37:21,729 INFO  [cfn_guard] Checking resources
2020-10-15 10:37:21,729 INFO  [cfn_guard] Applying rule 'CompoundRule(
    CompoundRule {
        compound_type: OR,
        raw_rule: "AWS::S3::Bucket BucketEncryption.ServerSideEncryptionConfiguration.*.ServerSideEncryptionByDefault.SSEAlgorithm == AES256",
        rule_list: [
            SimpleRule(
                Rule {
                    resource_type: "AWS::S3::Bucket",
                    field: "BucketEncryption.ServerSideEncryptionConfiguration.0.ServerSideEncryptionByDefault.SSEAlgorithm",
                    operation: Require,
                    value: "AES256",
                    rule_vtype: Value,
                    custom_msg: None,
                },
            ),
        ],
    },
)'
2020-10-15 10:37:21,742 INFO  [cfn_guard] Checking [S3Bucket1] which is of type "AWS::S3::Bucket"
2020-10-15 10:37:21,743 INFO  [cfn_guard] Checking [S3Bucket2] which is of type "AWS::S3::Bucket"
2020-10-15 10:37:21,743 INFO  [cfn_guard] Result: PASS
[S3Bucket1] failed because it does not contain the required property of [BucketEncryption.ServerSideEncryptionConfiguration.0.ServerSideEncryptionByDefault.SSEAlgorithm]
Number of failures: 1

Now, let's I remove S3Bucket2 from the template:

---
AWSTemplateFormatVersion: 2010-09-09
Resources:
  S3Bucket1:
    Type: 'AWS::S3::Bucket'
    Properties:
      AccessControl: Private

  # S3Bucket2:
  #   Type: 'AWS::S3::Bucket'
  #   Properties:
  #     AccessControl: Private
  #     BucketEncryption:
  #       ServerSideEncryptionConfiguration:
  #         - ServerSideEncryptionByDefault:
  #             SSEAlgorithm: AES256

It can be deleted or commented out, it doesn't matter.

If I re-run the same cfn-guard command, it does not check S3Bucket1 at all:

$ cfn-guard check -s --rule_set ../templates_cloudformation/rulesets/general.rules --template ../templates_cloudformation/template-s3a.yml -v
2020-10-15 10:38:45,957 INFO  [cfn_guard] CloudFormation Guard is checking the template '../templates_cloudformation/template-s3a.yml' against the rules in '../templates_cloudformation/rulesets/general.rules'
2020-10-15 10:38:45,959 INFO  [cfn_guard] Loading CloudFormation Template and Rule Set
2020-10-15 10:38:45,960 INFO  [cfn_guard] Parsing rule set
2020-10-15 10:38:45,965 INFO  [cfn_guard] Checking resources
2020-10-15 10:38:45,966 INFO  [cfn_guard] Applying rule 'CompoundRule(
    CompoundRule {
        compound_type: OR,
        raw_rule: "AWS::S3::Bucket BucketEncryption.ServerSideEncryptionConfiguration.*.ServerSideEncryptionByDefault.SSEAlgorithm == AES256",
        rule_list: [],
    },
)'
2020-10-15 10:38:45,971 INFO  [cfn_guard] All CloudFormation resources passed

I don't think this behavior is normal.

Create new forms for the inverse of the current wildcards

In other words, where

AWS::IAM::Role AssumeRolePolicyDocument.Statement.*.Principal.Service.* == lambda.amazonaws.com

means "At least one item that matches those wildcards should match that value“

and

AWS::IAM::Role AssumeRolePolicyDocument.Statement.*.Principal.Service.* != lambda.amazonaws.com

means "None of the items that those wildcards match should match that value“

Customers have some use cases where a wildcard syntax that’s equivalent to “All wildcards must match this value” would be very useful.

For example: if customers want the ability to ensure only whitelisted services are included in the trust policy. The only allowed values are specified in a list.

AWS::IAM::Role AssumeRolePolicyDocument.Statement.*.Principal.Service.* ONLY_IN ["lambda.amazonaws.com","ec2.amazonaws.com"]

means “All items must match a value in the list

And if they really want to make sure every wildcarded item only matches an exact value, we could add something like:

AWS::IAM::Role AssumeRolePolicyDocument.Statement.*.Principal.Service.* ONLY lambda.amazon

Use serde::rename to fix `strict_check` casing in cfn-guard-lambda

From

#[derive(Deserialize, Debug)]
struct CustomEvent {
    #[serde(rename = "template")]
    template: String,
    #[serde(rename = "ruleSet")]
    rule_set: String,
    strict_checks: bool,
}
```
To

```
#[derive(Deserialize, Debug)]
struct CustomEvent {
    #[serde(rename = "template")]
    template: String,
    #[serde(rename = "ruleSet")]
    rule_set: String,
    #[serde(rename = "strictChecks")]
    strict_checks: bool,
}
```

NOTE:  This requires updates to the Makefile and marks a breaking change in the lambda's API.

[BUG] Cfn-guard incorrectly passes the template that fails the rules file

Describe the bug
If I write a # comment in the same line as an existing rule, cfn-guard doesn't process that rule.

To Reproduce
An example rule set and template that results in the error
ebs-volume-template.zip
The commands you used to invoke the tool: cfn-guard -t ebs-volume-template.json -r ebs-volume-template.ruleset

Expected behavior
Expect cfn-guard to either process that rule or show an error that a comment and a rule cannot be on the same line.

Operating System:
Windows 10

Check existence and values of specific tag keys

Hi,

Is there away to enforce specific tags to be set, ideally with specific values?

AWS::EC2::EIP Tags == [{"Key":"Foo","Value":"abc"},{"Key":"Bar","Value":"def"}]

would fail if the resource has additional Tags

[BUG] Example payload in makefile failing to invoke cfn-guard-lambda

Describe the bug
When I execute any testing payload that comes with cfn-guard-lambda I receive a "An error occurred (InvalidRequestContentException) when calling the Invoke operation: Could not parse request body into json: Invalid UTF-8 start byte 0xab" error.

To Reproduce
Please supply:

  1. make install

The step will fail at "/Library/Developer/CommandLineTools/usr/bin/make fail" step. See error below:

➜  cfn-guard-lambda git:(master) ✗ make install
This is a Darwin machine...
env PKG_CONFIG_ALLOW_CROSS=1 cargo build --release --target x86_64-unknown-linux-musl
    Finished release [optimized] target(s) in 0.33s
cp target/x86_64-unknown-linux-musl/release/cfn-guard-lambda ./bootstrap
chmod +x bootstrap
zip lambda.zip bootstrap
  adding: bootstrap (deflated 67%)
aws lambda create-function --function-name cfn-guard-lambda --handler doesnt.matter --zip-file fileb://./lambda.zip --runtime provided --role arn:aws:iam::077927938888:role/lambda-cfn-guard  --environment Variables={RUST_BACKTRACE=1}
{
    "FunctionName": "cfn-guard-lambda",
    "FunctionArn": "arn:aws:lambda:us-west-2:077927938888:function:cfn-guard-lambda",
    "Runtime": "provided",
    "Role": "arn:aws:iam::077927938888:role/lambda-cfn-guard",
    "Handler": "doesnt.matter",
    "CodeSize": 2628443,
    "Description": "",
    "Timeout": 3,
    "MemorySize": 128,
    "LastModified": "2020-07-21T20:03:58.926+0000",
    "CodeSha256": "XFx2fanYnYCxLcYmBiRqeOYHPliM24MXo5F60WkrKSM=",
    "Version": "$LATEST",
    "Environment": {
        "Variables": {
            "RUST_BACKTRACE": "1"
        }
    },
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "74b3c713-5e6c-4c08-8c1b-58b24cb67a65",
    "State": "Active",
    "LastUpdateStatus": "Successful"
}
/Library/Developer/CommandLineTools/usr/bin/make fail
aws lambda invoke --function-name cfn-guard-lambda --payload '{ "template": "{\n    \"Resources\": {\n        \"NewVolume\" : {\n            \"Type\" : \"AWS::EC2::Volume\",\n            \"Properties\" : {\n                \"Size\" : 100,\n                \"Encrypted\": true,\n                \"AvailabilityZone\" : \"us-east-1b\"\n            }\n        },\n        \"NewVolume2\" : {\n            \"Type\" : \"AWS::EC2::Volume\",\n            \"Properties\" : {\n                \"Size\" : 99,\n                \"Encrypted\": true,\n                \"AvailabilityZone\" : \"us-east-1b\"\n            }\n        } }\n}", "ruleSet": "let require_encryption = true\nlet disallowed_azs = [us-east-1a,us-east-1b,us-east-1c]\n\nAWS::EC2::Volume AvailabilityZone NOT_IN %disallowed_azs\nAWS::EC2::Volume Encrypted != %require_encryption\nAWS::EC2::Volume Size == 101 |OR| AWS::EC2::Volume Size == 99\nAWS::IAM::Role AssumeRolePolicyDocument.Version == 2012-10-18\nAWS::EC2::Volume Lorem == true\nAWS::EC2::Volume Encrypted == %ipsum\nAWS::EC2::Volume AvailabilityZone != /us-east-.*/", "strict_checks": true}' output.json

An error occurred (InvalidRequestContentException) when calling the Invoke operation: Could not parse request body into json: Invalid UTF-8 start byte 0xab
 at [Source: (byte[])"�驕�^�����z��{h�鞝<�x�-��[�zs뢗��'��(�{]4�Iܯ*my�k������i���ܙ�w��欷V�s^�Z%�g��<�x�-��[�zs뢗��'��(�{�gw+ʛ^v��zp/j)Zn)b�&h��y�-չ瞻�y'v+�Z0yֳ�pH@�V�n��'r����"; line: 1, column: 6]�Y!�Z%�g����i���ܙ�w�92
make[1]: *** [fail] Error 254
make: *** [install] Error 2

Expected behavior
Install is finished with success.

Operating System:
MacOS

OS Version
10.13.6

More context

I can verify the lambda works, I tested with the following snippet:

Resources:
  lambdaFunction:
    Type: "AWS::Lambda::Function"
    Properties:
      Code:
        ZipFile: |
          def handler(event,context):
            return {
              'body': 'Hello there {0}'.format(event['requestContext']['identity']['sourceIp']),
              'headers': {
                'Content-Type': 'text/plain'
              },
              'statusCode': 200
            }
      Description: "Governance Lambda Demo"
      FunctionName: !Ref "lambdaFunctionName"
      Handler: "index.handler"
      MemorySize: 128
      Role: !GetAtt "lambdaIAMRole.Arn"
      Runtime: "python2.7"
      Timeout: 10

against the rule: "AWS::Lambda::Function Runtime == 'python2.7'" and it passes. See request/response:

{"apiRequest": {"requestId": "8e621a12-a8ca-4a4b-a2cb-ae2d7d129131", "requestName": "InvokeRequest"}}CfnGuardEngine response: {"message":[],"exit_status":"PASS"}

I'm currently running a687d7a commit.

[BUG] Installation Failing on CodeBuild

Describe the bug

While trying to use cfn-guard with CodeBuild, I was facing issues running 'make' command. I had tried to follow this blog post by AWS.

https://aws.amazon.com/blogs/security/integrating-aws-cloudformation-security-tests-with-aws-security-hub-and-aws-codebuild-reports/

To Reproduce

To reproduce the same, a CodeBuild Project is required with build image aws/codebuild/amazonlinux2-x86_64-standard:3.0 with compute 3 GB memory, 2 vCPUs.

Buildspec Install Phase :

install:
commands:
- export date=date +%Y-%m-%dT%H:%M:%S.%NZ
- echo Installing cloudformation-guard
- yum install glibc-devel gcc git make jq -y
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
- git clone https://github.com/aws-cloudformation/cloudformation-guard.git --depth 1 /usr/local/cloudformation-guard
- source $HOME/.cargo/env
- (cd /usr/local/cloudformation-guard && make )
- export PATH=$PATH:/usr/local/cloudformation-guard/bin/

The command in bold is failing.

ERROR CodeBuild LOG:

[Container] 2020/10/05 09:30:36 Running command (cd /usr/local/cloudformation-guard && make )
69 | cd cfn-guard; cargo build --release; cp target/release/cfn-guard ../bin
70 | Updating crates.io index
71 | Downloading crates ...
72 | Downloaded jni v0.14.0
73 | Downloaded combine v3.8.1
74 | Downloaded vec_map v0.8.2
75 | Downloaded byteorder v1.3.4
76 | Downloaded dtoa v0.4.6
77 | Downloaded simple_logger v1.9.0
78 | Downloaded unreachable v1.0.0
79 | Downloaded cesu8 v1.1.0
80 | Downloaded same-file v1.0.6
81 | Downloaded textwrap v0.11.0
82 | Downloaded yaml-rust v0.4.4
83 | Downloaded itoa v0.4.6
84 | Downloaded quote v1.0.7
85 | Downloaded object v0.20.0
86 | Downloaded serde_json v1.0.57
87 | Downloaded aho-corasick v0.7.13
88 | Downloaded syn v1.0.41
89 | Downloaded regex-syntax v0.6.18
90 | Downloaded clap v2.33.3
91 | Downloaded libc v0.2.77
92 | Downloaded gimli v0.22.0
93 | Downloaded regex v1.3.9
94 | Downloaded serde v1.0.116
95 | Downloaded chrono v0.4.15
96 | Downloaded proc-macro2 v1.0.21
97 | Downloaded memchr v2.3.3
98 | Downloaded linked-hash-map v0.5.3
99 | Downloaded lazy_static v1.4.0
100 | Downloaded error-chain v0.12.4
101 | Downloaded colored v1.9.3
102 | Downloaded cfg-if v0.1.10
103 | Downloaded bitflags v1.2.1
104 | Downloaded ansi_term v0.11.0
105 | Downloaded adler v0.2.3
106 | Downloaded addr2line v0.13.0
107 | Downloaded walkdir v2.3.1
108 | Downloaded void v1.0.2
109 | Downloaded version_check v0.9.2
110 | Downloaded unicode-width v0.1.8
111 | Downloaded time v0.1.44
112 | Downloaded strsim v0.8.0
113 | Downloaded serde_derive v1.0.116
114 | Downloaded ryu v1.0.5
115 | Downloaded rustc-demangle v0.1.16
116 | Downloaded num-traits v0.2.12
117 | Downloaded backtrace v0.3.50
118 | Downloaded ascii v0.9.3
119 | Downloaded jni-sys v0.3.0
120 | Downloaded num-integer v0.1.43
121 | Downloaded miniz_oxide v0.4.2
122 | Downloaded log v0.4.11
123 | Downloaded either v1.6.1
124 | Downloaded atty v0.2.14
125 | Downloaded unicode-xid v0.2.1
126 | Downloaded thread_local v1.0.1
127 | Downloaded serde_yaml v0.8.13
128 | Downloaded autocfg v1.0.1
129 | Compiling autocfg v1.0.1
130 | Compiling libc v0.2.77
131 | Compiling proc-macro2 v1.0.21
132 | Compiling unicode-xid v0.2.1
133 | Compiling syn v1.0.41
134 | Compiling cfg-if v0.1.10
135 | Compiling memchr v2.3.3
136 | Compiling serde_derive v1.0.116
137 | Compiling serde v1.0.116
138 | Compiling log v0.4.11
139 | Compiling lazy_static v1.4.0
140 | Compiling adler v0.2.3
141 | Compiling ryu v1.0.5
142 | Compiling byteorder v1.3.4
143 | Compiling gimli v0.22.0
144 | Compiling version_check v0.9.2
145 | Compiling bitflags v1.2.1
146 | Compiling same-file v1.0.6
147 | Compiling serde_json v1.0.57
148 | Compiling linked-hash-map v0.5.3
149 | Compiling object v0.20.0
150 | Compiling void v1.0.2
151 | Compiling unicode-width v0.1.8
152 | Compiling rustc-demangle v0.1.16
153 | Compiling strsim v0.8.0
154 | Compiling itoa v0.4.6
155 | Compiling ascii v0.9.3
156 | Compiling either v1.6.1
157 | Compiling vec_map v0.8.2
158 | Compiling ansi_term v0.11.0
159 | Compiling dtoa v0.4.6
160 | Compiling regex-syntax v0.6.18
161 | Compiling jni-sys v0.3.0
162 | Compiling cesu8 v1.1.0
163 | Compiling num-traits v0.2.12
164 | Compiling num-integer v0.1.43
165 | Compiling miniz_oxide v0.4.2
166 | Compiling thread_local v1.0.1
167 | Compiling error-chain v0.12.4
168 | Compiling addr2line v0.13.0
169 | Compiling walkdir v2.3.1
170 | Compiling yaml-rust v0.4.4
171 | Compiling unreachable v1.0.0
172 | Compiling textwrap v0.11.0
173 | Compiling jni v0.14.0
174 | Compiling atty v0.2.14
175 | Compiling time v0.1.44
176 | Compiling aho-corasick v0.7.13
177 | Compiling quote v1.0.7
178 | Compiling combine v3.8.1
179 | Compiling colored v1.9.3
180 | Compiling clap v2.33.3
181 | Compiling backtrace v0.3.50
182 | Compiling regex v1.3.9
183 | Compiling chrono v0.4.15
184 | Compiling simple_logger v1.9.0
185 | Compiling serde_yaml v0.8.13
186 | Compiling cfn-guard-rulegen v1.0.0 (/usr/local/cloudformation-guard/cfn-guard-rulegen)
187 | Compiling cfn-guard v1.0.0 (/usr/local/cloudformation-guard/cfn-guard)
188 | warning: `extern` fn uses type `str`, which is not FFI-safe
189 | --> cfn-guard/src/lib.rs:54:29
190 | \|
191 | 54 \|     template_file_contents: &str,
192 | \|                             ^^^^ not FFI-safe
193 | \|
194 | = note: `#[warn(improper_ctypes_definitions)]` on by default
195 | = help: consider using `*const u8` and a length instead
196 | = note: string slices have no C equivalent
197 |  
198 | warning: `extern` fn uses type `str`, which is not FFI-safe
199 | --> cfn-guard/src/lib.rs:55:26
200 | \|
201 | 55 \|     rules_file_contents: &str,
202 | \|                          ^^^^ not FFI-safe
203 | \|
204 | = help: consider using `*const u8` and a length instead
205 | = note: string slices have no C equivalent
206 |  
207 | warning: `extern` fn uses type `std::result::Result<(std::vec::Vec<std::string::String>, usize), std::string::String>`, which is not FFI-safe
208 | --> cfn-guard/src/lib.rs:57:6
209 | \|
210 | 57 \| ) -> Result<(Vec<String>, usize), String> {
211 | \|      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
212 | \|
213 | = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum
214 | = note: enum has no representation hint
215 |  
216 | warning: 3 warnings emitted
217 |  
218 | warning: use of deprecated item 'simple_logger::init_with_level': Please use the Builder pattern instead.
219 | --> cfn-guard/src/main.rs:78:9
220 | \|
221 | 78 \|         simple_logger::init_with_level(log_level).unwrap();
222 | \|         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
223 | \|
224 | = note: `#[warn(deprecated)]` on by default
225 |  
226 | warning: use of deprecated item 'simple_logger::init_with_level': Please use the Builder pattern instead.
227 | --> cfn-guard/src/main.rs:103:13
228 | \|
229 | 103 \|             simple_logger::init_with_level(log_level).unwrap();
230 | \|             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
231 |  
232 | warning: 2 warnings emitted
233 |  
234 | Finished release [optimized] target(s) in 2m 59s
235 | cp: cannot stat 'target/release/cfn-guard': No such file or directory
236 | make: *** [cfn-guard] Error 1
237 |  
238 | [Container] 2020/10/05 09:33:36 Command did not exit successfully (cd /usr/local/cloudformation-guard && make ) exit status 2

Expected behavior
It should have been installed.

Operating System:
Amazon Linux

[BUG] cfn-guard-rulegen output is not consistent

Describe the bug
When I tried to use the tool to generate the ruleset, it is not consistent between the different runs.

To Reproduce
Please supply:

  1. install cfn-guard-rulegen
  2. run against ebs-volume-template.json in the repo

first run

cfn-guard-rulegen ebs-volume-template.json
AWS::EC2::Volume AvailabilityZone == us-west-2b |OR| AWS::EC2::Volume AvailabilityZone == us-west-2c
AWS::EC2::Volume Encrypted == false
AWS::EC2::Volume Size == 99 |OR| AWS::EC2::Volume Size == 101

second run

cfn-guard-rulegen ebs-volume-template.json
AWS::EC2::Volume Encrypted == false
AWS::EC2::Volume AvailabilityZone == us-west-2c |OR| AWS::EC2::Volume AvailabilityZone == us-west-2b
AWS::EC2::Volume Size == 99 |OR| AWS::EC2::Volume Size == 101

NOTE: Please be sure that the templates, rule sets and logs you provide as part of your bug report do not contain any sensitive information.

Expected behavior
Should always produce the same ruleset in order.

[Enhancement] Run cfn-guard check without parameters

Is your feature request related to a problem? Please describe.
Currently you always have to specify both the ruleset and the templates. This makes scanning mulitple templates in one source code repository, or templates across different repositories difficult (see also #22 )

Describe the solution you'd like
Having a default filename for the rules and allowing a list of templates as input, would make it a lot easier to run. eg.

cfn-guard check template1.yaml template2.yaml
cfn-guard check template*.yaml
cfn-guard check --rule_set non-default-rules *.yaml

[BUG] installation instructions not working on cloud9

Describe the bug
I try to setup cloudformation guard on a fresh cloud9 IDE according to the current installation instructions for Linux from https://github.com/aws-cloudformation/cloudformation-guard
i.e.:

wget https://github.com/aws-cloudformation/cloudformation-guard/releases/download/VERSION/cfn-guard-linux-VERSION.tar.gz
tar -xvf cfn-guard-linux-1.0.0.tar.gz
cd ./cfn-guard-linux
./cfn-guard 

When I try to run ./cfn-guard I receive following error:

./cfn-guard: /lib64/libc.so.6: version `GLIBC_2.18' not found (required by ./cfn-guard)

Release Best Practices Rules

I think it is a lot of effort to write custom rules set and even you will miss some best practices or security settings.

I really looking forward that AWS releases a set of best practices and security-relevant rules sets, for example

  • Encryption of all data at rest
  • well-architected rules
  • RDS not started in a single AZ

For many of these best practices, AWS has already services in place which check these rules after or during deployment like Trusted Advisor and AWS Config. My hope AWS use their knowledge to transfer these into rules, so we as the customer can validate our template before we deploy Cloudformation stacks.

Simple regex seeking public CIDR not working

Using the public website to evaluate rust regexp (https://rustexp.lpil.uk/), I found i could use "CidrIp":\s*"0.0.0.0/0" to find public IP

But upon placing and then running tests against a security group, it rendered false positives

Test Case snippet of CFN
"Resources": {
"WebLoadBalancerSG": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"VpcId": {
"Ref": "VPCid"
},
"GroupDescription": "WS Load Balancer Security Group",
"Tags": [
{
"Key": "Environment",
"Value": {
"Ref": "Environment"
}
},
{
"Key": "Application",
"Value": {
"Ref": "Application"
}
},
{
"Key": "Vertical",
"Value": {
"Ref": "Vertical"
}
},
{
"Key": "Project",
"Value": {
"Ref": "Project"
}
}
],
"SecurityGroupIngress": [
{
"IpProtocol": "tcp",
"FromPort": "80",
"ToPort": "80",
"CidrIp": "10.0.0.1/8"
},
{
"IpProtocol": "tcp",
"FromPort": "80",
"ToPort": "80",
"CidrIp": "192.168.0.0/16"
},
{
"IpProtocol": "tcp",
"FromPort": "443",
"ToPort": "443",
"CidrIp": "0.0.0.1/0"
},
{
"IpProtocol": "tcp",
"FromPort": "443",
"ToPort": "443",
"CidrIp": "192.168.0.0/16"
},
{
"IpProtocol": "tcp",
"FromPort": "3389",
"ToPort": "3389",
"CidrIp": "10.0.0.1/8"
},
{
"IpProtocol": "tcp",
"FromPort": "3389",
"ToPort": "3389",
"CidrIp": "192.168.0.0/16"
}
]
}
}
},

Rule
AWS::EC2::SecurityGroup SecurityGroupIngress == /"CidrIp":"0.0.0.0/0"/ << Publicly accessible Security Group

Produces,

cfn-guard --template fake.json --rule_set tt.txt
[WebLoadBalancerSG] failed because [SecurityGroupIngress] is [[{"CidrIp":"10.0.0.1/8","FromPort":"80","IpProtocol":"tcp","ToPort":"80"},{"CidrIp":"192.168.0.0/16","FromPort":"80","IpProtocol":"tcp","ToPort":"80"},{"CidrIp":"0.0.0.1/0","FromPort":"443","IpProtocol":"tcp","ToPort":"443"},{"CidrIp":"192.168.0.0/16","FromPort":"443","IpProtocol":"tcp","ToPort":"443"},{"CidrIp":"10.0.0.1/8","FromPort":"3389","IpProtocol":"tcp","ToPort":"3389"},{"CidrIp":"192.168.0.0/16","FromPort":"3389","IpProtocol":"tcp","ToPort":"3389"}]] and Publicly accessible Security Group
Number of failures: 1

[Enhancement] Add a way to enforce encryption of EC2 instances in Block mappings

Maybe it is already supported and I just haven't configured it properly but is there a way to enforce encryption of volumes declared as in the block device mappings of an ec2 instance?

I tried this but assume it is because BlockDeviceMappings is a list object so it would need to iterate over every entry in list.

I didn't see an example of the syntax if it was supported. If it is not supported, can this be added to the roadmap?

AWS::EC2::Instance == BlockDeviceMappings.Ebs.Encrypted == true

I could always use the aws::ec2::volumeattachment resource after creating the volume but it would not fix the problem for the root volume

Pre-built binaries

It would be great if the project host pre-built binaries for major OS platforms through the GitHub release.

(and if it provides additional support for Homebrew installation for macOS, I'm going to dance with it ❤️)

[Enhancement] Regular expression inside json

Is your feature request related to a problem? Please describe.
I want to say thank you for this great tool. I tried to use regular expression in inside json, but it did not work

Describe the solution you'd like
I have the following policy:

{
    "Description": "Create Test Role",
    "Parameters": {
        "KmsDataKeyArn": {
            "Description": "Test",
            "Type": "String"
        }
    },
    "Resources": {
        "TestRole": {
            "Type": "AWS::IAM::Role",
            "Properties": {
                "AssumeRolePolicyDocument": {
                    "Version": "2012-10-17",
                    "Statement": [
                        {
                            "Sid": "TestPolicyDocument",
                            "Effect": "Allow",
                            "Principal": {
                                "AWS": [
                                    {
                                        "Fn::Sub": "${AWS::AccountId}"
                                    }
                                ],
                                "Service": [
                                    "iam.amazonaws.com",
                                    "ec2.amazonaws.com"
                                ]
                            },
                            "Action": "sts:AssumeRole"
                        }
                    ]
                },
                "Path": "/",
                "Policies": [
                    {
                        "PolicyName": "TestPolicy",
                        "PolicyDocument": {
                            "Version": "2012-10-17",
                            "Statement": [
                                {
                                    "Sid": "ACMPA",
                                    "Effect": "Allow",
                                    "Action": [
                                        "acm-pca:CreateCertificateAuthority",
                                        "acm-pca:IssueCertificate",
                                        "acm-pca:GetCertificate"
                                    ],
                                    "Resource": {
                                        "Fn::Sub": "arn:aws:acm-pca:${AWS::Region}:${AWS::AccountId}:certificate-authority/as"
                                    }
                                } 
                            ]
                        }
                    }
                ]
            }
        }
    }
}

I used the following rule and it worked as intended:
AWS::IAM::Role Policies.*.PolicyDocument.Statement.*.Resource == {"Fn::Sub": "arn:aws:acm-pca:${AWS::Region}:${AWS::AccountId}:certificate-authority/as"}

I am just wondering if I could do something with regular expression like
AWS::IAM::Role Policies.*.PolicyDocument.Statement.*.Resource == {/.+Sub/: "arn:aws:acm-pca:${AWS::Region}:${AWS::AccountId}:certificate-authority/as"}

or
AWS::IAM::Role Policies.*.PolicyDocument.Statement.*.Resource == {"/.+Sub/": "arn:aws:acm-pca:${AWS::Region}:${AWS::AccountId}:certificate-authority/as"}

to match the use cases below:

- "Resource": {
    "Fn::Sub": "arn:aws:acm-pca:${AWS::Region}:${AWS::AccountId}:certificate-authority/as"
}
- "Resource": {
    "!Sub": "arn:aws:acm-pca:${AWS::Region}:${AWS::AccountId}:certificate-authority/as"
}

Or a rule like the one below would not return any finding
AWS::IAM::Role Policies.*.PolicyDocument.Statement.*.Resource == {"Fn::Sub": "/.+\${AWS::Region}:\${AWS::AccountId}.+/"}

I am just wondering if I am using the right syntax or if I am missing something.

Thank you

[Enhancement] Maintain default sort order of result based on input ruleset

Is your feature request related to a problem? Please describe.
Currently we are alphabetically sorting result which makes it harder to parse the response if you want to pick first error message. It would be ideal if the response follows same order as the input ruleset.

Describe the solution you'd like
Make sorting optional by passing a sort variable
cfn-guard --rule_set --template -s
(-s is for sorted response).

Describe alternatives you've considered
An alternate to this could be to allow fail fast and return the first error only. While in most cases customers may want to see all the errors at once, there are cases where only first error is needed.

Additional context
None

[BUG]: Compilation is broken for Linux/Ubuntu with make

Describe the bug
After make compilation there is no cfn-guard binary that can be used and this breaks the CI. Before worked just fine.

   Compiling cfn-guard v1.0.0 (/codebuild/output/src853229193/src/cloudformation-guard/cfn-guard)
warning: use of deprecated item 'simple_logger::init_with_level': Please use the Builder pattern instead.
  --> cfn-guard-rulegen/src/main.rs:31:5
   |
31 |     simple_logger::init_with_level(log_level).unwrap();
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: `#[warn(deprecated)]` on by default

warning: use of deprecated item 'simple_logger::init_with_level': Please use the Builder pattern instead.
  --> cfn-guard/src/main.rs:78:9
   |
78 |         simple_logger::init_with_level(log_level).unwrap();
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: `#[warn(deprecated)]` on by default

warning: use of deprecated item 'simple_logger::init_with_level': Please use the Builder pattern instead.
   --> cfn-guard/src/main.rs:103:13
    |
103 |             simple_logger::init_with_level(log_level).unwrap();
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

   Compiling lambda_runtime v0.2.1
   Compiling cfn-guard-rulegen-lambda v1.0.0 (/codebuild/output/src853229193/src/cloudformation-guard/cfn-guard-rulegen-lambda)
warning: use of deprecated item 'simple_logger::init_with_level': Please use the Builder pattern instead.
  --> cfn-guard-rulegen-lambda/src/main.rs:18:5
   |
18 |     simple_logger::init_with_level(log::Level::Info)?;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: `#[warn(deprecated)]` on by default

   Compiling cfn-guard-lambda v1.0.0 (/codebuild/output/src853229193/src/cloudformation-guard/cfn-guard-lambda)
warning: use of deprecated item 'simple_logger::init_with_level': Please use the Builder pattern instead.
  --> cfn-guard-lambda/src/main.rs:30:5
   |
30 |     simple_logger::init_with_level(log::Level::Info)?;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: `#[warn(deprecated)]` on by default

    Finished release [optimized] target(s) in 5m 45s

To Reproduce

version: 0.2
env:
  variables:
    # Definining CloudFormation Teamplate and Ruleset as variables - part of the code repo
    CF_TEMPLATE: "cfn_template_file_example.yaml"
    CF_ORG_RULESET:  "cfn_guard_ruleset_example"
phases:
  install:
    commands:
      - apt-get update
      - apt-get install build-essential -y
      - apt-get install cargo -y
      - apt-get install git -y
  pre_build:
    commands:
      - echo "Setting up the environment for AWS CloudFormation Guard"
      - echo "More info https://github.com/aws-cloudformation/cloudformation-guard"
      - echo "Install Rust"
      - curl https://sh.rustup.rs -sSf | sh -s -- -y
      - echo "Clone code from AWS CloudFormation Guard github repo"
      - git clone https://github.com/aws-cloudformation/cloudformation-guard
  build:
    commands:
       - echo "Compiling Latest AWS CloudFormation Guard release"
       - echo "More info https://github.com/aws-cloudformation/cloudformation-guard#using-the-makefile"
       - cd cloudformation-guard
       - make
  post_build:
    commands:
        - cd .. 
        - echo "Validate CloudFormation template with cfn-guard tool"
        - echo "More information https://github.com/aws-cloudformation/cloudformation-guard/blob/master/cfn-guard/README.md"
        - cloudformation-guard/bin/cfn-guard check --rule_set $CF_ORG_RULESET --template $CF_TEMPLATE --strict-checks

Enforce mandatory tags for all tagging supported resources

My requirements are:

  • Enforce few mandatory tags (say ApplicationName, ApplicationOwner, SupportContact, Environment & CostCenter) to all tagging supported resources. cfn-guard should raise error if any of these tags are found missing.
  • Developers can add any number of tags other than the mandatory tags. cfn-guard should not fail if such tags are defined.

Below shown is a sample ruleset that I used to validate the mandatory tags for VPC.

AWS::EC2::VPC Tags == /.*"Key":"Environment".*/ << the mandatory Tag: Environment is not specified
AWS::EC2::VPC Tags == /.*"Key":"ApplicationName".*/ << the mandatory Tag: ApplicationName is not specified
AWS::EC2::VPC Tags == /.*"Key":"SupportContact".*/ << the mandatory Tag: SupportContact is not specified
AWS::EC2::VPC Tags == /.*"Key":"ApplicationOwner".*/ << the mandatory Tag: ApplicationOwner is not specified
AWS::EC2::VPC Tags == /.*"Key":"CostCenter".*/ << the mandatory Tag: CostCenter is not specified

By evaluating a CFT which contains only tag keys Service, Name and ApplicationName, using this above ruleset, I received the below output.

[VPC] failed because [Tags] is [[{"Key":"Service","Value":"VPC"},{"Key":"Name","Value":"DemoVPC"},{"Key":"ApplicationName","Value":"webapp"}]] and the mandatory Tag: ApplicationOwner is not specified
[VPC] failed because [Tags] is [[{"Key":"Service","Value":"VPC"},{"Key":"Name","Value":"DemoVPC"},{"Key":"ApplicationName","Value":"webapp"}]] and the mandatory Tag: CostCenter is not specified
[VPC] failed because [Tags] is [[{"Key":"Service","Value":"VPC"},{"Key":"Name","Value":"DemoVPC"},{"Key":"ApplicationName","Value":"webapp"}]] and the mandatory Tag: Environment is not specified
[VPC] failed because [Tags] is [[{"Key":"Service","Value":"VPC"},{"Key":"Name","Value":"DemoVPC"},{"Key":"ApplicationName","Value":"webapp"}]] and the mandatory Tag: SupportContact is not specified

Got two questions here:

  1. I have used regex in ruleset to validate tags. Is there any other method suggested for checking the existence of mandatory tags?
  2. As per my understanding, cfn-guard currently supports only adding rulesets for each resource type individually. Is there a way to apply this rule set globally for all resources?

[Enhancement]

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

Add a Java JNI dylib to use cfn-guard from java code.

Describe the solution you'd like
A clear and concise description of what you want to happen.

Use the jni crate to add a library function for JNI. build cfn-guard as a dylib alongside the executable.
Cargo.toml
Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

The main alternative is to impl a parser for the rules syntax in a language that is more commonly used for libraries (java/python/typescript). Obviously this is not good because there will always be a huge lag between multiple impls.

Additional context
Add any other context or screenshots about the feature request here.

Support JSON-string formatted objects

Given the rule:

AWS::IAM::Role AssumeRolePolicyDocument.Statement.*.Principal.Service.* == lambda.amazonaws.com

The following template correctly flags an issue:

Resources:
  LambdaRoleHelper:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - notlambda.amazonaws.com

However if I format the template to perform the same function, but using the JSON syntax like so:

Resources:
  LambdaRoleHelper:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument: |
        {
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "notlambda.amazonaws.com"
                ]
              }
            }
          ]
        }

The ruleset does not pick up on the violation.

[BUG] Running on CentOS 7

Describe the bug
Running cfn-guard in a CentOS 7 container produces

[root@6ef65b9fa037 /]# cfn-guard 
cfn-guard: /lib64/libc.so.6: version `GLIBC_2.18' not found (required by cfn-guard)

To Reproduce
Run using

FROM docker.io/centos:7

ENV TOOL_VERSION_CFN_GUARD="1.0.0"

RUN curl -L https://github.com/aws-cloudformation/cloudformation-guard/releases/download/${TOOL_VERSION_CFN_GUARD}/cfn-guard-linux-${TOOL_VERSION_CFN_GUARD}.tar.gz \
      -o cfn-guard-linux-${TOOL_VERSION_CFN_GUARD}.tar.gz \
    && tar -zxf cfn-guard-linux-${TOOL_VERSION_CFN_GUARD}.tar.gz \
    && mv cfn-guard-linux/cfn-guard /usr/local/bin/cfn-guard

Expected behavior
For cfn-guard to run without error

Screenshots
n/a

Operating System:
CentOS

OS Version
7

Additional context
Running with the following works fine

FROM docker.io/centos:8

ENV TOOL_VERSION_CFN_GUARD="1.0.0"

RUN curl -L https://github.com/aws-cloudformation/cloudformation-guard/releases/download/${TOOL_VERSION_CFN_GUARD}/cfn-guard-linux-${TOOL_VERSION_CFN_GUARD}.tar.gz \
      -o cfn-guard-linux-${TOOL_VERSION_CFN_GUARD}.tar.gz \
    && tar -zxf cfn-guard-linux-${TOOL_VERSION_CFN_GUARD}.tar.gz \
    && mv cfn-guard-linux/cfn-guard /usr/local/bin/cfn-guard

[Enhancement] Introduce Parameter Inputs for checking the CF scripts

Is your feature request related to a problem? Please describe.
In its current form, cfn-guard checks the Cloudformation script without any input.

Example, Lets say I have a Cloudformation script similar to the following (simplified version)

Parameters:
  RDSEncryption:
    Type: String

Resources:
  RDSPostgressDB:
    Type: AWS::RDS::DBInstance
    Properties: 
      StorageEncrypted: !Ref RDSEncryption

I want to create an RDS Database and I have a CF parameter input for encryption (true/false). Currently there is no way in cfn-guard to verify that the RDS will be created using encryption. I can only verify that the StorageEncrypted will have the value "RDSEncryption" (please correct me if I'm wrong)

Describe the solution you'd like
I would prefer an option to provide a json document with the CF parameter inputs and be able to check the Cloudformation script after all those values have been implemented.
The example I gave above would look like:

rules:

AWS::RDS::DBInstance StorageEncrypted == true

input.json:

[
  {
    "ParameterKey": "RDSEncryption",
    "ParameterValue": "true"
  }
]

cfn-guard:

cargo run -- -t rds.yaml -r rules -i input.json

cfn-guard will change the RDSEncryption Rerefence with the input true and will be able to check against that

Disallow Certain Type/Types

Customers may need to be able to disallow entire resource types, including those types of resources that do not have any required properties.

Possible Form:
!AWS::IAM::User

[BUG] Beginning and end of line Regex characters aren't working

Describe the bug
The ^ and & characters, which are used in Regex to denote the beginning and end of the line, don't seem to be recognised as valid, and a regex check using these characters doesn't match anything.
When testing my regex, I'm using the pattern ^([0-9]|[A-Z]){4}$, which should match any 4 digits or capitalised characters, such as 1234, however when running the test I get the following error:

[EC2Instance2] failed because [Tags.0.Value] is [1234] and the permitted pattern is [^([0-9]|[A-Z]){4}$]

If I remove the ^ and $ the regex matches, however it doesn't do an exact match for exactly 4 characters, so 12345 would then show as valid, as it contains 4 in a row, but with no defined end.

CloudFormation Guard 0.5.2

To Reproduce
Please supply:

  1. An example rule set and template that results in the error

Rule Set:

AWS::EC2::Instance Tags.0.Key == /(TestKey)/ AWS::EC2::Instance Tags.0.Value == /^([0-9]|[A-Z]){4}$/

Template:

{
  "AWSTemplateFormatVersion" : "2010-09-09",

  "Description" : "AWS CloudFormation Sample Template EC2InstanceWithSecurityGroupSample: Create an Amazon EC2 instance running the Amazon Linux AMI. The AMI is chosen based on the region in which the stack is run. This example creates an EC2 security group for the instance to give you SSH access. **WARNING** This template creates an Amazon EC2 instance. You will be billed for the AWS resources used if you create a stack from this template.",

  "Resources" : {
    "EC2Instance2" : {
      "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "InstanceType" : { "Ref" : "InstanceType" },
        "SecurityGroups" : [ { "Ref" : "InstanceSecurityGroup" } ],
        "KeyName" : { "Ref" : "KeyName" },
        "Tags": [
          {
            "Key": "TestKey",
            "Value": "1234"
          }
        ],
        "ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" },
                          { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] }
      }
    }
  }
}
  1. The commands you used to invoke the tool
    cfn-guard -vvv -t tests/test_failure/ec2.json -r cfn-test.cfn
  2. The output of a -vvv log level if it's not related to cfn-guard-lambda, or the relevant CloudWatch log messages if it is related to the lambda
2020-07-30 12:13:33,102 DEBUG [cfn_guard] Parameters are ArgMatches {
    args: {
        "rule_set": MatchedArg {
            occurs: 1,
            indices: [
                7,
            ],
            vals: [
                "cfn-test.cfn",
            ],
        },
        "template": MatchedArg {
            occurs: 1,
            indices: [
                5,
            ],
            vals: [
                "tests/test_failure/ec2.json",
            ],
        },
        "v": MatchedArg {
            occurs: 3,
            indices: [
                1,
                2,
                3,
            ],
            vals: [],
        },
    },
    subcommand: None,
    usage: Some(
        "USAGE:\n    cfn-guard [FLAGS] --rule_set <RULE_SET_FILE> --template <TEMPLATE_FILE>",
    ),
}
2020-07-30 12:13:33,103 INFO  [cfn_guard] CloudFormation Guard is checking the template 'tests/test_failure/ec2.json' against the rules in 'cfn-test.cfn'
2020-07-30 12:13:33,103 DEBUG [cfn_guard] Entered run
2020-07-30 12:13:33,103 TRACE [cfn_guard] Template file is 'tests/test_failure/ec2.json' and its contents are:
'{
  "AWSTemplateFormatVersion" : "2010-09-09",

  "Description" : "AWS CloudFormation Sample Template EC2InstanceWithSecurityGroupSample: Create an Amazon EC2 instance running the Amazon Linux AMI. The AMI is chosen based on the region in which the stack is run. This example creates an EC2 security group for the instance to give you SSH access. **WARNING** This template creates an Amazon EC2 instance. You will be billed for the AWS resources used if you create a stack from this template.",

  "Resources" : {
    "EC2Instance2" : {
      "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "InstanceType" : { "Ref" : "InstanceType" },
        "SecurityGroups" : [ { "Ref" : "InstanceSecurityGroup" } ],
        "KeyName" : { "Ref" : "KeyName" },
        "Tags": [
          {
            "Key": "TestKey",
            "Value": "1234"
          }
        ],
        "ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" },
                          { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] }
      }
    }
  }
}'
2020-07-30 12:13:33,103 TRACE [cfn_guard] Rules file is 'cfn-test.cfn' and its contents are: 

AWS::EC2::Instance Tags.0.Key == /(TestKey)/
AWS::EC2::Instance Tags.0.Value == /^([0-9]|[A-Z]){4}$/



2020-07-30 12:13:33,104 INFO  [cfn_guard] Loading CloudFormation Template and Rule Set
2020-07-30 12:13:33,104 DEBUG [cfn_guard] Entered run_check
2020-07-30 12:13:33,104 TRACE [cfn_guard] Normalizing booleans in inputs
2020-07-30 12:13:33,104 TRACE [cfn_guard] Cleaned template contents are:
'{
  "AWSTemplateFormatVersion" : "2010-09-09",

  "Description" : "AWS CloudFormation Sample Template EC2InstanceWithSecurityGroupSample: Create an Amazon EC2 instance running the Amazon Linux AMI. The AMI is chosen based on the region in which the stack is run. This example creates an EC2 security group for the instance to give you SSH access. **WARNING** This template creates an Amazon EC2 instance. You will be billed for the AWS resources used if you create a stack from this template.",

  "Resources" : {
    "EC2Instance2" : {
      "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "InstanceType" : { "Ref" : "InstanceType" },
        "SecurityGroups" : [ { "Ref" : "InstanceSecurityGroup" } ],
        "KeyName" : { "Ref" : "KeyName" },
        "Tags": [
          {
            "Key": "TestKey",
            "Value": "1234"
          }
        ],
        "ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" },
                          { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] }
      }
    }
  }
}'


2020-07-30 12:13:33,104 TRACE [cfn_guard] Cleaned rules file contents are:
'

AWS::EC2::Instance Tags.0.Key == /(TestKey)/
AWS::EC2::Instance Tags.0.Value == /^([0-9]|[A-Z]){4}$/


'
2020-07-30 12:13:33,104 DEBUG [cfn_guard] Deserializing CloudFormation template
2020-07-30 12:13:33,104 TRACE [cfn_guard] CFN Template is '{
    "Description": String(
        "AWS CloudFormation Sample Template EC2InstanceWithSecurityGroupSample: Create an Amazon EC2 instance running the Amazon Linux AMI. The AMI is chosen based on the region in which the stack is run. This example creates an EC2 security group for the instance to give you SSH access. **WARNING** This template creates an Amazon EC2 instance. You will be billed for the AWS resources used if you create a stack from this template.",
    ),
    "Resources": Object({
        "EC2Instance2": Object({
            "Properties": Object({
                "ImageId": Object({
                    "Fn::FindInMap": Array([
                        String(
                            "AWSRegionArch2AMI",
                        ),
                        Object({
                            "Ref": String(
                                "AWS::Region",
                            ),
                        }),
                        Object({
                            "Fn::FindInMap": Array([
                                String(
                                    "AWSInstanceType2Arch",
                                ),
                                Object({
                                    "Ref": String(
                                        "InstanceType",
                                    ),
                                }),
                                String(
                                    "Arch",
                                ),
                            ]),
                        }),
                    ]),
                }),
                "InstanceType": Object({
                    "Ref": String(
                        "InstanceType",
                    ),
                }),
                "KeyName": Object({
                    "Ref": String(
                        "KeyName",
                    ),
                }),
                "SecurityGroups": Array([
                    Object({
                        "Ref": String(
                            "InstanceSecurityGroup",
                        ),
                    }),
                ]),
                "Tags": Array([
                    Object({
                        "Key": String(
                            "TestKey",
                        ),
                        "Value": String(
                            "1234",
                        ),
                    }),
                ]),
            }),
            "Type": String(
                "AWS::EC2::Instance",
            ),
        }),
    }),
    "AWSTemplateFormatVersion": String(
        "2010-09-09",
    ),
}'
2020-07-30 12:13:33,105 TRACE [cfn_guard] CFN resources are: {"EC2Instance2": Object({"Properties": Object({"ImageId": Object({"Fn::FindInMap": Array([String("AWSRegionArch2AMI"), Object({"Ref": String("AWS::Region")}), Object({"Fn::FindInMap": Array([String("AWSInstanceType2Arch"), Object({"Ref": String("InstanceType")}), String("Arch")])})])}), "InstanceType": Object({"Ref": String("InstanceType")}), "KeyName": Object({"Ref": String("KeyName")}), "SecurityGroups": Array([Object({"Ref": String("InstanceSecurityGroup")})]), "Tags": Array([Object({"Key": String("TestKey"), "Value": String("1234")})])}), "Type": String("AWS::EC2::Instance")})}
2020-07-30 12:13:33,105 INFO  [cfn_guard] Parsing rule set
2020-07-30 12:13:33,105 DEBUG [cfn_guard::parser] Entered parse_rules
2020-07-30 12:13:33,105 TRACE [cfn_guard::parser] Parse rules entered with rules_file_contents: "\n\nAWS::EC2::Instance Tags.0.Key == /(TestKey)/\nAWS::EC2::Instance Tags.0.Value == /^([0-9]|[A-Z]){4}$/\n\n\n"
2020-07-30 12:13:33,105 TRACE [cfn_guard::parser] Parse rules entered with cfn_resources: {
    "EC2Instance2": Object({
        "Properties": Object({
            "ImageId": Object({
                "Fn::FindInMap": Array([
                    String(
                        "AWSRegionArch2AMI",
                    ),
                    Object({
                        "Ref": String(
                            "AWS::Region",
                        ),
                    }),
                    Object({
                        "Fn::FindInMap": Array([
                            String(
                                "AWSInstanceType2Arch",
                            ),
                            Object({
                                "Ref": String(
                                    "InstanceType",
                                ),
                            }),
                            String(
                                "Arch",
                            ),
                        ]),
                    }),
                ]),
            }),
            "InstanceType": Object({
                "Ref": String(
                    "InstanceType",
                ),
            }),
            "KeyName": Object({
                "Ref": String(
                    "KeyName",
                ),
            }),
            "SecurityGroups": Array([
                Object({
                    "Ref": String(
                        "InstanceSecurityGroup",
                    ),
                }),
            ]),
            "Tags": Array([
                Object({
                    "Key": String(
                        "TestKey",
                    ),
                    "Value": String(
                        "1234",
                    ),
                }),
            ]),
        }),
        "Type": String(
            "AWS::EC2::Instance",
        ),
    }),
}
2020-07-30 12:13:33,105 TRACE [cfn_guard::parser] Rules file lines: Lines(
    Map {
        iter: SplitTerminator(
            SplitInternal {
                start: 0,
                end: 105,
                matcher: CharSearcher {
                    haystack: "\n\nAWS::EC2::Instance Tags.0.Key == /(TestKey)/\nAWS::EC2::Instance Tags.0.Value == /^([0-9]|[A-Z]){4}$/\n\n\n",
                    finger: 0,
                    finger_back: 105,
                    needle: '\n',
                    utf8_size: 1,
                    utf8_encoded: [
                        10,
                        0,
                        0,
                        0,
                    ],
                },
                allow_trailing_empty: false,
                finished: false,
            },
        ),
    },
)
2020-07-30 12:13:33,106 DEBUG [cfn_guard::parser] Parsing ''
2020-07-30 12:13:33,106 DEBUG [cfn_guard::parser] Parsing ''
2020-07-30 12:13:33,106 DEBUG [cfn_guard::parser] Parsing 'AWS::EC2::Instance Tags.0.Key == /(TestKey)/'
2020-07-30 12:13:33,111 DEBUG [cfn_guard::parser] line_type is Rule
2020-07-30 12:13:33,111 DEBUG [cfn_guard::parser] Line is an 'AND' rule
2020-07-30 12:13:33,111 TRACE [cfn_guard::parser] Entered destructure_rule
2020-07-30 12:13:33,113 TRACE [cfn_guard::parser] Parsed rule's captures are: Captures(
    {
        0: Some(
            "AWS::EC2::Instance Tags.0.Key == /(TestKey)/",
        ),
        "resource_type": Some(
            "AWS::EC2::Instance",
        ),
        "resource_property": Some(
            "Tags.0.Key",
        ),
        "operator": Some(
            "==",
        ),
        "rule_value": Some(
            "/(TestKey)/",
        ),
    },
)
2020-07-30 12:13:33,114 TRACE [cfn_guard::parser] Destructured rules are: [
    Rule {
        resource_type: "AWS::EC2::Instance",
        field: "Tags.0.Key",
        operation: Require,
        value: "(TestKey)",
        rule_vtype: Regex,
        custom_msg: None,
    },
]
2020-07-30 12:13:33,114 DEBUG [cfn_guard::parser] Parsed rule is: CompoundRule {
    compound_type: AND,
    rule_list: [
        Rule {
            resource_type: "AWS::EC2::Instance",
            field: "Tags.0.Key",
            operation: Require,
            value: "(TestKey)",
            rule_vtype: Regex,
            custom_msg: None,
        },
    ],
}
2020-07-30 12:13:33,114 DEBUG [cfn_guard::parser] Parsing 'AWS::EC2::Instance Tags.0.Value == /^([0-9]|[A-Z]){4}$/'
2020-07-30 12:13:33,114 DEBUG [cfn_guard::parser] line_type is Rule
2020-07-30 12:13:33,114 DEBUG [cfn_guard::parser] Line is an 'AND' rule
2020-07-30 12:13:33,114 TRACE [cfn_guard::parser] Entered destructure_rule
2020-07-30 12:13:33,114 TRACE [cfn_guard::parser] Parsed rule's captures are: Captures(
    {
        0: Some(
            "AWS::EC2::Instance Tags.0.Value == /^([0-9]|[A-Z]){4}$/",
        ),
        "resource_type": Some(
            "AWS::EC2::Instance",
        ),
        "resource_property": Some(
            "Tags.0.Value",
        ),
        "operator": Some(
            "==",
        ),
        "rule_value": Some(
            "/^([0-9]|[A-Z]){4}$/",
        ),
    },
)
2020-07-30 12:13:33,114 TRACE [cfn_guard::parser] Destructured rules are: [
    Rule {
        resource_type: "AWS::EC2::Instance",
        field: "Tags.0.Value",
        operation: Require,
        value: "^([0-9]|[A-Z]){4}$",
        rule_vtype: Regex,
        custom_msg: None,
    },
]
2020-07-30 12:13:33,114 DEBUG [cfn_guard::parser] Parsed rule is: CompoundRule {
    compound_type: AND,
    rule_list: [
        Rule {
            resource_type: "AWS::EC2::Instance",
            field: "Tags.0.Value",
            operation: Require,
            value: "^([0-9]|[A-Z]){4}$",
            rule_vtype: Regex,
            custom_msg: None,
        },
    ],
}
2020-07-30 12:13:33,114 DEBUG [cfn_guard::parser] Parsing ''
2020-07-30 12:13:33,114 DEBUG [cfn_guard::parser] Parsing ''
2020-07-30 12:13:33,114 DEBUG [cfn_guard::parser] Variables dictionary is {"ENV_VSCODE_GIT_ASKPASS_MAIN": "********", "ENV_VSCODE_IPC_HOOK_CLI": "********", "ENV_VSCODE_GIT_ASKPASS_NODE": "********", "ENV_AWS_REGION": "********", "ENV_VSCODE_GIT_IPC_HANDLE": "********", "ENV_APPLICATION_INSIGHTS_NO_DIAGNOSTIC_CHANNEL": "********", "ENV__": "********", "ENV_HOME": "********", "ENV_NAME": "********", "ENV_PWD": "********", "ENV_SHLVL": "********", "ENV_AWS_SDK_LOAD_CONFIG": "********", "ENV_GIT_ASKPASS": "********", "ENV_TERM_PROGRAM": "********", "ENV_SHELL": "********", "ENV_LOGNAME": "********", "ENV_TERM": "********", "ENV_HOSTTYPE": "********", "ENV_AMD_ENTRYPOINT": "********", "ENV_LS_COLORS": "********", "ENV_PATH": "********", "ENV_VERBOSE_LOGGING": "********", "ENV_TERM_PROGRAM_VERSION": "********", "ENV_LESSCLOSE": "********", "ENV_USER": "********", "ENV_XDG_DATA_DIRS": "********", "ENV_LESSOPEN": "********", "ENV_WSLENV": "********", "ENV_COLORTERM": "********", "ENV_PIPE_LOGGING": "********", "ENV_LANG": "********"}
2020-07-30 12:13:33,114 DEBUG [cfn_guard::parser] Rule Set is [
    CompoundRule {
        compound_type: AND,
        rule_list: [
            Rule {
                resource_type: "AWS::EC2::Instance",
                field: "Tags.0.Key",
                operation: Require,
                value: "(TestKey)",
                rule_vtype: Regex,
                custom_msg: None,
            },
        ],
    },
    CompoundRule {
        compound_type: AND,
        rule_list: [
            Rule {
                resource_type: "AWS::EC2::Instance",
                field: "Tags.0.Value",
                operation: Require,
                value: "^([0-9]|[A-Z]){4}$",
                rule_vtype: Regex,
                custom_msg: None,
            },
        ],
    },
]
2020-07-30 12:13:33,115 INFO  [cfn_guard] Checking resources
2020-07-30 12:13:33,115 INFO  [cfn_guard] Applying rule 'CompoundRule {
    compound_type: AND,
    rule_list: [
        Rule {
            resource_type: "AWS::EC2::Instance",
            field: "Tags.0.Key",
            operation: Require,
            value: "(TestKey)",
            rule_vtype: Regex,
            custom_msg: None,
        },
    ],
}'
2020-07-30 12:13:33,115 DEBUG [cfn_guard] Applying rule 'Rule { resource_type: "AWS::EC2::Instance", field: "Tags.0.Key", operation: Require, value: "(TestKey)", rule_vtype: Regex, custom_msg: None }'
2020-07-30 12:13:33,115 INFO  [cfn_guard] Checking [EC2Instance2] which is of type "AWS::EC2::Instance"
2020-07-30 12:13:33,115 TRACE [cfn_guard::util] Getting ["Tags", "0", "Key"] from {"ImageId":{"Fn::FindInMap":["AWSRegionArch2AMI",{"Ref":"AWS::Region"},{"Fn::FindInMap":["AWSInstanceType2Arch",{"Ref":"InstanceType"},"Arch"]}]},"InstanceType":{"Ref":"InstanceType"},"KeyName":{"Ref":"KeyName"},"SecurityGroups":[{"Ref":"InstanceSecurityGroup"}],"Tags":[{"Key":"TestKey","Value":"1234"}]}
2020-07-30 12:13:33,115 TRACE [cfn_guard::util] field_list is ["Tags", "0", "Key"]
2020-07-30 12:13:33,115 TRACE [cfn_guard::util] next_field is "Tags" and field_list is now ["0", "Key"]
2020-07-30 12:13:33,115 TRACE [cfn_guard::util] props are Object({
    "ImageId": Object({
        "Fn::FindInMap": Array([
            String(
                "AWSRegionArch2AMI",
            ),
            Object({
                "Ref": String(
                    "AWS::Region",
                ),
            }),
            Object({
                "Fn::FindInMap": Array([
                    String(
                        "AWSInstanceType2Arch",
                    ),
                    Object({
                        "Ref": String(
                            "InstanceType",
                        ),
                    }),
                    String(
                        "Arch",
                    ),
                ]),
            }),
        ]),
    }),
    "InstanceType": Object({
        "Ref": String(
            "InstanceType",
        ),
    }),
    "KeyName": Object({
        "Ref": String(
            "KeyName",
        ),
    }),
    "SecurityGroups": Array([
        Object({
            "Ref": String(
                "InstanceSecurityGroup",
            ),
        }),
    ]),
    "Tags": Array([
        Object({
            "Key": String(
                "TestKey",
            ),
            "Value": String(
                "1234",
            ),
        }),
    ]),
})
2020-07-30 12:13:33,115 TRACE [cfn_guard::util] Getting ["0", "Key"] from [{"Key":"TestKey","Value":"1234"}]
2020-07-30 12:13:33,115 TRACE [cfn_guard::util] field_list is ["0", "Key"]
2020-07-30 12:13:33,115 TRACE [cfn_guard::util] next_field is 0 and field_list is now ["Key"]
2020-07-30 12:13:33,115 TRACE [cfn_guard::util] props are Array([
    Object({
        "Key": String(
            "TestKey",
        ),
        "Value": String(
            "1234",
        ),
    }),
])
2020-07-30 12:13:33,115 TRACE [cfn_guard::util] Getting ["Key"] from {"Key":"TestKey","Value":"1234"}
2020-07-30 12:13:33,115 TRACE [cfn_guard::util] field_list is ["Key"]
2020-07-30 12:13:33,115 TRACE [cfn_guard::util] next_field is "Key" and field_list is now []
2020-07-30 12:13:33,116 TRACE [cfn_guard::util] props are Object({
    "Key": String(
        "TestKey",
    ),
    "Value": String(
        "1234",
    ),
})
2020-07-30 12:13:33,116 DEBUG [cfn_guard] Template val is String("TestKey")
2020-07-30 12:13:33,116 TRACE [cfn_guard::util] Entered dereference_rule_value() with 'Rule {
    resource_type: "AWS::EC2::Instance",
    field: "Tags.0.Key",
    operation: Require,
    value: "(TestKey)",
    rule_vtype: Regex,
    custom_msg: None,
}' and Variables '{
    "ENV_VERBOSE_LOGGING": "********",
    "ENV_PATH": "********",
    "ENV_COLORTERM": "********",
    "ENV_TERM_PROGRAM_VERSION": "********",
    "ENV_LESSOPEN": "********",
    "ENV_PIPE_LOGGING": "********",
    "ENV_LOGNAME": "********",
    "ENV_SHLVL": "********",
    "ENV_NAME": "********",
    "ENV_VSCODE_GIT_ASKPASS_NODE": "********",
    "ENV_VSCODE_IPC_HOOK_CLI": "********",
    "ENV_PWD": "********",
    "ENV_USER": "********",
    "ENV_LESSCLOSE": "********",
    "ENV_LANG": "********",
    "ENV_WSLENV": "********",
    "ENV_XDG_DATA_DIRS": "********",
    "ENV_TERM_PROGRAM": "********",
    "ENV_AMD_ENTRYPOINT": "********",
    "ENV_AWS_REGION": "********",
    "ENV_GIT_ASKPASS": "********",
    "ENV_SHELL": "********",
    "ENV_TERM": "********",
    "ENV_APPLICATION_INSIGHTS_NO_DIAGNOSTIC_CHANNEL": "********",
    "ENV_HOSTTYPE": "********",
    "ENV__": "********",
    "ENV_VSCODE_GIT_ASKPASS_MAIN": "********",
    "ENV_AWS_SDK_LOAD_CONFIG": "********",
    "ENV_VSCODE_GIT_IPC_HANDLE": "********",
    "ENV_LS_COLORS": "********",
    "ENV_HOME": "********",
}'
2020-07-30 12:13:33,116 DEBUG [cfn_guard] rule_val is (TestKey) and val is "TestKey"
2020-07-30 12:13:33,116 DEBUG [cfn_guard] OpCode::Require with rule_val as (TestKey) and val as "TestKey" of RValueType::Regex
2020-07-30 12:13:33,117 INFO  [cfn_guard] Result: PASS
2020-07-30 12:13:33,117 INFO  [cfn_guard] Applying rule 'CompoundRule {
    compound_type: AND,
    rule_list: [
        Rule {
            resource_type: "AWS::EC2::Instance",
            field: "Tags.0.Value",
            operation: Require,
            value: "^([0-9]|[A-Z]){4}$",
            rule_vtype: Regex,
            custom_msg: None,
        },
    ],
}'
2020-07-30 12:13:33,117 DEBUG [cfn_guard] Applying rule 'Rule { resource_type: "AWS::EC2::Instance", field: "Tags.0.Value", operation: Require, value: "^([0-9]|[A-Z]){4}$", rule_vtype: Regex, custom_msg: None }'
2020-07-30 12:13:33,117 INFO  [cfn_guard] Checking [EC2Instance2] which is of type "AWS::EC2::Instance"
2020-07-30 12:13:33,117 TRACE [cfn_guard::util] Getting ["Tags", "0", "Value"] from {"ImageId":{"Fn::FindInMap":["AWSRegionArch2AMI",{"Ref":"AWS::Region"},{"Fn::FindInMap":["AWSInstanceType2Arch",{"Ref":"InstanceType"},"Arch"]}]},"InstanceType":{"Ref":"InstanceType"},"KeyName":{"Ref":"KeyName"},"SecurityGroups":[{"Ref":"InstanceSecurityGroup"}],"Tags":[{"Key":"TestKey","Value":"1234"}]}
2020-07-30 12:13:33,117 TRACE [cfn_guard::util] field_list is ["Tags", "0", "Value"]
2020-07-30 12:13:33,117 TRACE [cfn_guard::util] next_field is "Tags" and field_list is now ["0", "Value"]
2020-07-30 12:13:33,117 TRACE [cfn_guard::util] props are Object({
    "ImageId": Object({
        "Fn::FindInMap": Array([
            String(
                "AWSRegionArch2AMI",
            ),
            Object({
                "Ref": String(
                    "AWS::Region",
                ),
            }),
            Object({
                "Fn::FindInMap": Array([
                    String(
                        "AWSInstanceType2Arch",
                    ),
                    Object({
                        "Ref": String(
                            "InstanceType",
                        ),
                    }),
                    String(
                        "Arch",
                    ),
                ]),
            }),
        ]),
    }),
    "InstanceType": Object({
        "Ref": String(
            "InstanceType",
        ),
    }),
    "KeyName": Object({
        "Ref": String(
            "KeyName",
        ),
    }),
    "SecurityGroups": Array([
        Object({
            "Ref": String(
                "InstanceSecurityGroup",
            ),
        }),
    ]),
    "Tags": Array([
        Object({
            "Key": String(
                "TestKey",
            ),
            "Value": String(
                "1234",
            ),
        }),
    ]),
})
2020-07-30 12:13:33,118 TRACE [cfn_guard::util] Getting ["0", "Value"] from [{"Key":"TestKey","Value":"1234"}]
2020-07-30 12:13:33,118 TRACE [cfn_guard::util] field_list is ["0", "Value"]
2020-07-30 12:13:33,118 TRACE [cfn_guard::util] next_field is 0 and field_list is now ["Value"]
2020-07-30 12:13:33,118 TRACE [cfn_guard::util] props are Array([
    Object({
        "Key": String(
            "TestKey",
        ),
        "Value": String(
            "1234",
        ),
    }),
])
2020-07-30 12:13:33,118 TRACE [cfn_guard::util] Getting ["Value"] from {"Key":"TestKey","Value":"1234"}
2020-07-30 12:13:33,118 TRACE [cfn_guard::util] field_list is ["Value"]
2020-07-30 12:13:33,118 TRACE [cfn_guard::util] next_field is "Value" and field_list is now []
2020-07-30 12:13:33,118 TRACE [cfn_guard::util] props are Object({
    "Key": String(
        "TestKey",
    ),
    "Value": String(
        "1234",
    ),
})
2020-07-30 12:13:33,118 DEBUG [cfn_guard] Template val is String("1234")
2020-07-30 12:13:33,118 TRACE [cfn_guard::util] Entered dereference_rule_value() with 'Rule {
    resource_type: "AWS::EC2::Instance",
    field: "Tags.0.Value",
    operation: Require,
    value: "^([0-9]|[A-Z]){4}$",
    rule_vtype: Regex,
    custom_msg: None,
}' and Variables '{
    "ENV_LESSCLOSE": "********",
    "ENV_HOSTTYPE": "********",
    "ENV_LS_COLORS": "********",
    "ENV_VSCODE_IPC_HOOK_CLI": "********",
    "ENV_SHELL": "********",
    "ENV_XDG_DATA_DIRS": "********",
    "ENV_NAME": "********",
    "ENV_TERM_PROGRAM_VERSION": "********",
    "ENV_GIT_ASKPASS": "********",
    "ENV_AWS_REGION": "********",
    "ENV_VERBOSE_LOGGING": "********",
    "ENV_WSLENV": "********",
    "ENV_TERM_PROGRAM": "********",
    "ENV_HOME": "********",
    "ENV_SHLVL": "********",
    "ENV_USER": "********",
    "ENV_PATH": "********",
    "ENV_PWD": "********",
    "ENV_APPLICATION_INSIGHTS_NO_DIAGNOSTIC_CHANNEL": "********",
    "ENV_COLORTERM": "********",
    "ENV_TERM": "********",
    "ENV__": "********",
    "ENV_PIPE_LOGGING": "********",
    "ENV_AWS_SDK_LOAD_CONFIG": "********",
    "ENV_VSCODE_GIT_IPC_HANDLE": "********",
    "ENV_AMD_ENTRYPOINT": "********",
    "ENV_LANG": "********",
    "ENV_VSCODE_GIT_ASKPASS_MAIN": "********",
    "ENV_VSCODE_GIT_ASKPASS_NODE": "********",
    "ENV_LESSOPEN": "********",
    "ENV_LOGNAME": "********",
}'
2020-07-30 12:13:33,118 DEBUG [cfn_guard] rule_val is ^([0-9]|[A-Z]){4}$ and val is "1234"
2020-07-30 12:13:33,118 DEBUG [cfn_guard] OpCode::Require with rule_val as ^([0-9]|[A-Z]){4}$ and val as "1234" of RValueType::Regex
2020-07-30 12:13:33,119 INFO  [cfn_guard] Result: FAIL
2020-07-30 12:13:33,119 TRACE [cfn_guard::util] Removing spaces and newline characters from '1234'
2020-07-30 12:13:33,119 TRACE [cfn_guard::util] formatted_value is '1234'
2020-07-30 12:13:33,119 DEBUG [cfn_guard] Outcome was: '[
    "[EC2Instance2] failed because [Tags.0.Value] is [1234] and the permitted pattern is [^([0-9]|[A-Z]){4}$]",
]'
[EC2Instance2] failed because [Tags.0.Value] is [1234] and the permitted pattern is [^([0-9]|[A-Z]){4}$]
Number of failures: 1

NOTE: Please be sure that the templates, rule sets and logs you provide as part of your bug report do not contain any sensitive information.

Expected behavior
A clear and concise description of what you expected to happen.

The Regex I'm using is ^([0-9]|[A-Z]){4}$ - which should match any sequence of 4 digits and capitalised numbers, but not allow more or less than 4 characters.
However the regex fails to match at all, no matter what parameters are passed to it. I've also tried including (?m) in the regex string to turn on multiline, but that doesn't make a difference

Screenshots
If applicable, add screenshots to help explain your problem.

Operating System:
[eg, MacOS, Windows, Ubuntu, etc]
Ubuntu

OS Version
[eg Catalina, 10, 18.04, etc]
Ubuntu 18.04.4 LTS using WSL

Additional context
Add any other context about the problem here.

[Enhancement] Create example explaining that strict checking means missing resources properties and expected error.

When I started using cfn-guard (by the way, love the tool!), it took awhile to figure out if it would fail if a required resource property was completely missing. It's not completely obvious (well, to me at least) that "strict" equates to missing CloudFormation resource properties.

The Readme.md file is not easy to parse, and it took a few times reading through it to understand how the tool works. I actually figured out how strict works from a closed issue request.

I think it would help tremendously to include an example where (and why) strict is used, and what the output looks like for a failure.

For instance, I wanted to validate an EC2 Launch Template where EBS encryption is mandated.

AWSTemplateFormatVersion: "2010-09-09"
Resources:
    SampleLaunchTemplate:
        Type: "AWS::EC2::LaunchTemplate"
        Properties:
            LaunchTemplateData:
                BlockDeviceMappings:
                    Ebs:
                        Encrypted: true

with a ruleset file such as...

let encryption_flag = true

AWS::EC2::LaunchTemplate LaunchTemplateData.BlockDeviceMappings.Ebs.Encrypted == %encryption_flag

For the example, something like...

"In the example above, use strict (-s) to determine if a resource property is completely missing, such as removing the BlockDeviceMappings section". See below.

AWSTemplateFormatVersion: "2010-09-09"
Resources:
    SampleLaunchTemplate:
        Type: "AWS::EC2::LaunchTemplate"
        Properties:
            LaunchTemplateData:

Sample command:

>cargo run -- -t ec2launchtemplate-template.yaml -r ec2launchtemplate.ruleset -s

Sample result

[SampleLaunchTemplate] failed because it does not contain the required property of [BlockDeviceMappings]
Number of failures: 1

Many thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.