Write custom policies
We recommend to get familiar with the Policy concept before writing your first policy.
Write your first custom policy
Chainloop platform comes with a set of built-in policies that can be used out of the box but in some cases you may want to write your own.
Writing a policy in Chainloop usually involves
Write the Chainloop policy in a YAML document
Write the associated rego policy script
1 - Chainloop Policy YAML
A policy can be defined in a YAML document, like this:
In this particular example, we see:
- policies have a name (cyclonedx-licenses)
- they can be optionally applied to a specific type of material (check the documentation for the supported types). If no type is specified, a material name will need to be explicitly set in the contract, through selectors.
- they have a policy script that it’s evaluated against the material (in this case a CycloneDX SBOM report). Currently, only Rego language is supported.
- there can be multiple scripts, each associated with a different material type.
Policy scripts could also be specified in a detached form:
Supporting multiple material types
Policies can accept multiple material types. This is specially useful when a material can be specified in multiple format types, but from the user perspective, we still want to maintain one single policy.
For example, this policy would check for vulnerabilities in SARIF, CycloneDX and CSAF formats:
In these cases, Chainloop will choose the right script to execute, but externally it would be seen as a single policy.
If more than one path is executed (because they might have the same kind
), the evaluation result will be the sum of all evaluations.
Policy arguments
Policies may accept arguments to customize its behavior. If defined, the inputs
section, will be used by Chainloop to know with inputs arguments are supported by the policy
For example, this policy matches a “quality” score against a “threshold” argument:
- (1) the
input
section tells Chainloop which parameters should be expected. If missing, the argument will be ignored (an no value will be passed to the policy) - (2) input parametes are available in the
input.args
rego input field.
The above example can be instantiated with a custom threshold
parameter, by adding a with
property in the policy attachment in the contract:
(1) This is interpreted as a string, that’s why we need to add to_number
in the policy script
2 - Write the associated policy script
Rego language, from Open Policy Agent initiative, has become the de-facto standard for writing software supply chain policies. It’s a rule-oriented language, suitable for non-programmers that want to communicate and enforce business and security requirements in their pipelines.
Using Chainloop Template
Chainloop expects the rego scripts to expose a predefined set of rules so a good starting point is to use the following template:
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.
result
object has essentially three fields:
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.skip_reason
: if the policy evaluation was skipped, this property will contain some informative explanation of why this policy wasn’t evaluated.violations
: will hold the list of policy violations for a given input. Note that in this case,skipped
will be setfalse
, denoting that the input was evaluated against the policy, and it didn’t pass.
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:
valid_input
would fail if some preconditions were not met, like the input format.
Writing the policy logic
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:
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
:
When evaluated against an attestation, The policy will generate an output similar to this:
Make sure you test your policies in the Rego Playground.
Attaching it to the policy YAML spec
Once we have our Rego logic for our policy, we can create a Chainloop policy like this:
Give it a Try
and finally attach it to a contract:
Check our policies reference for more information on how to attach policies to contracts.
Configuring Policy inputs
As we can see in the above examples, Rego policies will receive and inputs
variable with all the payload to be evaluated. Chainloop will inject the evidence payload into that variable, for example a CycloneDX JSON document.
This way, input.specVersion
will denote the version of the CycloneDX document.
Additionally, Chainloop will inject the following fields:
-
input.args
: the list of arguments passed to the policy from the contract or the policy group. Each argument becomes a field in theargs
input:All arguments are passed as
String
type. So if you expect a numeric value you’ll need to convert it with theto_number
Rego builtin.Also, for convenience, comma-separated values are parsed and injected as arrays, as in the above example.
-
input.chainloop_metadata
: This is an In-toto descriptor JSON representation of the evidence, which Chainloop generates and stores in the attestation. Developers can create policies that check for specific fields in this payload.A typical
chainloop_metadata
field will look like this:Besides the basic information (name, digest) of the evidence, the
annotations
field will contain some useful metadata gathered by Chainloop during the attestation process. The example above corresponds to an OCI HELM_CHART evidence, for which Chainloop is able to detect thenotary
signature. You can write, for example, a policy that validates that your assets are properly signed, like this:
Policy engine constraints (Rego)
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:
opa.runtime
rego.parse_module
trace
Also http.send
has been isolated so only requests to the following domains are allowed:
chainloop.dev
cisa.gov
This prevents unexpected behavior and potential remote exploits, particularly since these policies are evaluated client-side.