Skip to content

Commit f2031f5

Browse files
authored
Start creation of a checktag command. (#149)
This command creates tag provenance (new!) and VSAs for those tags. There's more work to do to fully iron this out but it's ready for some testing. This was a fairly large refactor that involved generalizing the GitHubConnection class so that it doesn't assume there are branches and being able to mock the bnd verifier to make unit tests possible/easier. Signed-off-by: Tom Hennen <[email protected]>
1 parent d1ced8a commit f2031f5

File tree

26 files changed

+1070
-561
lines changed

26 files changed

+1070
-561
lines changed

.github/workflows/local_attest.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ name: SLSA Source
22
on:
33
push:
44
branches: [ "main" ]
5+
tags: ['**']
56

67
jobs:
78
# Whenever new source is pushed recompute the slsa source information.

DESIGN.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,8 @@ This tool can also check to see if the GitHub repo/ref is configured to require
233233
### IMMUTABLE_TAGS
234234

235235
This tool can also check to see if the GitHub repo is configured to require
236-
immutable tags. To do so it checks that the repo:
236+
immutable tags. To do so it checks that the repo enables the follow rules
237+
to ~ALL tags:
237238

238239
1. Doesn't allow tag updates
239240
2. Doesn't allow tag deletions
@@ -243,6 +244,11 @@ Importing [rulesets/tag_immutability.json](rulesets/tag_immutability.json)
243244
to a repos rulesets will enable the repo controls. The `immutable_tags`
244245
field in the policy then needs to be enabled too.
245246

247+
TODO: In the future this tool could be updated to allow some subset of tags
248+
to be updated (e.g. `latest`, `nightly`), but that feature is not yet
249+
supported. Tracked
250+
[here](https://github.com/slsa-framework/slsa-source-poc/issues/129).
251+
246252
## Open Issues
247253

248254
### Dealing with reliability

actions/slsa_with_provenance/action.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,18 @@ runs:
2222
- id: setup
2323
run: mkdir -p metadata
2424
shell: bash
25-
- id: determine_level
25+
- id: handle_branch_push
26+
if: ${{ startsWith(github.ref, 'refs/heads/') }}
2627
run: |
27-
echo "## SLSA Source Properties" >> $GITHUB_STEP_SUMMARY
28+
echo "## SLSA Source Properties Branch Push" >> $GITHUB_STEP_SUMMARY
2829
go run github.com/slsa-framework/slsa-source-poc/sourcetool@8de659f119d933d4cfaed300e7d8bd78528a48c7 --github_token ${{ github.token }} checklevelprov --commit ${{ github.sha }} --owner ${{ github.repository_owner }} --repo ${{ github.event.repository.name }} --branch ${{ github.ref_name }} --output_signed_bundle ${{ github.workspace }}/metadata/signed_bundle.intoto.jsonl >> $GITHUB_STEP_SUMMARY
2930
shell: bash
31+
- id: handle_tag_push
32+
if: ${{ startsWith(github.ref, 'refs/tags/') }}
33+
run: |
34+
echo "## SLSA Source Properties Tag Push" >> $GITHUB_STEP_SUMMARY
35+
echo "TODO"
36+
shell: bash
3037
- id: summary
3138
run: |
3239
echo "## Signed Bundle" >> $GITHUB_STEP_SUMMARY

go.work.sum

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
7373
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
7474
github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
7575
github.com/bufbuild/protocompile v0.10.0/go.mod h1:G9qQIQo0xZ6Uyj6CMNz0saGmx2so+KONo8/KrELABiY=
76+
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
7677
github.com/buildkite/agent/v3 v3.81.0/go.mod h1:edJeyycODRxaFvpT22rDGwaQ5oa4eB8GjtbjgX5VpFw=
7778
github.com/buildkite/go-pipeline v0.13.1/go.mod h1:2HHqlSFTYgHFhzedJu0LhLs9n5c9XkYnHiQFVN5HE4U=
7879
github.com/buildkite/interpolate v0.1.3/go.mod h1:UNVe6A+UfiBNKbhAySrBbZFZFxQ+DXr9nWen6WVt/A8=
@@ -180,7 +181,6 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3
180181
github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA=
181182
github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
182183
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
183-
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
184184
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
185185
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
186186
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=

policy/github.com/TomHennen/Concordance/source-policy.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55
"Name": "master",
66
"Since": "2025-03-23T18:08:43.25099739Z",
77
"target_slsa_source_level": "SLSA_SOURCE_LEVEL_3",
8-
"require_review": false,
9-
"immutable_tags": true
8+
"require_review": false
109
}
11-
]
10+
],
11+
"protected_tag": {
12+
"Since": "2025-03-23T18:08:43.25099739Z",
13+
"immutable_tags": true
14+
}
1215
}

sourcetool/cmd/checklevel.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,22 @@ func doCheckLevel(commit, owner, repo, branch, outputVsa, outputUnsignedVsa stri
4141
log.Fatal("Must set commit, owner, repo, and branch flags.")
4242
}
4343

44-
gh_connection := gh_control.NewGhConnection(owner, repo, branch).WithAuthToken(githubToken)
44+
gh_connection := gh_control.NewGhConnection(owner, repo, gh_control.BranchToFullRef(branch)).WithAuthToken(githubToken)
4545
ctx := context.Background()
4646

47-
controlStatus, err := gh_connection.GetControls(ctx, commit)
47+
controlStatus, err := gh_connection.GetBranchControls(ctx, commit, gh_connection.GetFullRef())
4848
if err != nil {
4949
log.Fatal(err)
5050
}
51-
pol := policy.NewPolicy()
52-
pol.UseLocalPolicy = checkLevelProvArgs.useLocalPolicy
53-
verifiedLevels, policyPath, err := pol.EvaluateControl(ctx, gh_connection, controlStatus)
51+
pe := policy.NewPolicyEvaluator()
52+
pe.UseLocalPolicy = checkLevelProvArgs.useLocalPolicy
53+
verifiedLevels, policyPath, err := pe.EvaluateControl(ctx, gh_connection, controlStatus)
5454
if err != nil {
5555
log.Fatal(err)
5656
}
5757
fmt.Print(verifiedLevels)
5858

59-
unsignedVsa, err := attest.CreateUnsignedSourceVsa(gh_connection, commit, verifiedLevels, policyPath)
59+
unsignedVsa, err := attest.CreateUnsignedSourceVsa(gh_connection.GetRepoUri(), gh_connection.GetFullRef(), commit, verifiedLevels, policyPath)
6060
if err != nil {
6161
log.Fatal(err)
6262
}

sourcetool/cmd/checklevelprov.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ var (
4646

4747
func doCheckLevelProv(checkLevelProvArgs CheckLevelProvArgs) {
4848
gh_connection :=
49-
gh_control.NewGhConnection(checkLevelProvArgs.owner, checkLevelProvArgs.repo, checkLevelProvArgs.branch).WithAuthToken(githubToken)
49+
gh_control.NewGhConnection(checkLevelProvArgs.owner, checkLevelProvArgs.repo, gh_control.BranchToFullRef(checkLevelProvArgs.branch)).WithAuthToken(githubToken)
5050
ctx := context.Background()
5151

5252
prevCommit := checkLevelProvArgs.prevCommit
@@ -58,22 +58,22 @@ func doCheckLevelProv(checkLevelProvArgs CheckLevelProvArgs) {
5858
}
5959
}
6060

61-
pa := attest.NewProvenanceAttestor(gh_connection, getVerificationOptions())
62-
prov, err := pa.CreateSourceProvenance(ctx, checkLevelProvArgs.prevBundlePath, checkLevelProvArgs.commit, prevCommit)
61+
pa := attest.NewProvenanceAttestor(gh_connection, getVerifier())
62+
prov, err := pa.CreateSourceProvenance(ctx, checkLevelProvArgs.prevBundlePath, checkLevelProvArgs.commit, prevCommit, gh_connection.GetFullRef())
6363
if err != nil {
6464
log.Fatal(err)
6565
}
6666

6767
// check p against policy
68-
pol := policy.NewPolicy()
69-
pol.UseLocalPolicy = checkLevelProvArgs.useLocalPolicy
70-
verifiedLevels, policyPath, err := pol.EvaluateProv(ctx, gh_connection, prov)
68+
pe := policy.NewPolicyEvaluator()
69+
pe.UseLocalPolicy = checkLevelProvArgs.useLocalPolicy
70+
verifiedLevels, policyPath, err := pe.EvaluateSourceProv(ctx, gh_connection, prov)
7171
if err != nil {
7272
log.Fatal(err)
7373
}
7474

7575
// create vsa
76-
unsignedVsa, err := attest.CreateUnsignedSourceVsa(gh_connection, checkLevelProvArgs.commit, verifiedLevels, policyPath)
76+
unsignedVsa, err := attest.CreateUnsignedSourceVsa(gh_connection.GetRepoUri(), gh_connection.GetFullRef(), checkLevelProvArgs.commit, verifiedLevels, policyPath)
7777
if err != nil {
7878
log.Fatal(err)
7979
}

sourcetool/cmd/checktag.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
Copyright © 2025 NAME HERE <EMAIL ADDRESS>
3+
*/
4+
package cmd
5+
6+
import (
7+
"context"
8+
"log"
9+
"os"
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/slsa-framework/slsa-source-poc/sourcetool/pkg/policy"
14+
"github.com/spf13/cobra"
15+
"google.golang.org/protobuf/encoding/protojson"
16+
)
17+
18+
type CheckTagArgs struct {
19+
commit string
20+
owner string
21+
repo string
22+
tagName string
23+
outputSignedBundle string
24+
useLocalPolicy string
25+
}
26+
27+
var (
28+
checkTagArgs CheckTagArgs
29+
// checktagCmd represents the checktag command
30+
checktagCmd = &cobra.Command{
31+
Use: "checktag",
32+
Short: "Checks to see if the tag operation should be allowed and issues a VSA",
33+
Run: func(cmd *cobra.Command, args []string) {
34+
doCheckTag(checkTagArgs)
35+
},
36+
}
37+
)
38+
39+
func doCheckTag(args CheckTagArgs) {
40+
gh_connection :=
41+
gh_control.NewGhConnection(args.owner, args.repo, gh_control.TagToFullRef(args.tagName)).WithAuthToken(githubToken)
42+
ctx := context.Background()
43+
verifier := getVerifier()
44+
45+
// Create tag provenance.
46+
pa := attest.NewProvenanceAttestor(gh_connection, verifier)
47+
prov, err := pa.CreateTagProvenance(ctx, args.commit, gh_control.TagToFullRef(args.tagName))
48+
if err != nil {
49+
log.Fatal(err)
50+
}
51+
52+
// check p against policy
53+
pe := policy.NewPolicyEvaluator()
54+
pe.UseLocalPolicy = args.useLocalPolicy
55+
verifiedLevels, policyPath, err := pe.EvaluateTagProv(ctx, gh_connection, prov)
56+
if err != nil {
57+
log.Fatal(err)
58+
}
59+
60+
// create vsa
61+
unsignedVsa, err := attest.CreateUnsignedSourceVsa(gh_connection.GetRepoUri(), gh_connection.GetFullRef(), args.commit, verifiedLevels, policyPath)
62+
if err != nil {
63+
log.Fatal(err)
64+
}
65+
66+
unsignedProv, err := protojson.Marshal(prov)
67+
if err != nil {
68+
log.Fatal(err)
69+
}
70+
71+
if args.outputSignedBundle != "" {
72+
f, err := os.OpenFile(args.outputSignedBundle, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
73+
if err != nil {
74+
log.Fatal(err)
75+
}
76+
defer f.Close()
77+
78+
signedProv, err := attest.Sign(string(unsignedProv))
79+
if err != nil {
80+
log.Fatal(err)
81+
}
82+
83+
signedVsa, err := attest.Sign(unsignedVsa)
84+
if err != nil {
85+
log.Fatal(err)
86+
}
87+
88+
f.WriteString(signedProv)
89+
f.WriteString("\n")
90+
f.WriteString(signedVsa)
91+
f.WriteString("\n")
92+
} else {
93+
log.Printf("unsigned prov: %s\n", unsignedProv)
94+
log.Printf("unsigned vsa: %s\n", unsignedVsa)
95+
}
96+
}
97+
98+
func init() {
99+
rootCmd.AddCommand(checktagCmd)
100+
101+
checktagCmd.Flags().StringVar(&checkTagArgs.commit, "commit", "", "The commit to check - required.")
102+
checktagCmd.Flags().StringVar(&checkTagArgs.owner, "owner", "", "The GitHub repository owner - required.")
103+
checktagCmd.Flags().StringVar(&checkTagArgs.repo, "repo", "", "The GitHub repository name - required.")
104+
checktagCmd.Flags().StringVar(&checkTagArgs.tagName, "tag_name", "", "The name of the new tag - required.")
105+
checktagCmd.Flags().StringVar(&checkTagArgs.outputSignedBundle, "output_signed_bundle", "", "The path to write a bundle of signed attestations.")
106+
checktagCmd.Flags().StringVar(&checkTagArgs.useLocalPolicy, "use_local_policy", "", "UNSAFE: Use the policy at this local path instead of the official one.")
107+
108+
}

sourcetool/cmd/createpolicy.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ var (
3535
)
3636

3737
func doCreatePolicy(policyRepoPath, owner, repo, branch string) {
38-
gh_connection := gh_control.NewGhConnection(owner, repo, branch).WithAuthToken(githubToken)
38+
gh_connection := gh_control.NewGhConnection(owner, repo, gh_control.BranchToFullRef(branch)).WithAuthToken(githubToken)
3939
ctx := context.Background()
4040
outpath, err := policy.CreateLocalPolicy(ctx, gh_connection, policyRepoPath)
4141
if err != nil {

sourcetool/cmd/prov.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ var (
3232
)
3333

3434
func doProv(prevAttPath, commit, prevCommit, owner, repo, branch string) {
35-
gh_connection := gh_control.NewGhConnection(owner, repo, branch).WithAuthToken(githubToken)
35+
gh_connection := gh_control.NewGhConnection(owner, repo, gh_control.BranchToFullRef(branch)).WithAuthToken(githubToken)
3636
ctx := context.Background()
37-
pa := attest.NewProvenanceAttestor(gh_connection, attest.DefaultVerifierOptions)
38-
newProv, err := pa.CreateSourceProvenance(ctx, prevAttPath, commit, prevCommit)
37+
pa := attest.NewProvenanceAttestor(gh_connection, getVerifier())
38+
newProv, err := pa.CreateSourceProvenance(ctx, prevAttPath, commit, prevCommit, gh_connection.GetFullRef())
3939
if err != nil {
4040
log.Fatal(err)
4141
}

0 commit comments

Comments
 (0)