Daggerized version of Chainloop that can be used to attest and collect pieces of evidence from your Dagger pipelines.
- This module requires existing familiarity with Chainloop and its attestation process. Please refer to this guide to learn more.
You need an
API Tokenpreviously generated by your Chainloop administrator. - By default, this dagger module points to Chainloop's upstream instance, to point to your own instance, see the section below.
The attestation process starts with its initialization (init) or resume, then adding as many materials/pieces of evidence as needed (add-raw-evidence or add-file-evidence), and finally, signing and pushing the attestation to the Chainloop control plane (push).
You can invoke this module in two ways: either from the Dagger CLI dagger call ... or from your own Dagger pipeline by importing this module as a dependency.
To use Chainloop in your module, first, you need to add it as a dependency.
dagger install github.com/chainloop-dev/chainloopOnce done, you'll have access to the Chainloop client via dag.Chainloop() and start the attestation process with Init().
You can find a full example of how to integrate attestation crafting in your Go pipeline here
The attestation process starts with its initialization (init) or resume, then adding as many materials/pieces of evidence as needed (add-raw-evidence or add-file-evidence), and finally, signing and pushing the attestation to the Chainloop control plane (push).
This module is designed to support function chaining, so after initializing the attestation, you can chain the subcommands to add pieces of evidence and push the attestation. For example
dagger call -m github.com/chainloop-dev/chainloop \
# Initialize the command
init --token env:CHAINLOOP_TOKEN \
# we chain subcommands after the initialization
# add a raw evidence
add-raw-evidence --name my-evidence --value "my-value" \
# and push the result
push --key file:/path/to/cosign.key --passphrase env:COSIGN_PASSPHRASEIf the attestation process end-to-end is not completed in one go, you can store the attestation-id after init and resume the attestation process using the resume method at any time down the line.
# Initialize but this time we store the attestation-id
ATTESTATION_ID=$(dagger call -m github.com/chainloop-dev/chainloop init --token env:CHAINLOOP_TOKEN attestation-id)
# and we use it to resume the attestation process
dagger call -m github.com/chainloop-dev/chainloop \
resume --token env:CHAINLOOP_TOKEN --attestation-id $ATTESTATION_ID \
# we chain subcommands after the initialization
....1 - Init attestation (docs)
Initialize an attestation using the Chainloop token stored in the CHAINLOOP_TOKEN environment variable.
NOTE:
--tokencan be provided only by referencing an environment variable (env:MY_VAR), not by value
# Initialize the attestation and get its ID
dagger call -m github.com/chainloop-dev/chainloop \
init \
--token env:CHAINLOOP_TOKEN \
--workflow-name the-name-of-the-workflow \
--project-name the-name-of-the-project \
--repository /path/to/repo \ # optional flag to automatically attest a Git repository \
--contract-name my-existing-contract \ # optional flag to specify an existing contract that will be used during the creation of a workflowThe optional --mark-latest flag controls whether the project version is promoted to latest. It is an enum with three values:
ON_CREATE(default): newly created versions becomelatest; existing versions are left unchanged.TRUE: force-promote a pre-release version tolatest.FALSE: do not promote the version tolatest.
TRUE and FALSE only affect promotion: they never demote a version that is already latest. In particular, FALSE prevents a newly created version from becoming latest, but it has no effect on an existing version (its latest status is left untouched).
dagger call -m github.com/chainloop-dev/chainloop \
init \
--token env:CHAINLOOP_TOKEN \
--workflow-name the-name-of-the-workflow \
--project-name the-name-of-the-project \
--version 1.0.0 \
--mark-latest FALSE # create the new version 1.0.0 without making it latest2 - Get the status (docs)
Resuming a previous attestation
dagger call -m github.com/chainloop-dev/chainloop \
resume --token env:CHAINLOOP_TOKEN --attestation-id $ATTESTATION_ID \
statusor chaining the command right after initialization
dagger call -m github.com/chainloop-dev/chainloop \
init --token env:CHAINLOOP_TOKEN \
status3 - Add pieces of evidence (docs)
You can attest pieces of evidence by providing its material name and its value, either in the form of a path to a file (--path) or a raw value (--value).
A path to a file is required for materials derived from artifacts, such as Software Bill Of materials, or any other file-based evidence.
# Provide a material of kind artifact through its path
# Remember, we first resume the attestation or chain the commands
dagger call -m github.com/chainloop-dev/chainloop \
resume --token env:CHAINLOOP_TOKEN --attestation-id $ATTESTATION_ID \
add-file-evidence --name my-sbom --path ./path/to/sbom.json# Or one with a raw value such as a container image reference
dagger call -m github.com/chainloop-dev/chainloop \
resume --token env:CHAINLOOP_TOKEN --attestation-id $ATTESTATION_ID \
add-raw-evidence --name my-container-image --value ghcr.io/chainloop-dev/chainloop/control-planeIf you're attesting materials that don't belong to the target contract, you can allow Chainloop to figure out its type and if discovered, it will be added to the attestation.
In order to do that, don't pass the --name, just provide the path to the file.
# Provide a material only through its path
dagger call -m github.com/chainloop-dev/chainloop \
resume --token env:CHAINLOOP_TOKEN --attestation-id $ATTESTATION_ID \
add-file-evidence --path ./path/to/sbom.jsonIn some cases, you might be providing a private container image as a piece of evidence. In this case, you'll also need to preload the container registry credentials.
dagger call -m github.com/chainloop-dev/chainloop \
resume --token env:CHAINLOOP_TOKEN --attestation-id $ATTESTATION_ID \
# Now load the registry credentials
with-registry --address ghcr.io --username my-username --password MY_PAT_TOKEN \
# And perform the attestation of the private container image
add-raw-evidence --name my-container-image --value ghcr.io/chainloop-dev/chainloop/control-plane4 - Sign and push attestation (docs)
Sign and push the attestation using a cosign key stored in a file and a passphrase stored in an environment variable.
NOTE: neither --signing-key nor --passphrase can be provided by value. You need to provide them either as a file (file:/) or an environment variable (env:/).
dagger call -m github.com/chainloop-dev/chainloop \
resume --token env:CHAINLOOP_TOKEN --attestation-id $ATTESTATION_ID \
push --key file:/path/to/cosign.key --passphrase env:COSIGN_PASSPHRASEAlternatively, you can also provide the signing key in an environment variable --key env:MY_COSIGN_KEY
dagger call -m github.com/chainloop-dev/chainloop \
resume --token env:CHAINLOOP_TOKEN --attestation-id $ATTESTATION_ID \
mark-failed --reason "Something went wrong"or cancel the attestation process
dagger call -m github.com/chainloop-dev/chainloop \
resume --token env:CHAINLOOP_TOKEN --attestation-id $ATTESTATION_ID \
mark-canceled --reason "nothing to see here"When running Chainloop via Dagger inside GitHub Actions or GitLab CI, you can enable automatic PR/MR metadata collection by passing the parent CI environment context to the init command. This allows Chainloop to automatically detect and include pull request or merge request information in your attestations.
More information about the feature can be found in the official documentation where we give a broader context on how Chainloop can help to build Control Gates in your GitHub and GitLab repositories.
When using the Dagger SDK in GitHub Actions, you can pass PR context as individual parameters. The event file would need to be provided as a File parameter from your module's calling context:
attestation, err := dag.Chainloop().Init(
ctx,
dag.SetSecret("chainloop-token", os.Getenv("CHAINLOOP_TOKEN")),
dagger.ChainloopInitOpts{
WorkflowName: "build",
ProjectName: "my-project",
Repository: dag.CurrentModule().Source(),
GithubEventName: os.Getenv("GITHUB_EVENT_NAME"),
GithubHeadRef: os.Getenv("GITHUB_HEAD_REF"),
GithubBaseRef: os.Getenv("GITHUB_BASE_REF"),
// GithubEventFile would be passed from caller if available
GithubEventFile: eventFile,
},
)
if err != nil {
return err
}Note: For full GitHub PR detection with event file parsing, using the Dagger CLI (see below) is recommended as it can directly mount the event file from
$GITHUB_EVENT_PATH.
Required Environment Variables:
GITHUB_EVENT_NAME- Must be "pull_request" or "pull_request_target"GITHUB_HEAD_REF- Source branch nameGITHUB_BASE_REF- Target branch nameGITHUB_EVENT_PATH- Path to event JSON file (for CLI usage)
GitLab MR detection from the Dagger SDK:
attestation, err := dag.Chainloop().Init(
ctx,
dag.SetSecret("chainloop-token", os.Getenv("CHAINLOOP_TOKEN")),
dagger.ChainloopInitOpts{
WorkflowName: "build",
ProjectName: "my-project",
Repository: dag.CurrentModule().Source(),
GitlabPipelineSource: os.Getenv("CI_PIPELINE_SOURCE"),
GitlabMRIID: os.Getenv("CI_MERGE_REQUEST_IID"),
GitlabMRTitle: os.Getenv("CI_MERGE_REQUEST_TITLE"),
GitlabMRDescription: os.Getenv("CI_MERGE_REQUEST_DESCRIPTION"),
GitlabMRSourceBranch: os.Getenv("CI_MERGE_REQUEST_SOURCE_BRANCH_NAME"),
GitlabMRTargetBranch: os.Getenv("CI_MERGE_REQUEST_TARGET_BRANCH_NAME"),
GitlabMRProjectURL: os.Getenv("CI_MERGE_REQUEST_PROJECT_URL"),
GitlabUserLogin: os.Getenv("GITLAB_USER_LOGIN"),
},
)
if err != nil {
return err
}Required Environment Variables:
CI_PIPELINE_SOURCE- Must be "merge_request_event"CI_MERGE_REQUEST_IID- MR numberCI_MERGE_REQUEST_TITLE- MR titleCI_MERGE_REQUEST_DESCRIPTION- MR descriptionCI_MERGE_REQUEST_SOURCE_BRANCH_NAME- Source branchCI_MERGE_REQUEST_TARGET_BRANCH_NAME- Target branchCI_MERGE_REQUEST_PROJECT_URL- Project URLGITLAB_USER_LOGIN- User login
When calling the Dagger module directly from the Dagger CLI in a GitHub Actions workflow:
dagger call -m github.com/chainloop-dev/chainloop \
init \
--token env:CHAINLOOP_TOKEN \
--workflow-name build \
--project-name my-project \
--github-event-file=$GITHUB_EVENT_PATH \
--github-event-name=$GITHUB_EVENT_NAME \
--github-head-ref=$GITHUB_HEAD_REF \
--github-base-ref=$GITHUB_BASE_REF \
add-raw-evidence \
--name evidence \
--value data \
push \
--key env:SIGNING_KEYNote: All GitHub PR parameters are passed as individual flags. The event file is automatically mounted by Dagger CLI.
dagger call -m github.com/chainloop-dev/chainloop \
init \
--token env:CHAINLOOP_TOKEN \
--workflow-name build \
--project-name my-project \
--gitlab-pipeline-source=$CI_PIPELINE_SOURCE \
--gitlab-mriid=$CI_MERGE_REQUEST_IID \
--gitlab-mrtitle=$CI_MERGE_REQUEST_TITLE \
--gitlab-mrdescription=$CI_MERGE_REQUEST_DESCRIPTION \
--gitlab-mrsource-branch=$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME \
--gitlab-mrtarget-branch=$CI_MERGE_REQUEST_TARGET_BRANCH_NAME \
--gitlab-mrproject-url=$CI_MERGE_REQUEST_PROJECT_URL \
--gitlab-user-login=$GITLAB_USER_LOGIN \
add-raw-evidence \
--name evidence \
--value data \
push \
--key env:SIGNING_KEYTo verify the signature of the attested commit with GitLab, also pass the GitLab CI context. The GitLab commit signature API does not accept the job token, so for private and internal projects pass a personal, project or group access token with the read_api scope in --gitlab-token.
If your self-managed GitLab server uses a certificate from a private CA, also pass that CA certificate, for example --gitlab-ca=$CI_SERVER_TLS_CA_FILE when the GitLab Runner is configured with it.
dagger call -m github.com/chainloop-dev/chainloop \
init \
--token env:CHAINLOOP_TOKEN \
--workflow-name build \
--project-name my-project \
--gitlab-ci=$GITLAB_CI \
--gitlab-server-url=$CI_SERVER_URL \
--gitlab-project-path=$CI_PROJECT_PATH \
--gitlab-job-url=$CI_JOB_URL \
--gitlab-job-token=env:CI_JOB_TOKEN \
--gitlab-token=env:GITLAB_TOKEN \
push- All PR/MR parameters are optional - if not provided, attestations work normally without PR metadata
- PR detection will fail gracefully if required env vars are missing or invalid - the attestation will continue without PR metadata
This module also provides access to the underlying Chainloop CLI by exposing some of its commands
dagger call -m github.com/chainloop-dev/chainloop \
workflow-create --token env:CHAINLOOP_TOKEN --name [your-workflow-name] --project [its-project]By default, this dagger module points to Chainloop's upstream instance, but if you want to point to your own instance, you can do it by running the with-instance command just right after dagger call
dagger call with-instance --controlplane-api api.cp.chainloop.dev:443 --cas-api api.cas.chainloop.dev:443 [SUBCOMMAND]example initializing an attestation
dagger call with-instance --controlplane-api api.cp.chainloop.dev:443 --cas-api api.cas.chainloop.dev:443 \
init --token env:CHAINLOOP_TOKEN --workflow-name test-from-dagger-2 attestation-idPlease run dagger call with-instance -h for more information on the available options.
To learn more, please visit the Chainloop project's documentation website, https://docs.chainloop.dev where you will find a getting started guide, FAQ, examples, and more.
Chainloop is developed in the open and is constantly improved by our users, contributors and maintainers. Got a question, comment, or idea? Please don't hesitate to reach out via: