diff --git a/docs/DESIGN.md b/docs/DESIGN.md index eaa52815..7f0723b3 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -243,7 +243,7 @@ Source provenance covers changes to a branch. It indicates: } } ], - "predicateType": "https://github.com/slsa-framework/slsa-source-poc/source-provenance/v1-draft", + "predicateType": "https://github.com/slsa-framework/source-tool/source-provenance/v1", "predicate": { "activity_type": "pr_merge", "actor": "TomHennen", @@ -296,7 +296,7 @@ Tag provenance records a tag creation event. It indicates: } } ], - "predicateType": "https://github.com/slsa-framework/slsa-source-poc/tag-provenance/v1-draft", + "predicateType": "https://github.com/slsa-framework/source-tool/tag-provenance/v1", "predicate": { "actor": "TomHennen", "controls": [ diff --git a/go.mod b/go.mod index d853eb4f..ae0fdd04 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/maxbrunsfeld/counterfeiter/v6 v6.13.0 github.com/migueleliasweb/go-github-mock v1.5.0 github.com/sigstore/sigstore-go v1.3.0 - github.com/slsa-framework/protos v0.0.0-20260905230943-612c99695f3b + github.com/slsa-framework/protos v0.0.0-20260928155916-9fa868b2a756 github.com/spf13/cobra v1.10.2 github.com/stretchr/testify v1.12.1 golang.org/x/mod v0.41.0 diff --git a/go.sum b/go.sum index 8d6c93da..4a576fb0 100644 --- a/go.sum +++ b/go.sum @@ -529,8 +529,8 @@ github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBB github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q= github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= -github.com/slsa-framework/protos v0.0.0-20260905230943-612c99695f3b h1:6IjAVZhOCYCgkcYc8gDYI8nr7pnHlQ8FaL1l7Y2OPfg= -github.com/slsa-framework/protos v0.0.0-20260905230943-612c99695f3b/go.mod h1:qIbxqThHVEmY1OTDwTg+iXPd/CU9AXyab7BBvk1V2gk= +github.com/slsa-framework/protos v0.0.0-20260928155916-9fa868b2a756 h1:JiWvV1tUpD33owzk0NCR6kH6DpUN1zBnwI5pHm0fSUM= +github.com/slsa-framework/protos v0.0.0-20260928155916-9fa868b2a756/go.mod h1:qIbxqThHVEmY1OTDwTg+iXPd/CU9AXyab7BBvk1V2gk= github.com/spdx/tools-golang v0.5.7 h1:+sWcKGnhwp3vLdMqPcLdA6QK679vd86cK9hQWH3AwCg= github.com/spdx/tools-golang v0.5.7/go.mod h1:jg7w0LOpoNAw6OxKEzCoqPC2GCTj45LyTlVmXubDsYw= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= diff --git a/pkg/attest/provenance.go b/pkg/attest/provenance.go index 3eb61f70..0ee18756 100644 --- a/pkg/attest/provenance.go +++ b/pkg/attest/provenance.go @@ -30,7 +30,7 @@ func GetSourceProvPred(statement *intoto.Statement) (*provenance.SourceProvenanc if statement == nil { return nil, errors.New("nil statement") } - if statement.GetPredicateType() != provenance.SourceProvPredicateType { + if !provenance.IsSourceProvPredicateType(statement.GetPredicateType()) { return nil, fmt.Errorf("unsupported predicate type: %s", statement.GetPredicateType()) } if statement.GetPredicate() == nil { @@ -57,7 +57,7 @@ func GetTagProvPred(statement *intoto.Statement) (*provenance.TagProvenancePred, if statement == nil { return nil, errors.New("nil statement") } - if statement.GetPredicateType() != provenance.TagProvPredicateType { + if !provenance.IsTagProvPredicateType(statement.GetPredicateType()) { return nil, fmt.Errorf("unsupported predicate type: %s", statement.GetPredicateType()) } if statement.GetPredicate() == nil { @@ -246,7 +246,8 @@ func (a *Attester) GetRevisionProvenance(ctx context.Context, branch *models.Bra // Configure the matcher to filter all the predicate types of the provenance matcher := &filters.PredicateTypeMatcher{ PredicateTypes: map[attestation.PredicateType]struct{}{ - attestation.PredicateType(provenance.SourceProvPredicateType): {}, + attestation.PredicateType(provenance.SourceProvPredicateType): {}, + attestation.PredicateType(provenance.SourceProvPredicateTypeDraft): {}, }, } @@ -411,6 +412,8 @@ var ( ProvenancePredicateTypes = []attestation.PredicateType{ attestation.PredicateType(provenance.SourceProvPredicateType), attestation.PredicateType(provenance.TagProvPredicateType), + attestation.PredicateType(provenance.SourceProvPredicateTypeDraft), + attestation.PredicateType(provenance.TagProvPredicateTypeDraft), } VSAPredicateTypes = []attestation.PredicateType{ attestation.PredicateType(VsaPredicateType), diff --git a/pkg/attest/provenance_test.go b/pkg/attest/provenance_test.go new file mode 100644 index 00000000..a6a7fbce --- /dev/null +++ b/pkg/attest/provenance_test.go @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: Copyright 2025 The SLSA Authors +// SPDX-License-Identifier: Apache-2.0 + +package attest + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/slsa-framework/source-tool/pkg/provenance" +) + +// The predicate getters must read statements issued under the current v1 +// predicate types as well as under the draft types source-tool used before +// the promotion, since attestations already stored under them remain valid. +func TestGetSourceProvPredPredicateTypes(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + name string + predicateType string + wantErr bool + }{ + {"v1", provenance.SourceProvPredicateType, false}, + {"draft", provenance.SourceProvPredicateTypeDraft, false}, + {"tag type", provenance.TagProvPredicateType, true}, + {"unknown", "https://example.com/other/v1", true}, + {"empty", "", true}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + pred := &provenance.SourceProvenancePred{ + PrevCommit: "abc", + RepoUri: "https://github.com/example/repo", + Controls: []*provenance.Control{{Name: "TEST_CONTROL"}}, + } + stmt, err := addPredToStatement(pred, tc.predicateType, "deadbeef") + require.NoError(t, err) + + got, err := GetSourceProvPred(stmt) + if tc.wantErr { + require.Error(t, err) + require.Nil(t, got) + return + } + require.NoError(t, err) + require.Equal(t, pred.GetPrevCommit(), got.GetPrevCommit()) + require.Equal(t, pred.GetRepoUri(), got.GetRepoUri()) + require.Len(t, got.GetControls(), 1) + require.Equal(t, "TEST_CONTROL", got.GetControls()[0].GetName()) + }) + } +} + +func TestGetTagProvPredPredicateTypes(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + name string + predicateType string + wantErr bool + }{ + {"v1", provenance.TagProvPredicateType, false}, + {"draft", provenance.TagProvPredicateTypeDraft, false}, + {"source type", provenance.SourceProvPredicateType, true}, + {"unknown", "https://example.com/other/v1", true}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + pred := &provenance.TagProvenancePred{ + RepoUri: "https://github.com/example/repo", + Tag: "refs/tags/v1.0.0", + } + stmt, err := addPredToStatement(pred, tc.predicateType, "deadbeef") + require.NoError(t, err) + + got, err := GetTagProvPred(stmt) + if tc.wantErr { + require.Error(t, err) + require.Nil(t, got) + return + } + require.NoError(t, err) + require.Equal(t, pred.GetTag(), got.GetTag()) + require.Equal(t, pred.GetRepoUri(), got.GetRepoUri()) + }) + } +} + +// New statements are always issued under the v1 predicate types. +func TestWritersEmitV1PredicateTypes(t *testing.T) { + t.Parallel() + require.Equal(t, "https://github.com/slsa-framework/source-tool/source-provenance/v1", provenance.SourceProvPredicateType) + require.Equal(t, "https://github.com/slsa-framework/source-tool/tag-provenance/v1", provenance.TagProvPredicateType) + require.True(t, provenance.IsSourceProvPredicateType(provenance.SourceProvPredicateTypeDraft)) + require.True(t, provenance.IsTagProvPredicateType(provenance.TagProvPredicateTypeDraft)) + require.False(t, provenance.IsSourceProvPredicateType(provenance.TagProvPredicateType)) +} diff --git a/pkg/provenance/predicate.go b/pkg/provenance/predicate.go index 9d604db7..45b0516c 100644 --- a/pkg/provenance/predicate.go +++ b/pkg/provenance/predicate.go @@ -9,14 +9,34 @@ package provenance import ( + "slices" + sourcetoolv1 "github.com/slsa-framework/protos/sourcetool/v1" ) +// Predicate types of the statements source-tool issues. The draft types are +// what source-tool wrote before the predicates were promoted to v1; the +// payload is the same, and attestations already issued under them remain +// valid, so readers accept both while writers only emit the v1 types. const ( - SourceProvPredicateType = "https://github.com/slsa-framework/slsa-source-poc/source-provenance/v1-draft" - TagProvPredicateType = "https://github.com/slsa-framework/slsa-source-poc/tag-provenance/v1-draft" + SourceProvPredicateType = sourcetoolv1.PredicateTypeSourceProvenance + TagProvPredicateType = sourcetoolv1.PredicateTypeTagProvenance + SourceProvPredicateTypeDraft = sourcetoolv1.PredicateTypeSourceProvenanceDraft + TagProvPredicateTypeDraft = sourcetoolv1.PredicateTypeTagProvenanceDraft ) +// IsSourceProvPredicateType returns true when predicateType identifies a +// statement carrying a SourceProvenancePred, in any of its versions. +func IsSourceProvPredicateType(predicateType string) bool { + return slices.Contains(sourcetoolv1.SourceProvenancePredicateTypes, predicateType) +} + +// IsTagProvPredicateType returns true when predicateType identifies a +// statement carrying a TagProvenancePred, in any of its versions. +func IsTagProvPredicateType(predicateType string) bool { + return slices.Contains(sourcetoolv1.TagProvenancePredicateTypes, predicateType) +} + type ( // SourceProvenancePred is the source provenance predicate. SourceProvenancePred = sourcetoolv1.SourceProvenancePred