Skip to content

Commit c0830ce

Browse files
authored
Generate predicates from protobuf (#239)
* Add provenance predicate proto definition Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> * Generate protos Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> * Use new proto generated predicates Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> * Update tests for new proto predicates Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> --------- Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]>
1 parent 3b4aefe commit c0830ce

File tree

14 files changed

+727
-232
lines changed

14 files changed

+727
-232
lines changed

proto/v1/provenance.proto

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// SPDX-FileCopyrightText: Copyright 2025 The SLSA Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
syntax = "proto3";
5+
package ampel.v1;
6+
7+
import "google/protobuf/timestamp.proto";
8+
9+
option go_package = "github.com/slsa-framework/slsa-source-poc/sourcetool/pkg/provenance";
10+
11+
// The predicate that encodes source provenance data.
12+
// The git commit this corresponds to is encoded in the surrounding statement.
13+
message SourceProvenancePred {
14+
// The commit preceding 'Commit' in the current context.
15+
string prev_commit = 1;
16+
string repo_uri = 2;
17+
string activity_type = 3;
18+
string actor = 4;
19+
string branch = 5;
20+
optional google.protobuf.Timestamp created_on = 6;
21+
// TODO: get the author of the PR (if this was from a PR).
22+
23+
// The controls enabled at the time this commit was pushed.
24+
repeated Control controls = 7;
25+
}
26+
27+
message Control {
28+
// The name of the control
29+
string name = 1;
30+
// The time from which this control has been continuously enforced/observed.
31+
google.protobuf.Timestamp since = 2;
32+
}
33+
34+
message TagProvenancePred {
35+
string repo_uri = 1;
36+
string actor = 2;
37+
string tag = 3;
38+
optional google.protobuf.Timestamp created_on = 4;
39+
40+
// The tag related controls enabled at the time this tag was created/updated.
41+
repeated Control controls = 7;
42+
repeated VsaSummary vsa_summaries = 8;
43+
}
44+
45+
// Summary of a summary
46+
message VsaSummary {
47+
repeated string source_refs = 1;
48+
repeated string verifiedLevels = 2;
49+
}
50+

sourcetool/internal/cmd/audit.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,11 @@ func printResult(ghc *ghcontrol.GitHubConnection, ar *audit.AuditCommitResult, m
140140
}
141141
if ar.ProvPred != nil {
142142
fmt.Print("\tprov:\n")
143-
fmt.Printf("\t\tcontrols: %v\n", ar.ProvPred.Controls)
144-
if ar.ProvPred.PrevCommit == ar.GhPriorCommit {
143+
fmt.Printf("\t\tcontrols: %v\n", ar.ProvPred.GetControls())
144+
if ar.ProvPred.GetPrevCommit() == ar.GhPriorCommit {
145145
fmt.Printf("\t\tPrevCommit matches GH commit: true\n")
146146
} else {
147-
fmt.Printf("\t\tPrevCommit matches GH commit: false: %s != %s\n", ar.ProvPred.PrevCommit, ar.GhPriorCommit)
147+
fmt.Printf("\t\tPrevCommit matches GH commit: false: %s != %s\n", ar.ProvPred.GetPrevCommit(), ar.GhPriorCommit)
148148
}
149149
} else {
150150
fmt.Printf("\tprov: none\n")

sourcetool/pkg/attest/provenance.go

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package attest
33
import (
44
"bufio"
55
"context"
6-
"encoding/json"
76
"errors"
87
"fmt"
98
"log"
@@ -13,7 +12,9 @@ import (
1312

1413
spb "github.com/in-toto/attestation/go/v1"
1514
"google.golang.org/protobuf/encoding/protojson"
15+
"google.golang.org/protobuf/proto"
1616
"google.golang.org/protobuf/types/known/structpb"
17+
"google.golang.org/protobuf/types/known/timestamppb"
1718

1819
"github.com/slsa-framework/slsa-source-poc/sourcetool/pkg/ghcontrol"
1920
"github.com/slsa-framework/slsa-source-poc/sourcetool/pkg/provenance"
@@ -46,7 +47,7 @@ func GetSourceProvPred(statement *spb.Statement) (*provenance.SourceProvenancePr
4647

4748
var predStruct provenance.SourceProvenancePred
4849
// Using regular json.Unmarshal because this is just a regular struct.
49-
err = json.Unmarshal(predJson, &predStruct)
50+
err = protojson.Unmarshal(predJson, &predStruct)
5051
if err != nil {
5152
return nil, fmt.Errorf("unmarshaling predicate: %w", err)
5253
}
@@ -73,7 +74,7 @@ func GetTagProvPred(statement *spb.Statement) (*provenance.TagProvenancePred, er
7374

7475
var predStruct provenance.TagProvenancePred
7576
// Using regular json.Unmarshal because this is just a regular struct.
76-
err = json.Unmarshal(predJson, &predStruct)
77+
err = protojson.Unmarshal(predJson, &predStruct)
7778
if err != nil {
7879
return nil, fmt.Errorf("unmarshaling predicate: %w", err)
7980
}
@@ -84,10 +85,16 @@ func GetTagProvPred(statement *spb.Statement) (*provenance.TagProvenancePred, er
8485
}
8586

8687
func addPredToStatement(provPred any, predicateType, commit string) (*spb.Statement, error) {
87-
// Using regular json.Marshal because this is just a regular struct and not from a proto.
88-
predJson, err := json.Marshal(provPred)
88+
msg, ok := provPred.(proto.Message)
89+
if !ok {
90+
return nil, fmt.Errorf("unable to serialize predicate as proto message")
91+
}
92+
predJson, err := protojson.MarshalOptions{
93+
Multiline: true,
94+
Indent: " ",
95+
}.Marshal(msg)
8996
if err != nil {
90-
return nil, err
97+
return nil, fmt.Errorf("marshaling predicate proto: %w", err)
9198
}
9299

93100
sub := []*spb.ResourceDescriptor{{
@@ -125,11 +132,11 @@ func (pa ProvenanceAttestor) createCurrentProvenance(ctx context.Context, commit
125132
curProvPred.Actor = controlStatus.ActorLogin
126133
curProvPred.ActivityType = controlStatus.ActivityType
127134
curProvPred.Branch = ref
128-
curProvPred.CreatedOn = curTime
135+
curProvPred.CreatedOn = timestamppb.New(curTime)
129136
curProvPred.Controls = controlStatus.Controls
130137

131138
// At the very least provenance is available starting now. :)
132-
curProvPred.Controls.AddControl(&slsa.Control{Name: slsa.ProvenanceAvailable, Since: curTime})
139+
curProvPred.AddControl(&provenance.Control{Name: slsa.ProvenanceAvailable.String(), Since: timestamppb.New(curTime)})
133140

134141
return addPredToStatement(&curProvPred, provenance.SourceProvPredicateType, commit)
135142
}
@@ -170,7 +177,7 @@ func (pa ProvenanceAttestor) getProvFromReader(reader *BundleReader, commit, ref
170177
if err != nil {
171178
return nil, nil, err
172179
}
173-
if pa.gh_connection.GetRepoUri() == provPred.RepoUri && (ref == ghcontrol.AnyReference || provPred.Branch == ref) {
180+
if pa.gh_connection.GetRepoUri() == provPred.GetRepoUri() && (ref == ghcontrol.AnyReference || provPred.GetBranch() == ref) {
174181
// Should be good!
175182
return stmt, provPred, nil
176183
} else {
@@ -223,13 +230,13 @@ func (pa ProvenanceAttestor) CreateSourceProvenance(ctx context.Context, prevAtt
223230

224231
// There was prior provenance, so update the Since field for each property
225232
// to the oldest encountered.
226-
for i, curControl := range curProvPred.Controls {
227-
prevControl := prevProvPred.Controls.GetControl(curControl.Name)
233+
for i, curControl := range curProvPred.GetControls() {
234+
prevControl := prevProvPred.GetControl(curControl.GetName())
228235
// No prior version of this control
229236
if prevControl == nil {
230237
continue
231238
}
232-
curControl.Since = slsa.EarlierTime(curControl.Since, prevControl.Since)
239+
curControl.Since = timestamppb.New(slsa.EarlierTime(curControl.GetSince().AsTime(), prevControl.GetSince().AsTime()))
233240
// Update the value.
234241
curProvPred.Controls[i] = curControl
235242
}
@@ -259,8 +266,6 @@ func (pa ProvenanceAttestor) CreateTagProvenance(ctx context.Context, commit, re
259266
return nil, nil
260267
}
261268

262-
curTime := time.Now()
263-
264269
vsaRefs, err := GetSourceRefsForCommit(vsaStatement, commit)
265270
if err != nil {
266271
return nil, fmt.Errorf("error getting source refs from vsa %w", err)
@@ -270,12 +275,12 @@ func (pa ProvenanceAttestor) CreateTagProvenance(ctx context.Context, commit, re
270275
RepoUri: pa.gh_connection.GetRepoUri(),
271276
Actor: actor,
272277
Tag: ref,
273-
CreatedOn: curTime,
278+
CreatedOn: timestamppb.Now(),
274279
Controls: controlStatus.Controls,
275-
VsaSummaries: []provenance.VsaSummary{
280+
VsaSummaries: []*provenance.VsaSummary{
276281
{
277282
SourceRefs: vsaRefs,
278-
VerifiedLevels: slsa.StringsToControlNames(vsaPred.GetVerifiedLevels()),
283+
VerifiedLevels: vsaPred.GetVerifiedLevels(),
279284
},
280285
},
281286
}

sourcetool/pkg/attest/provenance_test.go

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ package attest
22

33
import (
44
"encoding/json"
5-
"reflect"
65
"testing"
76
"time"
87

98
"github.com/google/go-github/v69/github"
109
"github.com/migueleliasweb/go-github-mock/src/mock"
1110
"github.com/stretchr/testify/require"
11+
"google.golang.org/protobuf/proto"
12+
"google.golang.org/protobuf/types/known/timestamppb"
1213

1314
"github.com/slsa-framework/slsa-source-poc/sourcetool/pkg/ghcontrol"
1415
"github.com/slsa-framework/slsa-source-poc/sourcetool/pkg/provenance"
@@ -36,7 +37,7 @@ func conditionsForTagImmutability() *github.RepositoryRulesetConditions {
3637

3738
func createTestProv(t *testing.T, repoUri, ref, commit string) string {
3839
provPred := provenance.SourceProvenancePred{RepoUri: repoUri, Branch: ref, ActivityType: "pr_merge", Actor: "test actor"}
39-
stmt, err := addPredToStatement(provPred, provenance.SourceProvPredicateType, commit)
40+
stmt, err := addPredToStatement(&provPred, provenance.SourceProvPredicateType, commit)
4041
if err != nil {
4142
t.Fatalf("failure creating test prov: %v", err)
4243
}
@@ -98,34 +99,38 @@ func timesEqualWithinMargin(t1, t2 time.Time, margin time.Duration) bool {
9899
}
99100

100101
func assertTagProvPredsEqual(t *testing.T, actual, expected *provenance.TagProvenancePred) {
101-
if actual.Actor != expected.Actor {
102-
t.Errorf("Actor %v does not match expected value %v", actual.Actor, expected.Actor)
102+
if actual.GetActor() != expected.GetActor() {
103+
t.Errorf("Actor %v does not match expected value %v", actual.GetActor(), expected.GetActor())
103104
}
104105

105-
if actual.RepoUri != expected.RepoUri {
106-
t.Errorf("RepoUri %v does not match expected value %v", actual.RepoUri, expected.RepoUri)
106+
if actual.GetRepoUri() != expected.GetRepoUri() {
107+
t.Errorf("RepoUri %v does not match expected value %v", actual.GetRepoUri(), expected.GetRepoUri())
107108
}
108109

109-
if actual.Tag != expected.Tag {
110-
t.Errorf("Tag %v does not match expected value %v", actual.Tag, expected.Tag)
110+
if actual.GetTag() != expected.GetTag() {
111+
t.Errorf("Tag %v does not match expected value %v", actual.GetTag(), expected.GetTag())
111112
}
112113

113-
if timesEqualWithinMargin(actual.CreatedOn, expected.CreatedOn, 5*time.Second) {
114-
t.Errorf("CreatedOn %v does not match expected value %v", actual.CreatedOn, expected.CreatedOn)
114+
if timesEqualWithinMargin(actual.GetCreatedOn().AsTime(), expected.GetCreatedOn().AsTime(), 5*time.Second) {
115+
t.Errorf("CreatedOn %v does not match expected value %v", actual.GetCreatedOn(), expected.GetCreatedOn())
115116
}
116117

117-
if len(actual.Controls) != len(expected.Controls) {
118-
t.Errorf("Control %v does not match expected value %v", actual.Controls, expected.Controls)
118+
if len(actual.GetControls()) != len(expected.GetControls()) {
119+
t.Errorf("Control %v does not match expected value %v", actual.GetControls(), expected.GetControls())
119120
} else {
120-
for ci := range actual.Controls {
121-
if !timesEqualWithinMargin(actual.Controls[ci].Since, expected.Controls[ci].Since, time.Second) {
121+
for ci := range actual.GetControls() {
122+
if !timesEqualWithinMargin(actual.GetControls()[ci].GetSince().AsTime(), expected.GetControls()[ci].GetSince().AsTime(), time.Second) {
122123
t.Errorf("control at [%d]'s time %v does not match expected time %v", ci,
123-
actual.Controls[ci].Since, expected.Controls[ci].Since)
124+
actual.GetControls()[ci].GetSince(), expected.GetControls()[ci].GetSince())
124125
}
125126
}
126127
}
127-
if !reflect.DeepEqual(actual.VsaSummaries, expected.VsaSummaries) {
128-
t.Errorf("VsaSummaries %v does not match expected value %v", actual.VsaSummaries, expected.VsaSummaries)
128+
129+
require.Len(t, actual.GetVsaSummaries(), len(expected.GetVsaSummaries()))
130+
for i := range actual.GetVsaSummaries() {
131+
if !proto.Equal(actual.GetVsaSummaries()[i], expected.GetVsaSummaries()[i]) {
132+
t.Errorf("VsaSummaries %v does not match expected value %v", actual.GetVsaSummaries(), expected.GetVsaSummaries())
133+
}
129134
}
130135
}
131136

@@ -204,17 +209,17 @@ func TestCreateTagProvenance(t *testing.T) {
204209
RepoUri: "https://github.com/owner/repo",
205210
Actor: "the-tag-pusher",
206211
Tag: "refs/tags/v1",
207-
CreatedOn: rulesetOldTime,
208-
Controls: []slsa.Control{
212+
CreatedOn: timestamppb.New(rulesetOldTime),
213+
Controls: []*provenance.Control{
209214
{
210215
Name: "TAG_HYGIENE",
211-
Since: rulesetOldTime,
216+
Since: timestamppb.New(rulesetOldTime),
212217
},
213218
},
214-
VsaSummaries: []provenance.VsaSummary{
219+
VsaSummaries: []*provenance.VsaSummary{
215220
{
216221
SourceRefs: []string{"refs/some/ref"},
217-
VerifiedLevels: []slsa.ControlName{"TEST_LEVEL"},
222+
VerifiedLevels: []string{"TEST_LEVEL"},
218223
},
219224
},
220225
}

sourcetool/pkg/audit/audit.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func (ar *AuditCommitResult) IsGood() bool {
3535
// Have to have provenance
3636
if ar.ProvPred == nil {
3737
good = false
38-
} else if ar.ProvPred.PrevCommit != ar.GhPriorCommit {
38+
} else if ar.ProvPred.GetPrevCommit() != ar.GhPriorCommit {
3939
// Commits need to be the same.
4040
good = false
4141
}

0 commit comments

Comments
 (0)