This is an example of how to create custom attestations using in-toto
and cosign
.
Directly from source:
$ go run main.go <attestation-type> [--flag]
Downloaded and extracted binary from GitHub Releases:
$ ./attestation <attestation-type> [--flag]
There are also working examples of each command in the Makefile.
All attestation types may use or require these flags.
--fulcio-url
: The Fulcio CA url for keyless signing. Defaults to https://fulcio.sigstore.dev
.
Intended only for use with ambient providers like GitHub Actions, as there are no options for overriding the default OIDC settings.
--rekor-url
: The transparency log URL. Defaults to https://rekor.sigstore.dev
.
--oidc-issuer-url
: Defaults to https://oauth2.sigstore.dev/auth
.
--oidc-client-id
: Defaults to sigstore
.
--id-token
: An optional flag to specify an id token to use for keyless signing.
--artifact-uri
: required URI of the OCI artifact i.e., the subject of the attestation.
ex: ghcr.io/liatrio/gh-trusted-builds-app
--artifact-digest
: required digest of the OCI artifact.
Used for retrieving related artifact attestations, and marking the attestation subject.
ex: sha256:60bcfdd293baac977357527bbd7ec2b5a7584ce276d33de0a4980c8ace6afd67
The following attestation types can be created by this app.
Which attestation type to create is given as a subcommand, ./attestation <attestation-type>
.
- Subcommand:
github-pull-request
- Predicate type:
https://liatr.io/attestations/github-pull-request/v1
This attestation type links a Git commit to a pull request, and includes information about the pull request.
It can be used to verify that:
- An author didn't approve their own pull request.
- A minimum threshold of reviewers approved the pull request.
- None of the pull request contributors approved the pull request.
_type: https://in-toto.io/Statement/v0.1
predicateType: https://liatr.io/attestations/github-pull-request/v1
subject:
- name: git+https://github.com/liatrio/gh-trusted-builds-app.git
digest:
sha1: e1f1d4396181766e12fca22f2ba856e8154b4304
- name: ghcr.io/liatrio/gh-trusted-builds-app
digest:
sha256: 6c3bf887638f7c0d86731e6208befa1b439e465cb435465d982c50609553b514
predicate:
link: https://github.com/liatrio/gh-trusted-builds-app/pull/1
title: 'docs: remove extra newline'
author: rcoy-v
mergedBy: rcoy-v
createdAt: '2023-05-22T15:27:05Z'
mergedAt: '2023-05-22T15:27:27Z'
base: main
head: rcoy-v-patch-1
approved: true
reviewers:
- name: alexashley
approved: true
reviewLink: >-
https://github.com/liatrio/gh-trusted-builds-app/pull/1#pullrequestreview-1436887240
timestamp: '2023-05-22T15:27:18Z'
contributors:
- name: rcoy-v
predicateCreatedAt: '2023-05-22T15:28:48.369418041Z'
The attestor expects to run inside a Git repository, as it will use the HEAD
sha to lookup pull requests.
For development, you can set the environment variable GH_PR_ATTESTOR_SHA_OVERRIDE
to use a different SHA; however, this will not work in CI servers
that set the CI
environment variable.
GITHUB_TOKEN
: A GitHub token with access to read pull request information from repository of the commit.
- Subcommand:
vsa
- Predicate type:
https://slsa.dev/verification_summary/v0.2
This creates a SLSA Verification Attestation Summary.
The following process is used to create a VSA:
- Retrieve all attestations related to the artifact which is the subject of the VSA, from Rekor.
The attestations include:
- Artifact-related e.g., Trivy scans.
- Source-related e.g., Pull request state.
- Provide all the collected attestations as input to the governance policy from liatrio/gh-trusted-builds-policy.
- Craft the in-toto attestation, using the policy results, provided attestations, and artifact digest.
- Sign the attestation, using either the KMS or Fulcio methods configured via flags.
- Upload the VSA to Rekor.
--debug
: Emit print logs from policy evaluation. Defaults to false
--policy-query
: The Rego query to use when evaluating the policy. Defaults to data.governance.allow
.
--policy-url
: Location of policy bundle that will be used to determine VSA result.
Supports http(s) urls for unauthenticated external downloads.
Absolute and relative paths can be used for an existing, local bundle or directory.
It's also possible to download files from a particular GitHub commit
Examples:
https://github.com/liatrio/gh-trusted-builds-policy/releases/download/v1.4.0/bundle.tar.gz
bundle.tar.gz
../bundle.tar.gz
/Users/myhome/bundle.tar.gz
../policy
https://github.com/liatrio/gh-trusted-builds-policy/tree/ef3194db6ca9a7a4b030686e4669c45db360a0c2/policy
--signer-identities-query
: A Rego query that should specify the expected attestation signer identities. The result should be a list of objects that can be unmarshalled into cosign.Identity
. Defaults to data.governance.signer_identities
.
[
{
"issuer": "https://token.actions.githubusercontent.com",
"subjectRegExp": `^https://github\.com/liatrio/gh-trusted-builds-workflows/\.github/workflows/build-and-push\.yaml@.*`,
}
]
--verifier-id
: ID of entity verifying the policy for the VSA.
- Subcommand:
version
Prints the build version information of the application.
In order to build the project, you'll need Go 1.22+.
Export any environment variables as described for the command being tested.
Each attestor should have a Makefile target to invoke it, like this: make github-pull-request
This application includes a suite of integration tests that verify the different attestation commands. In order to run the tests, you'll need to have these tools installed locally:
First, add the following entries to /etc/hosts
:
127.0.0.1 registry.local
127.0.0.1 rekor.rekor-system.svc
127.0.0.1 fulcio.fulcio-system.svc
127.0.0.1 ctlog.ctlog-system.svc
127.0.0.1 gettoken.default.svc
127.0.0.1 tuf.tuf-system.svc
Next, run make test-setup
. This will download resources from the Sigstore scaffolding repo, stand up a kind cluster, and deploy Rekor & Fulcio.
It will also create a TUF root that's used by the tests. The setup should take 5-10 minutes. It only needs to be run once.
cosign initialize
, meaning that if you have a custom TUF root configured, it will be temporarily overwritten in place of the TUF root created
by the scaffolding setup. The tests will attempt to save the TUF root in ~/.sigstore-backup
before running, and restore it after. If the tests fail to restore the custom root, you can remove it by running rm -rf ~/.sigstore
and mv ~/.sigstore-backup ~/.sigstore
.
If you're not using a custom TUF root, deleting the ~/.sigstore
directory should suffice.
Next, run make test
to start the tests. Unfortunately, there's some noise in the output, but you can usually ignore these logs about port-forwarding:
Handling connection for 8080
as well as the logs about issues with the TUF root metadata:
**Warning** Custom metadata not configured properly for target tsa_intermediate_0.crt.pem, skipping target
**Warning** Custom metadata not configured properly for target tsa_leaf.crt.pem, skipping target
You'll also see certificates printed in the output from keyless signing, these are generated during the tests:
Successfully verified SCT...
using ephemeral certificate:
-----BEGIN CERTIFICATE-----
MIIExTCCAq2gAwIBAgIUa1P4DGiAjiFev2fx+KCZ2NrK9VMwDQYJKoZIhvcNAQEL
BQAwfjEMMAoGA1UEBhMDVVNBMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH
...
Lastly, once you're done testing, you can run make test-teardown
to destroy the kind cluster. Optionally, you can also remove the entries from /etc/hosts
that were added in the first step.
The github-pull-request
attestor uses the GitHub API to populate the attestation.
In order to avoid depending on the live GitHub.com service, the tests use go-vcr
to replay past responses.
These responses are stored in test/fixtures/github
, organized by test name.
The fixture data is from liatrio/pr-attestation-fixtures
.
If you need to add a fixture for a new scenario, you can make changes in that repository.
Next, set GITHUB_TOKEN
to a fine-grained personal access token with the following scopes for the liatrio/pr-attestation-fixtures
repository:
contents
(read-only)metadata
(read-only)pull-requests
(read-only)
Finally, change the mode on GitHub API recorder to recorder.ModeRecordOnce
(if you're adding a new test) or recorder.ModeReplayWithNewEpisodes
(if you're making changes to an existing test).
r, err := recorder.NewWithOptions(&recorder.Options{
CassetteName: filepath.Join("fixtures", "github", t.Name()),
Mode: recorder.ModeRecordOnce,
RealTransport: oauth2Transport,
})