|
| 1 | +--- |
| 2 | +title: Policies |
| 3 | +--- |
| 4 | + |
| 5 | +Starting with Chainloop [0.93.8](https://github.com/chainloop-dev/chainloop/releases/tag/v0.93.8), operators can attach policies to contracts. |
| 6 | +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 |
| 7 | +before signing and sending it to Chainloop. |
| 8 | + |
| 9 | +Currently, policy violations won't block `attestation push` commands, but instead, we chose to include them in the attestation so that they can |
| 10 | +be used for building server side control gates. |
| 11 | + |
| 12 | +### Policy specification |
| 13 | +A policy can be defined in a YAML document, like this: |
| 14 | +```yaml |
| 15 | +# cyclonedx-licenses.yaml |
| 16 | +apiVersion: workflowcontract.chainloop.dev/v1 |
| 17 | +kind: Policy |
| 18 | +metadata: |
| 19 | + name: cyclonedx-licenses # (1) |
| 20 | +spec: |
| 21 | + type: SBOM_CYCLONEDX_JSON # (2) |
| 22 | + embedded: | # (3) |
| 23 | + package main |
| 24 | +
|
| 25 | + deny[msg] { |
| 26 | + count(without_license) > 0 |
| 27 | + msg := "SBOM has components without licenses" |
| 28 | + } |
| 29 | +
|
| 30 | + without_license = {comp.purl | |
| 31 | + some i |
| 32 | + comp := input.components[i] |
| 33 | + not comp.licenses |
| 34 | + } |
| 35 | +``` |
| 36 | +In this particular example, we see: |
| 37 | +* (1) policies have a name |
| 38 | +* (2) 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. |
| 39 | +* (3) 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. |
| 40 | +
|
| 41 | +Policy scripts could also be specified in a detached form: |
| 42 | +```yaml |
| 43 | +... |
| 44 | +spec: |
| 45 | + type: SBOM_CYCLONEDX_JSON |
| 46 | + path: my-script.rego |
| 47 | +``` |
| 48 | +
|
| 49 | +### Applying policies to contracts |
| 50 | +When defining a contract, a new `policies` section can be specified. Policies can be applied to any material, but also to the attestation statement as a whole. |
| 51 | +```yaml |
| 52 | +schemaVersion: v1 |
| 53 | +materials: |
| 54 | + - name: sbom |
| 55 | + type: SBOM_CYCLONEDX_JSON |
| 56 | + - name: another-sbom |
| 57 | + type: SBOM_CYCLONEDX_JSON |
| 58 | + - name: my-image |
| 59 | + type: CONTAINER_IMAGE |
| 60 | +policies: |
| 61 | + materials: # policies applied to materials |
| 62 | + - ref: cyclonedx-licenses.yaml # (1) |
| 63 | + attestation: # policies applied to the whole attestation |
| 64 | + - ref: https://github.com/chainloop/chainloop-dev/blob/main/docs/examples/policies/chainloop-commit.yaml # (2) |
| 65 | +``` |
| 66 | +Here we can see that: |
| 67 | +- (1) materials will be validated against `cyclonedx-licenses.yaml` policy. But, since that policy has a `type` property set to `SBOM_CYCLONEDX_JSON`, only SBOM materials (`sbom` and `another-sbom` in this case) will be evaluated. |
| 68 | + |
| 69 | + If we wanted to only evaluate the policy against the `sbom` material, and skip the other, we should filter them by name: |
| 70 | + ```yaml |
| 71 | + policies: |
| 72 | + materials: |
| 73 | + - ref: cyclonedx-licenses.yaml |
| 74 | + selector: # (3) |
| 75 | + name: sbom |
| 76 | + ``` |
| 77 | + Here, in (3), we are making explicit that only `sbom` material must be evaluated by the `cyclonedx-licenses.yaml` policy. |
| 78 | +- (2) the attestation in-toto statement as a whole will be evaluated against the remote policy `chainloop-commit.yaml`, which has a `type` property set to `ATTESTATION`. |
| 79 | + This brings the opportunity to validate global attestation properties, like annotations, the presence of a material, etc. You can see this policy and other examples in the [examples folder](https://github.com/chainloop-dev/chainloop/tree/main/docs/examples/policies). |
| 80 | + |
| 81 | +Finally, note that material policies are evaluated during `chainloop attestation add` commands, while attestation policies are evaluated in `chainloop attestation push` command. |
| 82 | + |
| 83 | +### Embedding or referencing policies |
| 84 | +There are two ways to attach a policy to a contract: |
| 85 | +* **By referencing it**, as it can be seen in the examples above. `ref` property admits a local (filesystem) or remote reference (HTTPS). For example: |
| 86 | + ```yaml |
| 87 | + policies: |
| 88 | + materials: |
| 89 | + - ref: cyclonedx-licenses.yaml # local reference |
| 90 | + ``` |
| 91 | + and |
| 92 | + ```yaml |
| 93 | + policies: |
| 94 | + materials: |
| 95 | + - ref: https://github.com/chainloop/chainloop-dev/blob/main/docs/examples/policies/cyclonedx-licenses.yaml |
| 96 | + ``` |
| 97 | + are both equivalent. The advantage of having remote policies is that they can be easily reused, allowing organizations to create policy catalogs. |
| 98 | + |
| 99 | +* If preferred, authors could create self-contained contracts **embedding policy specifications**. The main advantage of this method is that it ensures that the policy source cannot be changed, as it's stored and versioned within the contract: |
| 100 | + ```yaml |
| 101 | + schemaVersion: v1 |
| 102 | + materials: |
| 103 | + - name: sbom |
| 104 | + type: SBOM_CYCLONEDX_JSON |
| 105 | + policies: |
| 106 | + materials: |
| 107 | + - policy: # (1) |
| 108 | + apiVersion: workflowcontract.chainloop.dev/v1 |
| 109 | + kind: Policy |
| 110 | + metadata: |
| 111 | + name: sbom-licenses |
| 112 | + spec: |
| 113 | + type: SBOM_CYCLONEDX_JSON |
| 114 | + embedded: | |
| 115 | + package main |
| 116 | +
|
| 117 | + deny[msg] { |
| 118 | + count(without_license) > 0 |
| 119 | + msg := "SBOM has components without licenses" |
| 120 | + } |
| 121 | +
|
| 122 | + without_license = {comp.purl | |
| 123 | + some i |
| 124 | + comp := input.components[i] |
| 125 | + not comp.licenses |
| 126 | + } |
| 127 | + ``` |
| 128 | + In the example above, we can see that, when referenced by the `policy` attribute (1), a full policy can be embedded in the contract. |
| 129 | + |
| 130 | +### Rego scripts |
| 131 | +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. |
| 132 | +The only requirement of the policy is the existence of one or multiple `deny` rules, which evaluate to a **list of violation strings**. |
| 133 | +For example, this policy script: |
| 134 | +```yaml |
| 135 | +package main |
| 136 | +
|
| 137 | +deny[msg] { |
| 138 | + not is_approved |
| 139 | + |
| 140 | + msg:= "Container image is not approved" |
| 141 | +} |
| 142 | +
|
| 143 | +is_approved { |
| 144 | + input.predicate.materials[_].annotations["chainloop.material.type"] == "CONTAINER_IMAGE" |
| 145 | + |
| 146 | + input.predicate.annotations.approval == "true" |
| 147 | +} |
| 148 | +``` |
| 149 | +when evaluated against an attestation, will generate the following output if the expected annotation is not present: |
| 150 | +```json |
| 151 | +{ |
| 152 | + "deny": [ |
| 153 | + "Container image is not approved" |
| 154 | + ] |
| 155 | +} |
| 156 | +``` |
| 157 | +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/). |
0 commit comments