An attestation is a signed and verifiable unit of data sent to Chainloop. Users and CI systems use the Chainloop CLI to “craft” attestations, add pieces of evidence (materials) to them, and “push” them to Chainloop service (the evidence store).
Attestations are performed with the Chainloop command line interface (CLI) and its lifecycle has the following stages: init, add, push or reset.
As you can see, it mimics the workflow of a commonly used version control tool, and this is not by coincidence. Chainloop wants to make sure that the tooling feels familiar to developers and that no security jargon leaks into this stage of the process. For a developer, creating an attestation must be as simple as initializing it, adding pieces of evidence (materials) to it, and pushing it.
# initialize the attestation. Downloading the contract> chainloop att init --workflow workflow-name --project project-name# add materials to the attestation> chainloop att add --value ghcr.io/chainloop-dev/chainloop/control-plane:latest> chainloop att add --value reports/cyclonedx.json --kind SBOM_CYCLONEDX_JSON# generate, sign and push the attestation to chainloop> chainloop att push
This bundle contains the attestation in the form of a DSSE envelope, and the verification materials required to verify the attestation. These include intermediate certificates, in the case of ephemeral certificates or timestamp services.
Native CI/CD runner integrations (e.g., Jenkins plugin, GitHub action) are under development. In the meantime, please use the chainloop CLI as shown in these examples
Copy
# Example of GitHub action that # - Builds a go binary and associated container image using go-releaser# - Extract a CycloneDX SBOM using Syft# - Stores the required materials stated in this Chainloop contract# https://github.com/chainloop-dev/chainloop/blob/main/docs/examples/contracts/skynet/contract.yaml# - Pushes the resulting attestation to the control planename: Releaseon: push: tags: - "v*.*.*"jobs: release: env: # Version of Chainloop to install CHAINLOOP_TOKEN: ${{ secrets.CHAINLOOP_WF_RELEASE }} # The name of the workflow registered in Chainloop control plane CHAINLOOP_WORKFLOW_NAME: build-and-test CHAINLOOP_PROJECT_NAME: skynet name: "Release binary and container images" runs-on: ubuntu-latest permissions: contents: write # required for goreleaser steps: - name: Install Chainloop run: | curl -sfL https://dl.chainloop.dev/cli/install.sh | bash -s - name: Checkout uses: actions/checkout@v4 - name: Initialize Attestation run: | chainloop attestation init --workflow $CHAINLOOP_WORKFLOW_NAME --project $CHAINLOOP_PROJECT_NAME - name: Set up Go uses: actions/setup-go@v3 with: go-version: "1.23.6" # Generate SBOM using syft in cycloneDX format - uses: anchore/sbom-action@v0 with: image: ****.dkr.ecr.us-east-1.amazonaws.com/container-image:${{ github.ref_name }} format: cyclonedx-json output-file: /tmp/skynet.cyclonedx.json - name: Add Attestation Artifacts run: | # Add binaries created by goreleaser chainloop attestation add --name [binary-name] --value [binary-path] # Created container image chainloop attestation add --name control-plane-image --value ****.dkr.ecr.us-east-1.amazonaws.com/container-image:${{ github.ref_name }} # This is just an example of adding a key/val material type # Alternatively, GITHUB_SHA could have been added to the contract env variables allowList chainloop attestation add --name build-ref --value ${GITHUB_SHA} # Attach SBOM chainloop attestation add --name skynet-sbom --value /tmp/skynet.cyclonedx.json - name: Finish and Record Attestation if: ${{ success() }} run: | # Note that these commands are using CHAINLOOP_TOKEN env variable to authenticate chainloop attestation push - name: Mark attestation as failed if: ${{ failure() }} run: | chainloop attestation reset - name: Mark attestation as cancelled if: ${{ cancelled() }} run: | chainloop attestation reset --trigger cancellation
Copy
# Example of GitHub action that # - Builds a go binary and associated container image using go-releaser# - Extract a CycloneDX SBOM using Syft# - Stores the required materials stated in this Chainloop contract# https://github.com/chainloop-dev/chainloop/blob/main/docs/examples/contracts/skynet/contract.yaml# - Pushes the resulting attestation to the control planename: Releaseon: push: tags: - "v*.*.*"jobs: release: env: # Version of Chainloop to install CHAINLOOP_TOKEN: ${{ secrets.CHAINLOOP_WF_RELEASE }} # The name of the workflow registered in Chainloop control plane CHAINLOOP_WORKFLOW_NAME: build-and-test CHAINLOOP_PROJECT_NAME: skynet name: "Release binary and container images" runs-on: ubuntu-latest permissions: contents: write # required for goreleaser steps: - name: Install Chainloop run: | curl -sfL https://dl.chainloop.dev/cli/install.sh | bash -s - name: Checkout uses: actions/checkout@v4 - name: Initialize Attestation run: | chainloop attestation init --workflow $CHAINLOOP_WORKFLOW_NAME --project $CHAINLOOP_PROJECT_NAME - name: Set up Go uses: actions/setup-go@v3 with: go-version: "1.23.6" # Generate SBOM using syft in cycloneDX format - uses: anchore/sbom-action@v0 with: image: ****.dkr.ecr.us-east-1.amazonaws.com/container-image:${{ github.ref_name }} format: cyclonedx-json output-file: /tmp/skynet.cyclonedx.json - name: Add Attestation Artifacts run: | # Add binaries created by goreleaser chainloop attestation add --name [binary-name] --value [binary-path] # Created container image chainloop attestation add --name control-plane-image --value ****.dkr.ecr.us-east-1.amazonaws.com/container-image:${{ github.ref_name }} # This is just an example of adding a key/val material type # Alternatively, GITHUB_SHA could have been added to the contract env variables allowList chainloop attestation add --name build-ref --value ${GITHUB_SHA} # Attach SBOM chainloop attestation add --name skynet-sbom --value /tmp/skynet.cyclonedx.json - name: Finish and Record Attestation if: ${{ success() }} run: | # Note that these commands are using CHAINLOOP_TOKEN env variable to authenticate chainloop attestation push - name: Mark attestation as failed if: ${{ failure() }} run: | chainloop attestation reset - name: Mark attestation as cancelled if: ${{ cancelled() }} run: | chainloop attestation reset --trigger cancellation
For GitLab we support keyless attestations using GitLab’s OIDC tokens. Please refer to the GitLab Keyless Attestations guide for more information.
Copy
# Example of GitLab Pipeline that# - Builds a go binary and associated container image using go-releaser# - Extract a CycloneDX SBOM using Syft# - Stores the required materials stated in this Chainloop contract# https://github.com/chainloop-dev/chainloop/blob/main/docs/examples/contracts/container-image-sbom/gitlab.yaml# - Pushes the resulting attestation to the control planestages: - releasevariables: # Service account associated to this workflow in Chainloop's control plane CHAINLOOP_TOKEN: $CHAINLOOP_TOKEN # This job pushed container images to GitLab OCI registry DOCKER_REGISTRY: $CI_REGISTRY DOCKER_USERNAME: $CI_REGISTRY_USER DOCKER_PASSWORD: $CI_REGISTRY_PASSWORD GITLAB_TOKEN: $CI_JOB_TOKEN# Download and store Chainloop CLI as an artifact since the next phase uses docker:stable# runner image has not capabilities (wget/curl) to install itdownload_chainloop: stage: release only: refs: - tags script: # We need to install it in the current path in order to be archived - curl -sfL https://dl.chainloop.dev/cli/install.sh | bash -s -- --path . artifacts: paths: - chainloop expire_in: 5 minsrelease: stage: release image: docker:stable services: - docker:dind needs: - job: download_chainloop only: refs: - tags before_script: # Initialize attestation - chainloop att init --token $CHAINLOOP_TOKEN --workflow build-and-test --project skynet # Install Syft - wget --no-verbose https://raw.githubusercontent.com/anchore/syft/main/install.sh -O - | sh -s -- -b /usr/local/bin script: # Both CI_JOB_TOKEN and GITLAB_TOKEN required to be passed as env variables # https://github.com/goreleaser/goreleaser/blob/8ebefd251e0eddd3c294b4d45b6e637783a252f3/internal/client/gitlab.go#L500 - | docker run --rm --privileged \ -v $PWD:/tmp/release-job \ -w /tmp/release-job \ -v /var/run/docker.sock:/var/run/docker.sock \ -e DOCKER_USERNAME -e DOCKER_PASSWORD -e DOCKER_REGISTRY \ -e CI_JOB_TOKEN -e GITLAB_TOKEN \ goreleaser/goreleaser release --rm-dist # Build sbom - syft packages registry.gitlab.com/chainloop-dev/integration-demo:$CI_COMMIT_REF_NAME -o cyclonedx-json --file sbom.cyclonedx.json # Add attestation - chainloop attestation add --name sbom --value sbom.cyclonedx.json - chainloop attestation add --name image --value registry.gitlab.com/chainloop-dev/integration-demo:$CI_COMMIT_REF_NAME # Finish attestation - chainloop attestation push after_script: - chainloop attestation reset || true
Security and Compliance teams can define requirements on what must be included in the attestation through contracts.
Alternatively, and in addition to the materials defined on the contract, you can add as many contract-less materials as you like by providing its value.
The CLI will automatically discover the kind of material you are adding, but you can also specify it by providing the --kind flag.
For example
Copy
# Add a contract-less material by providing a name, value and kindchainloop att add --name not-in-contract --value ./cyberdyne.cyclonedx.sbom --kind SBOM_CYCLONEDX_JSON# If you do not provide the --kind, the CLI will automatically discover itchainloop att add --name not-in-contract --value --value ./cyberdyne.cyclonedx.sbom# and if you do not provide the name, the CLI will generate one for youchainloop att add --value ./cyberdyne.cyclonedx.sbom
Contract-less and auto-discovery and two features that walk hand by hand. They compose a new way of adding pieces of evidences to an existing contract in a frictionless way. You can see it in action in our quickstart guide.
By default, the attestation process state is stored locally. But this setup is not suitable when running a multi-step attestation process in a stateless environment, like our Dagger module, or when you want to leverage CI multi-job parallelism or similar.
For that, we implemented attestation remote state. Simply put, instead of the attestation CLI being in charge of maintaining the state during the attestation, this can be delegated to the server and retrieved at any time by providing an “attestation-id.”
Copy
# You can enable the feature by providing the --remote-state flag# and it will return an attestation-idchainloop attestation init --name my-workflow --project my-project --remote-state# Then you can add any piece of evidence by providing the attestation-idchainloop attestation add --value cyberdyne.cyclonedx.sbom --attestation-id deadbeef# And finally craft the attestation, sign-it and push itchainloop attestation push --attestation-id deadbeef
With this optional feature, as long as you have the attestation-id, you can add any piece of evidence to the attestation from anywhere.