Skip to content

Commit d6f3dd9

Browse files
committed
Start creation of a checktag command.
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 <tomhennen@google.com>
1 parent d1ced8a commit d6f3dd9

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)