Skip to content

Commit 367eaa1

Browse files
authored
Add command to verify a commit (#120)
This command will verify the SLSA Source Level of a given commit by looking for a valid VSA associated with that commit in the target repo. Updated GETTING_STARTED to use this command instead of checking the Actions.
1 parent b354374 commit 367eaa1

File tree

7 files changed

+339
-82
lines changed

7 files changed

+339
-82
lines changed

GETTING_STARTED.md

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ First, enable continuity controls within the target GitHub repo.
1919
Now, enable a workflow that will evaluate the SLSA level, create provenance, etc...
2020

2121
1. Create a clean checkout of the target repo
22-
2. Create a new file named `./github/workflows/compute_slsa_source.yml`
22+
2. Create a new file named `.github/workflows/compute_slsa_source.yml`
2323
3. Add the following content
2424

2525
```yaml
@@ -44,11 +44,15 @@ jobs:
4444

4545
Let's verify that everything is working
4646

47-
1. Go to the GitHub repo
48-
2. Click the 'Actions' option
49-
3. Find the most recent run of `SLSA Source` (it should have a green check mark)
50-
4. Click into it
51-
5. You should see the summary listing "SLSA Source Properties": `[SLSA_SOURCE_LEVEL_1]`
47+
1. Note the digest of **merged** commit that added the workflow above
48+
2. Run the verification command
49+
50+
`go run github.com/slsa-framework/slsa-source-poc/sourcetool@latest verifycommit --commit <commit digest> --owner <YOUR REPO'S ORG> --repo <YOUR REPO'S NAME> --branch main`
51+
52+
3. You should see the message
53+
`SUCCESS: commit <commit digest> verified with [SLSA_SOURCE_LEVEL_1]`
54+
55+
Move to the next section to get to `SLSA_SOURCE_LEVEL_3`.
5256

5357
## Create a policy file
5458

@@ -73,3 +77,16 @@ e.g. `"canonical_repo": "https://github.com/TomHennen/wrangle",`
7377
5. Add & commit the created policy file.
7478
6. Send a PR with the change to https://github.com/slsa-framework/slsa-source-poc
7579
7. Once it's approved you'll be at SLSA Source Level 3 for your next change.
80+
81+
## Validate Source Level 3 workflow
82+
83+
Let's verify that everything is working
84+
85+
1. Make and merge a change to the protected branch (`main`) in **your** repo.
86+
2. Note the digest of **merged** commit
87+
3. Run the verification command
88+
89+
`go run github.com/slsa-framework/slsa-source-poc/sourcetool@latest verifycommit --commit <commit digest> --owner <YOUR REPO'S ORG> --repo <YOUR REPO'S NAME> --branch main`
90+
91+
4. You should see the message
92+
`SUCCESS: commit <commit digest> verified with [SLSA_SOURCE_LEVEL_3]`

sourcetool/cmd/checklevelprov.go

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,6 @@ func doCheckLevelProv(checkLevelProvArgs CheckLevelProvArgs) {
4949
gh_control.NewGhConnection(checkLevelProvArgs.owner, checkLevelProvArgs.repo, checkLevelProvArgs.branch).WithAuthToken(githubToken)
5050
ctx := context.Background()
5151

52-
ver_options := attest.DefaultVerifierOptions
53-
if checkLevelProvArgs.expectedIssuer != "" {
54-
ver_options.ExpectedIssuer = checkLevelProvArgs.expectedIssuer
55-
}
56-
if checkLevelProvArgs.expectedSan != "" {
57-
ver_options.ExpectedSan = checkLevelProvArgs.expectedSan
58-
}
59-
6052
prevCommit := checkLevelProvArgs.prevCommit
6153
var err error
6254
if prevCommit == "" {
@@ -66,7 +58,7 @@ func doCheckLevelProv(checkLevelProvArgs CheckLevelProvArgs) {
6658
}
6759
}
6860

69-
pa := attest.NewProvenanceAttestor(gh_connection, ver_options)
61+
pa := attest.NewProvenanceAttestor(gh_connection, getVerificationOptions())
7062
prov, err := pa.CreateSourceProvenance(ctx, checkLevelProvArgs.prevBundlePath, checkLevelProvArgs.commit, prevCommit)
7163
if err != nil {
7264
log.Fatal(err)
@@ -142,8 +134,6 @@ func init() {
142134
checklevelprovCmd.Flags().StringVar(&checkLevelProvArgs.branch, "branch", "", "The branch within the repository - required.")
143135
checklevelprovCmd.Flags().StringVar(&checkLevelProvArgs.outputUnsignedBundle, "output_unsigned_bundle", "", "The path to write a bundle of unsigned attestations.")
144136
checklevelprovCmd.Flags().StringVar(&checkLevelProvArgs.outputSignedBundle, "output_signed_bundle", "", "The path to write a bundle of signed attestations.")
145-
checklevelprovCmd.Flags().StringVar(&checkLevelProvArgs.expectedIssuer, "expected_issuer", "", "The expected issuer of attestations.")
146-
checklevelprovCmd.Flags().StringVar(&checkLevelProvArgs.expectedSan, "expected_san", "", "The expect san of attestations.")
147137
checklevelprovCmd.Flags().StringVar(&checkLevelProvArgs.useLocalPolicy, "use_local_policy", "", "UNSAFE: Use the policy at this local path instead of the official one.")
148138

149139
}

sourcetool/cmd/root.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ package cmd
66
import (
77
"os"
88

9+
"github.com/slsa-framework/slsa-source-poc/sourcetool/pkg/attest"
910
"github.com/spf13/cobra"
1011
)
1112

1213
var (
13-
githubToken string
14+
githubToken string
15+
expectedIssuer string
16+
expectedSan string
1417

1518
// rootCmd represents the base command when called without any subcommands
1619
rootCmd = &cobra.Command{
@@ -28,6 +31,17 @@ to quickly create a Cobra application.`,
2831
}
2932
)
3033

34+
func getVerificationOptions() attest.VerificationOptions {
35+
options := attest.DefaultVerifierOptions
36+
if checkLevelProvArgs.expectedIssuer != "" {
37+
options.ExpectedIssuer = checkLevelProvArgs.expectedIssuer
38+
}
39+
if checkLevelProvArgs.expectedSan != "" {
40+
options.ExpectedSan = checkLevelProvArgs.expectedSan
41+
}
42+
return options
43+
}
44+
3145
// Execute adds all child commands to the root command and sets flags appropriately.
3246
// This is called by main.main(). It only needs to happen once to the rootCmd.
3347
func Execute() {
@@ -42,11 +56,8 @@ func init() {
4256
// Cobra supports persistent flags, which, if defined here,
4357
// will be global for your application.
4458

45-
// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.sourcetool.yaml)")
46-
4759
rootCmd.PersistentFlags().StringVar(&githubToken, "github_token", "", "the github token to use for auth")
60+
rootCmd.PersistentFlags().StringVar(&expectedIssuer, "expected_issuer", "", "The expected issuer of attestations.")
61+
rootCmd.PersistentFlags().StringVar(&expectedSan, "expected_san", "", "The expect san of attestations.")
4862

49-
// Cobra also supports local flags, which will only run
50-
// when this action is called directly.
51-
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
5263
}

sourcetool/cmd/verifycommit.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
Copyright © 2025 NAME HERE <EMAIL ADDRESS>
3+
*/
4+
package cmd
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"log"
10+
11+
"github.com/slsa-framework/slsa-source-poc/sourcetool/pkg/attest"
12+
"github.com/slsa-framework/slsa-source-poc/sourcetool/pkg/gh_control"
13+
"github.com/spf13/cobra"
14+
)
15+
16+
type VerifyCommitArgs struct {
17+
owner, repo, branch, commit string
18+
}
19+
20+
// checklevelCmd represents the checklevel command
21+
var (
22+
verifyCommitArgs VerifyCommitArgs
23+
verifycommitCmd = &cobra.Command{
24+
Use: "verifycommit",
25+
Short: "Verifies the specified commit is valid",
26+
Run: func(cmd *cobra.Command, args []string) {
27+
doVerifyCommit(verifyCommitArgs.commit, verifyCommitArgs.owner, verifyCommitArgs.repo, verifyCommitArgs.branch)
28+
},
29+
}
30+
)
31+
32+
func doVerifyCommit(commit, owner, repo, branch string) {
33+
if commit == "" || owner == "" || repo == "" || branch == "" {
34+
log.Fatal("Must set commit, owner, repo, and branch flags.")
35+
}
36+
37+
gh_connection := gh_control.NewGhConnection(owner, repo, branch).WithAuthToken(githubToken)
38+
ctx := context.Background()
39+
40+
pa := attest.NewProvenanceAttestor(gh_connection, getVerificationOptions())
41+
42+
_, vsaPred, err := pa.GetVsa(ctx, commit)
43+
if err != nil {
44+
log.Fatal(err)
45+
}
46+
if vsaPred == nil {
47+
fmt.Printf("FAILED: no VSA matching commit '%s' on branch '%s' found in github.com/%s/%s\n", commit, branch, owner, repo)
48+
return
49+
}
50+
51+
fmt.Printf("SUCCESS: commit %s verified with %v\n", commit, vsaPred.VerifiedLevels)
52+
}
53+
54+
func init() {
55+
rootCmd.AddCommand(verifycommitCmd)
56+
57+
verifycommitCmd.Flags().StringVar(&verifyCommitArgs.owner, "owner", "", "The GitHub repository owner - required.")
58+
verifycommitCmd.Flags().StringVar(&verifyCommitArgs.repo, "repo", "", "The GitHub repository name - required.")
59+
verifycommitCmd.Flags().StringVar(&verifyCommitArgs.branch, "branch", "", "The branch within the repository - required.")
60+
verifycommitCmd.Flags().StringVar(&verifyCommitArgs.commit, "commit", "", "The commit to check - required.")
61+
62+
}

sourcetool/pkg/attest/provenance.go

Lines changed: 29 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ import (
44
"bufio"
55
"context"
66
"encoding/json"
7-
"errors"
87
"fmt"
9-
"io"
108
"log"
119
"os"
1210
"strings"
@@ -92,15 +90,6 @@ func addPredToStatement(provPred *SourceProvenancePred, commit string) (*spb.Sta
9290
return &statementPb, nil
9391
}
9492

95-
func doesSubjectIncludeCommit(statement *spb.Statement, commit string) bool {
96-
for _, subject := range statement.Subject {
97-
if subject.Digest["gitCommit"] == commit {
98-
return true
99-
}
100-
}
101-
return false
102-
}
103-
10493
// Create provenance for the current commit without any context from the previous provenance (if any).
10594
func (pa ProvenanceAttestor) createCurrentProvenance(ctx context.Context, commit, prevCommit string) (*spb.Statement, error) {
10695
controlStatus, err := pa.gh_connection.GetControls(ctx, commit)
@@ -125,67 +114,49 @@ func (pa ProvenanceAttestor) createCurrentProvenance(ctx context.Context, commit
125114
return addPredToStatement(&curProvPred, commit)
126115
}
127116

128-
func (pa ProvenanceAttestor) convertLineToStatement(line string) (*spb.Statement, error) {
129-
// Is this a sigstore bundle with a statement?
130-
vr, err := Verify(line, pa.verification_options)
131-
if err == nil {
132-
// This is it.
133-
return vr.Statement, nil
134-
} else {
135-
// We ignore errors because there could be other stuff in the
136-
// bundle this line came from.
137-
log.Printf("Line %s failed verification: %v", line, err)
138-
}
139-
140-
// TODO: add support for 'regular' DSSEs.
141-
142-
return nil, errors.New("could not convert line to statement")
143-
}
144-
145117
// Gets provenance for the commit from git notes.
146118
func (pa ProvenanceAttestor) GetProvenance(ctx context.Context, commit string) (*spb.Statement, *SourceProvenancePred, error) {
147119
notes, err := pa.gh_connection.GetNotesForCommit(ctx, commit)
148120
if notes == "" {
149-
log.Printf("didn't find prev commit %s for branch %s", commit, pa.gh_connection.Branch)
121+
log.Printf("didn't find notes for commit %s", commit)
150122
return nil, nil, nil
151123
}
152124

153125
if err != nil {
154126
log.Fatal(err)
155127
}
156-
return pa.getProvFromReader(bufio.NewReader(strings.NewReader(notes)), commit)
128+
129+
bundleReader := NewBundleReader(bufio.NewReader(strings.NewReader(notes)), pa.verification_options)
130+
131+
return pa.getProvFromReader(bundleReader, commit)
157132
}
158133

159-
func (pa ProvenanceAttestor) getProvFromReader(reader *bufio.Reader, commit string) (*spb.Statement, *SourceProvenancePred, error) {
134+
func (pa ProvenanceAttestor) getProvFromReader(reader *BundleReader, commit string) (*spb.Statement, *SourceProvenancePred, error) {
160135
for {
161-
line, err := reader.ReadString('\n')
136+
stmt, err := reader.ReadStatement(MatchesTypeAndCommit(SourceProvPredicateType, commit))
162137
if err != nil {
163-
// Handle end of file gracefully
164-
if err != io.EOF {
165-
return nil, nil, err
166-
}
167-
if line == "" {
168-
// Nothing to see here.
169-
break
170-
}
138+
return nil, nil, err
139+
}
140+
if err != nil {
141+
// Ignore errors, we want to check all the lines.
142+
log.Printf("error while processing line: %v", err)
143+
continue
144+
}
145+
146+
if stmt == nil {
147+
// No statements left.
148+
break
149+
}
150+
151+
prevProdPred, err := GetProvPred(stmt)
152+
if err != nil {
153+
return nil, nil, err
171154
}
172-
// Is this source provenance?
173-
sp, err := pa.convertLineToStatement(line)
174-
if err == nil {
175-
if sp.PredicateType != SourceProvPredicateType {
176-
log.Printf("statement %v isn't source provenance", sp)
177-
continue
178-
}
179-
prevProdPred, err := GetProvPred(sp)
180-
if err != nil {
181-
return nil, nil, err
182-
}
183-
if doesSubjectIncludeCommit(sp, commit) && prevProdPred.Branch == pa.gh_connection.GetFullBranch() {
184-
// Should be good!
185-
return sp, prevProdPred, nil
186-
} else {
187-
log.Printf("prov '%v' does not reference commit '%s' for branch '%s', skipping", sp, commit, pa.gh_connection.GetFullBranch())
188-
}
155+
if prevProdPred.Branch == pa.gh_connection.GetFullBranch() {
156+
// Should be good!
157+
return stmt, prevProdPred, nil
158+
} else {
159+
log.Printf("prov '%v' does not reference commit '%s' for branch '%s', skipping", stmt, commit, pa.gh_connection.GetFullBranch())
189160
}
190161
}
191162

@@ -199,7 +170,7 @@ func (pa ProvenanceAttestor) getPrevProvenance(ctx context.Context, prevAttPath,
199170
if err != nil {
200171
return nil, nil, err
201172
}
202-
return pa.getProvFromReader(bufio.NewReader(f), prevCommit)
173+
return pa.getProvFromReader(NewBundleReader(bufio.NewReader(f), pa.verification_options), prevCommit)
203174
}
204175

205176
// Try to get the previous bundle ourselves...

0 commit comments

Comments
 (0)