

# Writing AWS CloudFormation Guard rules
<a name="writing-rules"></a>

In AWS CloudFormation Guard, *rules* are policy-as-code rules. You write rules in the Guard domain-specific language (DSL) that you can validate your JSON- or YAML-formatted data against. Rules are made up of *clauses*.

You can save rules written using the Guard DSL into plaintext files that use any file extension.

You can create multiple rule files and categorize them as a *rule set*. Rule sets allow you to validate your JSON- or YAML-formatted data against multiple rule files at the same time.

**Topics**
+ [Clauses](#clauses)
+ [Using queries in clauses](#clauses-queries)
+ [Using operators in clauses](#clauses-operators)
+ [Using custom messages in clauses](#clauses-custom-messages)
+ [Combining clauses](#combining-clauses)
+ [Using blocks with Guard rules](#blocks)
+ [Using built-in functions](#built-in-functions)
+ [Defining Guard queries and filtering](query-and-filtering.md)
+ [Assigning and referencing variables in Guard rules](variables.md)
+ [Composing named-rule blocks in AWS CloudFormation Guard](named-rule-block-composition.md)
+ [Writing clauses to perform context-aware evaluations](context-aware-evaluations.md)

## Clauses
<a name="clauses"></a>

Clauses are Boolean expressions that evaluate to either true (`PASS`) or false (`FAIL`). Clauses use either binary operators to compare two values or unary operators that operate on a single value.

**Examples of unary clauses**

The following unary clause evaluates whether the collection `TcpBlockedPorts` is empty.

```
InputParameters.TcpBlockedPorts not empty
```

The following unary clause evaluates whether the `ExecutionRoleArn` property is a string.

```
Properties.ExecutionRoleArn is_string
```

**Examples of binary clauses**

The following binary clause evaluates whether the `BucketName` property contains the string `encrypted`, regardless of casing.

```
Properties.BucketName != /(?i)encrypted/
```

The following binary clause evaluates whether the `ReadCapacityUnits` property is less than or equal to 5,000.

```
Properties.ProvisionedThroughput.ReadCapacityUnits <= 5000
```

### Syntax for writing Guard rule clauses
<a name="clauses-syntax"></a>

```
<query> <operator> [query|value literal] [custom message]
```

### Properties of Guard rule clauses
<a name="clauses-properties"></a>

`query`  <a name="clauses-properties-query"></a>
A dot (`.`) separated expression written to traverse hierarchical data. Query expressions can include filter expressions to target a subset of values. Queries can be assigned to variables so that you can write them once and reference them elsewhere in a rule set, which will allow you to access query results.  
For more information about writing queries and filtering, see [Defining queries and filtering](query-and-filtering.md).  
 *Required*: Yes

`operator`  <a name="clauses-properties-operator"></a>
A unary or binary operator that helps check the state of the query. The left-hand side (LHS) of a binary operator must be a query, and the right-hand side (RHS) must be either a query or a value literal.  
 *Supported binary operators*: `==` (Equal) \$1 `!=` (Not equal) \$1 `>` (Greater than) \$1 `>=` (Greater than or equal to) \$1 `<` (Less than) \$1 `<=` (Less than or equal to) \$1 `IN` (In a list of form [x, y, z]  
 *Supported unary operators*: `exists` \$1 `empty` \$1 `is_string` \$1 `is_list` \$1 `is_struct` \$1 `not(!)`  
 *Required*: Yes

`query|value literal`  <a name="clauses-properties-value-literal"></a>
A query or a supported value literal such as `string` or `integer(64)`.   
*Supported value literals*:  
+ All primitive types: `string`, `integer(64)`, `float(64)`, `bool`, `char`, `regex`
+ All specialized range types for expressing `integer(64)`, `float(64)`, or `char` ranges expressed as:
  + `r[<lower_limit>, <upper_limit>]`, which translates to any value `k` that satisfies the following expression: `lower_limit <= k <= upper_limit`
  + `r[<lower_limit>, <upper_limit>`), which translates to any value `k` that satisfies the following expression: `lower_limit <= k < upper_limit`
  + `r(<lower_limit>, <upper_limit>]`, which translates to any value `k` that satisfies the following expression: `lower_limit < k <= upper_limit`
  + `r(<lower_limit>, <upper_limit>),` which translates to any value `k` that satisfies the following expression: `lower_limit < k < upper_limit`
+ Associative arrays (maps) for nested key-value structure data. For example:

  `{ "my-map": { "nested-maps": [ { "key": 10, "value": 20 } ] } }`
+ Arrays of primitive types or associative array types
 *Required*: Conditional; required when a binary operator is used.

`custom message`  <a name="clauses-properties-custom-message"></a>
A string that provides information about the clause. The message is displayed in the verbose outputs of the `validate` and `test` commands and can be useful for understanding or debugging rule evaluation on hierarchical data.  
 *Required*: No

## Using queries in clauses
<a name="clauses-queries"></a>

For information about writing queries, see [Defining queries and filtering](query-and-filtering.md) and [Assigning and referencing variables in Guard rules](variables.md).

## Using operators in clauses
<a name="clauses-operators"></a>

The following are example CloudFormation templates, `Template-1` and `Template-2`. To demonstrate the use of supported operators, the example queries and clauses in this section refer to these example templates.

**Template-1**

```
Resources:
 S3Bucket:
   Type: AWS::S3::Bucket
   Properties:
     BucketName: MyServiceS3Bucket
     BucketEncryption:
       ServerSideEncryptionConfiguration:
         - ServerSideEncryptionByDefault:
             SSEAlgorithm: 'aws:kms'
             KMSMasterKeyID: 'arn:aws:kms:us-east-1:123456789:key/056ea50b-1013-3907-8617-c93e474e400'
     Tags:
       - Key: stage
         Value: prod
       - Key: service
         Value: myService
```

**Template-2**

```
Resources:
 NewVolume:
   Type: AWS::EC2::Volume
   Properties: 
     Size: 100
     VolumeType: io1
     Iops: 100
     AvailabilityZone:
       Fn::Select:
         - 0
         - Fn::GetAZs: us-east-1
     Tags:
       - Key: environment
         Value: test
   DeletionPolicy: Snapshot
```

### Examples of clauses that use unary operators
<a name="clauses-unary-operators"></a>
+ `empty` – Checks if a collection is empty. You can also use it to check if a query has values in a hierarchical data because queries result in a collection. You can't use it to check whether string value queries have an empty string (`""`) defined. For more information, see [Defining queries and filtering](query-and-filtering.md).

  The following clause checks whether the template has one or more resources defined. It evaluates to `PASS` because a resource with the logical ID `S3Bucket` is defined in `Template-1`.

  ```
  Resources !empty
  ```

  The following clause checks whether one or more tags are defined for the `S3Bucket` resource. It evaluates to `PASS` because `S3Bucket` has two tags defined for the `Tags` property in `Template-1`.

  ```
  Resources.S3Bucket.Properties.Tags !empty
  ```
+ `exists` – Checks whether each occurrence of the query has a value and can be used in place of `!= null`.

  The following clause checks whether the `BucketEncryption` property is defined for the `S3Bucket`. It evaluates to `PASS` because `BucketEncryption` is defined for `S3Bucket` in `Template-1`.

  ```
  Resources.S3Bucket.Properties.BucketEncryption exists
  ```

**Note**  
The `empty` and `not exists` checks evaluate to `true` for missing property keys when traversing the input data. For example, if the `Properties` section isn't defined in the template for the `S3Bucket`, the clause `Resources.S3Bucket.Properties.Tag empty` evaluates to `true`. The `exists` and `empty` checks don't display the JSON pointer path inside the document in the error messages. Both of these clauses often have retrieval errors that don't maintain this traversal information.
+ `is_string` – Checks whether each occurrence of the query is of `string` type.

  The following clause checks whether a string value is specified for the `BucketName` property of the `S3Bucket` resource. It evaluates to `PASS` because the string value `"MyServiceS3Bucket"` is specified for `BucketName` in `Template-1`.

  ```
  Resources.S3Bucket.Properties.BucketName is_string
  ```
+ `is_list` – Checks whether each occurrence of the query is of `list` type.

  The following clause checks whether a list is specified for the `Tags` property of the `S3Bucket` resource. It evaluates to `PASS` because two key-value pairs are specified for `Tags` in `Template-1`.

  ```
  Resources.S3Bucket.Properties.Tags is_list
  ```
+ `is_struct` – Checks whether each occurrence of the query is structured data.

  The following clause checks whether structured data is specified for the `BucketEncryption` property of the `S3Bucket` resource. It evaluates to `PASS` because `BucketEncryption` is specified using the `ServerSideEncryptionConfiguration` property type *(object)* in `Template-1`.

  ```
  Resources.S3Bucket.Properties.BucketEncryption is_struct
  ```

**Note**  
To check the inverse state, you can use the (` not !`) operator with the `is_string`, `is_list`, and `is_struct` operators .

### Examples of clauses that use binary operators
<a name="clauses-binary-operators"></a>

The following clause checks whether the value specified for the `BucketName` property of the `S3Bucket` resource in `Template-1` contains the string `encrypt`, regardless of casing. This evaluates to `PASS` because the specified bucket name `"MyServiceS3Bucket"` does not contain the string `encrypt`.

```
Resources.S3Bucket.Properties.BucketName != /(?i)encrypt/
```

The following clause checks whether the value specified for the `Size` property of the `NewVolume` resource in `Template-2` is within a specific range: 50 <= `Size` <= 200. It evaluates to `PASS` because `100` is specified for `Size`.

```
Resources.NewVolume.Properties.Size IN r[50,200]
```

The following clause checks whether the value specified for the `VolumeType` property of the `NewVolume` resource in `Template-2` is `io1`, `io2`, or `gp3`. It evaluates to `PASS` because `io1` is specified for `NewVolume`.

```
Resources.NewVolume.Properties.NewVolume.VolumeType IN [ 'io1','io2','gp3' ]
```

**Note**  
The example queries in this section demonstrate the use of operators using the resources with logical IDs `S3Bucket` and `NewVolume`. Resource names are often user-defined and can be arbitrarily named in an infrastructure as code (IaC) template. To write a rule that is generic and applies to all `AWS::S3::Bucket` resources defined in the template, the most common form of query used is `Resources.*[ Type == ‘AWS::S3::Bucket’ ]`. For more information, see [Defining queries and filtering](query-and-filtering.md) for details about usage and explore the [examples](https://github.com/aws-cloudformation/cloudformation-guard/tree/main/guard-examples) directory in the `cloudformation-guard` GitHub repository.

## Using custom messages in clauses
<a name="clauses-custom-messages"></a>

In the following example, clauses for `Template-2` include a custom message.

```
Resources.NewVolume.Properties.Size IN r(50,200) 
<<
    EC2Volume size must be between 50 and 200, 
    not including 50 and 200
>>
Resources.NewVolume.Properties.VolumeType IN [ 'io1','io2','gp3' ] <<Allowed Volume Types are io1, io2, and gp3>>
```

## Combining clauses
<a name="combining-clauses"></a>

In Guard, each clause written on a new line is combined implicitly with the next clause by using conjunction (Boolean `and` logic). See the following example.

```
# clause_A ^ clause_B ^ clause_C
clause_A
clause_B
clause_C
```

You can also use disjunction to combine a clause with the next clause by specifying `or|OR` at the end of the first clause.

```
<query> <operator> [query|value literal] [custom message] [or|OR]
```

In a Guard clause, disjunctions are evaluated first, followed by conjunctions. Guard rules can be defined as a conjunction of disjunction of clauses (an `and|AND` of `or|OR`s) that evaluate to either `true` (`PASS`) or `false` (`FAIL`). This is similar to [Conjunctive normal form](https://en.wikipedia.org/wiki/Conjunctive_normal_form). 

The following examples demonstrate the order of evaluations of clauses.

```
# (clause_E v clause_F) ^ clause_G
clause_E OR clause_F
clause_G

# (clause_H v clause_I) ^ (clause_J v clause_K)
clause_H OR
clause_I
clause_J OR
clause_K

# (clause_L v clause_M v clause_N) ^ clause_O
clause_L OR
clause_M OR
clause_N 
clause_O
```

All clauses that are based on the example `Template-1` can be combined by using conjunction. See the following example.

```
Resources.S3Bucket.Properties.BucketName is_string
Resources.S3Bucket.Properties.BucketName != /(?i)encrypt/
Resources.S3Bucket.Properties.BucketEncryption exists
Resources.S3Bucket.Properties.BucketEncryption is_struct
Resources.S3Bucket.Properties.Tags is_list
Resources.S3Bucket.Properties.Tags !empty
```

## Using blocks with Guard rules
<a name="blocks"></a>

Blocks are compositions that remove verbosity and repetition from a set of related clauses, conditions, or rules. There are three types of blocks:
+ Query blocks
+ `when` blocks
+ Named-rule blocks

### Query blocks
<a name="query-blocks"></a>

Following are the clauses that are based on the example `Template-1`. Conjunction was used to combine the clauses.

```
Resources.S3Bucket.Properties.BucketName is_string
Resources.S3Bucket.Properties.BucketName != /(?i)encrypt/
Resources.S3Bucket.Properties.BucketEncryption exists
Resources.S3Bucket.Properties.BucketEncryption is_struct
Resources.S3Bucket.Properties.Tags is_list
Resources.S3Bucket.Properties.Tags !empty
```

Parts of the query expression in each clause are repeated. You can improve composability and remove verbosity and repetition from a set of related clauses with the same initial query path by using a query block. The same set of clauses can be written as shown in the following example.

```
Resources.S3Bucket.Properties {
    BucketName is_string
    BucketName != /(?i)encrypt/
    BucketEncryption exists
    BucketEncryption is_struct
    Tags is_list
    Tags !empty
}
```

In a query block, the query preceding the block sets the context for the clauses inside the block.

For more information about using blocks, see [Composing named-rule blocks](named-rule-block-composition.md).

### `when` blocks
<a name="when-blocks"></a>

You can evaluate blocks conditionally by using `when` blocks, which take the following form.

```
  when <condition> {
       Guard_rule_1
       Guard_rule_2
       ...
   }
```

The `when` keyword designates the start of the `when` block. `condition` is a Guard rule. The block is only evaluated if the evaluation of the condition results in `true` (`PASS`).

The following is an example `when` block that is based on `Template-1`.

```
when Resources.S3Bucket.Properties.BucketName is_string {
     Resources.S3Bucket.Properties.BucketName != /(?i)encrypt/
 }
```

The clause within the `when` block is only evaluated if the value specified for `BucketName` is a string. If the value specified for `BucketName` is referenced in the `Parameters` section of the template as shown in the following example, the clause within the `when` block is not evaluated.

```
Parameters:
   S3BucketName:
     Type: String
 Resources:
   S3Bucket:
     Type: AWS::S3::Bucket
     Properties:
       BucketName: 
         Ref: S3BucketName
     ...
```

### Named-rule blocks
<a name="named-rule-blocks"></a>

You can assign a name to a set of rules (*rule set*), and then reference these modular validation blocks, called *named-rule blocks*, in other rules. Named-rule blocks take the following form.

```
  rule <rule name> [when <condition>] {
    Guard_rule_1
    Guard_rule_2
    ...
    }
```

The `rule` keyword designates the start of the named-rule block.

`rule name` is a human-readable string that uniquely identifies a named-rule block. It's a label for the Guard rule set that it encapsulates. In this use, the term *Guard rule* includes clauses, query blocks, `when` blocks, and named-rule blocks. The rule name can be used to refer to the evaluation result of the rule set that it encapsulates, which makes named-rule blocks reusable. The rule name also provides context about rule failures in the `validate` and `test` command outputs. The rule name is displayed along with the block’s evaluation status (`PASS`, `FAIL`, or `SKIP`) in the evaluation output of the rules file. See the following example.

```
# Sample output of an evaluation where check1, check2, and check3 are rule names.
template.json Status = **FAIL**
**SKIP rules**
check1 **SKIP**
**PASS rules**
check2 **PASS**
**FAILED rules**
check3 **FAIL**
```

You can also evaluate named-rule blocks conditionally by specifying the `when` keyword followed by a condition after the rule name.

Following is the example `when` block that was discussed previously in this topic.

```
rule checkBucketNameStringValue when Resources.S3Bucket.Properties.BucketName is_string {
    Resources.S3Bucket.Properties.BucketName != /(?i)encrypt/
}
```

Using named-rule blocks, the preceding can also be written as follows.

```
rule checkBucketNameIsString {
    Resources.S3Bucket.Properties.BucketName is_string
}
rule checkBucketNameStringValue when checkBucketNameIsString {
    Resources.S3Bucket.Properties.BucketName != /(?i)encrypt/
}
```

You can reuse and group named-rule blocks with other Guard rules. Following are a few examples.

```
rule rule_name_A {
    Guard_rule_1 OR
    Guard_rule_2
    ...
}

rule rule_name_B {
    Guard_rule_3
    Guard_rule_4
    ...
}

rule rule_name_C {
    rule_name_A OR rule_name_B
}

rule rule_name_D {
    rule_name_A
    rule_name_B
}

rule rule_name_E when rule_name_D {
    Guard_rule_5
    Guard_rule_6
    ...
}
```

## Using built-in functions
<a name="built-in-functions"></a>

AWS CloudFormation Guard provides built-in functions that you can use in your rules to perform operations such as string manipulation, JSON parsing, and data type conversion. Functions are supported only through assignment to a variable.

### Key functions
<a name="key-functions"></a>

`json_parse(json_string)`  
Parses inline JSON strings from a template. After parsing, you can evaluate properties of the resulting object.

`count(collection)`  
Returns the number of items that a query resolves to.

`regex_replace(base_string, regex_to_extract, regex_replacement)`  
Replaces parts of a string using regular expressions.

For a complete list of available functions including string manipulation, collection operations, and data type conversion functions, see the [Functions documentation](https://github.com/aws-cloudformation/cloudformation-guard/blob/main/docs/FUNCTIONS.md) in the Guard GitHub repository.

# Defining Guard queries and filtering
<a name="query-and-filtering"></a>

This topic covers writing queries and using filtering when writing Guard rule clauses.

## Prerequisites
<a name="query-filtering-prerequisites"></a>

Filtering is an advanced AWS CloudFormation Guard concept. We recommend that you review the following foundational topics before you learn about filtering:
+ [What is AWS CloudFormation Guard?](what-is-guard.md)
+ [Writing rules, clauses](writing-rules.md)

## Defining queries
<a name="defining-queries"></a>

Query expressions are simple dot (`.`) separated expressions written to traverse hierarchical data. Query expressions can include filter expressions to target a subset of values. When queries are evaluated, they result in a collection of values, similar to a result set returned from an SQL query.

The following example query searches a CloudFormation template for `AWS::IAM::Role` resources.

```
Resources.*[ Type == 'AWS::IAM::Role' ]
```

Queries follow these basic principles:
+ Each dot (`.`) part of the query traverses down the hierarchy when an explicit key term is used, such as `Resources` or `Properties.Encrypted.` If any part of the query doesn't match the incoming datum, Guard throws a retrieval error.
+ A dot (`.`) part of the query that uses a wildcard `*` traverses all values for the structure at that level.
+ A dot (`.`) part of the query that uses an array wildcard `[*]` traverses all indices for that array.
+ All collections can be filtered by specifying filters inside square brackets `[]`. Collections can be encountered in the following ways:
  + Naturally occurring arrays in datum are collections. Following are examples:

    Ports: `[20, 21, 110, 190]`

    Tags: `[{"Key": "Stage", "Value": "PROD"}, {"Key": "App", "Value": "MyService"}]`
  + When traversing all values for a structure like `Resources.*`
  + Any query result is itself a collection from which values can be further filtered. See the following example.

    ```
    # Query all resources
    let all_resources = Resource.*
    
    # Filter IAM resources from query results
    let iam_resources = %resources[ Type == /IAM/ ]
    
    # Further refine to get managed policies
    let managed_policies = %iam_resources[ Type == /ManagedPolicy/ ]
    
    # Traverse each managed policy
    %managed_policies {
        # Do something with each policy
    }
    ```

The following is an example CloudFormation template snippet.

```
Resources:
  SampleRole:
    Type: AWS::IAM::Role
    ...
  SampleInstance:
    Type: AWS::EC2::Instance
    ...
  SampleVPC:
     Type: AWS::EC2::VPC
    ...
  SampleSubnet1:
    Type: AWS::EC2::Subnet
    ...
  SampleSubnet2:
    Type: AWS::EC2::Subnet
    ...
```

Based on this template, the path traversed is `SampleRole` and the final value selected is `Type: AWS::IAM::Role`.

```
Resources:
  SampleRole:
    Type: AWS::IAM::Role
    ...
```

The resulting value of the query `Resources.*[ Type == 'AWS::IAM::Role' ]` in YAML format is shown in the following example.

```
- Type: AWS::IAM::Role
  ...
```

Some of the ways that you can use queries are as follows:
+ Assign a query to variables so that query results can be accessed by referencing those variables.
+ Follow the query with a block that tests against each of the selected values.
+ Compare a query directly against a basic clause.

## Assigning queries to variables
<a name="queries-and-filtering-variables"></a>

Guard supports one-shot variable assignments within a given scope. For more information about variables in Guard rules, see [Assigning and referencing variables in Guard rules](variables.md).

You can assign queries to variables so that you can write queries once and then reference them elsewhere in your Guard rules. See the following example variable assignments that demonstrate query principles discussed later in this section.

```
#
# Simple query assignment
#
let resources = Resources.* # All resources

#
# A more complex query here (this will be explained below)
#
let iam_policies_allowing_log_creates = Resources.*[
    Type in [/IAM::Policy/, /IAM::ManagedPolicy/]
    some Properties.PolicyDocument.Statement[*] {
         some Action[*] == 'cloudwatch:CreateLogGroup'
         Effect == 'Allow'
    }
]
```

## Directly looping through values from a variable assigned to a query
<a name="variable-assigned-from-query"></a>

Guard supports directly running against the results from a query. In the following example, the `when` block tests against the `Encrypted`, `VolumeType`, and `AvailabilityZone` property for each `AWS::EC2::Volume` resource found in a CloudFormation template.

```
let ec2_volumes = Resources.*[ Type == 'AWS::EC2::Volume' ] 

when %ec2_volumes !empty {
    %ec2_volumes {
        Properties {
            Encrypted == true
            VolumeType in ['gp2', 'gp3']
            AvailabilityZone in ['us-west-2b', 'us-west-2c']
        }
    }
}
```

## Direct clause-level comparisons
<a name="direct-clause-level-comparisons"></a>

Guard also supports queries as a part of direct comparisons. For example, see the following.

```
let resources = Resources.*
    
    some %resources.Properties.Tags[*].Key == /PROD$/
    some %resources.Properties.Tags[*].Value == /^App/
```

In the preceding example, the two clauses (starting with the `some` keyword) expressed in the form shown are considered independent clauses and are evaluated separately.

### Single clause and block clause form
<a name="single-versus-block-clause-form"></a>

Taken together, the two example clauses shown in the preceding section aren't equivalent to the following block.

```
let resources = Resources.*

some %resources.Properties.Tags[*] {
    Key == /PROD$/
    Value == /^App/
}
```

This block queries for each `Tag` value in the collection and compares its property values to the expected property values. The combined form of the clauses in the preceding section evaluates the two clauses independently. Consider the following input.

```
Resources:
  ...
  MyResource:
    ...
    Properties:
      Tags:
        - Key: EndPROD
          Value: NotAppStart
        - Key: NotPRODEnd
          Value: AppStart
```

Clauses in the first form evaluate to `PASS`. When validating the first clause in first form, the following path across `Resources`, `Properties`, `Tags`, and `Key` matches the value `NotPRODEnd` and does not match the expected value `PROD`.

```
Resources:
  ...
  MyResource:
    ...
    Properties:
      Tags:
        - Key: EndPROD
          Value: NotAppStart
        - Key: NotPRODEnd
          Value: AppStart
```

The same happens with the second clause of the first form. The path across `Resources`, `Properties`, `Tags`, and `Value` matches the value `AppStart`. As a result, the second clause independently.

The overall result is a `PASS`.

However, the block form evaluates as follows. For each `Tags` value, it compares if both the `Key` and `Value` does match; `NotAppStart` and `NotPRODEnd` values are not matched in the following example.

```
Resources:
  ...
  MyResource:
    ...
    Properties:
      Tags:
        - Key: EndPROD
          Value: NotAppStart
        - Key: NotPRODEnd
          Value: AppStart
```

Because evaluations check for both `Key == /PROD$/`, and `Value == /^App/`, the match is not complete. Therefore, the result is `FAIL`.

**Note**  
When working with collections, we recommend that you use the block clause form when you want to compare multiple values for each element in the collection. Use the single clause form when the collection is a set of scalar values, or when you only intend to compare a single attribute.

## Query outcomes and associated clauses
<a name="query-outcomes"></a>

All queries return a list of values. Any part of a traversal, such as a missing key, empty values for an array (`Tags: []`) when accessing all indices, or missing values for a map when encountering an empty map (`Resources: {}`), can lead to retrieval errors.

All retrieval errors are considered failures when evaluating clauses against such queries. The only exception is when explicit filters are used in the query. When filters are used, associated clauses are skipped.

The following block failures are associated with running queries.
+ If a template does not contain resources, then the query evaluates to `FAIL`, and the associated block level clauses also evaluate to `FAIL`.
+ When a template contains an empty resources block like `{ "Resources": {} }`, the query evaluates to `FAIL`, and the associated block level clauses also evaluate to `FAIL`.
+ If a template contains resources but none match the query, then the query returns empty results, and the block level clauses are skipped.

## Using filters in queries
<a name="filtering"></a>

Filters in queries are effectively Guard clauses that are used as selection criteria. Following is the structure of a clause.

```
 <query> <operator> [query|value literal] [message] [or|OR]
```

Keep in mind the following key points from [Writing AWS CloudFormation Guard rules](writing-rules.md) when you work with filters:
+ Combine clauses by using [Conjunctive Normal Form (CNF)](https://en.wikipedia.org/wiki/Conjunctive_normal_form).
+ Specify each conjunction (`and`) clause on a new line.
+ Specify disjunctions (`or`) by using the `or` keyword between two clauses.

The following example demonstrates conjunctive and disjunctive clauses.

```
resourceType == 'AWS::EC2::SecurityGroup'
InputParameters.TcpBlockedPorts not empty 

InputParameters.TcpBlockedPorts[*] {
    this in r(100, 400] or 
    this in r(4000, 65535]
}
```

### Using clauses for selection criteria
<a name="selection-criteria"></a>

You can apply filtering to any collection. Filtering can be applied directly on attributes in the input that are already a collection like `securityGroups: [....]`. You can also apply filtering against a query, which is always a collection of values. You can use all features of clauses, including conjunctive normal form, for filtering.

The following common query is often used when selecting resources by type from a CloudFormation template.

```
Resources.*[ Type == 'AWS::IAM::Role' ]
```

The query `Resources.*` returns all values present in the `Resources` section of the input. For the example template input in [Defining queries](#defining-queries), the query returns the following.

```
- Type: AWS::IAM::Role
  ...
- Type: AWS::EC2::Instance
  ...
- Type: AWS::EC2::VPC
  ...
- Type: AWS::EC2::Subnet
  ...
- Type: AWS::EC2::Subnet
  ...
```

Now, apply the filter against this collection. The criterion to match is `Type == AWS::IAM::Role`. Following is the output of the query after the filter is applied.

```
- Type: AWS::IAM::Role
  ...
```

Next, check various clauses for `AWS::IAM::Role` resources.

```
let all_resources = Resources.*
let all_iam_roles = %all_resources[ Type == 'AWS::IAM::Role' ]
```

The following is an example filtering query that selects all `AWS::IAM::Policy` and `AWS::IAM::ManagedPolicy` resources.

```
Resources.*[
    Type in [ /IAM::Policy/,
              /IAM::ManagedPolicy/ ]
]
```

The following example checks if these policy resources have a `PolicyDocument` specified.

```
Resources.*[ 
    Type in [ /IAM::Policy/,
              /IAM::ManagedPolicy/ ]
    Properties.PolicyDocument exists
]
```

### Building out more complex filtering needs
<a name="complex-filtering"></a>

Consider the following example of an AWS Config configuration item for ingress and egress security groups information.

```
---
resourceType: 'AWS::EC2::SecurityGroup'
configuration:
  ipPermissions:
    - fromPort: 172
      ipProtocol: tcp
      toPort: 172
      ipv4Ranges:
        - cidrIp: 10.0.0.0/24
        - cidrIp: 0.0.0.0/0
    - fromPort: 89
      ipProtocol: tcp
      ipv6Ranges:
        - cidrIpv6: '::/0'
      toPort: 189
      userIdGroupPairs: []
      ipv4Ranges:
        - cidrIp: 1.1.1.1/32
    - fromPort: 89
      ipProtocol: '-1'
      toPort: 189
      userIdGroupPairs: []
      ipv4Ranges:
        - cidrIp: 1.1.1.1/32
  ipPermissionsEgress:
    - ipProtocol: '-1'
      ipv6Ranges: []
      prefixListIds: []
      userIdGroupPairs: []
      ipv4Ranges:
        - cidrIp: 0.0.0.0/0
      ipRanges:
        - 0.0.0.0/0
  tags:
    - key: Name
      value: good-sg-delete-me
  vpcId: vpc-0123abcd
InputParameter:
  TcpBlockedPorts:
    - 3389
    - 20
    - 21
    - 110
    - 143
```

Note the following:
+ `ipPermissions` (ingress rules) is a collection of rules inside a configuration block.
+ Each rule structure contains attributes such as `ipv4Ranges` and `ipv6Ranges` to specify a collection of CIDR blocks.

Let’s write a rule that selects any ingress rules that allow connections from any IP address, and verifies that the rules do not allow TCP blocked ports to be exposed.

Start with the query portion that covers IPv4, as shown in the following example.

```
configuration.ipPermissions[
    #
    # at least one ipv4Ranges equals ANY IPv4
    #
    some ipv4Ranges[*].cidrIp == '0.0.0.0/0'
]
```

The `some` keyword is useful in this context. All queries return a collection of values that match the query. By default, Guard evaluates that all values returned as a result of the query are matched against checks. However, this behavior might not always be what you need for checks. Consider the following part of the input from the configuration item.

```
ipv4Ranges: 
  - cidrIp: 10.0.0.0/24
  - cidrIp: 0.0.0.0/0 # any IP allowed
```

There are two values present for `ipv4Ranges`. Not all `ipv4Ranges` values equal an IP address denoted by `0.0.0.0/0`. You want to see if at least one value matches `0.0.0.0/0`. You tell Guard that not all results returned from a query need to match, but at least one result must match. The `some` keyword tells Guard to ensure that one or more values from the resultant query match the check. If no query result values match, Guard throws an error.

Next, add IPv6, as shown in the following example.

```
configuration.ipPermissions[
    #
    # at-least-one ipv4Ranges equals ANY IPv4
    #
    some ipv4Ranges[*].cidrIp == '0.0.0.0/0' or
    #
    # at-least-one ipv6Ranges contains ANY IPv6
    #    
    some ipv6Ranges[*].cidrIpv6 == '::/0'
]
```

Finally, in the following example, validate that the protocol is not `udp`.

```
configuration.ipPermissions[
    #
    # at-least-one ipv4Ranges equals ANY IPv4
    #
    some ipv4Ranges[*].cidrIp == '0.0.0.0/0' or
    #
    # at-least-one ipv6Ranges contains ANY IPv6
    #    
    some ipv6Ranges[*].cidrIpv6 == '::/0'
    
    #
    # and ipProtocol is not udp
    #
    ipProtocol != 'udp' ] 
]
```

The following is the complete rule.

```
rule any_ip_ingress_checks
{

    let ports = InputParameter.TcpBlockedPorts[*]

    let targets = configuration.ipPermissions[
        #
        # if either ipv4 or ipv6 that allows access from any address
        #
        some ipv4Ranges[*].cidrIp == '0.0.0.0/0' or
        some ipv6Ranges[*].cidrIpv6 == '::/0'

        #
        # the ipProtocol is not UDP
        #
        ipProtocol != 'udp' ]
        
    when %targets !empty
    {
        %targets {
            ipProtocol != '-1'
            <<
              result: NON_COMPLIANT
              check_id: HUB_ID_2334
              message: Any IP Protocol is allowed
            >>

            when fromPort exists 
                 toPort exists 
            {
                let each_target = this
                %ports {
                    this < %each_target.fromPort or
                    this > %each_target.toPort
                    <<
                        result: NON_COMPLIANT
                        check_id: HUB_ID_2340
                        message: Blocked TCP port was allowed in range
                    >>
                }
            }

        }       
     }
}
```

### Separating collections based on their contained types
<a name="splitting-collection"></a>

When using infrastructure as code (IaC) configuration templates, you might encounter a collection that contains references to other entities within the configuration template. The following is an example CloudFormation template that describes Amazon Elastic Container Service (Amazon ECS) tasks with a local reference to `TaskRoleArn`, a reference to `TaskArn`, and a direct string reference.

```
Parameters:
  TaskArn:
    Type: String
Resources:
  ecsTask:
    Type: 'AWS::ECS::TaskDefinition'
    Metadata:
      SharedExectionRole: allowed
    Properties:
      TaskRoleArn: 'arn:aws:....'
      ExecutionRoleArn: 'arn:aws:...'
  ecsTask2:
    Type: 'AWS::ECS::TaskDefinition'
    Metadata:
      SharedExectionRole: allowed
    Properties:
      TaskRoleArn:
        'Fn::GetAtt':
          - iamRole
          - Arn
      ExecutionRoleArn: 'arn:aws:...2'
  ecsTask3:
    Type: 'AWS::ECS::TaskDefinition'
    Metadata:
      SharedExectionRole: allowed
    Properties:
      TaskRoleArn:
        Ref: TaskArn
      ExecutionRoleArn: 'arn:aws:...2'
  iamRole:
    Type: 'AWS::IAM::Role'
    Properties:
      PermissionsBoundary: 'arn:aws:...3'
```

Consider the following query.

```
let ecs_tasks = Resources.*[ Type == 'AWS::ECS::TaskDefinition' ]
```

This query returns a collection of values that contains all three `AWS::ECS::TaskDefinition` resources shown in the example template. Separate `ecs_tasks` that contain `TaskRoleArn` local references from others, as shown in the following example.

```
let ecs_tasks = Resources.*[ Type == 'AWS::ECS::TaskDefinition' ]

let ecs_tasks_role_direct_strings = %ecs_tasks[ 
    Properties.TaskRoleArn is_string ]

let ecs_tasks_param_reference = %ecs_tasks[
    Properties.TaskRoleArn.'Ref' exists ]

rule task_role_from_parameter_or_string {
    %ecs_tasks_role_direct_strings !empty or
    %ecs_tasks_param_reference !empty
}

rule disallow_non_local_references {
    # Known issue for rule access: Custom message must start on the same line
    not task_role_from_parameter_or_string 
    <<
        result: NON_COMPLIANT
        message: Task roles are not local to stack definition
    >>
}
```

# Assigning and referencing variables in Guard rules
<a name="variables"></a>

You can assign variables in your AWS CloudFormation Guard rules files to store information that you want to reference in your Guard rules. Guard supports one-shot variable assignment. Variables are evaluated lazily, meaning that Guard only evaluates variables when rules are run.

**Topics**
+ [Assigning variables](#assigning-variables)
+ [Referencing variables](#referencing-variables)
+ [Variable scope](#variable-scope)
+ [Examples of variables in Guard rules files](#variables-examples)

## Assigning variables
<a name="assigning-variables"></a>

Use the `let` keyword to initialize and assign a variable. As a best practice, use snake case for variable names. Variables can store static literals or dynamic properties resulting from queries. In the following example, the variable `ecs_task_definition_task_role_arn` stores the static string value `arn:aws:iam:123456789012:role/my-role-name`.

```
let ecs_task_definition_task_role_arn = 'arn:aws:iam::123456789012:role/my-role-name'
```

In the following example, the variable `ecs_tasks` stores the results of a query that searches for all `AWS::ECS::TaskDefinition` resources in an CloudFormation template. You could reference `ecs_tasks` to access information about those resources when you write rules.

```
let ecs_tasks = Resources.*[
    Type == 'AWS::ECS::TaskDefinition'
]
```

## Referencing variables
<a name="referencing-variables"></a>

Use the `%` prefix to reference a variable.

Based on the `ecs_task_definition_task_role_arn` variable example in [Assigning variables](#assigning-variables), you can reference `ecs_task_definition_task_role_arn` in the `query|value literal` section of a Guard rule clause. Using that reference ensures that the value specified for the `TaskDefinitionArn` property of any `AWS::ECS::TaskDefinition` resources in a CloudFormation template is the static string value `arn:aws:iam:123456789012:role/my-role-name`.

```
Resources.*.Properties.TaskDefinitionArn == %ecs_task_definition_role_arn
```

Based on the `ecs_tasks` variable example in [Assigning variables](#assigning-variables), you can reference `ecs_tasks` in a query (for example, %ecs\$1tasks.Properties). First, Guard evaluates the variable `ecs_tasks` and then uses the returned values to traverse the hierarchy. If the variable `ecs_tasks` resolves to non-string values, then Guard throws an error.

**Note**  
Currently, Guard doesn't support referencing variables inside custom error messages.

## Variable scope
<a name="variable-scope"></a>

Scope refers to the visibility of variables defined in a rules file. A variable name can only be used once within a scope. There are three levels where a variable can be declared, or three possible variable scopes:
+ **File-level** – Usually declared at the top of the rules file, you can use file-level variables in all rules within the rules file. They are visible to the entire file.

  In the following example rules file, the variables `ecs_task_definition_task_role_arn` and `ecs_task_definition_execution_role_arn` are initialized at the file-level.

  ```
  let ecs_task_definition_task_role_arn = 'arn:aws:iam::123456789012:role/my-task-role-name'
  let ecs_task_definition_execution_role_arn = 'arn:aws:iam::123456789012:role/my-execution-role-name'
  
  rule check_ecs_task_definition_task_role_arn
  {
      Resources.*.Properties.TaskRoleArn == %ecs_task_definition_task_role_arn
  }
  
  rule check_ecs_task_definition_execution_role_arn
  {
      Resources.*.Properties.ExecutionRoleArn == %ecs_task_definition_execution_role_arn
  }
  ```
+ **Rule-level** – Declared within a rule, rule-level variables are only visible to that specific rule. Any references outside of the rule result in an error.

  In the following example rules file, the variables `ecs_task_definition_task_role_arn` and `ecs_task_definition_execution_role_arn` are initialized at the rule-level. The `ecs_task_definition_task_role_arn` can only be referenced within the `check_ecs_task_definition_task_role_arn` named rule. You can only reference the `ecs_task_definition_execution_role_arn` variable within the `check_ecs_task_definition_execution_role_arn` named rule.

  ```
  rule check_ecs_task_definition_task_role_arn
  {
      let ecs_task_definition_task_role_arn = 'arn:aws:iam::123456789012:role/my-task-role-name'
      Resources.*.Properties.TaskRoleArn == %ecs_task_definition_task_role_arn
  }
  
  rule check_ecs_task_definition_execution_role_arn
  {
      let ecs_task_definition_execution_role_arn = 'arn:aws:iam::123456789012:role/my-execution-role-name'
      Resources.*.Properties.ExecutionRoleArn == %ecs_task_definition_execution_role_arn
  }
  ```
+ **Block-level** – Declared within a block, such as a `when` clause, block-level variables are only visible to that specific block. Any references outside of the block result in an error.

  In the following example rules file, the variables `ecs_task_definition_task_role_arn` and `ecs_task_definition_execution_role_arn` are initialized at the block-level within the `AWS::ECS::TaskDefinition` type block. You can only reference the `ecs_task_definition_task_role_arn` and `ecs_task_definition_execution_role_arn` variables within the `AWS::ECS::TaskDefinition` type blocks for their respective rules.

  ```
  rule check_ecs_task_definition_task_role_arn
  {
      AWS::ECS::TaskDefinition
      {
          let ecs_task_definition_task_role_arn = 'arn:aws:iam::123456789012:role/my-task-role-name'
          Properties.TaskRoleArn == %ecs_task_definition_task_role_arn
      }
  }
  
  rule check_ecs_task_definition_execution_role_arn
  {
      AWS::ECS::TaskDefinition
      {
          let ecs_task_definition_execution_role_arn = 'arn:aws:iam::123456789012:role/my-execution-role-name'
          Properties.ExecutionRoleArn == %ecs_task_definition_execution_role_arn
      }
  }
  ```

## Examples of variables in Guard rules files
<a name="variables-examples"></a>

The following sections provide examples of both static and dynamic assignment of variables.

### Static assignment
<a name="assigning-static-variables"></a>

The following is an example CloudFormation template.

```
Resources:
  EcsTask:
    Type: 'AWS::ECS::TaskDefinition'
    Properties:
      TaskRoleArn: 'arn:aws:iam::123456789012:role/my-role-name'
```

Based on this template, you can write a rule called `check_ecs_task_definition_task_role_arn` that ensures that the `TaskRoleArn` property of all `AWS::ECS::TaskDefinition` template resources is `arn:aws:iam::123456789012:role/my-role-name`.

```
rule check_ecs_task_definition_task_role_arn
{
    let ecs_task_definition_task_role_arn = 'arn:aws:iam::123456789012:role/my-role-name'
    Resources.*.Properties.TaskRoleArn == %ecs_task_definition_task_role_arn
}
```

Within the scope of the rule, you can initialize a variable called `ecs_task_definition_task_role_arn` and assign to it the static string value `'arn:aws:iam::123456789012:role/my-role-name'`. The rule clause checks whether the value specified for the `TaskRoleArn` property of the `EcsTask` resource is `arn:aws:iam::123456789012:role/my-role-name` by referencing the `ecs_task_definition_task_role_arn` variable in the `query|value literal` section.

### Dynamic assignment
<a name="example-dynamic-assignment"></a>

The following is an example CloudFormation template.

```
Resources:
  EcsTask:
    Type: 'AWS::ECS::TaskDefinition'
    Properties:
      TaskRoleArn: 'arn:aws:iam::123456789012:role/my-role-name'
```

Based on this template, you can initialize a variable called `ecs_tasks` within the scope of the file and assign to it the query `Resources.*[ Type == 'AWS::ECS::TaskDefinition'`. Guard queries all resources in the input template and stores information about them in `ecs_tasks`. You can also write a rule called `check_ecs_task_definition_task_role_arn` that ensures that the `TaskRoleArn` property of all `AWS::ECS::TaskDefinition` template resources is `arn:aws:iam::123456789012:role/my-role-name`

```
let ecs_tasks = Resources.*[
    Type == 'AWS::ECS::TaskDefinition'
]

rule check_ecs_task_definition_task_role_arn
{
    %ecs_tasks.Properties.TaskRoleArn == 'arn:aws:iam::123456789012:role/my-role-name'
}
```

The rule clause checks whether the value specified for the `TaskRoleArn` property of the `EcsTask` resource is `arn:aws:iam::123456789012:role/my-role-name` by referencing the `ecs_task_definition_task_role_arn` variable in the `query` section.

### Enforcing CloudFormation template configuration
<a name="example-3"></a>

Let’s walk through a more complex example of a production use case. In this example, we write Guard rules to ensure stricter controls on how Amazon ECS tasks are defined.

The following is an example CloudFormation template.

```
Resources:
  EcsTask:
    Type: 'AWS::ECS::TaskDefinition'
    Properties:
      TaskRoleArn: 
        'Fn::GetAtt': [TaskIamRole, Arn]
      ExecutionRoleArn:
        'Fn::GetAtt': [ExecutionIamRole, Arn]

  TaskIamRole:
    Type: 'AWS::IAM::Role'
    Properties:
      PermissionsBoundary: 'arn:aws:iam::123456789012:policy/MyExamplePolicy'

  ExecutionIamRole:
    Type: 'AWS::IAM::Role'
    Properties:
      PermissionsBoundary: 'arn:aws:iam::123456789012:policy/MyExamplePolicy'
```

Based on this template, we write the following rules to ensure that these requirements are met:
+ Each `AWS::ECS::TaskDefinition` resource in the template has both a task role and an execution role attached.
+ The task roles and execution roles are AWS Identity and Access Management (IAM) roles.
+ The roles are defined in the template.
+ The `PermissionsBoundary` property is specified for each role.

```
# Select all Amazon ECS task definition resources from the template
let ecs_tasks = Resources.*[
    Type == 'AWS::ECS::TaskDefinition'
]

# Select a subset of task definitions whose specified value for the TaskRoleArn property is an Fn::Gett-retrievable attribute
let task_role_refs = some %ecs_tasks.Properties.TaskRoleArn.'Fn::GetAtt'[0]

# Select a subset of TaskDefinitions whose specified value for the ExecutionRoleArn property is an Fn::Gett-retrievable attribute
let execution_role_refs = some %ecs_tasks.Properties.ExecutionRoleArn.'Fn::GetAtt'[0]

# Verify requirement #1
rule all_ecs_tasks_must_have_task_end_execution_roles 
    when %ecs_tasks !empty 
{
    %ecs_tasks.Properties {
        TaskRoleArn exists
        ExecutionRoleArn exists
    }
}

# Verify requirements #2 and #3
rule all_roles_are_local_and_type_IAM
    when all_ecs_tasks_must_have_task_end_execution_roles
{
    let task_iam_references = Resources.%task_role_refs
    let execution_iam_reference = Resources.%execution_role_refs

    when %task_iam_references !empty {
        %task_iam_references.Type == 'AWS::IAM::Role'
    }

    when %execution_iam_reference !empty {
        %execution_iam_reference.Type == 'AWS::IAM::Role'
    }
}

# Verify requirement #4
rule check_role_have_permissions_boundary
    when all_ecs_tasks_must_have_task_end_execution_roles
{
    let task_iam_references = Resources.%task_role_refs
    let execution_iam_reference = Resources.%execution_role_refs

    when %task_iam_references !empty {
        %task_iam_references.Properties.PermissionsBoundary exists
    }

    when %execution_iam_reference !empty {
        %execution_iam_reference.Properties.PermissionsBoundary exists
    }
}
```

# Composing named-rule blocks in AWS CloudFormation Guard
<a name="named-rule-block-composition"></a>

When writing named-rule blocks using AWS CloudFormation Guard, you can use the following two styles of composition:
+ Conditional dependency
+ Correlational dependency

Using either of these styles of dependency composition helps promote reusability and reduces verbosity and repetition in named-rule blocks.

**Topics**
+ [Prerequisites](#named-rules-prerequisites)
+ [Conditional dependency composition](#named-rules-conditional-dependency)
+ [Correlational dependency composition](#named-rules-correlational-dependency)

## Prerequisites
<a name="named-rules-prerequisites"></a>

Learn about named-rule blocks in [Writing rules](writing-rules.md#named-rule-blocks).

## Conditional dependency composition
<a name="named-rules-conditional-dependency"></a>

In this style of composition, the evaluation of a `when` block or a named-rule block has a conditional dependency on the evaluation result of one or more other named-rule blocks or clauses. The following example Guard rules file contains named-rule blocks that demonstrate conditional dependencies.

```
# Named-rule block, rule_name_A
rule rule_name_A {
    Guard_rule_1
    Guard_rule_2
    ...
}

# Example-1, Named-rule block, rule_name_B, takes a conditional dependency on rule_name_A
rule rule_name_B when rule_name_A {
    Guard_rule_3
    Guard_rule_4
    ...
}

# Example-2, when block takes a conditional dependency on rule_name_A
when rule_name_A {
    Guard_rule_3
    Guard_rule_4
    ...
}

# Example-3, Named-rule block, rule_name_C, takes a conditional dependency on rule_name_A ^ rule_name_B
rule rule_name_C when rule_name_A
                      rule_name_B {
    Guard_rule_3
    Guard_rule_4
    ...
}

# Example-4, Named-rule block, rule_name_D, takes a conditional dependency on (rule_name_A v clause_A) ^ clause_B ^ rule_name_B
rule rule_name_D when rule_name_A OR
                      clause_A
                      clause_B
                      rule_name_B {
    Guard_rule_3
    Guard_rule_4
    ...
}
```

In the preceding example rules file, `Example-1` has the following possible outcomes:
+ If `rule_name_A` evaluates to `PASS`, the Guard rules encapsulated by `rule_name_B` are evaluated.
+ If `rule_name_A` evaluates to `FAIL`, the Guard rules encapsulated by `rule_name_B` are not evaluated. `rule_name_B` evaluates to `SKIP`.
+ If `rule_name_A` evaluates to `SKIP`, the Guard rules encapsulated by `rule_name_B` are not evaluated. `rule_name_B` evaluates to `SKIP`.
**Note**  
This case happens if `rule_name_A` conditionally depends on a rule that evaluates to `FAIL` and results in `rule_name_A` evaluating to `SKIP`.

Following is an example of a configuration management database (CMDB) configuration item from an AWS Config item for ingress and egress security groups information. This example demonstrates conditional dependency composition.

```
rule check_resource_type_and_parameter {
    resourceType == /AWS::EC2::SecurityGroup/
    InputParameters.TcpBlockedPorts NOT EMPTY 
}

rule check_parameter_validity when check_resource_type_and_parameter {
    InputParameters.TcpBlockedPorts[*] {
        this in r[0,65535] 
    }
}

rule check_ip_procotol_and_port_range_validity when check_parameter_validity {
    let ports = InputParameters.TcpBlockedPorts[*]

    # 
    # select all ipPermission instances that can be reached by ANY IP address
    # IPv4 or IPv6 and not UDP
    #
    let configuration = configuration.ipPermissions[ 
        some ipv4Ranges[*].cidrIp == "0.0.0.0/0" or
        some ipv6Ranges[*].cidrIpv6 == "::/0"
        ipProtocol != 'udp' ] 
    when %configuration !empty {
        %configuration {
            ipProtocol != '-1'

            when fromPort exists 
                toPort exists {
                let ip_perm_block = this
                %ports {
                    this < %ip_perm_block.fromPort or
                    this > %ip_perm_block.toPort
                }
            }
        }
    }
}
```

In the preceding example, `check_parameter_validity` is conditionally dependent on `check_resource_type_and_parameter` and `check_ip_procotol_and_port_range_validity` is conditionally dependent on `check_parameter_validity`. The following is a configuration management database (CMDB) configuration item that conforms to the preceding rules.

```
---
version: '1.3'
resourceType: 'AWS::EC2::SecurityGroup'
resourceId: sg-12345678abcdefghi
configuration:
  description: Delete-me-after-testing
  groupName: good-sg-test-delete-me
  ipPermissions:
    - fromPort: 172
      ipProtocol: tcp
      ipv6Ranges: []
      prefixListIds: []
      toPort: 172
      userIdGroupPairs: []
      ipv4Ranges:
        - cidrIp: 0.0.0.0/0
      ipRanges:
        - 0.0.0.0/0
    - fromPort: 89
      ipProtocol: tcp
      ipv6Ranges:
        - cidrIpv6: '::/0'
      prefixListIds: []
      toPort: 89
      userIdGroupPairs: []
      ipv4Ranges:
        - cidrIp: 0.0.0.0/0
      ipRanges:
        - 0.0.0.0/0
  ipPermissionsEgress:
    - ipProtocol: '-1'
      ipv6Ranges: []
      prefixListIds: []
      userIdGroupPairs: []
      ipv4Ranges:
        - cidrIp: 0.0.0.0/0
      ipRanges:
        - 0.0.0.0/0
  tags:
    - key: Name
      value: good-sg-delete-me
  vpcId: vpc-0123abcd
InputParameters:
  TcpBlockedPorts:
    - 3389
    - 20
    - 110
    - 142
    - 1434
    - 5500
supplementaryConfiguration: {}
resourceTransitionStatus: None
```

## Correlational dependency composition
<a name="named-rules-correlational-dependency"></a>

In this style of composition, the evaluation of a `when` block or a named-rule block has a correlational dependency on the evaluation result of one or more other Guard rules. Correlational dependency can be achieved as follows.

```
# Named-rule block, rule_name_A, takes a correlational dependency on all of the Guard rules encapsulated by the named-rule block
rule rule_name_A {
    Guard_rule_1
    Guard_rule_2
    ...
}

# when block takes a correlational dependency on all of the Guard rules encapsulated by the when block
when condition {
    Guard_rule_1
    Guard_rule_2
    ...
}
```

To help you understand correlational dependency composition, review the following example of a Guard rules file.

```
#
# Allowed valid protocols for AWS::ElasticLoadBalancingV2::Listener resources
#
let allowed_protocols = [ "HTTPS", "TLS" ]

let elbs = Resources.*[ Type == 'AWS::ElasticLoadBalancingV2::Listener' ]

#
# If there are AWS::ElasticLoadBalancingV2::Listener resources present, ensure that they have protocols specified from the 
# list of allowed protocols and that the Certificates property is not empty
#
rule ensure_all_elbs_are_secure when %elbs !empty {
    %elbs.Properties {
        Protocol in %allowed_protocols
        Certificates !empty
    }
}

# 
# In addition to secure settings, ensure that AWS::ElasticLoadBalancingV2::Listener resources are private
#
rule ensure_elbs_are_internal_and_secure when %elbs !empty {
    ensure_all_elbs_are_secure
    %elbs.Properties.Scheme == 'internal'
}
```

In the preceding rules file, `ensure_elbs_are_internal_and_secure` has a correlational dependency on `ensure_all_elbs_are_secure`. The following is an example CloudFormation template that conforms to the preceding rules.

```
Resources:
  ServiceLBPublicListener46709EAA:
    Type: 'AWS::ElasticLoadBalancingV2::Listener'
    Properties:
      Scheme: internal
      Protocol: HTTPS
      Certificates:
        - CertificateArn: 'arn:aws:acm...'
  ServiceLBPublicListener4670GGG:
    Type: 'AWS::ElasticLoadBalancingV2::Listener'
    Properties:
      Scheme: internal
      Protocol: HTTPS
      Certificates:
        - CertificateArn: 'arn:aws:acm...'
```

# Writing clauses to perform context-aware evaluations
<a name="context-aware-evaluations"></a>

AWS CloudFormation Guard clauses are evaluated against hierarchical data. The Guard evaluation engine resolves queries against incoming data by following hierarchical data as specified, using a simple dotted notation. Frequently, multiple clauses are needed to evaluate against a map of data or a collection. Guard provides a convenient syntax to write such clauses. The engine is contextually aware and uses the corresponding data associated for evaluations.

The following is an example of a Kubernetes Pod configuration with containers, to which you can apply context-aware evaluations.

```
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
          cpu: 0.5
    - name: log-aggregator
      image: 'images.my-company.example/log-aggregator:v6'
      resources:
        requests:
          memory: 64Mi
          cpu: 0.25
        limits:
          memory: 128Mi
          cpu: 0.75
```

You can author Guard clauses to evaluate this data. When evaluating a rules file, the context is the entire input document. Following are example clauses that validate limits enforcement for containers specified in a Pod.

```
#
# At this level, the root document is available for evaluation
#

#
# Our rule only evaluates for apiVersion == v1 and K8s kind is Pod
#
rule ensure_container_limits_are_enforced
    when apiVersion == 'v1'
        kind == 'Pod' 
{
    spec.containers[*] {
        resources.limits {
            #
            # Ensure that cpu attribute is set
            #
            cpu exists
            <<
                Id: K8S_REC_18
                Description: CPU limit must be set for the container
            >> 

            #
            # Ensure that memory attribute is set
            #
            memory exists
            <<
                Id: K8S_REC_22
                Description: Memory limit must be set for the container
            >>
        }
    }
}
```

## Understanding `context` in evaluations
<a name="context"></a>

At the rule-block level, the incoming context is the complete document. Evaluations for the `when` condition happen against this incoming root context where the `apiVersion` and `kind` attributes are located. In the previous example, these conditions evaluate to `true`.

Now, traverse the hierarchy in `spec.containers[*]` shown in the preceding example. For each traverse of the hierarchy, the context value changes accordingly. After the traversal of the `spec` block is finished, the context changes, as shown in the following example.

```
containers:
  - name: app
    image: 'images.my-company.example/app:v4'
    resources:
      requests:
        memory: 64Mi
        cpu: 0.25
      limits:
        memory: 128Mi
        cpu: 0.5
  - name: log-aggregator
    image: 'images.my-company.example/log-aggregator:v6'
    resources:
      requests:
        memory: 64Mi
        cpu: 0.25
      limits:
        memory: 128Mi
        cpu: 0.75
```

After traversing the `containers` attribute, the context is shown in the following example.

```
- name: app
  image: 'images.my-company.example/app:v4'
  resources:
    requests:
      memory: 64Mi
      cpu: 0.25
    limits:
      memory: 128Mi
      cpu: 0.5
- name: log-aggregator
  image: 'images.my-company.example/log-aggregator:v6'
  resources:
    requests:
      memory: 64Mi
      cpu: 0.25
    limits:
      memory: 128Mi
      cpu: 0.75
```

## Understanding loops
<a name="loops"></a>

You can use the expression `[*]` to define a loop for all values contained in the array for the `containers` attribute. The block is evaluated for each element inside `containers`. In the preceding example rule snippet, the clauses contained inside the block define checks to be validated against a container definition. The block of clauses contained inside is evaluated twice, once for each container definition.

```
{
    spec.containers[*] {
       ...
    }
}
```

For each iteration, the context value is the value at that corresponding index.

**Note**  
The only index access format supported is `[<integer>]` or `[*]`. Currently, Guard does not support ranges like `[2..4]`.

## Arrays
<a name="arrays"></a>

Often in places where an array is accepted, single values are also accepted. For example, if there is only one container, the array can be dropped and the following input is accepted.

```
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"
        cpu: 0.5
```

If an attribute can accept an array, ensure that your rule uses the array form. In the preceding example, you use `containers[*]` and not `containers`. Guard evaluates correctly when traversing the data when it encounters only the single-value form.

**Note**  
Always use the array form when expressing access for a rule clause when an attribute accepts an array. Guard evaluates correctly even in the case that a single value is used.

## Using the form `spec.containers[*]` instead of `spec.containers`
<a name="containers"></a>

Guard queries return a collection of resolved values. When you use the form `spec.containers`, the resolved values for the query contain the array referred to by `containers`, not the elements inside it. When you use the form `spec.containers[*]`, you refer to each individual element contained. Remember to use the `[*]` form whenever you intend to evaluate each element contained in the array.

## Using `this` to reference the current context value
<a name="this"></a>

When you author a Guard rule, you can reference the context value by using `this`. Often, `this` is implicit because it's bound to the context’s value. For example, `this.apiVersion`, `this.kind`, and `this.spec` are bound to the root or document. In contrast, `this.resources` is bound to each value for `containers`, such as `/spec/containers/0/` and `/spec/containers/1`. Similarly, `this.cpu` and `this.memory` map to limits, specifically `/spec/containers/0/resources/limits` and `/spec/containers/1/resources/limits`. 

In the next example, the preceding rule for the Kubernetes Pod configuration is rewritten to use `this` explicitly.

```
rule ensure_container_limits_are_enforced
    when this.apiVersion == 'v1'
         this.kind == 'Pod' 
{
    this.spec.containers[*] {
        this.resources.limits {
            #
            # Ensure that cpu attribute is set
            #
            this.cpu exists
            <<
                Id: K8S_REC_18
                Description: CPU limit must be set for the container
            >> 

            #
            # Ensure that memory attribute is set
            #
            this.memory exists
            <<
                Id: K8S_REC_22
                Description: Memory limit must be set for the container
            >>
        }
    }
}
```

You don't need to use `this` explicitly. However, the `this` reference can be useful when working with scalars, as shown in the following example.

```
InputParameters.TcpBlockedPorts[*] {
    this in r[0, 65535) 
    <<
        result: NON_COMPLIANT
        message: TcpBlockedPort not in range (0, 65535)
    >>
}
```

In the previous example, `this` is used to refer to each port number.

## Potential errors with the usage of implicit `this`
<a name="common-errors"></a>

When authoring rules and clauses, there are some common mistakes when referencing elements from the implicit `this` context value. For example, consider the following input datum to evaluate against (this must pass).

```
resourceType: 'AWS::EC2::SecurityGroup'
InputParameters:
  TcpBlockedPorts: [21, 22, 110]
configuration:
  ipPermissions:
  - fromPort: 172
    ipProtocol: tcp
    ipv6Ranges: []
    prefixListIds: []
    toPort: 172
    userIdGroupPairs: []
    ipv4Ranges:
      - cidrIp: "0.0.0.0/0"   
  - fromPort: 89
    ipProtocol: tcp
    ipv6Ranges:
      - cidrIpv6: "::/0"
    prefixListIds: []
    toPort: 109
    userIdGroupPairs: []
    ipv4Ranges:
      - cidrIp: 10.2.0.0/24
```

When tested against the preceding template, the following rule results in an error because it makes an incorrect assumption of leveraging the implicit `this`.

```
rule check_ip_procotol_and_port_range_validity
{
    # 
    # select all ipPermission instances that can be reached by ANY IP address
    # IPv4 or IPv6 and not UDP
    #
    let any_ip_permissions = configuration.ipPermissions[ 
        some ipv4Ranges[*].cidrIp == "0.0.0.0/0" or
        some ipv6Ranges[*].cidrIpv6 == "::/0"

        ipProtocol != 'udp' ]
    
    when %any_ip_permissions !empty
    {
        %any_ip_permissions {
            ipProtocol != '-1' # this here refers to each ipPermission instance
            InputParameters.TcpBlockedPorts[*] {
                fromPort > this or 
                toPort   < this 
                <<
                    result: NON_COMPLIANT
                    message: Blocked TCP port was allowed in range
                >>
            }                
        }
    }
}
```

To walk through this example, save the preceding rules file with the name `any_ip_ingress_check.guard` and the data with the file name `ip_ingress.yaml`. Then, run the following `validate` command with these files.

```
cfn-guard validate -r any_ip_ingress_check.guard -d ip_ingress.yaml --show-clause-failures
```

In the following output, the engine indicates that its attempt to retrieve a property `InputParameters.TcpBlockedPorts[*]` on the value `/configuration/ipPermissions/0`, `/configuration/ipPermissions/1` failed.

```
Clause #2     FAIL(Block[Location[file:any_ip_ingress_check.guard, line:17, column:13]])

              Attempting to retrieve array index or key from map at Path = /configuration/ipPermissions/0, Type was not an array/object map, Remaining Query = InputParameters.TcpBlockedPorts[*]

Clause #3     FAIL(Block[Location[file:any_ip_ingress_check.guard, line:17, column:13]])

              Attempting to retrieve array index or key from map at Path = /configuration/ipPermissions/1, Type was not an array/object map, Remaining Query = InputParameters.TcpBlockedPorts[*]
```

To help understand this result, rewrite the rule using `this` explicitly referenced.

```
rule check_ip_procotol_and_port_range_validity
{
    # 
    # select all ipPermission instances that can be reached by ANY IP address
    # IPv4 or IPv6 and not UDP
    #
    let any_ip_permissions = this.configuration.ipPermissions[ 
        some ipv4Ranges[*].cidrIp == "0.0.0.0/0" or
        some ipv6Ranges[*].cidrIpv6 == "::/0"

        ipProtocol != 'udp' ]
    
    when %any_ip_permissions !empty
    {
        %any_ip_permissions {
            this.ipProtocol != '-1' # this here refers to each ipPermission instance
            this.InputParameters.TcpBlockedPorts[*] {
                this.fromPort > this or 
                this.toPort   < this 
                <<
                    result: NON_COMPLIANT
                    message: Blocked TCP port was allowed in range
                >>
            }                
        }
    }
}
```

`this.InputParameters` references each value contained inside the variable `any_ip_permissions`. The query assigned to the variable selects `configuration.ipPermissions` values that match. The error indicates an attempt to retrieve `InputParamaters` in this context, but `InputParameters` was in the root context.

The inner block also references variables that are out of scope, as shown in the following example.

```
{
    this.ipProtocol != '-1' # this here refers to each ipPermission instance
    this.InputParameter.TcpBlockedPorts[*] { # ERROR referencing InputParameter off /configuration/ipPermissions[*]
        this.fromPort > this or # ERROR: implicit this refers to values inside /InputParameter/TcpBlockedPorts[*]
        this.toPort   < this 
        <<
            result: NON_COMPLIANT
            message: Blocked TCP port was allowed in range
        >>
    }
}
```

`this` refers to each port value in `[21, 22, 110]`, but it also refers to `fromPort` and `toPort`. They both belong to the outer block scope.

### Resolving errors with the implicit use of `this`
<a name="common-errors-resolution"></a>

Use variables to explicitly assign and reference values. First, `InputParameter.TcpBlockedPorts` is part of the input (root) context. Move `InputParameter.TcpBlockedPorts` out of the inner block and assign it explicitly, as shown in the following example.

```
rule check_ip_procotol_and_port_range_validity
{
     let ports = InputParameters.TcpBlockedPorts[*]
    # ... cut off for illustrating change
}
```

Then, refer to this variable explicitly.

```
rule check_ip_procotol_and_port_range_validity
{
    #
    # Important: Assigning InputParameters.TcpBlockedPorts results in an ERROR. 
    # We need to extract each port inside the array. The difference is the query
    # InputParameters.TcpBlockedPorts returns [[21, 20, 110]] whereas the query 
    # InputParameters.TcpBlockedPorts[*] returns [21, 20, 110]. 
    #
    let ports = InputParameters.TcpBlockedPorts[*]

    # 
    # select all ipPermission instances that can be reached by ANY IP address
    # IPv4 or IPv6 and not UDP
    #
    let any_ip_permissions = configuration.ipPermissions[ 
        some ipv4Ranges[*].cidrIp == "0.0.0.0/0" or
        some ipv6Ranges[*].cidrIpv6 == "::/0"

        ipProtocol != 'udp' ]
    
    when %any_ip_permissions !empty
    {
        %any_ip_permissions {
            this.ipProtocol != '-1' # this here refers to each ipPermission instance
            %ports {
                this.fromPort > this or 
                this.toPort   < this 
                <<
                    result: NON_COMPLIANT
                    message: Blocked TCP port was allowed in range
                >>
            }
        }
    }        
}
```

Do the same for inner `this` references within `%ports`.

However, all errors aren't fixed yet because the loop inside `ports` still has an incorrect reference. The following example shows the removal of the incorrect reference.

```
rule check_ip_procotol_and_port_range_validity
{
    #
    # Important: Assigning InputParameters.TcpBlockedPorts results in an ERROR. 
    # We need to extract each port inside the array. The difference is the query
    # InputParameters.TcpBlockedPorts returns [[21, 20, 110]] whereas the query 
    # InputParameters.TcpBlockedPorts[*] returns [21, 20, 110].
    #
    let ports = InputParameters.TcpBlockedPorts[*]

    # 
    # select all ipPermission instances that can be reached by ANY IP address
    # IPv4 or IPv6 and not UDP
    #
    let any_ip_permissions = configuration.ipPermissions[
        #
        # if either ipv4 or ipv6 that allows access from any address
        #
        some ipv4Ranges[*].cidrIp == '0.0.0.0/0' or
        some ipv6Ranges[*].cidrIpv6 == '::/0'

        #
        # the ipProtocol is not UDP
        #
        ipProtocol != 'udp' ]
        
    when %any_ip_permissions !empty
    {
        %any_ip_permissions {
            ipProtocol != '-1'
            <<
              result: NON_COMPLIANT
              check_id: HUB_ID_2334
              message: Any IP Protocol is allowed
            >>

            when fromPort exists 
                 toPort exists 
            {
                let each_any_ip_perm = this
                %ports {
                    this < %each_any_ip_perm.fromPort or
                    this > %each_any_ip_perm.toPort
                    <<
                        result: NON_COMPLIANT
                        check_id: HUB_ID_2340
                        message: Blocked TCP port was allowed in range
                    >>
                }
            }
        }       
    }   
}
```

Next, run the `validate` command again. This time, it passes.

```
cfn-guard validate -r any_ip_ingress_check.guard -d ip_ingress.yaml --show-clause-failures
```

The following is the output of the `validate` command.

```
ip_ingress.yaml Status = PASS
PASS rules
check_ip_procotol_and_port_range_validity    PASS
```

To test this approach for failures, the following example uses a payload change.

```
resourceType: 'AWS::EC2::SecurityGroup'
InputParameters:
  TcpBlockedPorts: [21, 22, 90, 110]
configuration:
  ipPermissions:
    - fromPort: 172
      ipProtocol: tcp
      ipv6Ranges: []
      prefixListIds: []
      toPort: 172
      userIdGroupPairs: []
      ipv4Ranges:
        - cidrIp: "0.0.0.0/0"   
    - fromPort: 89
      ipProtocol: tcp
      ipv6Ranges:
        - cidrIpv6: "::/0"
      prefixListIds: []
      toPort: 109
      userIdGroupPairs: []
      ipv4Ranges:
        - cidrIp: 10.2.0.0/24
```

90 is within the range from 89–109 that has any IPv6 address allowed. The following is the output of the `validate` command after running it again.

```
Clause #3           FAIL(Clause(Location[file:any_ip_ingress_check.guard, line:43, column:21], Check: _  LESS THAN %each_any_ip_perm.fromPort))
                    Comparing Int((Path("/InputParameters/TcpBlockedPorts/2"), 90)) with Int((Path("/configuration/ipPermissions/1/fromPort"), 89)) failed
                    (DEFAULT: NO_MESSAGE)
Clause #4           FAIL(Clause(Location[file:any_ip_ingress_check.guard, line:44, column:21], Check: _  GREATER THAN %each_any_ip_perm.toPort))
                    Comparing Int((Path("/InputParameters/TcpBlockedPorts/2"), 90)) with Int((Path("/configuration/ipPermissions/1/toPort"), 109)) failed

                                            result: NON_COMPLIANT
                                            check_id: HUB_ID_2340
                                            message: Blocked TCP port was allowed in range
```