For the complete documentation index, see llms.txt. This page is also available as Markdown.

Template

Templates can be used in Rules in the following places:

  1. spec.audit

  2. spec.collect[].audit

  3. spec.remediation[].skip

  4. spec.remediation[].value

  5. spec.skip_finding

The spec.audit, spec.collect[].audit, and spec.remediation[].value use pre-pass inline templating. An inline template is denoted by {{ }} and fully expanded and converted to a string value. Once all inline templates are fully expanded and the value is a single string, it is used.

The spec.skip_finding and spec.remediation[].skip are evaluated as a complete template and then converted to a boolean value, as explained below. In these cases the {{ }} is optional. If the value evaluates to truthy then the remediation or remediation step is skipped.

The values that can be extracted by a template depends on where in the Rule execution cycle the template is evaluated. For example, you can use collection values in an audit, but you cannot use audit findings in a collection.

Example

...
  remediation:
    - command: "insert_after"
      path: "block"
      flags:
        prefix: "\n\n"
      skip: "vars.default.STAGE != 'PROD'"
      value: |
        resource "aws_s3_bucket_versioning" "{{ vars.default.STAGE }}_{{ vars.default.BUCKET_NAME }}_versioning" {
          bucket = aws_s3_bucket.{{ vars.default.BUCKET_NAME }}.id

          versioning_configuration {
            status = "Enabled"
          }
        }

In the above example let's say the variable vars.default.STAGE is "DEV". In this case the conditional would template to 'DEV' != 'PROD' which would evaluate to true. This remediation would then be skipped.

If vars.default.STAGE is PROD then it would evaluate to 'PROD' != 'PROD' and thus be false and the value template would be expanded and then inserted after block within the AST.

Language

Expr is an expression language that is evaluated within the {{ }} blocks. There are a set of built-in functions for working with various data structures. And {{ }} blocks can span multiple lines.

Handling Complex Templates

Template can be very complex which can have strange results in the real world. Here are some suggestions for handling complex templates:

  1. Use the Rule execution lifecycle to reduce template complexity as much as possible

  2. Use the audit query's inline logic to reduce the collections and findings as much as possible

  3. Don't rely on template logic spec.remediation[].value, instead use multiple steps and spec.remediation[].skip to skip the unwanted ones.

  4. Use the audit remediation command with a level of DEBUG to print all the stages of the template evaluation. These will show up in the remediation report only if the log level is set to "DEBUG" (i.e., -vvv).

Audit $env

$env is a special variable that represents the entire environment of the template. This can be useful in debugging rules.

This will only show up if ORL log level is set to DEBUG. Otherwise nothing is shown, this can allow you to keep debug statements in the rules.

Access Patterns

Within templates, data can be as follows:

  • Path: <item>.<path>[.<path>] - The path element cannot contain special characters, but can be either a key or an index (i.e., item.0). ?. can be used to return nil (instead of error) if the path element is missing (i.e., this.that?.foo will still work if that is missing).

  • Index: <item>[<index>] - Index is number. This will error if the index is out of bounds

  • Key: <item>["<key>"] - Key can be any string. If the key is missing then nil is returned.

Objects

The high-level objects that currently exist:

  • vars - Each variable file is loaded here. See variables for details.

  • collections - each named audit finding from each collect is here.

  • finding (or $) - each named capture group from each finding.

Skip Truthy Evaluation

The skip* conditionals are special in that they are evaluated and then converted to a truthy value as follows.

Type
Value
Truthy
Comment

Array

[]

false

Empty array is false

Bool

false

false

Unquoted false is false

Map

{}

false

Empty map is false

Nil

nil

false

Nil is false

Number

0

false

0 is false

String

"", "0", "false", "<nil>"

false

Empty string is false. Note, use "" because that is the value of string(nil).

Array

[1,2,3]

true

Non-empty array is true

Bool

true

true

Unquoted true is true

Map

{1: false}

true

Non-empty map is true

Number

-1

true

any value not 0 is true

String

"true", "this"

true

Any non-empty string that isn't specifically false is true

Examples

Simple variable expansion

vars.default.STAGE == 'PROD'

In this simple case, first, the variable is expanded. If missing an error is thrown and eval fails. If the value of STAGE is comparable to a string then the value is compared and a bool is returned. Otherwise an error is thrown.

Complex expansion

vars.default.name in collect(collections, 'all_names.*.name')

In this case variable vars.default.name is extracted, and the full collection of all_names is reduced to an array of names. The in operator checks if the variable's value is within the list. That executes as follows:

  1. Jane in ["John", "Jake"]

  2. false

  3. false is false

Lazy Eval

Because of the way items are evaluated all the following are equivalent:

  1. collect(collections, "any_names.*") -> [] => false

  2. collections.any_names != {} -> {} != {} -> false => false

  3. len(collections.any_names) -> len({}) -> 0 => false

  4. len(collections.any_names) > 0 -> len({}) > 0 -> 0 > 0 -> false => false

Custom Functions

Expr has a ton of built-in functions that can be used. We have added additional global ones as below. Additionally, each language can add additional functions.

Collect

{{ collect(root, "path.*.to.collect") }} -> ["array", "of", "leaves"]

This function takes a root object, and a path construct. It traverses the object looking for root items. * can be used to mean "any" key.

hasSubString

{{ hasSubString("This and that", "This") }} -> true

{{ hasSubString(["This", "that"], "this") }} -> false

{{ ["This", "that"] | hasSubString("This") }} -> true

This function takes two arguments. The first argument can be a string or a slice of strings. In the string case it checks if the second argument is a substring of the first. In the case of slices it checks each element for substring. If any have the substring then it returns true.

Last updated