Authoring Guardrails

Authoring Your Own Guardrails

Really

Really is a policy language used to constrain configuration inputs in Resourcely. Instead of needing to write code, we have created Really to match as closely to plain English as possible, making it easier to both read and write policies that comply with your company's best practices.

With the structured and logical syntax of Really, anyone responsible for creating and maintaining security policies can:

  • Write policies faster than with alternatives such as Rego

  • Read, understand, and update policies quickly and easily

Below is an example of a Really policy that requires a specific prefix on an S3 bucket:

GUARDRAIL "Require prefix on all S3 buckets"
    WHEN aws_s3_bucket
        REQUIRE bucket STARTS WITH "resourcely-"

Overview

There are three components in a guardrail, which are grouped together by indentation.

Declaration

The first component is the declaration of the guardrail. In this line you use the keyword GUARDRAIL followed by a string that represents the name of the guardrail. Typically these names are a basic description of what the guardrail does.

GUARDRAIL "Do not allow public S3 buckets"

Matcher

The second component is the matcher. The matcher lets you specify when a guardrail should be applied. It is specified using the keyword WHEN, and it is indented under the guardrail declaration. The statement can either be a direct resource name, or a logical operation to constrain the matcher even further.

For example the following will run the guardrail on all aws_s3_bucket resources that have a tag team with the value eng:

WHEN aws_s3_bucket.tags.team = "eng"

You can also use wildcards in matchers to match multiple resources.

WHEN aws_s3*

Requirements

The third component of a guardrail is the requirements. This is where you actually specify what you expect to be true about the configuration. You specify this using the keyword REQUIRE and indent it under the matcher. A requirement can constrain the inputs to your configuration.

For example if you have a list of instance types you want developers to use, you can write a requirement that requires instance_type to match specific wildcard expressions:

REQUIRE instance_type MATCHES ANY ["m5a*", "m7a*"]

A requirement with no operator constrains the property to be non-empty.

Example

GUARDRAIL "Ensure EC2 Instances have an owner tag"
    WHEN aws_instance
        REQUIRE tags.owner

Operators are used to further constrain the value.

Example

GUARDRAIL "Ensure EC2 Instances have an team tag"
    WHEN aws_instance
        REQUIRE tags.team IN ["engineering", "it", "marketing"]

Context

Guardrails can reference context answers using the CONTEXT keyword followed by the label of the context question: CONTEXT label or CONTEXT "label".

The CONTEXT keyword may be used on either side of an operation within WHEN, but only on the right-hand side within REQUIRE.

Examples

Here we use environment from the global context.

GUARDRAIL "Ensure bucket name starts with environment"
    WHEN aws_s3_bucket
        REQUIRE bucket STARTS WITH CONTEXT environment

All of the operators can be used with context answers. Here we use the MATCHES glob operator to apply the guardrail to all production environments.

GUARDRAIL "Ensure production buckets start with company name"
    WHEN aws_s3_bucket AND CONTEXT environment MATCHES "production*"
        REQUIRE bucket STARTS WITH "acme-"

Context answers accommodate both single- and multi-select with the same guardrail syntax.

The two statements of each of the following guardrails are equivalent.

GUARDRAIL "Ensure bucket name starts with environment"
    WHEN aws_s3_bucket
        REQUIRE bucket STARTS WITH CONTEXT environment
    WHEN aws_s3_bucket
        REQUIRE bucket STARTS WITH ANY CONTEXT environment
GUARDRAIL "Ensure bucket name matches one of the options in the bucket list"
    WHEN aws_s3_bucket
        REQUIRE bucket = CONTEXT bucket_list
    WHEN aws_s3_bucket
        REQUIRE bucket IN CONTEXT bucket_list

You can use EVERY, SOME and NO with CONTEXT on the left-hand side within WHEN.

The two statements of the following guardrail are equivalent.

GUARDRAIL "Ensure bucket name matches one of the options in the bucket list"
    WHEN aws_s3_bucket AND CONTEXT environment = "prod"
        REQUIRE bucket STARTS WITH "prod-"

    WHEN aws_s3_bucket AND SOME CONTEXT environment = "prod"
        REQUIRE bucket STARTS WITH "prod-"

However, this guardrail only applies if every value in "environment" is "prod".

GUARDRAIL "Ensure bucket name matches one of the options in the bucket list"
    WHEN aws_s3_bucket AND EVERY CONTEXT environment = "prod"
        REQUIRE bucket STARTS WITH "prod-"

Optional Context

Guardrails fails safe by default when a context value is missing. That is, Resourcely will request approval from the approver group, the same as if the guardrail was violated.

Example of a Guardrail Failing Safe
WHEN aws_s3_bucket AND CONTEXT environment = "prod"
  REQUIRE bucket STARTS WITH "prod-"
OVERRIDE WITH APPROVAL @platform

Assume the environment context answer is missing.

When a PR modifies an S3 bucket, Resourcely cannot determine if the guardrail should apply. The environment might be "prod", but Resourcely doesn't know. So we will loop-in a human by requesting approval from the platform group.

If Resourcely can be certain the guardrail is satisfied, even without the missing context, we won't request approval. As a trivial example, consider a PR for an EC2 instance. It doesn't touch any S3 buckets, so Resourcely doesn't need the environment to determine that the guardrail is (trivially) satisfied.

Sometimes failing safe is too strong. Maybe you intentionally haven't added the context answers to all .resourcely.yaml config roots yet. Or maybe you want to use the presence/absence of the context answer to condition the guardrail.

The OPTIONAL CONTEXT <foo> syntax disables the fail safe behavior for that context answer.

WHEN aws_s3_bucket AND OPTIONAL CONTEXT environment = "prod"
   REQUIRE bucket STARTS WITH "prod"

will not require approval when the environment context answer is missing.

This works in REQUIRE statements as well.

WHEN aws_s3_bucket
  REQUIRE bucket STARTS WITH OPTIONAL CONTEXT environment

will not require approval if the environment context answer is missing.

The OPTIONAL keyword disables the fail safe behavior only for that context answer.

WHEN aws_s3_bucket AND CONTEXT environment = "prod"
  REQUIRE bucket STARTS WITH OPTIONAL CONTEXT team

will not require approval if team is missing, but will if environment is missing.

Reference

Operators

The following operator can be used in both the Matcher and Requirement components of a guardrail.

=

Equality

Example

GUARDRAIL "Engineering team buckets must have bucket prefix resourcely-engineering"
    WHEN aws_s3_bucket.tags.team = "eng"
        REQUIRE bucket_prefix = "resourcely-engineering"

!=

Inquality

Example

GUARDRAIL "Non-engineering team buckets must not have bucket prefix resourcely-engineering"
    WHEN aws_s3_bucket.tags.visibility != "eng"
        REQUIRE bucket_prefix != "resourcely-engineering"

> / >=

Greater Than / Greater Than or Equal

Example

GUARDRAIL "IAM Account Password Policy must specify max password age"
    WHEN aws_iam_account_password_policy
        REQUIRE max_password_age > 0

< / <=

Less Than / Less Than or Equal

Example

GUARDRAIL "IAM Account Password Policy max password age must be less than or equal to 90"
    WHEN aws_iam_account_password_policy
        REQUIRE max_password_age <= 90

STARTS WITH

Check if a string property starts with a specified string.

Example

GUARDRAIL "Engineering team buckets must start with resourcely-engineering"
    WHEN aws_s3_bucket.tags.team = "eng"
        REQUIRE bucket STARTS WITH "resourcely-engineering"

ENDS WITH

Check if a string property ends with a specified string.

Example

GUARDRAIL "Production buckets must have suffix -prod"
    WHEN aws_s3_bucket.tags.environment = "prod"
        REQUIRE bucket ENDS WITH "-prod"

MATCHES

Check if a string property matches a wildcard pattern.

Example

GUARDRAIL "Production buckets must have prod in the bucket name"
    WHEN aws_s3_bucket.tags.environment = "prod"
        REQUIRE bucket MATCHES "*prod*"

MATCHES REGEX

Check if a string property matches a regex pattern.

Example

GUARDRAIL "EC2 Instances must have instance type m5a or m7a and be medium or large"
    WHEN aws_instance
        REQUIRE instance_type MATCHES REGEX "m[5,7]a.(medium|large)$"

MATCHES ANY

Check if a string property matches at least one wildcard pattern in a list of wildcard patterns.

Example

GUARDRAIL "EC2 Instances must have instance type m5a or m7a"
    WHEN aws_instance
        REQUIRE instance_type MATCHES ANY ["m5a*", "m7a*"]

MATCHES ANY REGEX

Check if a string property matches at least one regex pattern in a list of regex patterns.

Example

GUARDRAIL "EC2 Instances must have instance type m5a or m7a and be medium or large"
    WHEN aws_instance
        REQUIRE instance_type MATCHES ANY REGEX ["m5a.(medium|large)$", "m7a.(medium|large)$"]

IN

Check if a string property matches at least one literal string value in a list of strings.

Example

GUARDRAIL "EC2 Instances must have instance type m7a medium or large"
    WHEN aws_instance
        REQUIRE instance_type IN ["m7a.medium", "m7a.large"]

EXISTS

Checks if the property is specified on a resource

Allows empty lists [], the empty string "" , etc. To require that the property is not empty, omit the operator entirely.

Example

GUARDRAIL "Ensure team tag exists on EC2 Instances"
    WHEN aws_instance
        REQUIRE tags.team EXISTS

NOT

Negates an operator (can be used on any operator) in the form NOT <operator>

Example

GUARDRAIL "EC2 Instances must not have instance type m7g"
    WHEN aws_instance
        REQUIRE instance_type NOT MATCHES "m7g.*"

Connectors

You can string together operators with the connectors AND and OR. You can also use parentheses to separate multiple connectors.

AND

Requires that both operators on the left and right hand sides are satisfied.

Example

Note in this example that we could have used multiple REQUIRE statements in place of AND for better readability.

GUARDRAIL "S3 Bucket Public Access Block should set true on all attributes"
    WHEN aws_s3_bucket_public_access_block
        REQUIRE (block_public_acls = true AND block_public_policy = true) AND ignore_public_acls = true AND restrict_public_buckets = true

OR

Requires that at least one operator on the left and right hand sides are satisfied

Example

GUARDRAIL "EC2 Instances must have instance type m7a medium or large"
    WHEN aws_instance
        REQUIRE instance_type MATCHES "m7a.medium" OR instance_type MATCHES "m7a.large"

List Operators

In addition to the operators that evaluate against primitive properties, Really supports operators that can traverse over a list using EVERY, SOME, and NO.

EVERY

Iterates over a list and ensures that all values of that list are satisfied. If the list is empty, EVERY will return true. However, if a guardrail specifies a list property without any list operator, we infer the EVERY operator, except that we require the list to be non-empty (so an empty list will return false)

Example: Explicit declaration of EVERY

In this example ingress.cidr_blocks can be empty

GUARDRAIL "Do not allow 0.0.0.0/0 in ingress"
    WHEN aws_security_group
        REQUIRE EVERY ingress.cidr_blocks != "0.0.0.0/0"

Example: Implicit declaration of EVERY

In this example ingress.cidr_blocks can not be empty

GUARDRAIL "Do not allow 0.0.0.0/0 in ingress"
    WHEN aws_security_group
        REQUIRE ingress.cidr_blocks != "0.0.0.0/0"

SOME

Iterates over a list and ensures that at least one value of that list is satisfied. If the list is empty, SOME will return false.

Example

GUARDRAIL "Require port 22 to be open"
    WHEN aws_security_group
        REQUIRE SOME ingress.from_port = 22

NO

Iterates over a list and ensures that none of the values of the list are satisfied. If the list is empty, NO will return true.

Example

GUARDRAIL "Do not allow port 22 to be open"
    WHEN aws_security_group
        REQUIRE NO ingress.from_port = 22

Special Syntax

HAS

Additional syntax used to change the scope inside a guardrail, usually used to enhance the human readability of a guardrail.

Example

Without HAS, a guardrail to check that a bunch of keys exist on the tags of an aws_instance would look like:

GUARDRAIL "Ensure standard tags exist on EC2 Instances"
    WHEN aws_instance
        REQUIRE tags.team
        REQUIRE tags.environment
        REQUIRE tags.owner
        REQUIRE tags.pii

With HAS you can simplify it to be:

GUARDRAIL "Ensure standard tags exist on EC2 Instances"
    WHEN aws_instance
        REQUIRE tags HAS
            team
            environment
            owner
            pii

Last updated