Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 52 additions & 4 deletions GETTING_STARTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ commits using the embedded attestations stored in git notes in your git reposito
To verify the latest commit, run:

```bash
sourcetool verifycommit yourorg/yourrepo
sourcetool verify yourorg/yourrepo
```

## Onboarding Guide
Expand Down Expand Up @@ -151,6 +151,15 @@ was found and the current SLSA level of the repository:

![](docs/media/image04-status.png)

When the controls are eligible for a higher level than the policy verifies,
status explains the gap (for example, when the policy targets a lower level or
is not fully met). To print only the policy-verified SLSA source level, for
example when scripting, pass `--level`:

```bash
sourcetool status yourorg/yourrepo --level
```

## One-shot Repository Set Up

The quickest way to set up a repository is to use sourcetool’s one-shot set up
Expand Down Expand Up @@ -238,22 +247,61 @@ sourcetool policy view yourorg/yourrepo

![](docs/media/image09-policy.png )

## Verifying Commits
## Verifying Commits and Tags

Once the SLSA controls are in place, each commit pushed into the repository will
generate source provenance metadata and store it in git notes by default. These
attestations and VSAs can be used to verify the SLSA level of the repository.

To verify a commit, use the `verifycommit` subcommand. Pass it a commit locator
To verify a revision, use the `verify` subcommand. Pass it a commit locator
like this:

```bash
sourcetool verifycommit slsa-framework/slsa-source-poc@fc0f59a9332e7873bb146b95cc4b39232eada7d2
sourcetool verify slsa-framework/source-tool@fc0f59a9332e7873bb146b95cc4b39232eada7d2
```

![](docs/media/image10-pcy-json.png)

If you omit the commit SHA, sourcetool will verify the last commit in the branch.
You can also verify a tag with `--tag`:

```bash
sourcetool verify yourorg/yourrepo --tag v1.0.0
```

`verify` reports the policy-verified SLSA source level and exits with a non-zero
status when the revision cannot be verified, which makes it convenient to gate
scripts and CI.

### Reading From Different Attestation Sources

By default the reading subcommands (`verify`, `get`, `attest` and `audit`) read
attestations from the git notes stored in your repository. If your attestations
are also published to the GitHub attestations API (with `--push=github`), point
the reader at that source with `--from`:

```bash
sourcetool verify yourorg/yourrepo --from=github
sourcetool verify yourorg/yourrepo --from=github,note
```

Git notes is the default because reading from the GitHub attestations API
requires the token to have attestations read access, which is not always granted
in CI.

## Retrieving Attestations

To fetch and print the raw attestations for a revision, use the `get` subcommand:

```bash
sourcetool get yourorg/yourrepo
```

By default `get` prints both the source provenance and the VSA; use
`--provenance` or `--vsa` to select just one. It verifies every attestation it
prints and writes a warning to stderr when verification fails. Pass
`--require-verified` to make it exit with a non-zero status on a verification
failure.

## Troubleshooting

Expand Down
16 changes: 8 additions & 8 deletions docs/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ using GitHub's existing functionality.

* Users create a [policy](#policy) for the repo & branches they want to protect,
indicating their desired SLSA level.
* Users call the .github/workflows/slsa_with_provenance.yml reusable workflow on any
* Users call the `compute_slsa_source.yml` reusable workflow from
[source-actions](https://github.com/slsa-framework/source-actions) on any
`push` changes to protected branches.
* The slsa_with_provenance workflow gets the attestations, if any, for the prior
commit.
* The slsa_with_provenance workflow evaluates their controls, the current commit, and
prior attestations, to determine the SLSA Source level of the current commit.
* A VSA, 'source provenance', are created within the workflow, and
* The workflow gets the attestations, if any, for the prior commit.
* The workflow evaluates their controls, the current commit, and prior
attestations, to determine the SLSA Source level of the current commit.
* A VSA and 'source provenance' are created within the workflow, and
are stored in [git notes](https://git-scm.com/docs/git-notes) for the current commit.
* Downstream users can get the VSA for the revision they're consuming by getting the
git notes for that revision.
Expand All @@ -39,7 +39,7 @@ purposes and may be deprecated.

TODO: Should we cut this section and feature?

In the control-only approach the `sourcetool` with the `checklevel` command fetches the
In the control-only approach the `sourcetool status` command fetches the
rulesets that are _currently_ enabled on the source repository.

If all of the following are true:
Expand Down Expand Up @@ -243,7 +243,7 @@ Source provenance covers changes to a branch. It indicates:
}
}
],
"predicateType": "https://github.com/slsa-framework/source-tool/source-provenance/v1-draft",
"predicateType": "https://github.com/slsa-framework/slsa-source-poc/source-provenance/v1-draft",
"predicate": {
"activity_type": "pr_merge",
"actor": "TomHennen",
Expand Down
151 changes: 151 additions & 0 deletions internal/cmd/attest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// SPDX-FileCopyrightText: Copyright 2025 The SLSA Authors
// SPDX-License-Identifier: Apache-2.0

package cmd

import (
"errors"
"fmt"
"os"
"slices"

"github.com/spf13/cobra"

"github.com/slsa-framework/source-tool/pkg/sourcetool"
)

type attestOptions struct {
revisionOpts
pushOptions
fromOptions
allowMergeCommitsOptions
provenance bool
vsa bool
sign bool
output string
useLocalPolicy string
silentDowngrade bool
}

func (ao *attestOptions) Validate() error {
errs := []error{
ao.revisionOpts.Validate(),
ao.pushOptions.Validate(),
ao.fromOptions.Validate(),
}
if !ao.provenance && !ao.vsa {
errs = append(errs, errors.New("nothing to generate: enable --provenance and/or --vsa"))
}
return errors.Join(errs...)
}

func (ao *attestOptions) AddFlags(cmd *cobra.Command) {
ao.revisionOpts.AddFlags(cmd)
ao.pushOptions.AddFlags(cmd)
ao.fromOptions.AddFlags(cmd)
ao.allowMergeCommitsOptions.AddFlags(cmd)
cmd.PersistentFlags().BoolVar(&ao.provenance, "provenance", true, "write the provenance attestation")
cmd.PersistentFlags().BoolVar(&ao.vsa, "vsa", true, "write the verification summary attestation (VSA)")
cmd.PersistentFlags().BoolVar(&ao.sign, "sign", true, "sign the attestations")
cmd.PersistentFlags().StringVar(&ao.output, "output", "", "path to write the attestation bundle (default: stdout)")
cmd.PersistentFlags().StringVar(&ao.useLocalPolicy, "use-local-policy", "", "path to a local policy file to evaluate instead of the community policy")
cmd.PersistentFlags().BoolVar(&ao.silentDowngrade, "silent-downgrade", false, "warn instead of failing when the achieved level is below the policy target")
}

func addAttest(parentCmd *cobra.Command) {
opts := attestOptions{}
attestCmd := &cobra.Command{
Use: "attest [flags] owner/repo[@ref]",
GroupID: cmdGroupAttestation,
Short: "Generate the source attestations for a revision",
Long: `Generate the SLSA source attestations for a revision.

attest creates the source provenance and the verification summary
attestation (VSA) for a commit or a tag. Use --provenance and --vsa to
select which of the two are written. When the VSA is disabled the
repository policy is not evaluated and only the provenance is produced.

The attestations are written to stdout as a JSONL bundle unless --output
is given, and can be pushed to storage with --push.`,
SilenceUsage: true,
SilenceErrors: true,
PreRunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
if err := opts.ParseLocator(args[0]); err != nil {
return err
}
}

if err := opts.repoOptions.Validate(); err != nil {
return err
}

return opts.EnsureDefaults()
},
RunE: func(cmd *cobra.Command, args []string) error {
if err := opts.Validate(); err != nil {
return fmt.Errorf("validating options: %w", err)
}

var githubStorer, notesStorer, pushAttestations bool
if slices.Contains(opts.pushLocation, pushRepoGithub) {
pushAttestations = true
githubStorer = true
}
if slices.Contains(opts.pushLocation, pushRepoNote) {
pushAttestations = true
notesStorer = true
}

authenticator, err := CheckAuth()
if err != nil {
return err
}

srctool, err := sourcetool.New(
sourcetool.WithAuthenticator(authenticator),
sourcetool.WithAllowMergeCommits(opts.allowMergeCommits),
sourcetool.WithNotesStorer(notesStorer),
sourcetool.WithGithubStorer(githubStorer),
sourcetool.WithGithubCollector(opts.readGithub()),
sourcetool.WithNotesCollector(opts.readNotes()),
)
if err != nil {
return fmt.Errorf("creating sourcetool: %w", err)
}

result, err := srctool.AttestRevision(
cmd.Context(), opts.GetBranch(), opts.GetRevision(),
sourcetool.WithProvenance(opts.provenance),
sourcetool.WithVSA(opts.vsa),
sourcetool.WithSign(opts.sign),
sourcetool.WithLocalPolicy(opts.useLocalPolicy),
sourcetool.WithOutputPath(opts.output),
sourcetool.WithUseStdout(opts.output == ""),
sourcetool.WithPush(pushAttestations),
)
if err != nil {
return fmt.Errorf("attesting revision: %w", err)
}

// The attestations are generated (and optionally pushed) regardless
// of the policy outcome. When the achieved level is below the policy
// target return exit code 2, or just a warning with --silent-downgrade.
if result.Shortfall != nil {
msg := fmt.Sprintf(
"policy target level %s not met; achieved %s: %s",
result.Shortfall.TargetLevel, result.Shortfall.AchievedLevel, result.Shortfall.Reason,
)
if opts.silentDowngrade {
fmt.Fprintf(os.Stderr, "warning: %s\n", msg)
return nil
}
return &exitError{code: 2, err: errors.New(msg)}
}

return nil
},
}
opts.AddFlags(attestCmd)
parentCmd.AddCommand(attestCmd)
}
5 changes: 5 additions & 0 deletions internal/cmd/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ type auditOpts struct {
branchOptions
verifierOptions
outputOptions
fromOptions
auditDepth int
endingCommit string
auditMode AuditMode
Expand Down Expand Up @@ -104,6 +105,7 @@ func (ao *auditOpts) Validate() error {
ao.branchOptions.Validate(),
ao.verifierOptions.Validate(),
ao.outputOptions.Validate(),
ao.fromOptions.Validate(),
}
return errors.Join(errs...)
}
Expand All @@ -112,6 +114,7 @@ func (ao *auditOpts) AddFlags(cmd *cobra.Command) {
ao.branchOptions.AddFlags(cmd)
ao.verifierOptions.AddFlags(cmd)
ao.outputOptions.AddFlags(cmd)
ao.fromOptions.AddFlags(cmd)
cmd.PersistentFlags().IntVar(&ao.auditDepth, "depth", 0, "The max number of revisions to audit (depth <= audit all revisions).")
cmd.PersistentFlags().StringVar(&ao.endingCommit, "ending-commit", "", "The commit to stop auditing at.")
ao.auditMode = AuditModeBasic
Expand Down Expand Up @@ -165,6 +168,8 @@ Future:
srctool, err := sourcetool.New(
sourcetool.WithAuthenticator(authenticator),
sourcetool.WithExpectedIdentity(opts.expectedIssuer, opts.expectedSan),
sourcetool.WithGithubCollector(opts.readGithub()),
sourcetool.WithNotesCollector(opts.readNotes()),
)
if err != nil {
return err
Expand Down
7 changes: 4 additions & 3 deletions internal/cmd/checklevel.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,10 @@ func addCheckLevel(parentCmd *cobra.Command) {
opts := checkLevelOpts{}

checklevelCmd := &cobra.Command{
Use: "checklevel",
GroupID: cmdGroupAssessment,
Short: "Determines the SLSA Source Level of the repo",
Use: "checklevel",
Hidden: true,
Deprecated: `use "sourcetool status --level" instead`,
Short: "Determines the SLSA Source Level of the repo",
Long: `Determines the SLSA Source Level of the repo.

This is meant to be run within the corresponding GitHub Actions workflow.`,
Expand Down
9 changes: 5 additions & 4 deletions internal/cmd/checklevelprov.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,11 @@ func addCheckLevelProv(parentCmd *cobra.Command) {
opts := &checkLevelProvOpts{}

checklevelprovCmd := &cobra.Command{
Use: "checklevelprov",
GroupID: cmdGroupAssessment,
Example: `sourcetool checklevelprov owner/repo --push=note`,
Short: "Checks the given commit against policy using & creating provenance",
Use: "checklevelprov",
Hidden: true,
Deprecated: `use "sourcetool attest" instead`,
Example: `sourcetool checklevelprov owner/repo --push=note`,
Short: "Checks the given commit against policy using & creating provenance",
Long: `Checks the given commit against policy using & creating provenance.

The checklevelprov subcommand computes the SLSA level of a commit by retrieving
Expand Down
7 changes: 4 additions & 3 deletions internal/cmd/checktag.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ func addCheckTag(parentCmd *cobra.Command) {
opts := &checkTagOptions{}

checktagCmd := &cobra.Command{
Use: "checktag",
GroupID: cmdGroupAssessment,
Short: "Checks to see if the tag operation should be allowed and issues a VSA",
Use: "checktag",
Hidden: true,
Deprecated: `use "sourcetool attest --tag <name>" instead`,
Short: "Checks to see if the tag operation should be allowed and issues a VSA",
PreRunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
if err := opts.ParseLocator(args[0]); err != nil {
Expand Down
9 changes: 6 additions & 3 deletions internal/cmd/createpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,15 @@ func addCreatePolicy(parentCmd *cobra.Command) {
opts := createPolicyOptions{}

createpolicyCmd := &cobra.Command{
Use: "createpolicy",
GroupID: cmdGroupPolicy,
Short: "Creates a policy in a local copy of source-policies",
Use: "createpolicy",
Short: "Creates a policy in a local copy of source-policies",
Long: `Creates a SLSA source policy in a local copy of source-policies.

The created policy should then be sent as a PR to slsa-framework/source-policies.`,
// Deprecated in favor of "sourcetool policy create". Kept hidden and
// functional during the phase-out period.
Hidden: true,
Deprecated: `use "sourcetool policy create" instead`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := opts.Validate(); err != nil {
return err
Expand Down
Loading
Loading