Rule Constraint

Overview

A rule constraint is an additional constraint on pattern matching. This kind of constraints can be classified into the following:

  • Pattern-based rule constraint
  • Regex-based rule constraint

The remaining section describes what they are and how they behave with the following Terraform code example:

// (R1)
resource "foobar" "foo" {
attr1 = 1
}
// (R2)
resource "foobar" "foo" {
attr2 = 2
}
// (R3)
resource "foobar" "foo" {
size = 1
}

Pattern-based Rule Constraint

A pattern-based rule constraint can filter the matches for the pattern with yet another pattern. For example, the following rule set defines a rule which matches a resource block whose body DOES include size = (blah blah):

version: '1'
rules:
- id: sample-policy-1
language: hcl
pattern: |
resource :[X] :[Y] {
:[...Z]
}
constraints:
- target: Z
should: match
pattern: size = :[_]
message: |
here comes your own message

On the other hand, the following rule set defines a rule which matches a resource block whose body does NOT include size = (blah blah).

version: '1'
rules:
- id: sample-policy-2
language: hcl
pattern: |
resource :[X] :[Y] {
:[...Z]
}
constraints:
- target: Z
should: not-match
pattern: size = :[_]
message: |
here comes your own message

sample-policy-1 matches (R3), and sample-policy-2 matches (R1) and (R2).

Regex-based Rule Constraint

A regex-based rule constraint can filter the matches for the pattern with a regular expression. For example, the following rule set defines a rule which matches a resource block whose body matches regular expressions .*attr.*:

version: '1'
rules:
- id: sample-policy-3
language: hcl
pattern: |
resource :[X] :[Y] {
:[...Z]
}
constraints:
- target: Z
should: match-regex
pattern: .*attr.*
message: |
here comes your own message

A regex-based rule constraint included in the following rule behaves the opposite of one in sample-policy-3:

version: '1'
rules:
- id: sample-policy-4
language: hcl
pattern: |
resource :[X] :[Y] {
:[...Z]
}
constraints:
- target: Z
should: not-match-regex
pattern: .*attr.*
message: |
here comes your own message

sample-policy-3 matches (R1) and (R2), and sample-policy-4 matches (R3).

Predicates

Predicates equal to available should options. The currently available ones are:

  1. match
  2. no-match
  3. match-regex
  4. no-match-regex
  5. match-any-of
  6. not-match-any-of
  7. be-any-of
  8. not-be-any-of

The sections, Pattern-based Rule Constraint Predicate and Regex-based Rule Constraint have already explained the utilization of predicate options 1 - 4. Let's learn from 5 to 8.

A target Terraform file is below and each part shows you example rules and the expected results.

// (R1)
resource "foobar" "foo" {
attr1 = 1
}
// (R2)
resource "foobar" "foo" {
attr2 = 2
}
// (R3)
resource "foobar" "foo" {
size = 1
}

match-any-of

The predicate match-any-of supports both pattern-based and regex-based.

version: '1'
rules:
- id: sample-policy-match-any-of
language: hcl
pattern: |
resource "foobar" :[Y] {
:[...Z]
}
constraints:
- target: Z
should: match-any-of
patterns:
- pattern: attr1 = 1
- pattern: attr3 = 3
message: |
It includes either 'attr1 = 1' or 'attr3 = 3'
version: '1'
rules:
- id: sample-policy-match-any-of
language: hcl
pattern: |
resource "foobar" :[Y] {
:[...Z]
}
constraints:
- target: Z
should: match-any-of
regex-patterns:
- .*attr1.*
- .*attr3.*
message: |
It includes either 'attr1 = 1' or 'attr3 = 3'
$ cat example.tf | shisho check policy.yaml
[sample-policy-match-any-of]: It includes either 'attr1 = 1' or 'attr3 = 3'
In /dev/stdin:
|
2 | resource "foobar" "foo" {
3 | attr1 = 1
4 | }
|

not-match-any-of

The predicate not-match-any-of supports both pattern-based and regex-based.

version: '1'
rules:
- id: sample-policy-not-match-any-of
language: hcl
pattern: |
resource "foobar" :[Y] {
:[...Z]
}
constraints:
- target: Z
should: not-match-any-of
patterns:
- pattern: attr1 = 1
- pattern: attr3 = 3
message: |
It does not include either 'attr1 = 1' or 'attr3 = 3'
version: '1'
rules:
- id: sample-policy-not-match-any-of
language: hcl
pattern: |
resource "foobar" :[Y] {
:[...Z]
}
constraints:
- target: Z
should: not-match-any-of
regex-patterns:
- .*attr1.*
- .*attr3.*
message: |
It does not include either 'attr1 = 1' or 'attr3 = 3'
$ cat example.tf | shisho check policy.yaml
[sample-policy-not-match-any-of]: It does not include either 'attr1 = 1' or 'attr3 = 3'
In /dev/stdin:
|
7 | resource "foobar" "foo" {
8 | attr2 = 2
9 | }
|

be-any-of

A sub-parameter strings is available and this can have multiple values.

version: '1'
rules:
- id: sample-policy-be-any-of
language: hcl
pattern: |
resource "foobar" :[Y] {
:[...Z]
}
constraints:
- target: Z
should: be-any-of
strings:
- attr1 = 1
- attr3 = 3
message: |
It includes either 'attr1 = 1' or 'attr3 = 3'
$ cat example.tf | shisho check policy.yaml
[sample-policy-be-any-of]: It includes either 'attr1 = 1' or 'attr3 = 3'
In /dev/stdin:
|
2 | resource "foobar" "foo" {
3 | attr1 = 1
4 | }
|

not-be-any-of

A sub-parameter strings is available and this is can have multiple values.

version: '1'
rules:
- id: sample-policy-not-be-any-of
language: hcl
pattern: |
resource "foobar" :[Y] {
:[...Z]
}
constraints:
- target: Z
should: not-be-any-of
strings:
- attr1 = 1
- attr3 = 3
message: |
It does not include either 'attr1 = 1' or 'attr3 = 3'
$ cat example.tf | shisho check policy.yaml
[sample-policy-not-be-any-of]: It does not include either 'attr1 = 1' or 'attr3 = 3'
In /dev/stdin:
|
12 | resource "foobar" "foo" {
13 | size = 1
14 | }
|
[sample-policy-not-be-any-of]: It does not include either 'attr1 = 1' or 'attr3 = 3'
In /dev/stdin:
|
7 | resource "foobar" "foo" {
8 | attr2 = 2
9 | }
|

Advanced Usage

The above sections explain the fundamental utilization of rule constraints. The sections demonstrate advanced techniques for more complex cases and why Shisho is powerful.

Shared Constraint

A shared constraint allows sharing metavariables among multiple patterns. For instance, in the following rule, the constraint with target: NAME behaves if they are placed in each pattern.

version: "1"
rules:
- id: "use-trusted-base-images"
language: dockerfile
message: |
Use trusted base images if possible.
patterns:
- pattern: FROM :[NAME]
- pattern: FROM :[NAME] AS :[ALIAS]
- pattern: FROM :[NAME]@:[HASH]
- pattern: FROM :[NAME]@:[HASH] AS :[ALIAS]
- pattern: FROM :[NAME]::[TAG]
- pattern: FROM :[NAME]::[TAG] AS :[ALIAS]
- pattern: FROM :[NAME]::[TAG]@:[HASH]
- pattern: FROM :[NAME]::[TAG]@:[HASH] AS :[ALIAS]
constraints:
- target: NAME
should: be-any-of
strings:
- node
- php

Suppose you apply the above rule to the following Dockerfile:

FROM node:10-alpine
RUN mkdir /app
COPY . /app
RUN chown -R node:node /app
CMD ["node", "index.js"]

In this case, you'll get the following outputs from Shisho:

$ cat Dockerfile.sample | shisho check policy.yaml
[use-trusted-base-images]: Use trusted base images if possible.
In /dev/stdin:
|
1 | FROM node:10-alpine
|

Nested Constraint

Nested constraints can take a pattern from the parent constraint and for rewrite options, you can use metavariables from the inside constraint pattern.

What the below example rule policy.yaml does is:

  1. Search a block resource while capturing the body of the block as a metavariable X
  2. Search an inner block in the value of the metavariable X while capturing the body of the block as a metavariable Z
  3. Search test attribute in the value of the metavariable Z while capturing the value of the attribute as :[HOO]
  4. If it matches, extract the value of the metavariable HOO as a value of the test attribute inside the block block
version: "1"
rules:
- id: "test"
language: hcl
message: |
test
pattern: |
resource "block" :[NAME] {
:[...X]
}
constraints:
- target: X
should: match
pattern: |
inner {
:[...Z]
}
constraints:
- target: Z
should: match
pattern: |
test = :[HOO]
rewrite_options:
- |
resource "block" :[NAME] {
test = :[HOO]
}

This is a target file example.tf.

resource "block" "foo" {
inner {
test = true
}
}

An expected result is below.

$ cat example.tf | shisho check policy.yaml
[test]: test.
In /dev/stdin:
|
1 | resource "block" "foo" {
2 | inner {
3 | test = true
4 | }
5 | }
|
Suggested changes (1):
1 1 | resource "block" "foo" {
2 | - inner {
3 | - test = true
4 | - }
2 | + test = true
5 3 | }