Workflow Creation
Next, let's introduce two of the most important entities in Chainloop's control plane, workflows and workflow contracts.
Workflows
A workflow represents the identity of any automation, any CI/CD workflow you want to register in the Control Plane, so you can receive their attestation and artifacts. A Workflow is associated with a Workflow Contract, explained next.
Workflow Contracts
A Workflow Contract defines the expectations of what content a workflow must send as part of their attestation. For example, a contract could explicitly state that the URI@digest of the generated container image, the container rootfs used during build, and the Software Bill of Materials of that container image must be present in the attestation.
There are two additional properties of Workflow Contracts that aims to ease the management of workflows in your organization
- A Workflow Contract is immutable and versioned, new revisions of the contract with new/different requirements can be added over time. This is especially useful for iterative integrations.
- A Workflow Contract can belong to more than one Workflow. This means that a change in a single contract will be propagated to many workflows. This is especially useful for org-wide standardization and fleet management.
Example: three CI/CD pipelines associated with their respective Chainloop workflows. Two of those Workflows are sharing the same Contract (using the same or different revision).
A Workflow Contract can be provided in json
, yaml
or cue
formats.
Chainloop API Token
A Chainloop API Token is a long-lasting, though revokable, secret token associated with a Chainloop organization. It's meant to be used in the target CI/CD pipeline during the attestation process and/or for unattended operations with the controlplane. This token along with the crafting CLI are the only two things development teams need to perform attestations.
Workflow and Contract creation
Let's say that we have a CI pipeline that we want to integrate with Chainloop so we can get visibility on as well as make it SLSA compliant via signed attestation/artifacts.
To achieve that, we will need to
- Create a new Workflow Contract (optional)
- Create a Workflow associated with a new or existing Contract
- Create an API Token for the CI/CD integration
Workflow Create
Let's create a Workflow for our build and test
CI pipeline
$ chainloop workflow create \
--name "build-and-test" \
--project "skynet" \
--team "cyberdyne core"
┌──────────────────────────────────────┬────────────────┬─────────┬────────────────┬─────────────────────┬────────┬─────────────────┐
│ ID │ NAME │ PROJECT │ TEAM │ CREATED AT │ # RUNS │ LAST RUN STATUS │
├──────────────────────────────────────┼────────────────┼─────────┼────────────────┼─────────────────────┼────────┼─────────────────┤
│ 2d289d33-8241-47b7-9ea2-8bd8b7c126f8 │ build-and-test │ skynet │ cyberdyne core │ 01 Nov 22 23:09 UTC │ 0 │ │
└──────────────────────────────────────┴────────────────┴─────────┴────────────────┴─────────────────────┴────────┴─────────────────┘
By default, if no contract is provided, a new, empty one will be created
$ chainloop workflow contract describe --name build-and-test
┌─────────────────────────────────────────────────────────────┐
│ Contract │
├──────────────────────┬──────────────────────────────────────┤
│ Name │ build-and-test │
├──────────────────────┼──────────────────────────────────────┤
│ ID │ fd489047-67f1-45d4-9f3b-27eba4051929 │
├──────────────────────┼──────────────────────────────────────┤
│ Associated Workflows │ 2d289d33-8241-47b7-9ea2-8bd8b7c126f8 │
├──────────────────────┼──────────────────────────────────────┤
│ Revision number │ 1 │
├──────────────────────┼──────────────────────────────────────┤
│ Revision Created At │ 01 Nov 22 23:09 UTC │
└──────────────────────┴──────────────────────────────────────┘
┌─────────────────────────┐
│ { │
│ "schemaVersion": "v1" │
│ } │
└─────────────────────────┘
Add materials to the Contract
We are going to update the contract with the materials we expect the attestation for this specific workflow to contain
- built container image as output
- rootfs directory used during build
- dockerfile (optional)
- commit sha
- Software Bill Of Materials in CycloneDX format
- A custom env variable to be resolved
- Github Action as target runner context. This means that this contract is valid only for that platform.
Setting the runner context type is optional, see runner contexts for more information.
- yaml
- cue
- json
schemaVersion: v1
# Arbitrary set of annotations can be added to the contract and will be part of the attestation
annotations:
- name: version
value: oss # if the value is left empty, it will be required and resolved at attestation time
# https://docs.chainloop.dev/reference/operator/material-types
materials:
# CONTAINER_IMAGE kinds will get resolved to retrieve their repository digest
- type: CONTAINER_IMAGE
name:
skynet-control-plane
# The output flag indicates that the material will be part of the attestation subject
output: true
# Arbitrary annotations can be added to the material
annotations:
- name: component
value: control-plane
# The value can be left empty so it can be provided at attestation time
- name: asset
# ARTIFACT kinds will first get uploaded to your artifact registry via the built-in Content Addressable Storage (CAS)
# Optional dockerfile
- type: ARTIFACT
name: dockerfile
optional: true
# SBOMs will be uploaded to the artifact registry and referenced in the attestation
# Both SBOM_CYCLONEDX_JSON and SBOM_SPDX_JSON are supported
- type: SBOM_CYCLONEDX_JSON
name: skynet-sbom
# CSAF_VEX and OPENVEX are supported
- type: OPENVEX
name: disclosure
# And static analysis reports in SARIF format
- type: SARIF
name: static-out
# or additional tools
- type: TWISTCLI_SCAN_JSON
name: scan-result
# https://docs.chainloop.dev/reference/policies
policies:
materials: # policies applied to materials
- ref: file://cyclonedx-licenses.yaml
attestation: # policies applied to the whole attestation
- ref: https://github.com/chainloop/chainloop-dev/blob/main/docs/examples/policies/chainloop-commit.yaml # (2)
# Env vars we want the system to resolve and inject during attestation initialization
# Additional ones can be inherited from the specified runner context below
envAllowList:
- CUSTOM_VAR
# Enforce in what runner context the attestation must happen
# If not specified, the attestation crafting process is allowed to run anywhere
runner:
type: "GITHUB_ACTION"
schemaVersion: "v1"
// Arbitrary set of annotations can be added to the contract and will be part of the attestation
annotations: [
{
name: "version"
value: "oss" // if the value is left empty, it will be required and resolved at attestation time
}
]
// Three required and one optional materials of three different kinds
// The output flag indicates that the material will be part of the attestation subject
materials: [
// CONTAINER_IMAGE kinds will get resolved to retrieve their repository digest
{
type: "CONTAINER_IMAGE"
name: "skynet-control-plane"
output: true
// Arbitrary annotations can be added to the material
annotations: [
{
name: "component"
value: "control-plane"
},
{
// The value can be left empty so it can be provided at attestation time
name: "asset"
},
]
},
// ARTIFACT kinds will first get uploaded to the built-in Content Addressable Storage (CAS)
{type: "ARTIFACT", name: "rootfs"},
{type: "ARTIFACT", name: "dockerfile", optional: true},
// STRING kind materials will be injected as simple keypairs
{type: "STRING", name: "build-ref"},
// SBOMs will be uploaded to the CAS and referenced in the attestation
// Both SBOM_CYCLONEDX_JSON and SBOM_SPDX_JSON are supported
{type: "SBOM_CYCLONEDX_JSON", name: "skynet-sbom"},
// CSAF_VEX and OPENVEX are supported
{type: "OPENVEX", name: "disclosure"},
// And static analysis reports in SARIF format
{type: "SARIF", name: "static-out"},
]
// Env vars we want the system to resolve and inject during attestation initialization
// Additional ones can be inherited from the specified runner context below
envAllowList: [ "CUSTOM_VAR"]
// Enforce in what runner context the attestation must happen
// If not specified, the attestation crafting process is allowed to run anywhere
runner: type: "GITHUB_ACTION"
{
"schemaVersion": "v1",
"annotations": [
{
"name": "version",
"value": "oss"
}
],
"materials": [
{
"type": "CONTAINER_IMAGE",
"name": "skynet-control-plane",
"output": true,
"annotations": [
{
"name": "component",
"value": "control-plane"
},
{
"name": "asset"
}
]
},
{ "type": "ARTIFACT", "name": "rootfs" },
{ "type": "ARTIFACT", "name": "dockerfile", "optional": true },
{ "type": "STRING", "name": "build-ref" },
{ "type": "SBOM_CYCLONEDX_JSON", "name": "skynet-sbom" },
{ "type": "OPENVEX", "name": "disclosure" },
{ "type": "SARIF", "name": "static-output" }
],
"envAllowList": ["CUSTOM_VAR"],
"runner": { "type": "GITHUB_ACTION" }
}
Update the name and schema, notice the revision increment
$ chainloop workflow contract update \
--name build-and-test \
-f https://raw.githubusercontent.com/chainloop-dev/chainloop/main/docs/examples/contracts/skynet/contract.yaml
┌─────────────────────────────────────────────────────────────┐
│ Contract │
├──────────────────────┬──────────────────────────────────────┤
│ Name │ skynet-contract │
├──────────────────────┼──────────────────────────────────────┤
│ ID │ fd489047-67f1-45d4-9f3b-27eba4051929 │
├──────────────────────┼──────────────────────────────────────┤
│ Associated Workflows │ 2d289d33-8241-47b7-9ea2-8bd8b7c126f8 │
├──────────────────────┼──────────────────────────────────────┤
│ Revision number │ 2 │
├──────────────────────┼──────────────────────────────────────┤
│ Revision Created At │ 02 Nov 22 09:08 UTC │
└──────────────────────┴──────────────────────────────────────┘
┌───────────────────────────────────────┐
│ { │
│ "schemaVersion": "v1", │
│ "materials": [ │
│ { │
│ "type": "CONTAINER_IMAGE", │
│ "name": "skynet-control-plane", │
│ "output": true │
...
We could have reached the same result by first creating the contract via chainloop workflow contract create -f ...
and then attaching it during workflow creation chainloop workflow create ... --contract deadbeef
API Token Creation
The final step is to create an API Token that will be shared with the development team so they can start the integration.
- API Tokens are attached to a single organization and can be used to perform attestations in multiple workflows.
- You can have multiple API Tokens per organization
- Tokens can be revoked via
chainloop org api-token revoke
command
$ chainloop org api-token create --name prod-ci
┌───────────────────────────────── ─────┬─────────┬─────────────┬─────────────────────┬────────────┬────────────┐
│ ID │ NAME │ DESCRIPTION │ CREATED AT │ EXPIRES AT │ REVOKED AT │
├──────────────────────────────────────┼─────────┼─────────────┼─────────────────────┼────────────┼────────────┤
│ 7016d5ad-6d2b-4da4-b657-4ad1ddcb4469 │ prod-ci │ │ 05 Jun 24 14:56 UTC │ │ │
└──────────────────────────────────────┴─────────┴─────────────┴─────────────────────┴────────────┴────────────┘
Save the following token since it will not printed again:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.REDACTED.GzTiR2r8YuccAmn-eZjCMNTSY5MU2gcRGNzu5rBhl80
We have everything we need to integrate our CI with Chainloop!
Optional setup
CAS Backend
As part of an attestation process, you might want to collect different pieces of evidence such as Software Bill Of Materials (SBOMs), test results, runner logs, etc and then attach them to the final in-toto attestation.
By default, Chainloop comes pre-configured with what we call an inline
backend. The inline backend embeds the pieces of evidence in the resulting attestations.
This is useful to get started quickly but since the metadata is embedded in the attestation, their size is limited.
We recommend that once you get closer to a production-ready setup, you switch to a more robust backend.
Refer to CAS Backends section for more information.
Third-Party integrations
Optionally you can enable third-party integrations such as DependencyTrack so the received Software Bill Of Materials (SBOMs) are forwarded there for analysis.