Skip to content

Commit

Permalink
Add validator APIs for Provenance v1 predicate
Browse files Browse the repository at this point in the history
Signed-off-by: Marcela Melara <[email protected]>
  • Loading branch information
marcelamelara committed Sep 25, 2023
1 parent 6af5732 commit aedbff9
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 0 deletions.
113 changes: 113 additions & 0 deletions go/predicates/provenance/v1/provenance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
Validator APIs for SLSA Provenance v1 protos.
*/
package v1

import (
"errors"
"fmt"
)

// all of the following errors apply to SLSA Build L1 and above
var (
ErrBuilderRequired = errors.New("RunDetails.Builder required")
ErrBuilderIdRequired = errors.New("Builder.Id required")
ErrBuildDefinitionRequired = errors.New("BuildeDefinition required")
ErrBuildTypeRequired = errors.New("BuildDefinition.BuildType required")
ErrExternalParamsRequired = errors.New("BuildDefinition.ExternalParameters required")
ErrRunDetailsRequired = errors.New("RunDetails required")
)

func (b *Builder) Validate() error {
// the id field is required for SLSA Build L1
if b.GetId() == "" {
return ErrBuilderIdRequired
}

// check that all builderDependencies are valid RDs
builderDeps := b.GetBuilderDependencies()
if len(builderDeps) > 0 {
for i, rd := range builderDeps {
if err := rd.Validate(); err != nil {
return fmt.Errorf("Invalid Builder.BuilderDependencies[%d]: %w", i, err)
}
}
}

return nil
}

func (b *BuildDefinition) Validate() error {
// the buildType field is required for SLSA Build L1
if b.GetBuildType() == "" {
return ErrBuildTypeRequired
}

// the externalParameters field is required for SLSA Build L1
if b.GetExternalParameters() == nil {
return ErrExternalParamsRequired
}

// check that all resolvedDependencies are valid RDs
resolvedDeps := b.GetResolvedDependencies()
if len(resolvedDeps) > 0 {
for i, rd := range resolvedDeps {
if err := rd.Validate(); err != nil {
return fmt.Errorf("Invalid BuildDefinition.ResolvedDependencies[%d]: %w", i, err)
}
}
}

return nil
}

func (r *RunDetails) Validate() error {
// the builder field is required for SLSA Build L1
builder := r.GetBuilder()
if builder == nil {
return ErrBuilderRequired
}

// check the Builder
if err := builder.Validate(); err != nil {
return err
}

// check that all byproducts are valid RDs
byproducts := r.GetByproducts()
if len(byproducts) > 0 {
for i, rd := range byproducts {
if err := rd.Validate(); err != nil {
return fmt.Errorf("Invalid RunDetails.Byproducts[%d]: %w", i, err)
}
}
}

return nil
}

func (p *Provenance) Validate() error {
// the buildDefinition field is required for SLSA Build L1
buildDef := p.GetBuildDefinition()
if buildDef == nil {
return ErrBuildDefinitionRequired
}

// check the BuildDefinition
if err := buildDef.Validate(); err != nil {
return err
}

// the runDetails field is required for SLSA Build L1
runDetails := p.GetRunDetails()
if runDetails == nil {
return ErrRunDetailsRequired
}

// check the RunDetails
if err := runDetails.Validate(); err != nil {
return err
}

return nil
}
72 changes: 72 additions & 0 deletions go/predicates/provenance/v1/provenance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
Tests for SLSA Provenance v1 protos.
*/

package v1

import (
"testing"

ita1 "github.com/in-toto/attestation/go/v1"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/structpb"
)

func createTestProvenance(t *testing.T) *Provenance {
// Create a Provenance

t.Helper()

rd := &ita1.ResourceDescriptor{
Name: "theResource",
Digest: map[string]string{"alg1": "abc123"},
}

builder := &Builder{
Id: "theId",
Version: map[string]string{"theComponent": "v0.1"},
BuilderDependencies: []*ita1.ResourceDescriptor{rd},
}

buildMeta := &BuildMetadata{
InvocationId: "theInvocationId",
}

runDetails := &RunDetails{
Builder: builder,
Metadata: buildMeta,
Byproducts: []*ita1.ResourceDescriptor{rd},
}

externalParams, err := structpb.NewStruct(map[string]interface{}{
"param1": map[string]interface{}{
"subKey": "subVal"}})
if err != nil {
t.Fatal(err)
}

buildDef := &BuildDefinition{
BuildType: "theBuildType",
ExternalParameters: externalParams,
ResolvedDependencies: []*ita1.ResourceDescriptor{rd},
}

return &Provenance{
BuildDefinition: buildDef,
RunDetails: runDetails,
}
}

func TestJsonUnmarshalProvenance(t *testing.T) {
var wantSt = `{"buildDefinition":{"buildType":"theBuildType","externalParameters":{"param1":{"subKey":"subVal"}},"resolvedDependencies":[{"name":"theResource","digest":{"alg1":"abc123"}}]},"runDetails":{"builder":{"id":"theId","version":{"theComponent":"v0.1"},"builderDependencies":[{"name":"theResource","digest":{"alg1":"abc123"}}]},"metadata":{"invocationId":"theInvocationId"},"byproducts":[{"name":"theResource","digest":{"alg1":"abc123"}}]}}`

got := &Provenance{}
err := protojson.Unmarshal([]byte(wantSt), got)
assert.NoError(t, err, "error during JSON unmarshalling")

want := createTestProvenance(t)
assert.NoError(t, err, "unexpected error during test Statement creation")
assert.True(t, proto.Equal(got, want), "protos do not match")
}

0 comments on commit aedbff9

Please sign in to comment.