Skip to content

Commit c681e0f

Browse files
authored
feat(docs): Rego how-to for Chainloop policies (#1394)
Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>
1 parent 87dd4ec commit c681e0f

3 files changed

Lines changed: 144 additions & 40 deletions

File tree

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
---
2+
title: Writing Chainloop Rego policies
3+
categories:
4+
- policies
5+
---
6+
7+
import PolicyTemplate from "!!raw-loader!/examples/policies/policy-template.rego";
8+
import CodeBlock from "@theme/CodeBlock";
9+
10+
Chainloop policies consists of a YAML document with some metadata and a Rego script which holds the policy logic.
11+
You can check [this document](/reference/policies) for a quick reference on policies. Read the following sections for a better understanding
12+
on how to write Rego code for your Chainloop policies.
13+
14+
### Rego language
15+
16+
[Rego language](https://www.openpolicyagent.org/docs/latest/policy-language/), from [Open Policy Agent](https://www.openpolicyagent.org/) initiative, has become the de-facto standard for writing software supply chain policies.
17+
It's a rule-oriented language, suitable for non-programmers that want to communicate and enforce business and security requirements in their pipelines.
18+
19+
### Chainloop Rego implementation
20+
A typical Chainloop Rego policy looks like this:
21+
22+
<CodeBlock language="go" title="policy-template.rego" showLineNumbers>
23+
{PolicyTemplate}
24+
</CodeBlock>
25+
26+
In the above template we can see there is a common section (1). Chainloop will look for the main rule `result`, if present. Older versions of Chainloop will only check for a `violations` rule.
27+
`result` object has essentially three fields:
28+
* `skipped`: whether the policy evaluation was skipped. This property would be set to true when the input, for whatever reason, cannot be evaluated (unexpected format, etc.). This property is useful to avoid false positives.
29+
* `skip_reason`: if the policy evaluation was skipped, this property will contain some informative explanation of why this policy wasn't evaluated.
30+
* `violations`: will hold the list of policy violations for a given input. Note that in this case, `skipped` will be set `false`, denoting that the input was evaluated against the policy, and it didn't pass.
31+
32+
Note that there is no need to modify the common section. Policy developers will only need to fill in the `valid_input` and `violations` rules:
33+
* `valid_input` would fail if some preconditions were not met, like the input format.
34+
35+
### Example
36+
37+
Let's say we want to write a policy that checks our SBOM in CycloneDX format to match a specific version. A `valid_input` rule would look like this:
38+
39+
```go
40+
# It's a valid input if format is CycloneDX and has specVersion field that we can check later
41+
valid_input if {
42+
input.bomFormat == "CycloneDX"
43+
input.specVersion
44+
}
45+
```
46+
47+
`violations` rule would return the list of policy violations, given that `valid_input` evaluates to `true`. If we wanted the CycloneDX report to be version `1.5`:
48+
```go
49+
violations contains msg if {
50+
valid_input
51+
input.specVersion != "1.5"
52+
msg := sprintf("wrong CycloneDX version. Expected 1.5, but it was %s", [input.specVersion])
53+
}
54+
```
55+
56+
When evaluated against an attestation, The policy will generate an output similar to this:
57+
```json
58+
{
59+
"result": {
60+
"skipped": false,
61+
"violations": [
62+
"wrong CycloneDX version. Expected 1.5, but it was 1.4"
63+
]
64+
}
65+
}
66+
```
67+
68+
Make sure you test your policies in [the Rego Playground](https://play.openpolicyagent.org/).
69+
70+
#### Chainloop policy
71+
Once we have our Rego logic for our policy, we can create a Chainloop policy like this:
72+
73+
```yaml
74+
# cyclonedx-version.yaml
75+
apiVersion: workflowcontract.chainloop.dev/v1
76+
kind: Policy
77+
metadata:
78+
name: cyclonedx-version
79+
spec:
80+
policies:
81+
- kind: SBOM_CYCLONEDX_JSON
82+
name: cyclonedx-version.rego
83+
```
84+
85+
and finally attach it to a contract:
86+
```yaml
87+
schemaVersion: v1
88+
policies:
89+
materials:
90+
- ref: file://cyclonedx-version.yaml
91+
```
92+
Check our [policies reference](/reference/policies) for more information on how to attach policies to contracts.

docs/docs/reference/policies.mdx

Lines changed: 11 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ title: Policies
55
import PolicyYAML from "!!raw-loader!/examples/policies/sbom/cyclonedx-licenses.yaml";
66
import CodeBlock from "@theme/CodeBlock";
77

8-
Starting with Chainloop [0.93.8](https://github.com/chainloop-dev/chainloop/releases/tag/v0.93.8), operators can attach policies to contracts.
9-
These policies will be evaluated against the different materials and the statement metadata, if required. The result of the evaluation is informed as a list of possible violations and added to the attestation statement
10-
before signing and sending it to Chainloop.
8+
You can use policies to implement control gates and security checks in your attestations.
119

12-
Currently, policy violations won't block `attestation push` commands, but instead, we chose to include them in the attestation so that they can
13-
be used for building server side control gates.
10+
Operators can attach policies to workflow contracts. Those policies will be evaluated against the different materials and the statement metadata. The result of the evaluation is informed as a list of possible violations and added to the attestation statement
11+
before signing and sending it to Chainloop.
12+
13+
Currently, policy violations won't block `attestation push` commands. Instead, Chainloop will include them in the attestation so that they can be used for building server side control gates.
1414

1515
## Policy specification
1616
A policy can be defined in a YAML document, like this:
@@ -21,8 +21,8 @@ A policy can be defined in a YAML document, like this:
2121

2222
In this particular example, we see:
2323
* policies have a name (cyclonedx-licenses)
24-
* they can be optionally applied to a specific type of material (check [the documentation](./operator/contract#material-schema) for the supported types). If no type is specified, a material name will need to be provided explicitly in the contract.
25-
* they have a policy script that it's evaluated against the material (in this case a CycloneDX SBOM report). Currently, only [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/#learning-rego) policies are supported.
24+
* they can be optionally applied to a specific type of material (check [the documentation](./operator/contract#material-schema) for the supported types). If no type is specified, a material name will need to be explicitly set in the contract, through selectors.
25+
* they have a policy script that it's evaluated against the material (in this case a CycloneDX SBOM report). Currently, only [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/#learning-rego) language is supported.
2626
* there can be multiple scripts, each associated with a different material type.
2727

2828
Policy scripts could also be specified in a detached form:
@@ -128,7 +128,7 @@ There are two ways to attach a policy to a contract:
128128
In the example above, we can see that, when referenced by the `embedded` attribute (1), a full policy can be embedded in the contract.
129129

130130
### Policy arguments
131-
Policies may accept arguments to customize its behaviour. See this policy that matches a "quality" score against a "threshold" argument:
131+
Policies may accept arguments to customize its behaviour. For example, this policy matches a "quality" score against a "threshold" argument:
132132
```yaml
133133
# quality.yaml
134134
apiVersion: workflowcontract.chainloop.dev/v1
@@ -179,39 +179,10 @@ For example
179179
- ref: https://raw.githubusercontent.com/chainloop-dev/chainloop/main/docs/examples/policies/sbom/cyclonedx-banned-licenses.yaml@sha256:5b40425cb7bcba16ac47e3d8a8d3af7288afeeb632096994e741decedd5d38b3
180180
```
181181

182-
## Policy scripts format (REGO)
183-
Currently, policy scripts are assumed to be written in [Rego language](https://www.openpolicyagent.org/docs/latest/policy-language/#learning-rego). Other policy engines might be implemented in the future.
184-
The only requirement of the policy is the existence of one or multiple `violations` rules, which evaluate to a **set of violation messages**.
185-
For example, this policy script:
186-
```yaml
187-
package main
188-
189-
import rego.v1
190-
191-
violations contains msg if {
192-
not is_approved
193-
194-
msg:= "Container image is not approved"
195-
}
196-
197-
is_approved if {
198-
some material in input.predicate.materials
199-
material.annotations["chainloop.material.type"] == "CONTAINER_IMAGE"
200-
201-
input.predicate.annotations.approval == "true"
202-
}
203-
```
204-
when evaluated against an attestation, will generate the following output if the expected annotation is not present:
205-
```json
206-
{
207-
"violations": [
208-
"Container image is not approved"
209-
]
210-
}
211-
```
212-
Make sure you test your policies in https://play.openpolicyagent.org/, since you might get different results when using Rego V1 syntax, as there are [some breaking changes](https://www.openpolicyagent.org/docs/latest/opa-1/).
182+
## How to write a Chainloop policy
183+
Check [this how-to](/guides/rego-policies) to know how you can write Chainloop policies in [Rego language](https://www.openpolicyagent.org/docs/latest/policy-language/#learning-rego).
213184

214-
## Policy engine constraints (REGO)
185+
## Policy engine constraints (Rego)
215186
To ensure the policy engine work as pure and as fast as possible, we have deactivated some of the OPA built-in functions. The following functions are not allowed in the policy scripts:
216187
- `opa.runtime`
217188
- `rego.parse_module`
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package main
2+
3+
import rego.v1
4+
5+
# (1)
6+
################################
7+
# Common section do NOT change #
8+
################################
9+
10+
# (2)
11+
result := {
12+
"skipped": skipped,
13+
"violations": violations,
14+
"skip_reason": skip_reason,
15+
}
16+
17+
default skip_reason := ""
18+
19+
skip_reason := m if {
20+
not valid_input
21+
m := "invalid input"
22+
}
23+
24+
default skipped := true
25+
26+
skipped := false if valid_input
27+
28+
########################################
29+
# EO Common section, custom code below #
30+
########################################
31+
32+
# Validates if the input is valid and can be understood by this policy (3)
33+
valid_input if {
34+
# insert code here
35+
}
36+
37+
# If the input is valid, check for any policy violation here (4)
38+
violations contains msg if {
39+
valid_input
40+
# insert code here
41+
}

0 commit comments

Comments
 (0)