From 63410d4dc730f70fbd34aacfc20d9af730629c53 Mon Sep 17 00:00:00 2001 From: John Kjell Date: Fri, 2 Feb 2024 23:42:05 -0600 Subject: [PATCH 01/33] Initial link attestor Signed-off-by: John Kjell --- attestation/link/link.go | 109 ++++++++++++++++++++++++++++++++++ attestation/link/link_test.go | 15 +++++ go.mod | 3 +- go.sum | 2 + 4 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 attestation/link/link.go create mode 100644 attestation/link/link_test.go diff --git a/attestation/link/link.go b/attestation/link/link.go new file mode 100644 index 00000000..0f8f7d69 --- /dev/null +++ b/attestation/link/link.go @@ -0,0 +1,109 @@ +// Copyright 2024 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package link + +import ( + v0 "github.com/in-toto/attestation/go/predicates/link/v0" + v1 "github.com/in-toto/attestation/go/v1" + "github.com/in-toto/go-witness/attestation" + "github.com/in-toto/go-witness/attestation/commandrun" + "github.com/in-toto/go-witness/attestation/environment" + "github.com/in-toto/go-witness/attestation/material" + "google.golang.org/protobuf/types/known/structpb" +) + +const ( + Name = "link" + Type = "https://witness.dev/attestations/link/v0.1" + RunType = attestation.PostProductRunType +) + +// This is a hacky way to create a compile time error in case the attestor +// doesn't implement the expected interfaces. +var ( + _ attestation.Attestor = &Link{} +) + +func init() { + attestation.RegisterAttestation(Name, Type, RunType, func() attestation.Attestor { + return New() + }) +} + +type Link struct { + pbLink v0.Link + products map[string]attestation.Product +} + +func New() *Link { + return &Link{ + pbLink: v0.Link{ + Name: Name, + }, + } +} + +func (l *Link) Name() string { + return Name +} + +func (l *Link) Type() string { + return Type +} + +func (l *Link) RunType() attestation.RunType { + return RunType +} + +func (l *Link) Attest(ctx *attestation.AttestationContext) error { + for name, digestSet := range ctx.Materials() { + digests, _ := digestSet.ToNameMap() + l.pbLink.Materials = append(l.pbLink.Materials, &v1.ResourceDescriptor{ + Name: name, + Digest: digests, + }) + } + + l.products = ctx.Products() + + for _, attestor := range ctx.CompletedAttestors() { + switch name := attestor.Attestor.Name(); name { + case commandrun.Name: + l.pbLink.Command = attestor.Attestor.(*commandrun.CommandRun).Cmd + case material.Name: + mats := attestor.Attestor.(*material.Attestor).Materials() + for name, digestSet := range mats { + digests, _ := digestSet.ToNameMap() + l.pbLink.Materials = append(l.pbLink.Materials, &v1.ResourceDescriptor{ + Name: name, + Digest: digests, + }) + } + case environment.Name: + envs := attestor.Attestor.(*environment.Attestor).Variables + pbEnvs := make(map[string]interface{}, len(envs)) + for name, value := range envs { + pbEnvs[name] = value + } + + var err error + l.pbLink.Environment, err = structpb.NewStruct(pbEnvs) + if err != nil { + return err + } + } + } + return nil +} diff --git a/attestation/link/link_test.go b/attestation/link/link_test.go new file mode 100644 index 00000000..5b8a8be1 --- /dev/null +++ b/attestation/link/link_test.go @@ -0,0 +1,15 @@ +// Copyright 2022 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package link diff --git a/go.mod b/go.mod index 808640c7..10bf5fb8 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/edwarnicke/gitoid v0.0.0-20220710194850-1be5bfda1f9d github.com/go-git/go-git/v5 v5.11.0 github.com/in-toto/archivista v0.2.0 + github.com/in-toto/attestation v1.0.1 github.com/mattn/go-isatty v0.0.20 github.com/open-policy-agent/opa v0.49.2 github.com/owenrumney/go-sarif v1.1.1 @@ -93,7 +94,7 @@ require ( golang.org/x/term v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a // indirect - google.golang.org/protobuf v1.32.0 // indirect + google.golang.org/protobuf v1.32.0 gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index c1021839..056a8e07 100644 --- a/go.sum +++ b/go.sum @@ -105,6 +105,8 @@ github.com/honeycombio/beeline-go v1.10.0 h1:cUDe555oqvw8oD76BQJ8alk7FP0JZ/M/zXp github.com/honeycombio/libhoney-go v1.16.0 h1:kPpqoz6vbOzgp7jC6SR7SkNj7rua7rgxvznI6M3KdHc= github.com/in-toto/archivista v0.2.0 h1:FViuHMVVETborvOqlmSYdROY8RmX3CO0V0MOhU/Rl20= github.com/in-toto/archivista v0.2.0/go.mod h1:qt9uN4TkHWUgR5A2wxRqQIBizSl32P2nI2AjESskkr0= +github.com/in-toto/attestation v1.0.1 h1:DgX1XuBkryTpj1Piq8AiMK3CMfEcec3Qv6+Ku+uI3WY= +github.com/in-toto/attestation v1.0.1/go.mod h1:hCR5COCuENh5+VfojEkJnt7caOymbEgvyZdKifD6pOw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= From 7da776c9fb4963ea4a7946b6f40d5e307aa88ef8 Mon Sep 17 00:00:00 2001 From: Mikhail Swift Date: Mon, 29 Jan 2024 10:33:50 -0500 Subject: [PATCH 02/33] refactor: move gitoid code to cyrptoutil, use digestvalue everywhere (#139) When the functionality to calculate gitoids was added, there was a bit of tech debt incurred since they didn't implement hash.Hash. This remedies this with an admitedly hacky implementation of hash.Hash that wraps the gitoid code. This also standardizes our cryptoutil fucntions around the DigestValue struct that was added around this time to differentiate between gitoids and regular hash functions. Signed-off-by: Mikhail Swift Signed-off-by: John Kjell --- attestation/aws-iid/aws-iid.go | 4 +- attestation/commandrun/tracing_linux.go | 3 +- attestation/context.go | 10 +-- attestation/file/file.go | 29 +-------- attestation/file/file_test.go | 6 +- attestation/gcp-iit/gcp-iit.go | 2 +- attestation/git/git.go | 2 +- attestation/github/github.go | 2 +- attestation/gitlab/gitlab.go | 2 +- attestation/maven/maven.go | 2 +- attestation/oci/oci.go | 2 +- attestation/oci/oci_test.go | 2 +- attestation/product/product_test.go | 4 +- cryptoutil/digestset.go | 35 ++++++---- cryptoutil/gitoid.go | 85 +++++++++++++++++++++++++ 15 files changed, 130 insertions(+), 60 deletions(-) create mode 100644 cryptoutil/gitoid.go diff --git a/attestation/aws-iid/aws-iid.go b/attestation/aws-iid/aws-iid.go index a9bd0a6f..5aacbe16 100644 --- a/attestation/aws-iid/aws-iid.go +++ b/attestation/aws-iid/aws-iid.go @@ -80,7 +80,7 @@ func init() { type Attestor struct { ec2metadata.EC2InstanceIdentityDocument - hashes []crypto.Hash + hashes []cryptoutil.DigestValue session session.Session conf *aws.Config RawIID string `json:"rawiid"` @@ -195,7 +195,7 @@ func (a *Attestor) Verify() error { } func (a *Attestor) Subjects() map[string]cryptoutil.DigestSet { - hashes := []crypto.Hash{crypto.SHA256} + hashes := []cryptoutil.DigestValue{{Hash: crypto.SHA256}} subjects := make(map[string]cryptoutil.DigestSet) if ds, err := cryptoutil.CalculateDigestSetFromBytes([]byte(a.EC2InstanceIdentityDocument.InstanceID), hashes); err == nil { subjects[fmt.Sprintf("instanceid:%s", a.EC2InstanceIdentityDocument.InstanceID)] = ds diff --git a/attestation/commandrun/tracing_linux.go b/attestation/commandrun/tracing_linux.go index 53d20d3d..cd473c47 100644 --- a/attestation/commandrun/tracing_linux.go +++ b/attestation/commandrun/tracing_linux.go @@ -18,7 +18,6 @@ package commandrun import ( "bytes" - "crypto" "fmt" "os" "os/exec" @@ -42,7 +41,7 @@ type ptraceContext struct { mainProgram string processes map[int]*ProcessInfo exitCode int - hash []crypto.Hash + hash []cryptoutil.DigestValue environmentBlockList map[string]struct{} } diff --git a/attestation/context.go b/attestation/context.go index 748829c5..d4271c8e 100644 --- a/attestation/context.go +++ b/attestation/context.go @@ -56,7 +56,7 @@ func WithContext(ctx context.Context) AttestationContextOption { } } -func WithHashes(hashes []crypto.Hash) AttestationContextOption { +func WithHashes(hashes []cryptoutil.DigestValue) AttestationContextOption { return func(ctx *AttestationContext) { if len(hashes) > 0 { ctx.hashes = hashes @@ -83,7 +83,7 @@ type AttestationContext struct { ctx context.Context attestors []Attestor workingDir string - hashes []crypto.Hash + hashes []cryptoutil.DigestValue completedAttestors []CompletedAttestor products map[string]Product materials map[string]cryptoutil.DigestSet @@ -104,7 +104,7 @@ func NewContext(attestors []Attestor, opts ...AttestationContextOption) (*Attest ctx: context.Background(), attestors: attestors, workingDir: wd, - hashes: []crypto.Hash{crypto.SHA256}, + hashes: []cryptoutil.DigestValue{{Hash: crypto.SHA256}, {Hash: crypto.SHA256, GitOID: true}, {Hash: crypto.SHA1, GitOID: true}}, materials: make(map[string]cryptoutil.DigestSet), products: make(map[string]Product), } @@ -222,8 +222,8 @@ func (ctx *AttestationContext) WorkingDir() string { return ctx.workingDir } -func (ctx *AttestationContext) Hashes() []crypto.Hash { - hashes := make([]crypto.Hash, len(ctx.hashes)) +func (ctx *AttestationContext) Hashes() []cryptoutil.DigestValue { + hashes := make([]cryptoutil.DigestValue, len(ctx.hashes)) copy(hashes, ctx.hashes) return hashes } diff --git a/attestation/file/file.go b/attestation/file/file.go index 40752b28..36a37e4e 100644 --- a/attestation/file/file.go +++ b/attestation/file/file.go @@ -15,12 +15,10 @@ package file import ( - "crypto" "io/fs" "os" "path/filepath" - "github.com/edwarnicke/gitoid" "github.com/in-toto/go-witness/cryptoutil" "github.com/in-toto/go-witness/log" ) @@ -28,7 +26,7 @@ import ( // recordArtifacts will walk basePath and record the digests of each file with each of the functions in hashes. // If file already exists in baseArtifacts and the two artifacts are equal the artifact will not be in the // returned map of artifacts. -func RecordArtifacts(basePath string, baseArtifacts map[string]cryptoutil.DigestSet, hashes []crypto.Hash, visitedSymlinks map[string]struct{}) (map[string]cryptoutil.DigestSet, error) { +func RecordArtifacts(basePath string, baseArtifacts map[string]cryptoutil.DigestSet, hashes []cryptoutil.DigestValue, visitedSymlinks map[string]struct{}) (map[string]cryptoutil.DigestSet, error) { artifacts := make(map[string]cryptoutil.DigestSet) err := filepath.Walk(basePath, func(path string, info fs.FileInfo, err error) error { if err != nil { @@ -80,31 +78,6 @@ func RecordArtifacts(basePath string, baseArtifacts map[string]cryptoutil.Digest return err } - fileReader, err := os.Open(path) - if err != nil { - return err - } - - goidSha1, err := gitoid.New(fileReader) - if err != nil { - return err - } - - goidSha256, err := gitoid.New(fileReader, gitoid.WithSha256()) - if err != nil { - return err - } - - artifact[cryptoutil.DigestValue{ - Hash: crypto.SHA1, - GitOID: true, - }] = goidSha1.URI() - - artifact[cryptoutil.DigestValue{ - Hash: crypto.SHA256, - GitOID: true, - }] = goidSha256.URI() - if shouldRecord(relPath, artifact, baseArtifacts) { artifacts[relPath] = artifact } diff --git a/attestation/file/file_test.go b/attestation/file/file_test.go index 9f4dc0da..73344bff 100644 --- a/attestation/file/file_test.go +++ b/attestation/file/file_test.go @@ -38,13 +38,13 @@ func TestBrokenSymlink(t *testing.T) { symTestDir := filepath.Join(dir, "symTestDir") require.NoError(t, os.Symlink(testDir, symTestDir)) - _, err := RecordArtifacts(dir, map[string]cryptoutil.DigestSet{}, []crypto.Hash{crypto.SHA256}, map[string]struct{}{}) + _, err := RecordArtifacts(dir, map[string]cryptoutil.DigestSet{}, []cryptoutil.DigestValue{{Hash: crypto.SHA256}}, map[string]struct{}{}) require.NoError(t, err) // remove the symlinks and make sure we don't get an error back require.NoError(t, os.RemoveAll(testDir)) require.NoError(t, os.RemoveAll(testFile)) - _, err = RecordArtifacts(dir, map[string]cryptoutil.DigestSet{}, []crypto.Hash{crypto.SHA256}, map[string]struct{}{}) + _, err = RecordArtifacts(dir, map[string]cryptoutil.DigestSet{}, []cryptoutil.DigestValue{{Hash: crypto.SHA256}}, map[string]struct{}{}) require.NoError(t, err) } @@ -58,6 +58,6 @@ func TestSymlinkCycle(t *testing.T) { require.NoError(t, os.Symlink(dir, symTestDir)) // if a symlink cycle weren't properly handled this would be an infinite loop - _, err := RecordArtifacts(dir, map[string]cryptoutil.DigestSet{}, []crypto.Hash{crypto.SHA256}, map[string]struct{}{}) + _, err := RecordArtifacts(dir, map[string]cryptoutil.DigestSet{}, []cryptoutil.DigestValue{{Hash: crypto.SHA256}}, map[string]struct{}{}) require.NoError(t, err) } diff --git a/attestation/gcp-iit/gcp-iit.go b/attestation/gcp-iit/gcp-iit.go index 84ea433d..99691859 100644 --- a/attestation/gcp-iit/gcp-iit.go +++ b/attestation/gcp-iit/gcp-iit.go @@ -176,7 +176,7 @@ func (a *Attestor) getInstanceData() { func (a *Attestor) Subjects() map[string]cryptoutil.DigestSet { subjects := make(map[string]cryptoutil.DigestSet) - hashes := []crypto.Hash{crypto.SHA256} + hashes := []cryptoutil.DigestValue{{Hash: crypto.SHA256}} if ds, err := cryptoutil.CalculateDigestSetFromBytes([]byte(a.InstanceID), hashes); err == nil { subjects[fmt.Sprintf("instanceid:%v", a.InstanceID)] = ds } else { diff --git a/attestation/git/git.go b/attestation/git/git.go index 1a92634e..cbcc189c 100644 --- a/attestation/git/git.go +++ b/attestation/git/git.go @@ -220,7 +220,7 @@ func (a *Attestor) Attest(ctx *attestation.AttestationContext) error { func (a *Attestor) Subjects() map[string]cryptoutil.DigestSet { subjects := make(map[string]cryptoutil.DigestSet) - hashes := []crypto.Hash{crypto.SHA256} + hashes := []cryptoutil.DigestValue{{Hash: crypto.SHA256}} subjectName := fmt.Sprintf("commithash:%v", a.CommitHash) subjects[subjectName] = cryptoutil.DigestSet{ diff --git a/attestation/github/github.go b/attestation/github/github.go index 46bce445..436c98a5 100644 --- a/attestation/github/github.go +++ b/attestation/github/github.go @@ -145,7 +145,7 @@ func (a *Attestor) Attest(ctx *attestation.AttestationContext) error { // Subjects returns a map of subjects and their corresponding digest sets. func (a *Attestor) Subjects() map[string]cryptoutil.DigestSet { subjects := make(map[string]cryptoutil.DigestSet) - hashes := []crypto.Hash{crypto.SHA256} + hashes := []cryptoutil.DigestValue{{Hash: crypto.SHA256}} if pipelineSubj, err := cryptoutil.CalculateDigestSetFromBytes([]byte(a.PipelineUrl), hashes); err == nil { subjects[fmt.Sprintf("pipelineurl:%v", a.PipelineUrl)] = pipelineSubj } else { diff --git a/attestation/gitlab/gitlab.go b/attestation/gitlab/gitlab.go index b9663b0a..eea831d7 100644 --- a/attestation/gitlab/gitlab.go +++ b/attestation/gitlab/gitlab.go @@ -118,7 +118,7 @@ func (a *Attestor) Attest(ctx *attestation.AttestationContext) error { func (a *Attestor) Subjects() map[string]cryptoutil.DigestSet { subjects := make(map[string]cryptoutil.DigestSet) - hashes := []crypto.Hash{crypto.SHA256} + hashes := []cryptoutil.DigestValue{{Hash: crypto.SHA256}} if ds, err := cryptoutil.CalculateDigestSetFromBytes([]byte(a.PipelineUrl), hashes); err == nil { subjects[fmt.Sprintf("pipelineurl:%v", a.PipelineUrl)] = ds } else { diff --git a/attestation/maven/maven.go b/attestation/maven/maven.go index 6392c35a..326535a9 100644 --- a/attestation/maven/maven.go +++ b/attestation/maven/maven.go @@ -133,7 +133,7 @@ func (a *Attestor) Attest(ctx *attestation.AttestationContext) error { func (a *Attestor) Subjects() map[string]cryptoutil.DigestSet { subjects := make(map[string]cryptoutil.DigestSet) - hashes := []crypto.Hash{crypto.SHA256} + hashes := []cryptoutil.DigestValue{{Hash: crypto.SHA256}} projectSubject := fmt.Sprintf("project:%v/%v@%v", a.GroupId, a.ArtifactId, a.Version) if ds, err := cryptoutil.CalculateDigestSetFromBytes([]byte(projectSubject), hashes); err == nil { subjects[projectSubject] = ds diff --git a/attestation/oci/oci.go b/attestation/oci/oci.go index 0f6dcb93..853b0580 100644 --- a/attestation/oci/oci.go +++ b/attestation/oci/oci.go @@ -231,7 +231,7 @@ func (a *Attestor) parseMaifest(ctx *attestation.AttestationContext) error { } func (a *Attestor) Subjects() map[string]cryptoutil.DigestSet { - hashes := []crypto.Hash{crypto.SHA256} + hashes := []cryptoutil.DigestValue{{Hash: crypto.SHA256}} subj := make(map[string]cryptoutil.DigestSet) subj[fmt.Sprintf("manifestdigest:%s", a.ManifestDigest[cryptoutil.DigestValue{Hash: crypto.SHA256}])] = a.ManifestDigest subj[fmt.Sprintf("tardigest:%s", a.TarDigest[cryptoutil.DigestValue{Hash: crypto.SHA256}])] = a.TarDigest diff --git a/attestation/oci/oci_test.go b/attestation/oci/oci_test.go index faca7495..fd64e74f 100644 --- a/attestation/oci/oci_test.go +++ b/attestation/oci/oci_test.go @@ -92,7 +92,7 @@ func TestAttestor_Attest(t *testing.T) { t.Fatal(err) } - hashes := []crypto.Hash{crypto.SHA256} + hashes := []cryptoutil.DigestValue{{Hash: crypto.SHA256}} tarDigest, err := cryptoutil.CalculateDigestSetFromBytes([]byte(decoded), hashes) if err != nil { diff --git a/attestation/product/product_test.go b/attestation/product/product_test.go index 21aead8a..6495f52f 100644 --- a/attestation/product/product_test.go +++ b/attestation/product/product_test.go @@ -30,7 +30,7 @@ import ( ) func TestFromDigestMap(t *testing.T) { - testDigest, err := cryptoutil.CalculateDigestSetFromBytes([]byte("test"), []crypto.Hash{crypto.SHA256}) + testDigest, err := cryptoutil.CalculateDigestSetFromBytes([]byte("test"), []cryptoutil.DigestValue{{Hash: crypto.SHA256}}) assert.NoError(t, err) testDigestSet := make(map[string]cryptoutil.DigestSet) testDigestSet["test"] = testDigest @@ -57,7 +57,7 @@ func TestAttestorRunType(t *testing.T) { func TestAttestorAttest(t *testing.T) { a := New() - testDigest, err := cryptoutil.CalculateDigestSetFromBytes([]byte("test"), []crypto.Hash{crypto.SHA256}) + testDigest, err := cryptoutil.CalculateDigestSetFromBytes([]byte("test"), []cryptoutil.DigestValue{{Hash: crypto.SHA256}}) if err != nil { t.Errorf("Failed to calculate digest set from bytes: %v", err) } diff --git a/cryptoutil/digestset.go b/cryptoutil/digestset.go index ec291da4..c75d57c0 100644 --- a/cryptoutil/digestset.go +++ b/cryptoutil/digestset.go @@ -75,6 +75,14 @@ type DigestValue struct { GitOID bool } +func (dv DigestValue) New() hash.Hash { + if dv.GitOID { + return &gitoidHasher{hash: dv.Hash, buf: &bytes.Buffer{}} + } + + return dv.Hash.New() +} + type DigestSet map[DigestValue]string func HashToString(h crypto.Hash) (string, error) { @@ -142,13 +150,13 @@ func NewDigestSet(digestsByName map[string]string) (DigestSet, error) { return ds, nil } -func CalculateDigestSet(r io.Reader, hashes []crypto.Hash) (DigestSet, error) { +func CalculateDigestSet(r io.Reader, digestValues []DigestValue) (DigestSet, error) { digestSet := make(DigestSet) writers := []io.Writer{} - hashfuncs := map[crypto.Hash]hash.Hash{} - for _, hash := range hashes { - hashfunc := hash.New() - hashfuncs[hash] = hashfunc + hashfuncs := map[DigestValue]hash.Hash{} + for _, digestValue := range digestValues { + hashfunc := digestValue.New() + hashfuncs[digestValue] = hashfunc writers = append(writers, hashfunc) } @@ -157,21 +165,26 @@ func CalculateDigestSet(r io.Reader, hashes []crypto.Hash) (DigestSet, error) { return digestSet, err } - for hash, hashfunc := range hashfuncs { - digestValue := DigestValue{ - Hash: hash, - GitOID: false, + for digestValue, hashfunc := range hashfuncs { + // gitoids are somewhat special... we're using a custom implementation of hash.Hash + // to wrap the gitoid library. Sum will return a gitoid URI, so we don't want to hex + // encode it as it's already a string with a hex encoded hash. + if digestValue.GitOID { + digestSet[digestValue] = string(hashfunc.Sum(nil)) + continue } + digestSet[digestValue] = string(HexEncode(hashfunc.Sum(nil))) } + return digestSet, nil } -func CalculateDigestSetFromBytes(data []byte, hashes []crypto.Hash) (DigestSet, error) { +func CalculateDigestSetFromBytes(data []byte, hashes []DigestValue) (DigestSet, error) { return CalculateDigestSet(bytes.NewReader(data), hashes) } -func CalculateDigestSetFromFile(path string, hashes []crypto.Hash) (DigestSet, error) { +func CalculateDigestSetFromFile(path string, hashes []DigestValue) (DigestSet, error) { file, err := os.Open(path) if err != nil { return DigestSet{}, err diff --git a/cryptoutil/gitoid.go b/cryptoutil/gitoid.go new file mode 100644 index 00000000..f3fe365c --- /dev/null +++ b/cryptoutil/gitoid.go @@ -0,0 +1,85 @@ +// Copyright 2023 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cryptoutil + +import ( + "bytes" + "crypto" + "encoding/hex" + "fmt" + + "github.com/edwarnicke/gitoid" +) + +// gitoidHasher implements io.Writer so we can generate gitoids with our CalculateDigestSet function. +// CalculateDigestSet takes in an io.Reader pointing to some data we want to hash, and writes it to a +// MultiWriter that forwards it to writers for each hash we wish to calculate. +// This is a bit hacky -- it maintains an internal buffer and then when asked for the Sum, it calculates +// the gitoid. We may be able to contribute to the gitoid library to make this smoother +type gitoidHasher struct { + buf *bytes.Buffer + hash crypto.Hash +} + +// Write implments the io.Writer interface, and writes to the internal buffer +func (gh *gitoidHasher) Write(p []byte) (n int, err error) { + return gh.buf.Write(p) +} + +// Sum appends the current hash to b and returns the resulting slice. +// It does not change the underlying hash state. +func (gh *gitoidHasher) Sum(b []byte) []byte { + opts := []gitoid.Option{} + if gh.hash == crypto.SHA256 { + opts = append(opts, gitoid.WithSha256()) + } + + g, err := gitoid.New(gh.buf, opts...) + if err != nil { + return []byte{} + } + + return append(b, []byte(g.URI())...) +} + +// Reset resets the Hash to its initial state. +func (gh *gitoidHasher) Reset() { + gh.buf = &bytes.Buffer{} +} + +// Size returns the number of bytes Sum will return. +func (gh *gitoidHasher) Size() int { + hashName, err := HashToString(gh.hash) + if err != nil { + return 0 + } + + // this is somewhat fragile and knows too much about the internals of the gitoid code... + // we're assuming that the default gitoid content type will remain BLOB, and that our + // string representations of hash functions will remain consistent with their... + // and that the URI format will remain consistent. + // this should probably be changed, and this entire thing could maybe be upstreamed to the + // gitoid library. + return len(fmt.Sprintf("gitoid:%s:%s:", gitoid.BLOB, hashName)) + hex.EncodedLen(gh.hash.Size()) +} + +// BlockSize returns the hash's underlying block size. +// The Write method must be able to accept any amount +// of data, but it may operate more efficiently if all writes +// are a multiple of the block size. +func (gh *gitoidHasher) BlockSize() int { + hf := gh.hash.New() + return hf.BlockSize() +} From 924eb1f6bef4bd03361fdd677f4deb6b8581e9d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 19:21:03 +0000 Subject: [PATCH 03/33] chore: bump actions/upload-artifact from 4.2.0 to 4.3.0 (#142) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.2.0 to 4.3.0. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/694cdabd8bdb0f10b2cea11669e1bf5453eed0a6...26f96dfa697d77e81fd5907df203aa23a56210a8) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: John Kjell --- .github/workflows/scorecards.yml | 2 +- .github/workflows/witness.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 270ac217..72b9f063 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -77,7 +77,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 + uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 with: name: SARIF file path: results.sarif diff --git a/.github/workflows/witness.yml b/.github/workflows/witness.yml index d68e0399..a3e8f571 100644 --- a/.github/workflows/witness.yml +++ b/.github/workflows/witness.yml @@ -85,7 +85,7 @@ jobs: run: ${{ inputs.command }} - if: ${{ inputs.artifact-upload-path != '' && inputs.artifact-upload-name != ''}} - uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 + uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 with: name: ${{ inputs.artifact-upload-name }} path: ${{ inputs.artifact-upload-path }} From 856b50024ded95807018e626792741b333864536 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 21:44:29 -0600 Subject: [PATCH 04/33] chore: bump github/codeql-action from 3.23.1 to 3.23.2 (#143) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.23.1 to 3.23.2. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/0b21cf2492b6b02c465a3e5d7c473717ad7721ba...b7bf0a3ed3ecfa44160715d7c442788f65f0f923) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Tom Meadows Signed-off-by: John Kjell --- .github/workflows/codeql.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d83055b4..e64b4960 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -64,7 +64,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # v3.23.1 + uses: github/codeql-action/init@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 # v3.23.2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -74,7 +74,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # v3.23.1 + uses: github/codeql-action/autobuild@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 # v3.23.2 # ℹī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -87,6 +87,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # v3.23.1 + uses: github/codeql-action/analyze@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 # v3.23.2 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 72b9f063..c0304687 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -85,6 +85,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # v3.23.1 + uses: github/codeql-action/upload-sarif@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 # v3.23.2 with: sarif_file: results.sarif From 315793e81ae915fcabff8c041b5c067f18312e79 Mon Sep 17 00:00:00 2001 From: Tom Meadows Date: Tue, 30 Jan 2024 03:54:39 +0000 Subject: [PATCH 05/33] Adding job to auto cut releases (#141) adding job to auto cut releases Signed-off-by: chaosinthecrd Signed-off-by: John Kjell --- .github/workflows/release.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2785e946..c67e6dc5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -55,3 +55,13 @@ jobs: command: go test -v -coverprofile=profile.cov -covermode=atomic ./... artifact-upload-name: profile.cov artifact-upload-path: profile.cov + + release: + needs: [fmt, sast, unit-tests] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Release + uses: softprops/action-gh-release@v1 From ad61b8a27f15ef8cbb76f105ba2ea841399361a8 Mon Sep 17 00:00:00 2001 From: Tom Meadows Date: Thu, 1 Feb 2024 19:38:20 +0000 Subject: [PATCH 06/33] fixing error in github actions workflow (#147) fixing error in workflow Signed-off-by: chaosinthecrd Signed-off-by: John Kjell --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c67e6dc5..216dbd20 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -57,7 +57,7 @@ jobs: artifact-upload-path: profile.cov release: - needs: [fmt, sast, unit-tests] + needs: [fmt, sast, unit-test] if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') runs-on: ubuntu-latest steps: From ed1dfef45447a63a446045727b788c0db7ec1770 Mon Sep 17 00:00:00 2001 From: Tom Meadows Date: Fri, 2 Feb 2024 15:23:33 +0000 Subject: [PATCH 07/33] RunAttestors refactor (#131) * improving run attestors Signed-off-by: chaosinthecrd * finalising changes. Signed-off-by: chaosinthecrd * improving run attestors Signed-off-by: chaosinthecrd * finalising changes. Signed-off-by: chaosinthecrd * addressing review, restoring run type order Signed-off-by: chaosinthecrd * updating error handling logic Signed-off-by: chaosinthecrd * updating to go 1.21 for errors.Join Signed-off-by: chaosinthecrd --------- Signed-off-by: chaosinthecrd Signed-off-by: Tom Meadows Signed-off-by: John Kjell --- attestation/commandrun/commandrun.go | 7 +- attestation/context.go | 111 +++++++++------------------ go.mod | 2 +- go.sum | 48 ++++++++++++ run.go | 15 +++- 5 files changed, 102 insertions(+), 81 deletions(-) diff --git a/attestation/commandrun/commandrun.go b/attestation/commandrun/commandrun.go index 5379700e..1aea1784 100644 --- a/attestation/commandrun/commandrun.go +++ b/attestation/commandrun/commandrun.go @@ -115,9 +115,10 @@ type CommandRun struct { func (rc *CommandRun) Attest(ctx *attestation.AttestationContext) error { if len(rc.Cmd) == 0 { - return attestation.ErrInvalidOption{ - Option: "Cmd", - Reason: "CommandRun attestation requires a command to run", + return attestation.ErrAttestor{ + Name: rc.Name(), + RunType: rc.RunType(), + Reason: "CommandRun attestation requires a command to run", } } diff --git a/attestation/context.go b/attestation/context.go index d4271c8e..d73655da 100644 --- a/attestation/context.go +++ b/attestation/context.go @@ -35,17 +35,22 @@ const ( PostProductRunType RunType = "postproduct" ) +func runTypeOrder() []RunType { + return []RunType{PreMaterialRunType, MaterialRunType, ExecuteRunType, ProductRunType, PostProductRunType} +} + func (r RunType) String() string { return string(r) } -type ErrInvalidOption struct { - Option string - Reason string +type ErrAttestor struct { + Name string + RunType RunType + Reason string } -func (e ErrInvalidOption) Error() string { - return fmt.Sprintf("invalid value for option %v: %v", e.Option, e.Reason) +func (e ErrAttestor) Error() string { + return fmt.Sprintf("error returned for attestor %s of run type %s: %s", e.Name, e.RunType, e.Reason) } type AttestationContextOption func(ctx *AttestationContext) @@ -117,82 +122,40 @@ func NewContext(attestors []Attestor, opts ...AttestationContextOption) (*Attest } func (ctx *AttestationContext) RunAttestors() error { - preAttestors := []Attestor{} - materialAttestors := []Attestor{} - exeucteAttestors := []Attestor{} - productAttestors := []Attestor{} - postAttestors := []Attestor{} - + attestors := make(map[RunType][]Attestor) for _, attestor := range ctx.attestors { - switch attestor.RunType() { - case PreMaterialRunType: - preAttestors = append(preAttestors, attestor) - - case MaterialRunType: - materialAttestors = append(materialAttestors, attestor) - - case ExecuteRunType: - exeucteAttestors = append(exeucteAttestors, attestor) - - case ProductRunType: - productAttestors = append(productAttestors, attestor) - - case PostProductRunType: - postAttestors = append(postAttestors, attestor) - - default: - return ErrInvalidOption{ - Option: "attestor.RunType", - Reason: fmt.Sprintf("unknown run type %v", attestor.RunType()), + if attestor.RunType() == "" { + return ErrAttestor{ + Name: attestor.Name(), + RunType: attestor.RunType(), + Reason: "attestor run type not set", } } - } - for _, attestor := range preAttestors { - if err := ctx.runAttestor(attestor); err != nil { - return err - } + attestors[attestor.RunType()] = append(attestors[attestor.RunType()], attestor) } - for _, attestor := range materialAttestors { - if err := ctx.runAttestor(attestor); err != nil { - return err - } - } - - for _, attestor := range exeucteAttestors { - if err := ctx.runAttestor(attestor); err != nil { - return err - } - } - - for _, attestor := range productAttestors { - if err := ctx.runAttestor(attestor); err != nil { - return err - } - } - - for _, attestor := range postAttestors { - if err := ctx.runAttestor(attestor); err != nil { - return err + order := runTypeOrder() + for _, k := range order { + log.Debugf("starting %s attestors...", k.String()) + for _, att := range attestors[k] { + log.Infof("Starting %v attestor...", att.Name()) + ctx.runAttestor(att) } } return nil } -func (ctx *AttestationContext) runAttestor(attestor Attestor) error { - log.Infof("Starting %v attestor...", attestor.Name()) +func (ctx *AttestationContext) runAttestor(attestor Attestor) { startTime := time.Now() if err := attestor.Attest(ctx); err != nil { - log.Errorf("Error running %v attestor: %w", attestor.Name(), err) ctx.completedAttestors = append(ctx.completedAttestors, CompletedAttestor{ Attestor: attestor, StartTime: startTime, EndTime: time.Now(), Error: err, }) - return err } ctx.completedAttestors = append(ctx.completedAttestors, CompletedAttestor{ @@ -205,17 +168,15 @@ func (ctx *AttestationContext) runAttestor(attestor Attestor) error { ctx.addMaterials(materialer) } - if producter, ok := attestor.(Producer); ok { - ctx.addProducts(producter) + if producer, ok := attestor.(Producer); ok { + ctx.addProducts(producer) } - - return nil } func (ctx *AttestationContext) CompletedAttestors() []CompletedAttestor { - attestors := make([]CompletedAttestor, len(ctx.completedAttestors)) - copy(attestors, ctx.completedAttestors) - return attestors + out := make([]CompletedAttestor, len(ctx.completedAttestors)) + copy(out, ctx.completedAttestors) + return out } func (ctx *AttestationContext) WorkingDir() string { @@ -233,21 +194,19 @@ func (ctx *AttestationContext) Context() context.Context { } func (ctx *AttestationContext) Materials() map[string]cryptoutil.DigestSet { - matCopy := make(map[string]cryptoutil.DigestSet) + out := make(map[string]cryptoutil.DigestSet) for k, v := range ctx.materials { - matCopy[k] = v + out[k] = v } - - return matCopy + return out } func (ctx *AttestationContext) Products() map[string]Product { - prodCopy := make(map[string]Product) + out := make(map[string]Product) for k, v := range ctx.products { - prodCopy[k] = v + out[k] = v } - - return ctx.products + return out } func (ctx *AttestationContext) addMaterials(materialer Materialer) { diff --git a/go.mod b/go.mod index 10bf5fb8..23a42772 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/in-toto/go-witness -go 1.19 +go 1.21 require ( github.com/digitorus/pkcs7 v0.0.0-20230220124406-51331ccfc40f diff --git a/go.sum b/go.sum index 056a8e07..63e67187 100644 --- a/go.sum +++ b/go.sum @@ -13,17 +13,23 @@ github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjA github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aws/aws-sdk-go v1.44.334 h1:h2bdbGb//fez6Sv6PaYv868s9liDeoYM6hYsAqTB4MU= github.com/aws/aws-sdk-go v1.44.334/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA= +github.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= @@ -35,7 +41,9 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg= +github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw= github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/digitorus/pkcs7 v0.0.0-20221019075359-21b8b40e6bb4/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc= @@ -48,21 +56,29 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m github.com/edwarnicke/gitoid v0.0.0-20220710194850-1be5bfda1f9d h1:4l+Uq5zFWSagXgGFaKRRVWJrnlzeathyagWgYUltCgY= github.com/edwarnicke/gitoid v0.0.0-20220710194850-1be5bfda1f9d/go.mod h1:WxWwA3EYuCQjlR5EBUX3uaTS8bh9BOa7BcqVREHQ0uQ= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw= +github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA= github.com/facebookgo/limitgroup v0.0.0-20150612190941-6abd8d71ec01 h1:IeaD1VDVBPlx3viJT9Md8if8IxxJnO+x0JCGb054heg= +github.com/facebookgo/limitgroup v0.0.0-20150612190941-6abd8d71ec01/go.mod h1:ypD5nozFk9vcGw1ATYefw6jHe/jZP++Z15/+VTMcWhc= github.com/facebookgo/muster v0.0.0-20150708232844-fd3d7953fd52 h1:a4DFiKFJiDRGFD1qIcqGLX/WlUMD9dyLSLDt+9QZgt8= +github.com/facebookgo/muster v0.0.0-20150708232844-fd3d7953fd52/go.mod h1:yIquW87NGRw1FU5p5lEkpnt/QxoH5uPAOUlOVkAUuMg= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/foxcpp/go-mockdns v0.0.0-20210729171921-fb145fc6f897 h1:E52jfcE64UG42SwLmrW0QByONfGynWuzBvm86BoB9z8= +github.com/foxcpp/go-mockdns v0.0.0-20210729171921-fb145fc6f897/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= +github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= @@ -72,12 +88,15 @@ github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-rod/rod v0.112.6 h1:zMirUmhsBeshMWyf285BD0UGtGq54HfThLDGSjcP3lU= +github.com/go-rod/rod v0.112.6/go.mod h1:ElViL9ABbcshNQw93+11FrYRH92RRhMKleuILo6+5V0= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= +github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -87,6 +106,7 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/flatbuffers v2.0.8+incompatible h1:ivUb1cGomAB101ZM1T0nOiWz9pSrTMoa9+EiY7igmkM= github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -94,6 +114,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.13.0 h1:y1C7Z3e149OJbOPDBxLYR8ITPz8dTKqQwjErKVHJC8k= github.com/google/go-containerregistry v0.13.0/go.mod h1:J9FQ+eSS4a1aC2GNZxvNpbWhgp0487v+cgiilB4FqDo= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -102,7 +123,9 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 h1:1JYBfzqrWPcCclBwxFCPAou9n+q86mfnu7NAeHfte7A= github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0/go.mod h1:YDZoGHuwE+ov0c8smSH49WLF3F2LaWnYYuDVd+EWrc0= github.com/honeycombio/beeline-go v1.10.0 h1:cUDe555oqvw8oD76BQJ8alk7FP0JZ/M/zXpNvOEDLDc= +github.com/honeycombio/beeline-go v1.10.0/go.mod h1:Zz5WMeQCJzFt2Mvf8t6HC1X8RLskLVR/e8rvcmXB1G8= github.com/honeycombio/libhoney-go v1.16.0 h1:kPpqoz6vbOzgp7jC6SR7SkNj7rua7rgxvznI6M3KdHc= +github.com/honeycombio/libhoney-go v1.16.0/go.mod h1:izP4fbREuZ3vqC4HlCAmPrcPT9gxyxejRjGtCYpmBn0= github.com/in-toto/archivista v0.2.0 h1:FViuHMVVETborvOqlmSYdROY8RmX3CO0V0MOhU/Rl20= github.com/in-toto/archivista v0.2.0/go.mod h1:qt9uN4TkHWUgR5A2wxRqQIBizSl32P2nI2AjESskkr0= github.com/in-toto/attestation v1.0.1 h1:DgX1XuBkryTpj1Piq8AiMK3CMfEcec3Qv6+Ku+uI3WY= @@ -114,6 +137,7 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548 h1:dYTbLf4m0a5u0KLmPfB6mgxbcV7588bOCx79hxa5Sr4= +github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVzF6no3QaDSMLGLEHtHSBSefs+MgcDWnmhmo= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= @@ -121,23 +145,29 @@ github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= +github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/letsencrypt/boulder v0.0.0-20221109233200-85aa52084eaf h1:ndns1qx/5dL43g16EQkPV/i8+b3l5bYQwLeoSBe7tS8= github.com/letsencrypt/boulder v0.0.0-20221109233200-85aa52084eaf/go.mod h1:aGkAgvWY/IUcVFfuly53REpfv5edu25oij+qHRFaraA= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= +github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/open-policy-agent/opa v0.49.2 h1:n8ntRq/yDWy+cmYaqSLrHXmrT3tX8WlK28vjFQdC6W8= github.com/open-policy-agent/opa v0.49.2/go.mod h1:7L3lN5qe8xboRmEHxC5lGjo5KsRMdK+CCLiFoOCP7rU= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -151,12 +181,17 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI= +github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= @@ -167,12 +202,15 @@ github.com/sigstore/sigstore v1.5.2 h1:rvZSPJDH2ysoc8kjW9v4nv1UX3XwSA8y4x6Dk7hA0 github.com/sigstore/sigstore v1.5.2/go.mod h1:wxhp9KoaOpeb1VLKILruD283KJqPSqX+3TuBByVDZ6E= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= +github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spiffe/go-spiffe/v2 v2.1.7 h1:VUkM1yIyg/x8X7u1uXqSRVRCdMdfRIEdFBzpqoeASGk= github.com/spiffe/go-spiffe/v2 v2.1.7/go.mod h1:QJDGdhXllxjxvd5B+2XnhhXB/+rC8gr+lNrtOryiWeE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -191,9 +229,11 @@ github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= @@ -203,8 +243,11 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1: github.com/yashtewari/glob-intersection v0.1.0 h1:6gJvMYQlTDOL3dMsPF6J0+26vwX9MB8/1q3uAdhmTrg= github.com/yashtewari/glob-intersection v0.1.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ= +github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18= github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE= +github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg= github.com/ysmood/leakless v0.8.0 h1:BzLrVoiwxikpgEQR0Lk8NyBN5Cit2b1z+u0mgL4ZJak= +github.com/ysmood/leakless v0.8.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -214,6 +257,7 @@ github.com/zclconf/go-cty v1.12.1/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeW github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs= github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.step.sm/crypto v0.25.2 h1:NgoI3bcNF0iLI+Rwq00brlJyFfMqseLOa8L8No3Daog= go.step.sm/crypto v0.25.2/go.mod h1:4pUEuZ+4OAf2f70RgW5oRv/rJudibcAAWQg5prC3DT8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -258,6 +302,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -328,10 +373,12 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alexcesaro/statsd.v2 v2.0.0 h1:FXkZSCZIH17vLCO5sO2UucTHsH9pc+17F6pl3JVCwMc= +gopkg.in/alexcesaro/statsd.v2 v2.0.0/go.mod h1:i0ubccKGzBVNBpdGV5MocxyA/XlLUJzA7SLonnE4drU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= @@ -356,3 +403,4 @@ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h6 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/run.go b/run.go index dcd52c82..e8f872db 100644 --- a/run.go +++ b/run.go @@ -17,6 +17,7 @@ package witness import ( "bytes" "encoding/json" + "errors" "fmt" "github.com/in-toto/go-witness/attestation" @@ -82,10 +83,22 @@ func Run(stepName string, signer cryptoutil.Signer, opts ...RunOption) (RunResul return result, fmt.Errorf("failed to create attestation context: %w", err) } - if err := runCtx.RunAttestors(); err != nil { + if err = runCtx.RunAttestors(); err != nil { return result, fmt.Errorf("failed to run attestors: %w", err) } + errs := make([]error, 0) + for _, r := range runCtx.CompletedAttestors() { + if r.Error != nil { + errs = append(errs, r.Error) + } + } + + if len(errs) > 0 { + errs := append([]error{errors.New("attestors failed with error messages")}, errs...) + return result, errors.Join(errs...) + } + result.Collection = attestation.NewCollection(ro.stepName, runCtx.CompletedAttestors()) result.SignedEnvelope, err = signCollection(result.Collection, dsse.SignWithSigners(ro.signer), dsse.SignWithTimestampers(ro.timestampers...)) if err != nil { From ed519d1ff0bb2d22196c01d39f46a3f3b86889e2 Mon Sep 17 00:00:00 2001 From: Tom Meadows Date: Sat, 3 Feb 2024 05:31:58 +0100 Subject: [PATCH 08/33] Adding workaround due to failing workflows (#145) adding workaround due to failing workflows Signed-off-by: chaosinthecrd Signed-off-by: John Kjell --- .github/workflows/golangci-lint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 0b6a231c..9789855d 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -43,3 +43,4 @@ jobs: with: version: latest args: --timeout=3m + skip-pkg-cache: true From 04a8ef4f39eff41d177bf58db9718fed757cd116 Mon Sep 17 00:00:00 2001 From: Tom Meadows Date: Sat, 3 Feb 2024 05:55:10 +0100 Subject: [PATCH 09/33] Checking policy signature against cert constraints (#144) * adding logic so policy signature can be checked against constraints * threaded options into policy validation functionary --------- Signed-off-by: chaosinthecrd Signed-off-by: John Kjell Co-authored-by: John Kjell Signed-off-by: John Kjell --- internal/test/util.go | 145 +++++++++++++++++++++++++++++++++++++ policy/policy.go | 30 ++------ policy/step.go | 27 +++++++ verify.go | 91 ++++++++++++++++++++++-- verify_test.go | 162 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 425 insertions(+), 30 deletions(-) create mode 100644 internal/test/util.go create mode 100644 verify_test.go diff --git a/internal/test/util.go b/internal/test/util.go new file mode 100644 index 00000000..bccdc6ff --- /dev/null +++ b/internal/test/util.go @@ -0,0 +1,145 @@ +// Copyright 2024 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "time" + + "github.com/in-toto/go-witness/cryptoutil" +) + +func CreateRsaKey() (*rsa.PrivateKey, *rsa.PublicKey, error) { + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, err + } + + return priv, &priv.PublicKey, nil +} + +func CreateTestKey() (cryptoutil.Signer, cryptoutil.Verifier, []byte, error) { + privKey, _, err := CreateRsaKey() + if err != nil { + return nil, nil, nil, err + } + + signer := cryptoutil.NewRSASigner(privKey, crypto.SHA256) + verifier := cryptoutil.NewRSAVerifier(&privKey.PublicKey, crypto.SHA256) + keyBytes, err := x509.MarshalPKIXPublicKey(&privKey.PublicKey) + if err != nil { + return nil, nil, nil, err + } + + pemBytes := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: keyBytes}) + + return signer, verifier, pemBytes, nil +} + +func CreateCert(priv, pub interface{}, temp, parent *x509.Certificate) (*x509.Certificate, error) { + var err error + temp.SerialNumber, err = rand.Int(rand.Reader, big.NewInt(4294967295)) + if err != nil { + return nil, err + } + + certBytes, err := x509.CreateCertificate(rand.Reader, temp, parent, pub, priv) + if err != nil { + return nil, err + } + + return x509.ParseCertificate(certBytes) +} + +func CreateRoot() (*x509.Certificate, interface{}, error) { + priv, pub, err := CreateRsaKey() + if err != nil { + return nil, nil, err + } + + template := &x509.Certificate{ + DNSNames: []string{"in-toto.io"}, + Subject: pkix.Name{ + Country: []string{"US"}, + Organization: []string{"in-toto"}, + CommonName: "Test Root", + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(24 * time.Hour), + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + BasicConstraintsValid: true, + IsCA: true, + MaxPathLenZero: false, + MaxPathLen: 2, + } + + cert, err := CreateCert(priv, pub, template, template) + return cert, priv, err +} + +func CreateIntermediate(parent *x509.Certificate, parentPriv interface{}) (*x509.Certificate, interface{}, error) { + priv, pub, err := CreateRsaKey() + if err != nil { + return nil, nil, err + } + + template := &x509.Certificate{ + Subject: pkix.Name{ + Country: []string{"US"}, + Organization: []string{"TestifySec"}, + CommonName: "Test Intermediate", + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(24 * time.Hour), + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + BasicConstraintsValid: true, + IsCA: true, + MaxPathLenZero: false, + MaxPathLen: 1, + } + + cert, err := CreateCert(parentPriv, pub, template, parent) + return cert, priv, err +} + +func CreateLeaf(parent *x509.Certificate, parentPriv interface{}) (*x509.Certificate, interface{}, error) { + priv, pub, err := CreateRsaKey() + if err != nil { + return nil, nil, err + } + + template := &x509.Certificate{ + DNSNames: []string{"in-toto.io"}, + Subject: pkix.Name{ + Country: []string{"US"}, + Organization: []string{"In-toto"}, + CommonName: "Test Leaf", + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(24 * time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature, + BasicConstraintsValid: true, + IsCA: false, + } + + cert, err := CreateCert(parentPriv, pub, template, parent) + return cert, priv, err +} diff --git a/policy/policy.go b/policy/policy.go index 3eddcd94..799a2c0b 100644 --- a/policy/policy.go +++ b/policy/policy.go @@ -236,35 +236,13 @@ func (step Step) checkFunctionaries(verifiedStatements []source.VerifiedCollecti } for _, verifier := range verifiedStatement.Verifiers { - verifierID, err := verifier.KeyID() - if err != nil { - log.Debugf("(policy) skipping verifier: could not get key id: %w", err) - continue - } - for _, functionary := range step.Functionaries { - if functionary.PublicKeyID != "" && functionary.PublicKeyID == verifierID { - collections = append(collections, verifiedStatement) - break - } - - x509Verifier, ok := verifier.(*cryptoutil.X509Verifier) - if !ok { - log.Debugf("(policy) skipping verifier: verifier with ID %v is not a public key verifier or a x509 verifier", verifierID) - continue - } - - if len(functionary.CertConstraint.Roots) == 0 { - log.Debugf("(policy) skipping verifier: verifier with ID %v is an x509 verifier, but step %v does not have any truested roots", verifierID, step) - continue - } - - if err := functionary.CertConstraint.Check(x509Verifier, trustBundles); err != nil { - log.Debugf("(policy) skipping verifier: verifier with ID %v doesn't meet certificate constraint: %w", verifierID, err) + if err := functionary.Validate(verifier, trustBundles); err != nil { + log.Debugf("(policy) skipping verifier: %w", err) continue + } else { + collections = append(collections, verifiedStatement) } - - collections = append(collections, verifiedStatement) } } } diff --git a/policy/step.go b/policy/step.go index b1b63ef0..ea451bf8 100644 --- a/policy/step.go +++ b/policy/step.go @@ -19,6 +19,7 @@ import ( "strings" "github.com/in-toto/go-witness/attestation" + "github.com/in-toto/go-witness/cryptoutil" "github.com/in-toto/go-witness/source" ) @@ -80,6 +81,32 @@ type RejectedCollection struct { Reason error } +func (f Functionary) Validate(verifier cryptoutil.Verifier, trustBundles map[string]TrustBundle) error { + verifierID, err := verifier.KeyID() + if err != nil { + return fmt.Errorf("could not get key id: %w", err) + } + + if f.PublicKeyID != "" && f.PublicKeyID == verifierID { + return nil + } + + x509Verifier, ok := verifier.(*cryptoutil.X509Verifier) + if !ok { + return fmt.Errorf("verifier with ID %v is not a public key verifier or a x509 verifier", verifierID) + } + + if len(f.CertConstraint.Roots) == 0 { + return fmt.Errorf("verifier with ID %v is an x509 verifier, but no trusted roots provided in functionary", verifierID) + } + + if err := f.CertConstraint.Check(x509Verifier, trustBundles); err != nil { + return fmt.Errorf("verifier with ID %v doesn't meet certificate constraint: %w", verifierID, err) + } + + return nil +} + // validateAttestations will test each collection against to ensure the expected attestations // appear in the collection as well as that any rego policies pass for the step. func (s Step) validateAttestations(verifiedCollections []source.VerifiedCollection) StepResult { diff --git a/verify.go b/verify.go index a0d2bdab..2aa4c74f 100644 --- a/verify.go +++ b/verify.go @@ -17,12 +17,14 @@ package witness import ( "context" "crypto/x509" + "encoding/base64" "encoding/json" "fmt" "io" "github.com/in-toto/go-witness/cryptoutil" "github.com/in-toto/go-witness/dsse" + "github.com/in-toto/go-witness/log" "github.com/in-toto/go-witness/policy" "github.com/in-toto/go-witness/source" "github.com/in-toto/go-witness/timestamp" @@ -43,6 +45,11 @@ type verifyOptions struct { policyTimestampAuthorities []timestamp.TimestampVerifier policyCARoots []*x509.Certificate policyCAIntermediates []*x509.Certificate + policyCommonName string + policyDNSNames []string + policyEmails []string + policyOrganizations []string + policyURIs []string policyEnvelope dsse.Envelope policyVerifiers []cryptoutil.Verifier collectionSource source.Sourcer @@ -85,22 +92,39 @@ func VerifyWithPolicyCAIntermediates(intermediates []*x509.Certificate) VerifyOp } } +func VerifyWithPolicyCertConstraints(commonName string, dnsNames []string, emails []string, organizations []string, uris []string) VerifyOption { + return func(vo *verifyOptions) { + vo.policyCommonName = commonName + vo.policyDNSNames = dnsNames + vo.policyEmails = emails + vo.policyOrganizations = organizations + vo.policyURIs = uris + } +} + // Verify verifies a set of attestations against a provided policy. The set of attestations that satisfy the policy will be returned // if verifiation is successful. func Verify(ctx context.Context, policyEnvelope dsse.Envelope, policyVerifiers []cryptoutil.Verifier, opts ...VerifyOption) (map[string][]source.VerifiedCollection, error) { vo := verifyOptions{ - policyEnvelope: policyEnvelope, - policyVerifiers: policyVerifiers, + policyEnvelope: policyEnvelope, + policyVerifiers: policyVerifiers, + policyCommonName: "*", + policyDNSNames: []string{"*"}, + policyOrganizations: []string{"*"}, + policyURIs: []string{"*"}, + policyEmails: []string{"*"}, } for _, opt := range opts { opt(&vo) } - if _, err := vo.policyEnvelope.Verify(dsse.VerifyWithVerifiers(vo.policyVerifiers...), dsse.VerifyWithTimestampVerifiers(vo.policyTimestampAuthorities...), dsse.VerifyWithRoots(vo.policyCARoots...), dsse.VerifyWithIntermediates(vo.policyCAIntermediates...)); err != nil { - return nil, fmt.Errorf("could not verify policy: %w", err) + if err := verifyPolicySignature(ctx, vo); err != nil { + return nil, fmt.Errorf("failed to verify policy signature: %w", err) } + log.Info("Policy signature verification passed") + pol := policy.Policy{} if err := json.Unmarshal(vo.policyEnvelope.Payload, &pol); err != nil { return nil, fmt.Errorf("failed to unmarshal policy from envelope: %w", err) @@ -154,3 +178,62 @@ func Verify(ctx context.Context, policyEnvelope dsse.Envelope, policyVerifiers [ return accepted, nil } + +func verifyPolicySignature(ctx context.Context, vo verifyOptions) error { + passedPolicyVerifiers, err := vo.policyEnvelope.Verify(dsse.VerifyWithVerifiers(vo.policyVerifiers...), dsse.VerifyWithTimestampVerifiers(vo.policyTimestampAuthorities...), dsse.VerifyWithRoots(vo.policyCARoots...), dsse.VerifyWithIntermediates(vo.policyCAIntermediates...)) + if err != nil { + return fmt.Errorf("could not verify policy: %w", err) + } + + var passed bool + for _, verifier := range passedPolicyVerifiers { + kid, err := verifier.Verifier.KeyID() + if err != nil { + return fmt.Errorf("could not get verifier key id: %w", err) + } + + var f policy.Functionary + trustBundle := make(map[string]policy.TrustBundle) + if _, ok := verifier.Verifier.(*cryptoutil.X509Verifier); ok { + rootIDs := make([]string, 0) + for _, root := range vo.policyCARoots { + id := base64.StdEncoding.EncodeToString(root.Raw) + rootIDs = append(rootIDs, id) + trustBundle[id] = policy.TrustBundle{ + Root: root, + } + } + + f = policy.Functionary{ + Type: "root", + CertConstraint: policy.CertConstraint{ + Roots: rootIDs, + CommonName: vo.policyCommonName, + URIs: vo.policyURIs, + Emails: vo.policyEmails, + Organizations: vo.policyOrganizations, + DNSNames: vo.policyDNSNames, + }, + } + + } else { + f = policy.Functionary{ + Type: "key", + PublicKeyID: kid, + } + } + + err = f.Validate(verifier.Verifier, trustBundle) + if err != nil { + log.Debugf("Policy Verifier %s failed failed to match supplied constraints: %w, continuing...", kid, err) + continue + } + passed = true + } + + if !passed { + return fmt.Errorf("no policy verifiers passed verification") + } else { + return nil + } +} diff --git a/verify_test.go b/verify_test.go new file mode 100644 index 00000000..cfb32d5d --- /dev/null +++ b/verify_test.go @@ -0,0 +1,162 @@ +// Copyright 2024 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package witness + +import ( + "bytes" + "context" + "crypto/x509" + "fmt" + "testing" + "time" + + "github.com/in-toto/go-witness/cryptoutil" + "github.com/in-toto/go-witness/dsse" + "github.com/in-toto/go-witness/internal/test" + "github.com/in-toto/go-witness/intoto" + "github.com/in-toto/go-witness/timestamp" +) + +func TestVerifyPolicySignature(t *testing.T) { + // we dont care about the content of th envelope for this test + rsaSigner, rsaVerifier, _, err := test.CreateTestKey() + if err != nil { + t.Fatal(err) + } + + badRootCert, _, err := test.CreateRoot() + if err != nil { + t.Fatal(err) + } + + rootCert, key, err := test.CreateRoot() + if err != nil { + t.Fatal(err) + } + + leafCert, leafPriv, err := test.CreateLeaf(rootCert, key) + if err != nil { + t.Fatal(err) + } + + x509Signer, err := cryptoutil.NewSigner(leafPriv, cryptoutil.SignWithCertificate(leafCert)) + if err != nil { + t.Fatal(err) + } + + timestampers := []timestamp.FakeTimestamper{ + {T: time.Now()}, + {T: time.Now().Add(12 * time.Hour)}, + } + + // Define the test cases. + tests := []struct { + name string + signer cryptoutil.Signer + verifier cryptoutil.Verifier + timestampers []timestamp.FakeTimestamper + Roots []*x509.Certificate + Intermediates []*x509.Certificate + certConstraints VerifyOption + wantErr bool + }{ + { + name: "valid rsa signature", + signer: rsaSigner, + verifier: rsaVerifier, + // passing in timestampers to ensure that it is ignored + timestampers: timestampers, + wantErr: false, + }, + { + name: "invalid rsa signature", + signer: rsaSigner, + Roots: []*x509.Certificate{rootCert}, + wantErr: true, + }, + { + name: "valid x509 signature", + signer: x509Signer, + // We're going to pass in to ensure that it is ignored + Roots: []*x509.Certificate{rootCert}, + wantErr: false, + }, + { + name: "valid x509 signature w/ constraints", + signer: x509Signer, + // We're going to pass in to ensure that it is ignored + Roots: []*x509.Certificate{rootCert}, + certConstraints: VerifyWithPolicyCertConstraints(leafCert.Subject.CommonName, leafCert.DNSNames, []string{"*"}, []string{"*"}, []string{"*"}), + timestampers: timestampers, + wantErr: false, + }, + { + name: "valid x509 signature w/ bad constraints", + signer: x509Signer, + // We're going to pass in to ensure that it is ignored + Roots: []*x509.Certificate{rootCert}, + certConstraints: VerifyWithPolicyCertConstraints("foo", []string{"bar"}, []string{"baz"}, []string{"qux"}, []string{"quux"}), + wantErr: true, + }, + { + name: "unknown root", + signer: x509Signer, + // We're going to pass in to ensure that it is ignored + Roots: []*x509.Certificate{badRootCert}, + wantErr: true, + }, + } + + for _, tt := range tests { + var ts []timestamp.Timestamper + for _, t := range tt.timestampers { + ts = append(ts, t) + } + + env, err := dsse.Sign(intoto.PayloadType, bytes.NewReader([]byte("this is some test data")), dsse.SignWithTimestampers(ts...), dsse.SignWithSigners(tt.signer)) + if err != nil { + t.Fatal(err) + } + + var tv []timestamp.TimestampVerifier + for _, t := range tt.timestampers { + tv = append(tv, t) + } + + vo := verifyOptions{ + policyEnvelope: env, + policyVerifiers: []cryptoutil.Verifier{tt.verifier}, + policyCARoots: tt.Roots, + policyTimestampAuthorities: tv, + policyCommonName: "*", + policyDNSNames: []string{"*"}, + policyOrganizations: []string{"*"}, + policyURIs: []string{"*"}, + policyEmails: []string{"*"}, + } + + if tt.certConstraints != nil { + tt.certConstraints(&vo) + } + + err = verifyPolicySignature(context.TODO(), vo) + if err != nil && !tt.wantErr { + t.Errorf("testName = %s, error = %v, wantErr %v", tt.name, err, tt.wantErr) + } else { + fmt.Printf("test %s passed\n", tt.name) + } + + } +} From 3cff01c4989d2dff029ed3f46a04f9a45339b43a Mon Sep 17 00:00:00 2001 From: StepSecurity Bot Date: Fri, 2 Feb 2024 21:01:28 -0800 Subject: [PATCH 10/33] [StepSecurity] ci: Harden GitHub Actions (#148) Signed-off-by: StepSecurity Bot Signed-off-by: John Kjell --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 216dbd20..96391b33 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,6 +62,6 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - name: Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1 From 3d7747b85a655afb665185031c7e21e3228141e7 Mon Sep 17 00:00:00 2001 From: John Kjell Date: Sat, 3 Feb 2024 01:18:23 -0600 Subject: [PATCH 11/33] Add import for init and export variables Signed-off-by: John Kjell --- attestation/link/link.go | 16 ++++++++-------- imports.go | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/attestation/link/link.go b/attestation/link/link.go index 0f8f7d69..1f2de0e9 100644 --- a/attestation/link/link.go +++ b/attestation/link/link.go @@ -43,13 +43,13 @@ func init() { } type Link struct { - pbLink v0.Link - products map[string]attestation.Product + PbLink v0.Link `json:"Pblink"` + Products map[string]attestation.Product `json:"products"` } func New() *Link { return &Link{ - pbLink: v0.Link{ + PbLink: v0.Link{ Name: Name, }, } @@ -70,23 +70,23 @@ func (l *Link) RunType() attestation.RunType { func (l *Link) Attest(ctx *attestation.AttestationContext) error { for name, digestSet := range ctx.Materials() { digests, _ := digestSet.ToNameMap() - l.pbLink.Materials = append(l.pbLink.Materials, &v1.ResourceDescriptor{ + l.PbLink.Materials = append(l.PbLink.Materials, &v1.ResourceDescriptor{ Name: name, Digest: digests, }) } - l.products = ctx.Products() + l.Products = ctx.Products() for _, attestor := range ctx.CompletedAttestors() { switch name := attestor.Attestor.Name(); name { case commandrun.Name: - l.pbLink.Command = attestor.Attestor.(*commandrun.CommandRun).Cmd + l.PbLink.Command = attestor.Attestor.(*commandrun.CommandRun).Cmd case material.Name: mats := attestor.Attestor.(*material.Attestor).Materials() for name, digestSet := range mats { digests, _ := digestSet.ToNameMap() - l.pbLink.Materials = append(l.pbLink.Materials, &v1.ResourceDescriptor{ + l.PbLink.Materials = append(l.PbLink.Materials, &v1.ResourceDescriptor{ Name: name, Digest: digests, }) @@ -99,7 +99,7 @@ func (l *Link) Attest(ctx *attestation.AttestationContext) error { } var err error - l.pbLink.Environment, err = structpb.NewStruct(pbEnvs) + l.PbLink.Environment, err = structpb.NewStruct(pbEnvs) if err != nil { return err } diff --git a/imports.go b/imports.go index 3736edf3..0d1d1bf5 100644 --- a/imports.go +++ b/imports.go @@ -25,6 +25,7 @@ import ( _ "github.com/in-toto/go-witness/attestation/github" _ "github.com/in-toto/go-witness/attestation/gitlab" _ "github.com/in-toto/go-witness/attestation/jwt" + _ "github.com/in-toto/go-witness/attestation/link" _ "github.com/in-toto/go-witness/attestation/maven" _ "github.com/in-toto/go-witness/attestation/oci" _ "github.com/in-toto/go-witness/attestation/sarif" From 7a1a1f7bd706431fe9d682b0146c8d48a5dc738d Mon Sep 17 00:00:00 2001 From: John Kjell Date: Sun, 4 Feb 2024 07:57:25 -0600 Subject: [PATCH 12/33] Add mulitple results to run to allow exporting attestors to indivudal files Signed-off-by: John Kjell --- attestation/link/link.go | 71 ++++++++++++++++++++++++---------------- run.go | 35 +++++++++++++++++--- 2 files changed, 73 insertions(+), 33 deletions(-) diff --git a/attestation/link/link.go b/attestation/link/link.go index 1f2de0e9..0f5e7026 100644 --- a/attestation/link/link.go +++ b/attestation/link/link.go @@ -15,25 +15,30 @@ package link import ( + "encoding/json" + "fmt" + v0 "github.com/in-toto/attestation/go/predicates/link/v0" - v1 "github.com/in-toto/attestation/go/v1" "github.com/in-toto/go-witness/attestation" "github.com/in-toto/go-witness/attestation/commandrun" "github.com/in-toto/go-witness/attestation/environment" "github.com/in-toto/go-witness/attestation/material" + "github.com/in-toto/go-witness/attestation/product" + "github.com/in-toto/go-witness/cryptoutil" "google.golang.org/protobuf/types/known/structpb" ) const ( Name = "link" - Type = "https://witness.dev/attestations/link/v0.1" + Type = "https://in-toto.io/attestation/link/v0.3" RunType = attestation.PostProductRunType ) // This is a hacky way to create a compile time error in case the attestor // doesn't implement the expected interfaces. var ( - _ attestation.Attestor = &Link{} + _ attestation.Attestor = &Link{} + _ attestation.Subjecter = &Link{} ) func init() { @@ -43,16 +48,12 @@ func init() { } type Link struct { - PbLink v0.Link `json:"Pblink"` - Products map[string]attestation.Product `json:"products"` + PbLink v0.Link + products map[string]attestation.Product } func New() *Link { - return &Link{ - PbLink: v0.Link{ - Name: Name, - }, - } + return &Link{} } func (l *Link) Name() string { @@ -68,29 +69,20 @@ func (l *Link) RunType() attestation.RunType { } func (l *Link) Attest(ctx *attestation.AttestationContext) error { - for name, digestSet := range ctx.Materials() { - digests, _ := digestSet.ToNameMap() - l.PbLink.Materials = append(l.PbLink.Materials, &v1.ResourceDescriptor{ - Name: name, - Digest: digests, - }) - } - - l.Products = ctx.Products() - + l.PbLink.Name = "stepNameHere" for _, attestor := range ctx.CompletedAttestors() { switch name := attestor.Attestor.Name(); name { case commandrun.Name: l.PbLink.Command = attestor.Attestor.(*commandrun.CommandRun).Cmd case material.Name: - mats := attestor.Attestor.(*material.Attestor).Materials() - for name, digestSet := range mats { - digests, _ := digestSet.ToNameMap() - l.PbLink.Materials = append(l.PbLink.Materials, &v1.ResourceDescriptor{ - Name: name, - Digest: digests, - }) - } + // mats := attestor.Attestor.(*material.Attestor).Materials() + // for name, digestSet := range mats { + // digests, _ := digestSet.ToNameMap() + // l.Materials = append(l.Materials, &v1.ResourceDescriptor{ + // Name: name, + // Digest: digests, + // }) + // } case environment.Name: envs := attestor.Attestor.(*environment.Attestor).Variables pbEnvs := make(map[string]interface{}, len(envs)) @@ -103,7 +95,30 @@ func (l *Link) Attest(ctx *attestation.AttestationContext) error { if err != nil { return err } + case product.Name: + l.products = attestor.Attestor.(*product.Attestor).Products() } } return nil } + +func (l *Link) MarshalJSON() ([]byte, error) { + return json.Marshal(l.PbLink) +} + +func (l *Link) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, &l.PbLink); err != nil { + return err + } + + return nil +} + +func (l *Link) Subjects() map[string]cryptoutil.DigestSet { + subjects := make(map[string]cryptoutil.DigestSet) + for productName, product := range l.products { + subjects[fmt.Sprintf("file:%v", productName)] = product.Digest + } + + return subjects +} diff --git a/run.go b/run.go index e8f872db..dbe7518d 100644 --- a/run.go +++ b/run.go @@ -23,6 +23,7 @@ import ( "github.com/in-toto/go-witness/attestation" "github.com/in-toto/go-witness/attestation/environment" "github.com/in-toto/go-witness/attestation/git" + "github.com/in-toto/go-witness/attestation/link" "github.com/in-toto/go-witness/cryptoutil" "github.com/in-toto/go-witness/dsse" "github.com/in-toto/go-witness/intoto" @@ -60,9 +61,22 @@ func RunWithTimestampers(ts ...timestamp.Timestamper) RunOption { type RunResult struct { Collection attestation.Collection SignedEnvelope dsse.Envelope + AttestorName string } func Run(stepName string, signer cryptoutil.Signer, opts ...RunOption) (RunResult, error) { + results, err := run(stepName, signer, []string{}, opts) + if len(results) > 1 { + return RunResult{}, errors.New("expected a single result, got multiple") + } + return results[0], err +} + +func ExportedRun(stepName string, signer cryptoutil.Signer, opts ...RunOption) ([]RunResult, error) { + return run(stepName, signer, []string{"link"}, opts) +} + +func run(stepName string, signer cryptoutil.Signer, exportAtt []string, opts []RunOption) ([]RunResult, error) { ro := runOptions{ stepName: stepName, signer: signer, @@ -73,7 +87,7 @@ func Run(stepName string, signer cryptoutil.Signer, opts ...RunOption) (RunResul opt(&ro) } - result := RunResult{} + result := []RunResult{} if err := validateRunOpts(ro); err != nil { return result, err } @@ -91,6 +105,16 @@ func Run(stepName string, signer cryptoutil.Signer, opts ...RunOption) (RunResul for _, r := range runCtx.CompletedAttestors() { if r.Error != nil { errs = append(errs, r.Error) + } else if r.Attestor.Name() == link.Name { + r.Attestor.(*link.Link).PbLink.Name = ro.stepName + + if subjecter, ok := r.Attestor.(attestation.Subjecter); ok { + linkEnvelope, err := createAndSignEnvelope(r.Attestor, r.Attestor.Type(), subjecter.Subjects(), dsse.SignWithSigners(ro.signer), dsse.SignWithTimestampers(ro.timestampers...)) + if err != nil { + return result, fmt.Errorf("failed to sign envelope: %w", err) + } + result = append(result, RunResult{SignedEnvelope: linkEnvelope, AttestorName: r.Attestor.Name()}) + } } } @@ -99,8 +123,9 @@ func Run(stepName string, signer cryptoutil.Signer, opts ...RunOption) (RunResul return result, errors.Join(errs...) } - result.Collection = attestation.NewCollection(ro.stepName, runCtx.CompletedAttestors()) - result.SignedEnvelope, err = signCollection(result.Collection, dsse.SignWithSigners(ro.signer), dsse.SignWithTimestampers(ro.timestampers...)) + var collectionResult RunResult + collectionResult.Collection = attestation.NewCollection(ro.stepName, runCtx.CompletedAttestors()) + collectionResult.SignedEnvelope, err = createAndSignEnvelope(collectionResult.Collection, attestation.CollectionType, collectionResult.Collection.Subjects(), dsse.SignWithSigners(ro.signer), dsse.SignWithTimestampers(ro.timestampers...)) if err != nil { return result, fmt.Errorf("failed to sign collection: %w", err) } @@ -120,13 +145,13 @@ func validateRunOpts(ro runOptions) error { return nil } -func signCollection(collection attestation.Collection, opts ...dsse.SignOption) (dsse.Envelope, error) { +func createAndSignEnvelope(collection interface{}, predType string, subjects map[string]cryptoutil.DigestSet, opts ...dsse.SignOption) (dsse.Envelope, error) { data, err := json.Marshal(&collection) if err != nil { return dsse.Envelope{}, err } - stmt, err := intoto.NewStatement(attestation.CollectionType, data, collection.Subjects()) + stmt, err := intoto.NewStatement(predType, data, subjects) if err != nil { return dsse.Envelope{}, err } From fb27f554eb156ea353522859d13819b21aed472d Mon Sep 17 00:00:00 2001 From: John Kjell Date: Sun, 4 Feb 2024 08:08:38 -0600 Subject: [PATCH 13/33] Add collection to result array Signed-off-by: John Kjell --- run.go | 1 + 1 file changed, 1 insertion(+) diff --git a/run.go b/run.go index dbe7518d..86a4994d 100644 --- a/run.go +++ b/run.go @@ -129,6 +129,7 @@ func run(stepName string, signer cryptoutil.Signer, exportAtt []string, opts []R if err != nil { return result, fmt.Errorf("failed to sign collection: %w", err) } + result = append(result, collectionResult) return result, nil } From af0470ff5fca2837a95f1a58e8902404acb34a64 Mon Sep 17 00:00:00 2001 From: John Kjell Date: Mon, 5 Feb 2024 06:28:06 -0600 Subject: [PATCH 14/33] Replace export parameters in run with attestor option Signed-off-by: John Kjell --- attestation/link/link.go | 52 +++++++++++++++++++++++++++++++--------- run.go | 24 ++++++++++++------- 2 files changed, 56 insertions(+), 20 deletions(-) diff --git a/attestation/link/link.go b/attestation/link/link.go index 0f5e7026..61805bdd 100644 --- a/attestation/link/link.go +++ b/attestation/link/link.go @@ -19,12 +19,14 @@ import ( "fmt" v0 "github.com/in-toto/attestation/go/predicates/link/v0" + v1 "github.com/in-toto/attestation/go/v1" "github.com/in-toto/go-witness/attestation" "github.com/in-toto/go-witness/attestation/commandrun" "github.com/in-toto/go-witness/attestation/environment" "github.com/in-toto/go-witness/attestation/material" "github.com/in-toto/go-witness/attestation/product" "github.com/in-toto/go-witness/cryptoutil" + "github.com/in-toto/go-witness/registry" "google.golang.org/protobuf/types/known/structpb" ) @@ -32,6 +34,8 @@ const ( Name = "link" Type = "https://in-toto.io/attestation/link/v0.3" RunType = attestation.PostProductRunType + + defaultExport = false ) // This is a hacky way to create a compile time error in case the attestor @@ -42,14 +46,36 @@ var ( ) func init() { - attestation.RegisterAttestation(Name, Type, RunType, func() attestation.Attestor { - return New() - }) + attestation.RegisterAttestation(Name, Type, RunType, + func() attestation.Attestor { return New() }, + registry.BoolConfigOption( + "export", + "Export the link attestation to its own file", + defaultExport, + func(a attestation.Attestor, export bool) (attestation.Attestor, error) { + linkAttestor, ok := a.(*Link) + if !ok { + return a, fmt.Errorf("unexpected attestor type: %T is not a link attestor", a) + } + WithExport(export)(linkAttestor) + return linkAttestor, nil + }, + ), + ) +} + +type Option func(*Link) + +func WithExport(export bool) Option { + return func(l *Link) { + l.export = export + } } type Link struct { PbLink v0.Link products map[string]attestation.Product + export bool } func New() *Link { @@ -68,6 +94,10 @@ func (l *Link) RunType() attestation.RunType { return RunType } +func (l *Link) Export() bool { + return l.export +} + func (l *Link) Attest(ctx *attestation.AttestationContext) error { l.PbLink.Name = "stepNameHere" for _, attestor := range ctx.CompletedAttestors() { @@ -75,14 +105,14 @@ func (l *Link) Attest(ctx *attestation.AttestationContext) error { case commandrun.Name: l.PbLink.Command = attestor.Attestor.(*commandrun.CommandRun).Cmd case material.Name: - // mats := attestor.Attestor.(*material.Attestor).Materials() - // for name, digestSet := range mats { - // digests, _ := digestSet.ToNameMap() - // l.Materials = append(l.Materials, &v1.ResourceDescriptor{ - // Name: name, - // Digest: digests, - // }) - // } + mats := attestor.Attestor.(*material.Attestor).Materials() + for name, digestSet := range mats { + digests, _ := digestSet.ToNameMap() + l.PbLink.Materials = append(l.PbLink.Materials, &v1.ResourceDescriptor{ + Name: name, + Digest: digests, + }) + } case environment.Name: envs := attestor.Attestor.(*environment.Attestor).Variables pbEnvs := make(map[string]interface{}, len(envs)) diff --git a/run.go b/run.go index 86a4994d..9cfdffb3 100644 --- a/run.go +++ b/run.go @@ -64,19 +64,21 @@ type RunResult struct { AttestorName string } +// Should this be deprecated? +// Deprecated: Use RunWithExports instead func Run(stepName string, signer cryptoutil.Signer, opts ...RunOption) (RunResult, error) { - results, err := run(stepName, signer, []string{}, opts) + results, err := run(stepName, signer, opts) if len(results) > 1 { return RunResult{}, errors.New("expected a single result, got multiple") } return results[0], err } -func ExportedRun(stepName string, signer cryptoutil.Signer, opts ...RunOption) ([]RunResult, error) { - return run(stepName, signer, []string{"link"}, opts) +func RunWithExports(stepName string, signer cryptoutil.Signer, opts ...RunOption) ([]RunResult, error) { + return run(stepName, signer, opts) } -func run(stepName string, signer cryptoutil.Signer, exportAtt []string, opts []RunOption) ([]RunResult, error) { +func run(stepName string, signer cryptoutil.Signer, opts []RunOption) ([]RunResult, error) { ro := runOptions{ stepName: stepName, signer: signer, @@ -106,14 +108,18 @@ func run(stepName string, signer cryptoutil.Signer, exportAtt []string, opts []R if r.Error != nil { errs = append(errs, r.Error) } else if r.Attestor.Name() == link.Name { + // TODO: Find a better way to set stepName r.Attestor.(*link.Link).PbLink.Name = ro.stepName - if subjecter, ok := r.Attestor.(attestation.Subjecter); ok { - linkEnvelope, err := createAndSignEnvelope(r.Attestor, r.Attestor.Type(), subjecter.Subjects(), dsse.SignWithSigners(ro.signer), dsse.SignWithTimestampers(ro.timestampers...)) - if err != nil { - return result, fmt.Errorf("failed to sign envelope: %w", err) + // TODO: Add Exporter interface to attestors + if r.Attestor.(*link.Link).Export() { + if subjecter, ok := r.Attestor.(attestation.Subjecter); ok { + linkEnvelope, err := createAndSignEnvelope(r.Attestor, r.Attestor.Type(), subjecter.Subjects(), dsse.SignWithSigners(ro.signer), dsse.SignWithTimestampers(ro.timestampers...)) + if err != nil { + return result, fmt.Errorf("failed to sign envelope: %w", err) + } + result = append(result, RunResult{SignedEnvelope: linkEnvelope, AttestorName: r.Attestor.Name()}) } - result = append(result, RunResult{SignedEnvelope: linkEnvelope, AttestorName: r.Attestor.Name()}) } } } From 8e2aaa40daefb823843a9ee018cee38f666c7820 Mon Sep 17 00:00:00 2001 From: John Kjell Date: Mon, 5 Feb 2024 08:20:49 -0600 Subject: [PATCH 15/33] Fix golang lint isues Signed-off-by: John Kjell --- attestation/link/link.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/attestation/link/link.go b/attestation/link/link.go index 61805bdd..a068f04a 100644 --- a/attestation/link/link.go +++ b/attestation/link/link.go @@ -133,7 +133,7 @@ func (l *Link) Attest(ctx *attestation.AttestationContext) error { } func (l *Link) MarshalJSON() ([]byte, error) { - return json.Marshal(l.PbLink) + return json.Marshal(&l.PbLink) } func (l *Link) UnmarshalJSON(data []byte) error { From 62057c36736a1ea7eecd255e1c33fe644c707bc7 Mon Sep 17 00:00:00 2001 From: John Kjell Date: Mon, 12 Feb 2024 09:51:47 -0600 Subject: [PATCH 16/33] Update link attestor testing Signed-off-by: John Kjell --- .gitignore | 2 + attestation/link/link_test.go | 177 ++++++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+) diff --git a/.gitignore b/.gitignore index a8e05fde..c65f66d8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ log sarif-report.json test/log .idea/ +.vscode +profile.cov diff --git a/attestation/link/link_test.go b/attestation/link/link_test.go index 5b8a8be1..1165ed5a 100644 --- a/attestation/link/link_test.go +++ b/attestation/link/link_test.go @@ -13,3 +13,180 @@ // limitations under the License. package link + +import ( + "testing" + + "github.com/in-toto/go-witness/attestation" + "github.com/in-toto/go-witness/cryptoutil" +) + +func TestName(t *testing.T) { + link := New() + if link.Name() != Name { + t.Errorf("expected %s, got %s", Name, link.Name()) + } +} + +func TestType(t *testing.T) { + link := New() + if link.Type() != Type { + t.Errorf("expected %s, got %s", Type, link.Type()) + } +} + +func TestRunType(t *testing.T) { + link := New() + if link.RunType() != RunType { + t.Errorf("expected %s, got %s", RunType, link.RunType()) + } +} + +func TestExport(t *testing.T) { + link := New() + if link.export != defaultExport { + t.Errorf("expected %t, got %t", defaultExport, link.export) + } + + WithExport(true)(link) + if !link.export { + t.Errorf("expected %t, got %t", true, link.export) + } + + if link.Export() != true { + t.Errorf("expected %t, got %t", true, link.Export()) + } +} + +func TestUnmarshalJSON(t *testing.T) { + link := New() + if err := link.UnmarshalJSON([]byte(testLinkJSON)); err != nil { + t.Errorf("unexpected error: %s", err) + } +} + +func TestUnmarshalBadJSON(t *testing.T) { + link := New() + if err := link.UnmarshalJSON([]byte("}")); err == nil { + t.Error("Expected error") + } +} + +func TestMarshalJSON(t *testing.T) { + link := New() + if err := link.UnmarshalJSON([]byte(testLinkJSON)); err != nil { + t.Errorf("unexpected error: %s", err) + } + + _, err := link.MarshalJSON() + if err != nil { + t.Errorf("unexpected error: %s", err) + } +} + +func TestAttest(t *testing.T) { + link := New() + // var attestorData []attestation.CompletedAttestor + // ctx := attestation.AttestationContext{ + // completedAttestors: attestorData, + // } + + if err := link.Attest(&attestation.AttestationContext{}); err != nil { + t.Errorf("unexpected error: %s", err) + } +} + +func setupContext(t *testing.T) *attestation.AttestationContext { + ctx := attestation.AttestationContext{} + ctx.CompletedAttestors() + + return &ctx + +} + +func TestSubjects(t *testing.T) { + link := setupLink(t) + + subjects := link.Subjects() + + if len(subjects) != 1 { + t.Errorf("expected 1 subjects, got %d", len(subjects)) + } + + digests := subjects["file:test.txt"] + nameMap, err := digests.ToNameMap() + if err != nil { + t.Errorf("unexpected error: %s", err) + } + + if len(nameMap) != 1 { + t.Errorf("expected 1 digest found, got %d", len(nameMap)) + } + + if nameMap["sha256"] != "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" { + t.Errorf("expected e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855, got %s", nameMap["sha256"]) + } +} + +func setupLink(t *testing.T) *Link { + link := New() + link.UnmarshalJSON([]byte(testLinkJSON)) + + link.products = make(map[string]attestation.Product) + digestsByName := make(map[string]string) + digestsByName["sha256"] = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + digestSet, err := cryptoutil.NewDigestSet(digestsByName) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + link.products["test.txt"] = attestation.Product{ + MimeType: "text/plain", + Digest: digestSet, + } + + return link +} +func TestRegistration(t *testing.T) { + registrations := attestation.RegistrationEntries() + + var found bool + for _, registration := range registrations { + if registration.Name == Name { + found = true + } + } + + if !found { + t.Errorf("expected %s to be registered", Name) + } + +} + +const testLinkJSON = ` +{ + "name": "test", + "command": [ + "touch", + "test.txt" + ], + "materials": [ + { + "name": "test1", + "digest": { + "sha256": "a53d0741798b287c6dd7afa64aee473f305e65d3f49463bb9d7408ec3b12bf5f" + } + }, + { + "name": "test2", + "digest": { + "sha256": "a53d0741798b287c6dd7afa64aee473f305e65d3f49463bb9d7408ec3b12bf5f" + } + } + ], + "environment": { + "COLORFGBG": "7;0", + "COLORTERM": "truecolor" + } +} +` From b11d528f80c330d3ce66b9270ce1986b3e1f4826 Mon Sep 17 00:00:00 2001 From: John Kjell Date: Sat, 23 Mar 2024 14:33:42 +0100 Subject: [PATCH 17/33] Add SLSA attestor Signed-off-by: John Kjell --- attestation/git/git.go | 10 ++ attestation/slsa/slsa.go | 199 ++++++++++++++++++++++++++++++++++ attestation/slsa/slsa_test.go | 192 ++++++++++++++++++++++++++++++++ imports.go | 1 + run.go | 12 ++ 5 files changed, 414 insertions(+) create mode 100644 attestation/slsa/slsa.go create mode 100644 attestation/slsa/slsa_test.go diff --git a/attestation/git/git.go b/attestation/git/git.go index cbcc189c..1e3e8a54 100644 --- a/attestation/git/git.go +++ b/attestation/git/git.go @@ -75,6 +75,7 @@ type Attestor struct { ParentHashes []string `json:"parenthashes,omitempty"` TreeHash string `json:"treehash,omitempty"` Refs []string `json:"refs,omitempty"` + Remotes []string `json:"remotes,omitempty"` Tags []Tag `json:"tags,omitempty"` } @@ -125,6 +126,15 @@ func (a *Attestor) Attest(ctx *attestation.AttestationContext) error { }: commit.Hash.String(), } + remotes, err := repo.Remotes() + if err != nil { + return err + } + + for _, remote := range remotes { + a.Remotes = append(a.Remotes, remote.Config().URLs...) + } + //get all the refs for the repo refs, err := repo.References() if err != nil { diff --git a/attestation/slsa/slsa.go b/attestation/slsa/slsa.go new file mode 100644 index 00000000..c89a580b --- /dev/null +++ b/attestation/slsa/slsa.go @@ -0,0 +1,199 @@ +// Copyright 2024 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package slsa + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + prov "github.com/in-toto/attestation/go/predicates/provenance/v1" + v1 "github.com/in-toto/attestation/go/v1" + "github.com/in-toto/go-witness/attestation" + "github.com/in-toto/go-witness/attestation/commandrun" + "github.com/in-toto/go-witness/attestation/environment" + "github.com/in-toto/go-witness/attestation/git" + "github.com/in-toto/go-witness/attestation/material" + "github.com/in-toto/go-witness/attestation/product" + "github.com/in-toto/go-witness/cryptoutil" + "github.com/in-toto/go-witness/registry" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" +) + +const ( + Name = "slsa" + Type = "https://slsa.dev/provenance/v1.0" + RunType = attestation.PostProductRunType + + defaultExport = false +) + +// This is a hacky way to create a compile time error in case the attestor +// doesn't implement the expected interfaces. +var ( + _ attestation.Attestor = &Provenance{} + _ attestation.Subjecter = &Provenance{} +) + +func init() { + attestation.RegisterAttestation(Name, Type, RunType, + func() attestation.Attestor { return New() }, + registry.BoolConfigOption( + "export", + "Export the SLSA provenance attestation to its own file", + defaultExport, + func(a attestation.Attestor, export bool) (attestation.Attestor, error) { + slsaAttestor, ok := a.(*Provenance) + if !ok { + return a, fmt.Errorf("unexpected attestor type: %T is not a SLSA provenance attestor", a) + } + WithExport(export)(slsaAttestor) + return slsaAttestor, nil + }, + ), + ) +} + +type Option func(*Provenance) + +func WithExport(export bool) Option { + return func(l *Provenance) { + l.export = export + } +} + +type Provenance struct { + PbProvenance prov.Provenance + products map[string]attestation.Product + export bool +} + +func New() *Provenance { + return &Provenance{} +} + +func (l *Provenance) Name() string { + return Name +} + +func (l *Provenance) Type() string { + return Type +} + +func (l *Provenance) RunType() attestation.RunType { + return RunType +} + +func (l *Provenance) Export() bool { + return l.export +} + +func (l *Provenance) Attest(ctx *attestation.AttestationContext) error { + builder := prov.Builder{} + metadata := prov.BuildMetadata{} + l.PbProvenance.BuildDefinition = &prov.BuildDefinition{} + l.PbProvenance.RunDetails = &prov.RunDetails{Builder: &builder, Metadata: &metadata} + + l.PbProvenance.BuildDefinition.BuildType = "https://witness.dev/slsa-build@v0.1" + l.PbProvenance.RunDetails.Builder.Id = "https://witness.dev/witness-github-action@v0.1" + l.PbProvenance.RunDetails.Metadata.InvocationId = "gha-workflow-ref" + + internalParamaters := make(map[string]interface{}) + + for _, attestor := range ctx.CompletedAttestors() { + switch name := attestor.Attestor.Name(); name { + case git.Name: + digestSet := attestor.Attestor.(*git.Attestor).CommitDigest + remotes := attestor.Attestor.(*git.Attestor).Remotes + digests, _ := digestSet.ToNameMap() + + for _, remote := range remotes { + l.PbProvenance.BuildDefinition.ResolvedDependencies = append( + l.PbProvenance.BuildDefinition.ResolvedDependencies, + &v1.ResourceDescriptor{ + Name: remote, + Digest: digests, + }) + } + + case commandrun.Name: + var err error + ep := make(map[string]interface{}) + ep["command"] = strings.Join(attestor.Attestor.(*commandrun.CommandRun).Cmd, " ") + l.PbProvenance.BuildDefinition.ExternalParameters, err = structpb.NewStruct(ep) + if err != nil { + return err + } + // We have start and finish time at the collection level, how do we access it here? + l.PbProvenance.RunDetails.Metadata.StartedOn = timestamppb.New(time.Now()) + l.PbProvenance.RunDetails.Metadata.FinishedOn = timestamppb.New(time.Now()) + + case material.Name: + mats := attestor.Attestor.(*material.Attestor).Materials() + for name, digestSet := range mats { + digests, _ := digestSet.ToNameMap() + l.PbProvenance.BuildDefinition.ResolvedDependencies = append( + l.PbProvenance.BuildDefinition.ResolvedDependencies, + &v1.ResourceDescriptor{ + Name: name, + Digest: digests, + }) + } + + case environment.Name: + envs := attestor.Attestor.(*environment.Attestor).Variables + pbEnvs := make(map[string]interface{}, len(envs)) + for name, value := range envs { + pbEnvs[name] = value + } + + internalParamaters["env"] = pbEnvs + + case product.Name: + l.products = attestor.Attestor.(*product.Attestor).Products() + } + } + + var err error + l.PbProvenance.BuildDefinition.InternalParameters, err = structpb.NewStruct(internalParamaters) + if err != nil { + return err + } + + return nil +} + +func (l *Provenance) MarshalJSON() ([]byte, error) { + return json.Marshal(&l.PbProvenance) +} + +func (l *Provenance) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, &l.PbProvenance); err != nil { + return err + } + + return nil +} + +func (l *Provenance) Subjects() map[string]cryptoutil.DigestSet { + subjects := make(map[string]cryptoutil.DigestSet) + for productName, product := range l.products { + subjects[fmt.Sprintf("file:%v", productName)] = product.Digest + } + + return subjects +} diff --git a/attestation/slsa/slsa_test.go b/attestation/slsa/slsa_test.go new file mode 100644 index 00000000..20574a19 --- /dev/null +++ b/attestation/slsa/slsa_test.go @@ -0,0 +1,192 @@ +// Copyright 2022 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package slsa + +import ( + "testing" + + "github.com/in-toto/go-witness/attestation" + "github.com/in-toto/go-witness/cryptoutil" +) + +func TestName(t *testing.T) { + link := New() + if link.Name() != Name { + t.Errorf("expected %s, got %s", Name, link.Name()) + } +} + +func TestType(t *testing.T) { + link := New() + if link.Type() != Type { + t.Errorf("expected %s, got %s", Type, link.Type()) + } +} + +func TestRunType(t *testing.T) { + link := New() + if link.RunType() != RunType { + t.Errorf("expected %s, got %s", RunType, link.RunType()) + } +} + +func TestExport(t *testing.T) { + link := New() + if link.export != defaultExport { + t.Errorf("expected %t, got %t", defaultExport, link.export) + } + + WithExport(true)(link) + if !link.export { + t.Errorf("expected %t, got %t", true, link.export) + } + + if link.Export() != true { + t.Errorf("expected %t, got %t", true, link.Export()) + } +} + +func TestUnmarshalJSON(t *testing.T) { + link := New() + if err := link.UnmarshalJSON([]byte(testLinkJSON)); err != nil { + t.Errorf("unexpected error: %s", err) + } +} + +func TestUnmarshalBadJSON(t *testing.T) { + link := New() + if err := link.UnmarshalJSON([]byte("}")); err == nil { + t.Error("Expected error") + } +} + +func TestMarshalJSON(t *testing.T) { + link := New() + if err := link.UnmarshalJSON([]byte(testLinkJSON)); err != nil { + t.Errorf("unexpected error: %s", err) + } + + _, err := link.MarshalJSON() + if err != nil { + t.Errorf("unexpected error: %s", err) + } +} + +func TestAttest(t *testing.T) { + link := New() + // var attestorData []attestation.CompletedAttestor + // ctx := attestation.AttestationContext{ + // completedAttestors: attestorData, + // } + + if err := link.Attest(&attestation.AttestationContext{}); err != nil { + t.Errorf("unexpected error: %s", err) + } +} + +func setupContext(t *testing.T) *attestation.AttestationContext { + ctx := attestation.AttestationContext{} + ctx.CompletedAttestors() + + return &ctx + +} + +func TestSubjects(t *testing.T) { + link := setupLink(t) + + subjects := link.Subjects() + + if len(subjects) != 1 { + t.Errorf("expected 1 subjects, got %d", len(subjects)) + } + + digests := subjects["file:test.txt"] + nameMap, err := digests.ToNameMap() + if err != nil { + t.Errorf("unexpected error: %s", err) + } + + if len(nameMap) != 1 { + t.Errorf("expected 1 digest found, got %d", len(nameMap)) + } + + if nameMap["sha256"] != "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" { + t.Errorf("expected e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855, got %s", nameMap["sha256"]) + } +} + +func setupLink(t *testing.T) *Link { + link := New() + link.UnmarshalJSON([]byte(testLinkJSON)) + + link.products = make(map[string]attestation.Product) + digestsByName := make(map[string]string) + digestsByName["sha256"] = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + digestSet, err := cryptoutil.NewDigestSet(digestsByName) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + link.products["test.txt"] = attestation.Product{ + MimeType: "text/plain", + Digest: digestSet, + } + + return link +} +func TestRegistration(t *testing.T) { + registrations := attestation.RegistrationEntries() + + var found bool + for _, registration := range registrations { + if registration.Name == Name { + found = true + } + } + + if !found { + t.Errorf("expected %s to be registered", Name) + } + +} + +const testLinkJSON = ` +{ + "name": "test", + "command": [ + "touch", + "test.txt" + ], + "materials": [ + { + "name": "test1", + "digest": { + "sha256": "a53d0741798b287c6dd7afa64aee473f305e65d3f49463bb9d7408ec3b12bf5f" + } + }, + { + "name": "test2", + "digest": { + "sha256": "a53d0741798b287c6dd7afa64aee473f305e65d3f49463bb9d7408ec3b12bf5f" + } + } + ], + "environment": { + "COLORFGBG": "7;0", + "COLORTERM": "truecolor" + } +} +` diff --git a/imports.go b/imports.go index 95720e48..03ef1759 100644 --- a/imports.go +++ b/imports.go @@ -31,6 +31,7 @@ import ( _ "github.com/in-toto/go-witness/attestation/oci" _ "github.com/in-toto/go-witness/attestation/product" _ "github.com/in-toto/go-witness/attestation/sarif" + _ "github.com/in-toto/go-witness/attestation/slsa" // signer providers _ "github.com/in-toto/go-witness/signer/file" diff --git a/run.go b/run.go index 9cfdffb3..fed2fe6b 100644 --- a/run.go +++ b/run.go @@ -24,6 +24,7 @@ import ( "github.com/in-toto/go-witness/attestation/environment" "github.com/in-toto/go-witness/attestation/git" "github.com/in-toto/go-witness/attestation/link" + "github.com/in-toto/go-witness/attestation/slsa" "github.com/in-toto/go-witness/cryptoutil" "github.com/in-toto/go-witness/dsse" "github.com/in-toto/go-witness/intoto" @@ -121,6 +122,17 @@ func run(stepName string, signer cryptoutil.Signer, opts []RunOption) ([]RunResu result = append(result, RunResult{SignedEnvelope: linkEnvelope, AttestorName: r.Attestor.Name()}) } } + } else if r.Attestor.Name() == slsa.Name { + // TODO: Add Exporter interface to attestors + if r.Attestor.(*slsa.Provenance).Export() { + if subjecter, ok := r.Attestor.(attestation.Subjecter); ok { + linkEnvelope, err := createAndSignEnvelope(r.Attestor, r.Attestor.Type(), subjecter.Subjects(), dsse.SignWithSigners(ro.signer), dsse.SignWithTimestampers(ro.timestampers...)) + if err != nil { + return result, fmt.Errorf("failed to sign envelope: %w", err) + } + result = append(result, RunResult{SignedEnvelope: linkEnvelope, AttestorName: r.Attestor.Name()}) + } + } } } From ae52a37998e6034dd74639c08f9a45b622c3a58e Mon Sep 17 00:00:00 2001 From: John Kjell Date: Sat, 23 Mar 2024 17:51:49 +0100 Subject: [PATCH 18/33] Add interface for product attestor Signed-off-by: John Kjell --- attestation/link/link.go | 2 +- attestation/product/product.go | 28 +++-- attestation/product/product_test.go | 6 +- attestation/slsa/slsa.go | 58 +++++----- attestation/slsa/slsa_test.go | 167 ++++++++++++++++------------ internal/attestors/attestors.go | 40 +++++++ 6 files changed, 191 insertions(+), 110 deletions(-) create mode 100644 internal/attestors/attestors.go diff --git a/attestation/link/link.go b/attestation/link/link.go index a068f04a..40b86816 100644 --- a/attestation/link/link.go +++ b/attestation/link/link.go @@ -125,7 +125,7 @@ func (l *Link) Attest(ctx *attestation.AttestationContext) error { if err != nil { return err } - case product.Name: + case product.ProductName: l.products = attestor.Attestor.(*product.Attestor).Products() } } diff --git a/attestation/product/product.go b/attestation/product/product.go index 1754d841..61b9f511 100644 --- a/attestation/product/product.go +++ b/attestation/product/product.go @@ -31,9 +31,9 @@ import ( ) const ( - Name = "product" - Type = "https://witness.dev/attestations/product/v0.1" - RunType = attestation.ProductRunType + ProductName = "product" + ProductType = "https://witness.dev/attestations/product/v0.1" + ProductRunType = attestation.ProductRunType defaultIncludeGlob = "*" defaultExcludeGlob = "" @@ -47,8 +47,22 @@ var ( _ attestation.Producer = &Attestor{} ) +type ProductAttestor interface { + // Attestor + Name() string + Type() string + RunType() attestation.RunType + Attest(ctx *attestation.AttestationContext) error + + // Subjector + Subjects() map[string]cryptoutil.DigestSet + + // Producter + Products() map[string]attestation.Product +} + func init() { - attestation.RegisterAttestation(Name, Type, RunType, func() attestation.Attestor { return New() }, + attestation.RegisterAttestation(ProductName, ProductType, ProductRunType, func() attestation.Attestor { return New() }, registry.StringConfigOption( "include-glob", "Pattern to use when recording products. Files that match this pattern will be included as subjects on the attestation.", @@ -126,15 +140,15 @@ func fromDigestMap(digestMap map[string]cryptoutil.DigestSet) map[string]attesta } func (a *Attestor) Name() string { - return Name + return ProductName } func (a *Attestor) Type() string { - return Type + return ProductType } func (a *Attestor) RunType() attestation.RunType { - return RunType + return ProductRunType } func New(opts ...Option) *Attestor { diff --git a/attestation/product/product_test.go b/attestation/product/product_test.go index 6495f52f..cc30a30a 100644 --- a/attestation/product/product_test.go +++ b/attestation/product/product_test.go @@ -42,17 +42,17 @@ func TestFromDigestMap(t *testing.T) { func TestAttestorName(t *testing.T) { a := New() - assert.Equal(t, a.Name(), Name) + assert.Equal(t, a.Name(), ProductName) } func TestAttestorType(t *testing.T) { a := New() - assert.Equal(t, a.Type(), Type) + assert.Equal(t, a.Type(), ProductType) } func TestAttestorRunType(t *testing.T) { a := New() - assert.Equal(t, a.RunType(), RunType) + assert.Equal(t, a.RunType(), ProductRunType) } func TestAttestorAttest(t *testing.T) { diff --git a/attestation/slsa/slsa.go b/attestation/slsa/slsa.go index c89a580b..6db5fad8 100644 --- a/attestation/slsa/slsa.go +++ b/attestation/slsa/slsa.go @@ -71,8 +71,8 @@ func init() { type Option func(*Provenance) func WithExport(export bool) Option { - return func(l *Provenance) { - l.export = export + return func(p *Provenance) { + p.export = export } } @@ -86,31 +86,31 @@ func New() *Provenance { return &Provenance{} } -func (l *Provenance) Name() string { +func (p *Provenance) Name() string { return Name } -func (l *Provenance) Type() string { +func (p *Provenance) Type() string { return Type } -func (l *Provenance) RunType() attestation.RunType { +func (p *Provenance) RunType() attestation.RunType { return RunType } -func (l *Provenance) Export() bool { - return l.export +func (p *Provenance) Export() bool { + return p.export } -func (l *Provenance) Attest(ctx *attestation.AttestationContext) error { +func (p *Provenance) Attest(ctx *attestation.AttestationContext) error { builder := prov.Builder{} metadata := prov.BuildMetadata{} - l.PbProvenance.BuildDefinition = &prov.BuildDefinition{} - l.PbProvenance.RunDetails = &prov.RunDetails{Builder: &builder, Metadata: &metadata} + p.PbProvenance.BuildDefinition = &prov.BuildDefinition{} + p.PbProvenance.RunDetails = &prov.RunDetails{Builder: &builder, Metadata: &metadata} - l.PbProvenance.BuildDefinition.BuildType = "https://witness.dev/slsa-build@v0.1" - l.PbProvenance.RunDetails.Builder.Id = "https://witness.dev/witness-github-action@v0.1" - l.PbProvenance.RunDetails.Metadata.InvocationId = "gha-workflow-ref" + p.PbProvenance.BuildDefinition.BuildType = "https://witness.dev/slsa-build@v0.1" + p.PbProvenance.RunDetails.Builder.Id = "https://witness.dev/witness-github-action@v0.1" + p.PbProvenance.RunDetails.Metadata.InvocationId = "gha-workflow-ref" internalParamaters := make(map[string]interface{}) @@ -122,8 +122,8 @@ func (l *Provenance) Attest(ctx *attestation.AttestationContext) error { digests, _ := digestSet.ToNameMap() for _, remote := range remotes { - l.PbProvenance.BuildDefinition.ResolvedDependencies = append( - l.PbProvenance.BuildDefinition.ResolvedDependencies, + p.PbProvenance.BuildDefinition.ResolvedDependencies = append( + p.PbProvenance.BuildDefinition.ResolvedDependencies, &v1.ResourceDescriptor{ Name: remote, Digest: digests, @@ -134,20 +134,20 @@ func (l *Provenance) Attest(ctx *attestation.AttestationContext) error { var err error ep := make(map[string]interface{}) ep["command"] = strings.Join(attestor.Attestor.(*commandrun.CommandRun).Cmd, " ") - l.PbProvenance.BuildDefinition.ExternalParameters, err = structpb.NewStruct(ep) + p.PbProvenance.BuildDefinition.ExternalParameters, err = structpb.NewStruct(ep) if err != nil { return err } // We have start and finish time at the collection level, how do we access it here? - l.PbProvenance.RunDetails.Metadata.StartedOn = timestamppb.New(time.Now()) - l.PbProvenance.RunDetails.Metadata.FinishedOn = timestamppb.New(time.Now()) + p.PbProvenance.RunDetails.Metadata.StartedOn = timestamppb.New(time.Now()) + p.PbProvenance.RunDetails.Metadata.FinishedOn = timestamppb.New(time.Now()) case material.Name: mats := attestor.Attestor.(*material.Attestor).Materials() for name, digestSet := range mats { digests, _ := digestSet.ToNameMap() - l.PbProvenance.BuildDefinition.ResolvedDependencies = append( - l.PbProvenance.BuildDefinition.ResolvedDependencies, + p.PbProvenance.BuildDefinition.ResolvedDependencies = append( + p.PbProvenance.BuildDefinition.ResolvedDependencies, &v1.ResourceDescriptor{ Name: name, Digest: digests, @@ -163,13 +163,13 @@ func (l *Provenance) Attest(ctx *attestation.AttestationContext) error { internalParamaters["env"] = pbEnvs - case product.Name: - l.products = attestor.Attestor.(*product.Attestor).Products() + case product.ProductName: + p.products = attestor.Attestor.(*product.Attestor).Products() } } var err error - l.PbProvenance.BuildDefinition.InternalParameters, err = structpb.NewStruct(internalParamaters) + p.PbProvenance.BuildDefinition.InternalParameters, err = structpb.NewStruct(internalParamaters) if err != nil { return err } @@ -177,21 +177,21 @@ func (l *Provenance) Attest(ctx *attestation.AttestationContext) error { return nil } -func (l *Provenance) MarshalJSON() ([]byte, error) { - return json.Marshal(&l.PbProvenance) +func (p *Provenance) MarshalJSON() ([]byte, error) { + return json.Marshal(&p.PbProvenance) } -func (l *Provenance) UnmarshalJSON(data []byte) error { - if err := json.Unmarshal(data, &l.PbProvenance); err != nil { +func (p *Provenance) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, &p.PbProvenance); err != nil { return err } return nil } -func (l *Provenance) Subjects() map[string]cryptoutil.DigestSet { +func (p *Provenance) Subjects() map[string]cryptoutil.DigestSet { subjects := make(map[string]cryptoutil.DigestSet) - for productName, product := range l.products { + for productName, product := range p.products { subjects[fmt.Sprintf("file:%v", productName)] = product.Digest } diff --git a/attestation/slsa/slsa_test.go b/attestation/slsa/slsa_test.go index 20574a19..e60974bf 100644 --- a/attestation/slsa/slsa_test.go +++ b/attestation/slsa/slsa_test.go @@ -19,95 +19,91 @@ import ( "github.com/in-toto/go-witness/attestation" "github.com/in-toto/go-witness/cryptoutil" + "github.com/in-toto/go-witness/internal/attestors" ) func TestName(t *testing.T) { - link := New() - if link.Name() != Name { - t.Errorf("expected %s, got %s", Name, link.Name()) + provenance := New() + if provenance.Name() != Name { + t.Errorf("expected %s, got %s", Name, provenance.Name()) } } func TestType(t *testing.T) { - link := New() - if link.Type() != Type { - t.Errorf("expected %s, got %s", Type, link.Type()) + provenance := New() + if provenance.Type() != Type { + t.Errorf("expected %s, got %s", Type, provenance.Type()) } } func TestRunType(t *testing.T) { - link := New() - if link.RunType() != RunType { - t.Errorf("expected %s, got %s", RunType, link.RunType()) + provenance := New() + if provenance.RunType() != RunType { + t.Errorf("expected %s, got %s", RunType, provenance.RunType()) } } func TestExport(t *testing.T) { - link := New() - if link.export != defaultExport { - t.Errorf("expected %t, got %t", defaultExport, link.export) + provenance := New() + if provenance.export != defaultExport { + t.Errorf("expected %t, got %t", defaultExport, provenance.export) } - WithExport(true)(link) - if !link.export { - t.Errorf("expected %t, got %t", true, link.export) + WithExport(true)(provenance) + if !provenance.export { + t.Errorf("expected %t, got %t", true, provenance.export) } - if link.Export() != true { - t.Errorf("expected %t, got %t", true, link.Export()) + if provenance.Export() != true { + t.Errorf("expected %t, got %t", true, provenance.Export()) } } func TestUnmarshalJSON(t *testing.T) { - link := New() - if err := link.UnmarshalJSON([]byte(testLinkJSON)); err != nil { + provenance := New() + if err := provenance.UnmarshalJSON([]byte(testProvenanceJSON)); err != nil { t.Errorf("unexpected error: %s", err) } } func TestUnmarshalBadJSON(t *testing.T) { - link := New() - if err := link.UnmarshalJSON([]byte("}")); err == nil { + provenance := New() + if err := provenance.UnmarshalJSON([]byte("}")); err == nil { t.Error("Expected error") } } func TestMarshalJSON(t *testing.T) { - link := New() - if err := link.UnmarshalJSON([]byte(testLinkJSON)); err != nil { + provenance := New() + if err := provenance.UnmarshalJSON([]byte(testProvenanceJSON)); err != nil { t.Errorf("unexpected error: %s", err) } - _, err := link.MarshalJSON() + _, err := provenance.MarshalJSON() if err != nil { t.Errorf("unexpected error: %s", err) } } func TestAttest(t *testing.T) { - link := New() - // var attestorData []attestation.CompletedAttestor - // ctx := attestation.AttestationContext{ - // completedAttestors: attestorData, - // } + p := &attestors.TestProductAttestor{} + s := &Provenance{} - if err := link.Attest(&attestation.AttestationContext{}); err != nil { - t.Errorf("unexpected error: %s", err) + ctx, err := attestation.NewContext([]attestation.Attestor{p, s}) + if err != nil { + t.Errorf("error creating attestation context: %s", err) } -} - -func setupContext(t *testing.T) *attestation.AttestationContext { - ctx := attestation.AttestationContext{} - ctx.CompletedAttestors() - - return &ctx + err = s.Attest(ctx) + if err != nil { + t.Errorf("error attesting: %s", err.Error()) + } } func TestSubjects(t *testing.T) { - link := setupLink(t) + provenance := setupProvenance(t) - subjects := link.Subjects() + subjects := provenance.Subjects() if len(subjects) != 1 { t.Errorf("expected 1 subjects, got %d", len(subjects)) @@ -116,7 +112,7 @@ func TestSubjects(t *testing.T) { digests := subjects["file:test.txt"] nameMap, err := digests.ToNameMap() if err != nil { - t.Errorf("unexpected error: %s", err) + t.Errorf("unexpected error: %s", err.Error()) } if len(nameMap) != 1 { @@ -128,11 +124,11 @@ func TestSubjects(t *testing.T) { } } -func setupLink(t *testing.T) *Link { - link := New() - link.UnmarshalJSON([]byte(testLinkJSON)) +func setupProvenance(t *testing.T) *Provenance { + provenance := New() + provenance.UnmarshalJSON([]byte(testProvenanceJSON)) - link.products = make(map[string]attestation.Product) + provenance.products = make(map[string]attestation.Product) digestsByName := make(map[string]string) digestsByName["sha256"] = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" digestSet, err := cryptoutil.NewDigestSet(digestsByName) @@ -140,12 +136,12 @@ func setupLink(t *testing.T) *Link { t.Fatalf("unexpected error: %s", err) } - link.products["test.txt"] = attestation.Product{ + provenance.products["test.txt"] = attestation.Product{ MimeType: "text/plain", Digest: digestSet, } - return link + return provenance } func TestRegistration(t *testing.T) { registrations := attestation.RegistrationEntries() @@ -163,30 +159,61 @@ func TestRegistration(t *testing.T) { } -const testLinkJSON = ` +const testProvenanceJSON = ` { - "name": "test", - "command": [ - "touch", - "test.txt" - ], - "materials": [ - { - "name": "test1", - "digest": { - "sha256": "a53d0741798b287c6dd7afa64aee473f305e65d3f49463bb9d7408ec3b12bf5f" - } - }, - { - "name": "test2", - "digest": { - "sha256": "a53d0741798b287c6dd7afa64aee473f305e65d3f49463bb9d7408ec3b12bf5f" - } - } - ], - "environment": { - "COLORFGBG": "7;0", - "COLORTERM": "truecolor" + "type": "https://slsa.dev/provenance/v1.0", + "attestation": { + "build_definition": { + "build_type": "https://witness.dev/slsa-build@v0.1", + "external_parameters": { + "command": "touch test.txt" + }, + "internal_parameters": { + "env": { + "COLORFGBG": "7;0", + "COLORTERM": "truecolor", + "COMMAND_MODE": "unix2003", + "SHELL": "/bin/zsh", + "SHLVL": "1", + "TERM": "xterm-256color", + "TERM_PROGRAM": "iTerm.app", + "TERM_PROGRAM_VERSION": "3.4.23", + "TERM_SESSION_ID": "w0t1p0:8939AC72-EB13-417F-9500-DD193C48127E", + "TMPDIR": "/var/folders/qy/kpkfp9r140s08yk29dccpx540000gn/T/", + "XPC_FLAGS": "0x0", + "XPC_SERVICE_NAME": "0", + "_": "/opt/homebrew/bin/go", + "_P9K_SSH_TTY": "/dev/ttys005", + "_P9K_TTY": "/dev/ttys005", + "__CFBundleIdentifier": "com.googlecode.iterm2", + "__CF_USER_TEXT_ENCODING": "0x1F5:0x0:0x0" + } + }, + "resolved_dependencies": [ + { + "name": "git@github.com:in-toto/witness.git", + "digest": { + "sha1": "51d0fa68cb991b7d3979df491e05fbf7765d6d1c" + } + } + ] + }, + "run_details": { + "builder": { + "id": "https://witness.dev/witness-github-action@v0.1" + }, + "metadata": { + "invocation_id": "gha-workflow-ref", + "started_on": { + "seconds": 1711199861, + "nanos": 560152000 + }, + "finished_on": { + "seconds": 1711199861, + "nanos": 560152000 + } + } + } } } ` diff --git a/internal/attestors/attestors.go b/internal/attestors/attestors.go new file mode 100644 index 00000000..11297832 --- /dev/null +++ b/internal/attestors/attestors.go @@ -0,0 +1,40 @@ +package attestors + +import ( + "github.com/in-toto/go-witness/attestation" + "github.com/in-toto/go-witness/attestation/product" + "github.com/in-toto/go-witness/cryptoutil" +) + +type TestProductAttestor struct { + prodAtt product.ProductAttestor +} + +func (t *TestProductAttestor) New() *TestProductAttestor { + att := &product.Attestor{} + return &TestProductAttestor{prodAtt: att} +} + +func (t *TestProductAttestor) Name() string { + return t.prodAtt.Name() +} + +func (t *TestProductAttestor) Type() string { + return t.prodAtt.Type() +} + +func (t *TestProductAttestor) RunType() attestation.RunType { + return t.prodAtt.RunType() +} + +func (t *TestProductAttestor) Attest(ctx *attestation.AttestationContext) error { + return nil +} + +func (t *TestProductAttestor) Subjects() map[string]cryptoutil.DigestSet { + return nil +} + +func (t *TestProductAttestor) Products() map[string]attestation.Product { + return nil +} From 8f016d972494615a7d7dbe1c693fe8b14ee7346e Mon Sep 17 00:00:00 2001 From: John Kjell Date: Sat, 23 Mar 2024 19:15:57 +0100 Subject: [PATCH 19/33] Add more attestor interfaces Signed-off-by: John Kjell --- attestation/commandrun/commandrun.go | 9 ++++ attestation/git/git.go | 14 ++++++ attestation/github/github.go | 21 +++++++-- attestation/gitlab/gitlab.go | 15 +++++++ attestation/material/material.go | 12 +++++ attestation/slsa/slsa_test.go | 6 ++- internal/attestors/commandrun.go | 40 +++++++++++++++++ internal/attestors/git.go | 44 +++++++++++++++++++ internal/attestors/github.go | 44 +++++++++++++++++++ internal/attestors/gitlab.go | 44 +++++++++++++++++++ internal/attestors/material.go | 40 +++++++++++++++++ .../attestors/{attestors.go => product.go} | 4 ++ 12 files changed, 289 insertions(+), 4 deletions(-) create mode 100644 internal/attestors/commandrun.go create mode 100644 internal/attestors/git.go create mode 100644 internal/attestors/github.go create mode 100644 internal/attestors/gitlab.go create mode 100644 internal/attestors/material.go rename internal/attestors/{attestors.go => product.go} (93%) diff --git a/attestation/commandrun/commandrun.go b/attestation/commandrun/commandrun.go index 1aea1784..bec7953a 100644 --- a/attestation/commandrun/commandrun.go +++ b/attestation/commandrun/commandrun.go @@ -35,8 +35,17 @@ const ( // doesn't implement the expected interfaces. var ( _ attestation.Attestor = &CommandRun{} + _ CommandRunAttestor = &CommandRun{} ) +type CommandRunAttestor interface { + // Attestor + Name() string + Type() string + RunType() attestation.RunType + Attest(ctx *attestation.AttestationContext) error +} + func init() { attestation.RegisterAttestation(Name, Type, RunType, func() attestation.Attestor { return New() diff --git a/attestation/git/git.go b/attestation/git/git.go index 1e3e8a54..51e43aca 100644 --- a/attestation/git/git.go +++ b/attestation/git/git.go @@ -41,6 +41,20 @@ var ( _ attestation.BackReffer = &Attestor{} ) +type GitAttestor interface { + // Attestor + Name() string + Type() string + RunType() attestation.RunType + Attest(ctx *attestation.AttestationContext) error + + // Subjecter + Subjects() map[string]cryptoutil.DigestSet + + // Backreffer + BackRefs() map[string]cryptoutil.DigestSet +} + func init() { attestation.RegisterAttestation(Name, Type, RunType, func() attestation.Attestor { return New() diff --git a/attestation/github/github.go b/attestation/github/github.go index 436c98a5..2a007a94 100644 --- a/attestation/github/github.go +++ b/attestation/github/github.go @@ -48,8 +48,23 @@ var ( _ attestation.Attestor = &Attestor{} _ attestation.Subjecter = &Attestor{} _ attestation.BackReffer = &Attestor{} + _ GitHubAttestor = &Attestor{} ) +type GitHubAttestor interface { + // Attestor + Name() string + Type() string + RunType() attestation.RunType + Attest(ctx *attestation.AttestationContext) error + + // Subjecter + Subjects() map[string]cryptoutil.DigestSet + + // Backreffer + BackRefs() map[string]cryptoutil.DigestSet +} + // init registers the github attestor. func init() { attestation.RegisterAttestation(Name, Type, RunType, func() attestation.Attestor { @@ -58,10 +73,10 @@ func init() { } // ErrNotGitlab is an error type that indicates the environment is not a github ci job. -type ErrNotGitlab struct{} +type ErrNotGitHub struct{} // Error returns the error message for ErrNotGitlab. -func (e ErrNotGitlab) Error() string { +func (e ErrNotGitHub) Error() string { return "not in a github ci job" } @@ -111,7 +126,7 @@ func (a *Attestor) RunType() attestation.RunType { // Attest performs the attestation for the github environment. func (a *Attestor) Attest(ctx *attestation.AttestationContext) error { if os.Getenv("GITHUB_ACTIONS") != "true" { - return ErrNotGitlab{} + return ErrNotGitHub{} } jwtString, err := fetchToken(a.tokenURL, os.Getenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN"), "witness") diff --git a/attestation/gitlab/gitlab.go b/attestation/gitlab/gitlab.go index eea831d7..50b7bbac 100644 --- a/attestation/gitlab/gitlab.go +++ b/attestation/gitlab/gitlab.go @@ -38,8 +38,23 @@ var ( _ attestation.Attestor = &Attestor{} _ attestation.Subjecter = &Attestor{} _ attestation.BackReffer = &Attestor{} + _ GitLabAttestor = &Attestor{} ) +type GitLabAttestor interface { + // Attestor + Name() string + Type() string + RunType() attestation.RunType + Attest(ctx *attestation.AttestationContext) error + + // Subjecter + Subjects() map[string]cryptoutil.DigestSet + + // Backreffer + BackRefs() map[string]cryptoutil.DigestSet +} + func init() { attestation.RegisterAttestation(Name, Type, RunType, func() attestation.Attestor { return New() diff --git a/attestation/material/material.go b/attestation/material/material.go index 458515a1..10125918 100644 --- a/attestation/material/material.go +++ b/attestation/material/material.go @@ -33,8 +33,20 @@ const ( var ( _ attestation.Attestor = &Attestor{} _ attestation.Materialer = &Attestor{} + _ MaterialAttestor = &Attestor{} ) +type MaterialAttestor interface { + // Attestor + Name() string + Type() string + RunType() attestation.RunType + Attest(ctx *attestation.AttestationContext) error + + // Materialer + Materials() map[string]cryptoutil.DigestSet +} + func init() { attestation.RegisterAttestation(Name, Type, RunType, func() attestation.Attestor { return New() diff --git a/attestation/slsa/slsa_test.go b/attestation/slsa/slsa_test.go index e60974bf..a02838ae 100644 --- a/attestation/slsa/slsa_test.go +++ b/attestation/slsa/slsa_test.go @@ -86,10 +86,14 @@ func TestMarshalJSON(t *testing.T) { } func TestAttest(t *testing.T) { + g := &attestors.TestGitAttestor{} + gh := &attestors.TestGitHubAttestor{} + m := &attestors.TestMaterialAttestor{} + c := &attestors.TestCommandRunAttestor{} p := &attestors.TestProductAttestor{} s := &Provenance{} - ctx, err := attestation.NewContext([]attestation.Attestor{p, s}) + ctx, err := attestation.NewContext([]attestation.Attestor{g, gh, m, c, p, s}) if err != nil { t.Errorf("error creating attestation context: %s", err) } diff --git a/internal/attestors/commandrun.go b/internal/attestors/commandrun.go new file mode 100644 index 00000000..6faadb13 --- /dev/null +++ b/internal/attestors/commandrun.go @@ -0,0 +1,40 @@ +package attestors + +import ( + "github.com/in-toto/go-witness/attestation" + "github.com/in-toto/go-witness/attestation/commandrun" + "github.com/in-toto/go-witness/cryptoutil" +) + +var ( + _ commandrun.CommandRunAttestor = &TestCommandRunAttestor{} +) + +type TestCommandRunAttestor struct { + comAtt commandrun.CommandRunAttestor +} + +func (t *TestCommandRunAttestor) New() *TestCommandRunAttestor { + att := &commandrun.CommandRun{} + return &TestCommandRunAttestor{comAtt: att} +} + +func (t *TestCommandRunAttestor) Name() string { + return t.comAtt.Name() +} + +func (t *TestCommandRunAttestor) Type() string { + return t.comAtt.Type() +} + +func (t *TestCommandRunAttestor) RunType() attestation.RunType { + return t.comAtt.RunType() +} + +func (t *TestCommandRunAttestor) Attest(ctx *attestation.AttestationContext) error { + return nil +} + +func (t *TestCommandRunAttestor) CommandRuns() map[string]cryptoutil.DigestSet { + return nil +} diff --git a/internal/attestors/git.go b/internal/attestors/git.go new file mode 100644 index 00000000..e0894d76 --- /dev/null +++ b/internal/attestors/git.go @@ -0,0 +1,44 @@ +package attestors + +import ( + "github.com/in-toto/go-witness/attestation" + "github.com/in-toto/go-witness/attestation/git" + "github.com/in-toto/go-witness/cryptoutil" +) + +var ( + _ git.GitAttestor = &TestGitAttestor{} +) + +type TestGitAttestor struct { + gitAtt git.GitAttestor +} + +func (t *TestGitAttestor) New() *TestGitAttestor { + att := &git.Attestor{} + return &TestGitAttestor{gitAtt: att} +} + +func (t *TestGitAttestor) Name() string { + return t.gitAtt.Name() +} + +func (t *TestGitAttestor) Type() string { + return t.gitAtt.Type() +} + +func (t *TestGitAttestor) RunType() attestation.RunType { + return t.gitAtt.RunType() +} + +func (t *TestGitAttestor) Attest(ctx *attestation.AttestationContext) error { + return nil +} + +func (t *TestGitAttestor) Subjects() map[string]cryptoutil.DigestSet { + return nil +} + +func (t *TestGitAttestor) BackRefs() map[string]cryptoutil.DigestSet { + return nil +} diff --git a/internal/attestors/github.go b/internal/attestors/github.go new file mode 100644 index 00000000..5814f77b --- /dev/null +++ b/internal/attestors/github.go @@ -0,0 +1,44 @@ +package attestors + +import ( + "github.com/in-toto/go-witness/attestation" + "github.com/in-toto/go-witness/attestation/github" + "github.com/in-toto/go-witness/cryptoutil" +) + +var ( + _ github.GitHubAttestor = &TestGitHubAttestor{} +) + +type TestGitHubAttestor struct { + githubAtt github.GitHubAttestor +} + +func (t *TestGitHubAttestor) New() *TestGitHubAttestor { + att := &github.Attestor{} + return &TestGitHubAttestor{githubAtt: att} +} + +func (t *TestGitHubAttestor) Name() string { + return t.githubAtt.Name() +} + +func (t *TestGitHubAttestor) Type() string { + return t.githubAtt.Type() +} + +func (t *TestGitHubAttestor) RunType() attestation.RunType { + return t.githubAtt.RunType() +} + +func (t *TestGitHubAttestor) Attest(ctx *attestation.AttestationContext) error { + return nil +} + +func (t *TestGitHubAttestor) Subjects() map[string]cryptoutil.DigestSet { + return nil +} + +func (t *TestGitHubAttestor) BackRefs() map[string]cryptoutil.DigestSet { + return nil +} diff --git a/internal/attestors/gitlab.go b/internal/attestors/gitlab.go new file mode 100644 index 00000000..8be212fc --- /dev/null +++ b/internal/attestors/gitlab.go @@ -0,0 +1,44 @@ +package attestors + +import ( + "github.com/in-toto/go-witness/attestation" + "github.com/in-toto/go-witness/attestation/gitlab" + "github.com/in-toto/go-witness/cryptoutil" +) + +var ( + _ gitlab.GitLabAttestor = &TestGitLabAttestor{} +) + +type TestGitLabAttestor struct { + gitlabAtt gitlab.GitLabAttestor +} + +func (t *TestGitLabAttestor) New() *TestGitLabAttestor { + att := &gitlab.Attestor{} + return &TestGitLabAttestor{gitlabAtt: att} +} + +func (t *TestGitLabAttestor) Name() string { + return t.gitlabAtt.Name() +} + +func (t *TestGitLabAttestor) Type() string { + return t.gitlabAtt.Type() +} + +func (t *TestGitLabAttestor) RunType() attestation.RunType { + return t.gitlabAtt.RunType() +} + +func (t *TestGitLabAttestor) Attest(ctx *attestation.AttestationContext) error { + return nil +} + +func (t *TestGitLabAttestor) Subjects() map[string]cryptoutil.DigestSet { + return nil +} + +func (t *TestGitLabAttestor) BackRefs() map[string]cryptoutil.DigestSet { + return nil +} diff --git a/internal/attestors/material.go b/internal/attestors/material.go new file mode 100644 index 00000000..2eb2f8a8 --- /dev/null +++ b/internal/attestors/material.go @@ -0,0 +1,40 @@ +package attestors + +import ( + "github.com/in-toto/go-witness/attestation" + "github.com/in-toto/go-witness/attestation/material" + "github.com/in-toto/go-witness/cryptoutil" +) + +var ( + _ material.MaterialAttestor = &TestMaterialAttestor{} +) + +type TestMaterialAttestor struct { + matAtt material.MaterialAttestor +} + +func (t *TestMaterialAttestor) New() *TestMaterialAttestor { + att := &material.Attestor{} + return &TestMaterialAttestor{matAtt: att} +} + +func (t *TestMaterialAttestor) Name() string { + return t.matAtt.Name() +} + +func (t *TestMaterialAttestor) Type() string { + return t.matAtt.Type() +} + +func (t *TestMaterialAttestor) RunType() attestation.RunType { + return t.matAtt.RunType() +} + +func (t *TestMaterialAttestor) Attest(ctx *attestation.AttestationContext) error { + return nil +} + +func (t *TestMaterialAttestor) Materials() map[string]cryptoutil.DigestSet { + return nil +} diff --git a/internal/attestors/attestors.go b/internal/attestors/product.go similarity index 93% rename from internal/attestors/attestors.go rename to internal/attestors/product.go index 11297832..b8647b77 100644 --- a/internal/attestors/attestors.go +++ b/internal/attestors/product.go @@ -6,6 +6,10 @@ import ( "github.com/in-toto/go-witness/cryptoutil" ) +var ( + _ product.ProductAttestor = &TestProductAttestor{} +) + type TestProductAttestor struct { prodAtt product.ProductAttestor } From 885a436f163a11a2446eda1a0ffb933627f85050 Mon Sep 17 00:00:00 2001 From: John Kjell Date: Mon, 25 Mar 2024 15:32:58 -0500 Subject: [PATCH 20/33] Address some review feedback, licenses, and golanglint Signed-off-by: John Kjell --- attestation/slsa/slsa.go | 6 +++--- internal/attestors/commandrun.go | 14 ++++++++++++++ internal/attestors/git.go | 14 ++++++++++++++ internal/attestors/github.go | 14 ++++++++++++++ internal/attestors/gitlab.go | 14 ++++++++++++++ internal/attestors/material.go | 14 ++++++++++++++ internal/attestors/product.go | 14 ++++++++++++++ run.go | 4 ++-- verify.go | 10 +++++++--- verify_test.go | 3 +-- 10 files changed, 97 insertions(+), 10 deletions(-) diff --git a/attestation/slsa/slsa.go b/attestation/slsa/slsa.go index 6db5fad8..331fddcd 100644 --- a/attestation/slsa/slsa.go +++ b/attestation/slsa/slsa.go @@ -112,7 +112,7 @@ func (p *Provenance) Attest(ctx *attestation.AttestationContext) error { p.PbProvenance.RunDetails.Builder.Id = "https://witness.dev/witness-github-action@v0.1" p.PbProvenance.RunDetails.Metadata.InvocationId = "gha-workflow-ref" - internalParamaters := make(map[string]interface{}) + internalParameters := make(map[string]interface{}) for _, attestor := range ctx.CompletedAttestors() { switch name := attestor.Attestor.Name(); name { @@ -161,7 +161,7 @@ func (p *Provenance) Attest(ctx *attestation.AttestationContext) error { pbEnvs[name] = value } - internalParamaters["env"] = pbEnvs + internalParameters["env"] = pbEnvs case product.ProductName: p.products = attestor.Attestor.(*product.Attestor).Products() @@ -169,7 +169,7 @@ func (p *Provenance) Attest(ctx *attestation.AttestationContext) error { } var err error - p.PbProvenance.BuildDefinition.InternalParameters, err = structpb.NewStruct(internalParamaters) + p.PbProvenance.BuildDefinition.InternalParameters, err = structpb.NewStruct(internalParameters) if err != nil { return err } diff --git a/internal/attestors/commandrun.go b/internal/attestors/commandrun.go index 6faadb13..f3cdc065 100644 --- a/internal/attestors/commandrun.go +++ b/internal/attestors/commandrun.go @@ -1,3 +1,17 @@ +// Copyright 2024 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package attestors import ( diff --git a/internal/attestors/git.go b/internal/attestors/git.go index e0894d76..099871de 100644 --- a/internal/attestors/git.go +++ b/internal/attestors/git.go @@ -1,3 +1,17 @@ +// Copyright 2024 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package attestors import ( diff --git a/internal/attestors/github.go b/internal/attestors/github.go index 5814f77b..c4e62eaa 100644 --- a/internal/attestors/github.go +++ b/internal/attestors/github.go @@ -1,3 +1,17 @@ +// Copyright 2024 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package attestors import ( diff --git a/internal/attestors/gitlab.go b/internal/attestors/gitlab.go index 8be212fc..334e53b3 100644 --- a/internal/attestors/gitlab.go +++ b/internal/attestors/gitlab.go @@ -1,3 +1,17 @@ +// Copyright 2024 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package attestors import ( diff --git a/internal/attestors/material.go b/internal/attestors/material.go index 2eb2f8a8..1d7ac6f0 100644 --- a/internal/attestors/material.go +++ b/internal/attestors/material.go @@ -1,3 +1,17 @@ +// Copyright 2024 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package attestors import ( diff --git a/internal/attestors/product.go b/internal/attestors/product.go index b8647b77..d6f96452 100644 --- a/internal/attestors/product.go +++ b/internal/attestors/product.go @@ -1,3 +1,17 @@ +// Copyright 2024 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package attestors import ( diff --git a/run.go b/run.go index fed2fe6b..bb0c8083 100644 --- a/run.go +++ b/run.go @@ -164,8 +164,8 @@ func validateRunOpts(ro runOptions) error { return nil } -func createAndSignEnvelope(collection interface{}, predType string, subjects map[string]cryptoutil.DigestSet, opts ...dsse.SignOption) (dsse.Envelope, error) { - data, err := json.Marshal(&collection) +func createAndSignEnvelope(predicate interface{}, predType string, subjects map[string]cryptoutil.DigestSet, opts ...dsse.SignOption) (dsse.Envelope, error) { + data, err := json.Marshal(&predicate) if err != nil { return dsse.Envelope{}, err } diff --git a/verify.go b/verify.go index 3ff44fb8..8d68f6d7 100644 --- a/verify.go +++ b/verify.go @@ -119,7 +119,7 @@ func Verify(ctx context.Context, policyEnvelope dsse.Envelope, policyVerifiers [ opt(&vo) } - if err := verifyPolicySignature(ctx, vo); err != nil { + if err := verifyPolicySignature(vo); err != nil { return nil, fmt.Errorf("failed to verify policy signature: %w", err) } @@ -179,8 +179,12 @@ func Verify(ctx context.Context, policyEnvelope dsse.Envelope, policyVerifiers [ return accepted, nil } -func verifyPolicySignature(ctx context.Context, vo verifyOptions) error { - passedPolicyVerifiers, err := vo.policyEnvelope.Verify(dsse.VerifyWithVerifiers(vo.policyVerifiers...), dsse.VerifyWithTimestampVerifiers(vo.policyTimestampAuthorities...), dsse.VerifyWithRoots(vo.policyCARoots...), dsse.VerifyWithIntermediates(vo.policyCAIntermediates...)) +func verifyPolicySignature(vo verifyOptions) error { + passedPolicyVerifiers, err := vo.policyEnvelope.Verify( + dsse.VerifyWithVerifiers(vo.policyVerifiers...), + dsse.VerifyWithTimestampVerifiers(vo.policyTimestampAuthorities...), + dsse.VerifyWithRoots(vo.policyCARoots...), + dsse.VerifyWithIntermediates(vo.policyCAIntermediates...)) if err != nil { return fmt.Errorf("could not verify policy: %w", err) } diff --git a/verify_test.go b/verify_test.go index cfb32d5d..09775ece 100644 --- a/verify_test.go +++ b/verify_test.go @@ -16,7 +16,6 @@ package witness import ( "bytes" - "context" "crypto/x509" "fmt" "testing" @@ -151,7 +150,7 @@ func TestVerifyPolicySignature(t *testing.T) { tt.certConstraints(&vo) } - err = verifyPolicySignature(context.TODO(), vo) + err = verifyPolicySignature(vo) if err != nil && !tt.wantErr { t.Errorf("testName = %s, error = %v, wantErr %v", tt.name, err, tt.wantErr) } else { From 0bf0842149773de9eaf9e87f9584251c4a452e02 Mon Sep 17 00:00:00 2001 From: John Kjell Date: Mon, 25 Mar 2024 15:40:22 -0500 Subject: [PATCH 21/33] More golangcilint errors Signed-off-by: John Kjell --- attestation/link/link_test.go | 12 +++--------- attestation/slsa/slsa_test.go | 4 +++- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/attestation/link/link_test.go b/attestation/link/link_test.go index 1165ed5a..c91f0547 100644 --- a/attestation/link/link_test.go +++ b/attestation/link/link_test.go @@ -96,14 +96,6 @@ func TestAttest(t *testing.T) { } } -func setupContext(t *testing.T) *attestation.AttestationContext { - ctx := attestation.AttestationContext{} - ctx.CompletedAttestors() - - return &ctx - -} - func TestSubjects(t *testing.T) { link := setupLink(t) @@ -130,7 +122,9 @@ func TestSubjects(t *testing.T) { func setupLink(t *testing.T) *Link { link := New() - link.UnmarshalJSON([]byte(testLinkJSON)) + if err := link.UnmarshalJSON([]byte(testLinkJSON)); err != nil { + t.Errorf("unexpected error: %s", err) + } link.products = make(map[string]attestation.Product) digestsByName := make(map[string]string) diff --git a/attestation/slsa/slsa_test.go b/attestation/slsa/slsa_test.go index a02838ae..0227bf14 100644 --- a/attestation/slsa/slsa_test.go +++ b/attestation/slsa/slsa_test.go @@ -130,7 +130,9 @@ func TestSubjects(t *testing.T) { func setupProvenance(t *testing.T) *Provenance { provenance := New() - provenance.UnmarshalJSON([]byte(testProvenanceJSON)) + if err := provenance.UnmarshalJSON([]byte(testProvenanceJSON)); err != nil { + t.Errorf("unexpected error: %s", err) + } provenance.products = make(map[string]attestation.Product) digestsByName := make(map[string]string) From 21006a2cbdc88bf6dc737d62c60564be3d097033 Mon Sep 17 00:00:00 2001 From: John Kjell Date: Tue, 2 Apr 2024 01:16:00 -0500 Subject: [PATCH 22/33] WIP - Improve testing interfaces for exposing data fields Signed-off-by: John Kjell --- attestation/commandrun/commandrun.go | 5 ++ attestation/git/git.go | 1 + attestation/github/github.go | 5 ++ attestation/slsa/slsa.go | 104 +++++++++++++++++++-------- attestation/slsa/slsa_test.go | 14 ++-- go.mod | 15 ++-- go.sum | 18 ++++- internal/attestors/commandrun.go | 12 ++-- internal/attestors/git.go | 12 ++-- internal/attestors/github.go | 15 ++-- internal/attestors/material.go | 4 +- internal/attestors/product.go | 4 +- 12 files changed, 146 insertions(+), 63 deletions(-) diff --git a/attestation/commandrun/commandrun.go b/attestation/commandrun/commandrun.go index bec7953a..c148544e 100644 --- a/attestation/commandrun/commandrun.go +++ b/attestation/commandrun/commandrun.go @@ -44,6 +44,7 @@ type CommandRunAttestor interface { Type() string RunType() attestation.RunType Attest(ctx *attestation.AttestationContext) error + Data() *CommandRun } func init() { @@ -138,6 +139,10 @@ func (rc *CommandRun) Attest(ctx *attestation.AttestationContext) error { return nil } +func (rc *CommandRun) Data() *CommandRun { + return rc +} + func (rc *CommandRun) Name() string { return Name } diff --git a/attestation/git/git.go b/attestation/git/git.go index 51e43aca..ef8e7aad 100644 --- a/attestation/git/git.go +++ b/attestation/git/git.go @@ -47,6 +47,7 @@ type GitAttestor interface { Type() string RunType() attestation.RunType Attest(ctx *attestation.AttestationContext) error + Data() *Attestor // Subjecter Subjects() map[string]cryptoutil.DigestSet diff --git a/attestation/github/github.go b/attestation/github/github.go index 2a007a94..33080b18 100644 --- a/attestation/github/github.go +++ b/attestation/github/github.go @@ -57,6 +57,7 @@ type GitHubAttestor interface { Type() string RunType() attestation.RunType Attest(ctx *attestation.AttestationContext) error + Data() *Attestor // Subjecter Subjects() map[string]cryptoutil.DigestSet @@ -157,6 +158,10 @@ func (a *Attestor) Attest(ctx *attestation.AttestationContext) error { return nil } +func (a *Attestor) Data() *Attestor { + return a +} + // Subjects returns a map of subjects and their corresponding digest sets. func (a *Attestor) Subjects() map[string]cryptoutil.DigestSet { subjects := make(map[string]cryptoutil.DigestSet) diff --git a/attestation/slsa/slsa.go b/attestation/slsa/slsa.go index 331fddcd..f8abf7c1 100644 --- a/attestation/slsa/slsa.go +++ b/attestation/slsa/slsa.go @@ -26,20 +26,27 @@ import ( "github.com/in-toto/go-witness/attestation/commandrun" "github.com/in-toto/go-witness/attestation/environment" "github.com/in-toto/go-witness/attestation/git" + "github.com/in-toto/go-witness/attestation/github" + "github.com/in-toto/go-witness/attestation/gitlab" "github.com/in-toto/go-witness/attestation/material" + "github.com/in-toto/go-witness/attestation/oci" "github.com/in-toto/go-witness/attestation/product" "github.com/in-toto/go-witness/cryptoutil" "github.com/in-toto/go-witness/registry" + "golang.org/x/exp/maps" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" ) const ( - Name = "slsa" - Type = "https://slsa.dev/provenance/v1.0" - RunType = attestation.PostProductRunType - - defaultExport = false + Name = "slsa" + Type = "https://slsa.dev/provenance/v1.0" + RunType = attestation.PostProductRunType + defaultExport = false + BuildType = "https://witness.dev/slsa-build@v0.1" + DefaultBuilderId = "https://witness.dev/witness-default-builder@v0.1" + GHABuilderId = "https://witness.dev/witness-github-action-builder@v0.1" + GLCBuilderId = "https://witness.dev/witness-gitlab-component-builder@v0.1" ) // This is a hacky way to create a compile time error in case the attestor @@ -108,17 +115,26 @@ func (p *Provenance) Attest(ctx *attestation.AttestationContext) error { p.PbProvenance.BuildDefinition = &prov.BuildDefinition{} p.PbProvenance.RunDetails = &prov.RunDetails{Builder: &builder, Metadata: &metadata} - p.PbProvenance.BuildDefinition.BuildType = "https://witness.dev/slsa-build@v0.1" - p.PbProvenance.RunDetails.Builder.Id = "https://witness.dev/witness-github-action@v0.1" - p.PbProvenance.RunDetails.Metadata.InvocationId = "gha-workflow-ref" + p.PbProvenance.BuildDefinition.BuildType = BuildType + p.PbProvenance.RunDetails.Builder.Id = DefaultBuilderId internalParameters := make(map[string]interface{}) for _, attestor := range ctx.CompletedAttestors() { switch name := attestor.Attestor.Name(); name { + // Pre-material Attestors + case environment.Name: + envs := attestor.Attestor.(*environment.Attestor).Variables + pbEnvs := make(map[string]interface{}, len(envs)) + for name, value := range envs { + pbEnvs[name] = value + } + + internalParameters["env"] = pbEnvs + case git.Name: - digestSet := attestor.Attestor.(*git.Attestor).CommitDigest - remotes := attestor.Attestor.(*git.Attestor).Remotes + digestSet := attestor.Attestor.(git.GitAttestor).Data().CommitDigest + remotes := attestor.Attestor.(git.GitAttestor).Data().Remotes digests, _ := digestSet.ToNameMap() for _, remote := range remotes { @@ -130,20 +146,37 @@ func (p *Provenance) Attest(ctx *attestation.AttestationContext) error { }) } - case commandrun.Name: - var err error - ep := make(map[string]interface{}) - ep["command"] = strings.Join(attestor.Attestor.(*commandrun.CommandRun).Cmd, " ") - p.PbProvenance.BuildDefinition.ExternalParameters, err = structpb.NewStruct(ep) - if err != nil { - return err - } - // We have start and finish time at the collection level, how do we access it here? - p.PbProvenance.RunDetails.Metadata.StartedOn = timestamppb.New(time.Now()) - p.PbProvenance.RunDetails.Metadata.FinishedOn = timestamppb.New(time.Now()) - + case github.Name: + gh := attestor.Attestor.(github.GitHubAttestor) + p.PbProvenance.RunDetails.Builder.Id = GHABuilderId + p.PbProvenance.RunDetails.Metadata.InvocationId = gh.Data().PipelineUrl + digest := make(map[string]string) + digest["sha1"] = gh.Data().JWT.Claims["sha"].(string) + + p.PbProvenance.BuildDefinition.ResolvedDependencies = append( + p.PbProvenance.BuildDefinition.ResolvedDependencies, + &v1.ResourceDescriptor{ + Name: gh.Data().ProjectUrl, + Digest: digest, + }) + + case gitlab.Name: + gl := attestor.Attestor.(*gitlab.Attestor) + p.PbProvenance.RunDetails.Builder.Id = GLCBuilderId + p.PbProvenance.RunDetails.Metadata.InvocationId = gl.PipelineUrl + digest := make(map[string]string) + digest["sha1"] = gl.JWT.Claims["sha"].(string) + + p.PbProvenance.BuildDefinition.ResolvedDependencies = append( + p.PbProvenance.BuildDefinition.ResolvedDependencies, + &v1.ResourceDescriptor{ + Name: gl.ProjectUrl, + Digest: digest, + }) + + // Material Attestors case material.Name: - mats := attestor.Attestor.(*material.Attestor).Materials() + mats := attestor.Attestor.(material.MaterialAttestor).Materials() for name, digestSet := range mats { digests, _ := digestSet.ToNameMap() p.PbProvenance.BuildDefinition.ResolvedDependencies = append( @@ -154,17 +187,26 @@ func (p *Provenance) Attest(ctx *attestation.AttestationContext) error { }) } - case environment.Name: - envs := attestor.Attestor.(*environment.Attestor).Variables - pbEnvs := make(map[string]interface{}, len(envs)) - for name, value := range envs { - pbEnvs[name] = value + // CommandRun Attestors + case commandrun.Name: + var err error + ep := make(map[string]interface{}) + ep["command"] = strings.Join(attestor.Attestor.(commandrun.CommandRunAttestor).Data().Cmd, " ") + p.PbProvenance.BuildDefinition.ExternalParameters, err = structpb.NewStruct(ep) + if err != nil { + return err } + // We have start and finish time at the collection level, how do we access it here? + p.PbProvenance.RunDetails.Metadata.StartedOn = timestamppb.New(time.Now()) + p.PbProvenance.RunDetails.Metadata.FinishedOn = timestamppb.New(time.Now()) - internalParameters["env"] = pbEnvs - + // Product Attestors case product.ProductName: - p.products = attestor.Attestor.(*product.Attestor).Products() + maps.Copy(p.products, attestor.Attestor.(product.ProductAttestor).Products()) + + // Post Attestors + case oci.Name: + maps.Copy(p.products, attestor.Attestor.(product.ProductAttestor).Products()) } } diff --git a/attestation/slsa/slsa_test.go b/attestation/slsa/slsa_test.go index 0227bf14..ea8a7445 100644 --- a/attestation/slsa/slsa_test.go +++ b/attestation/slsa/slsa_test.go @@ -86,19 +86,19 @@ func TestMarshalJSON(t *testing.T) { } func TestAttest(t *testing.T) { - g := &attestors.TestGitAttestor{} - gh := &attestors.TestGitHubAttestor{} - m := &attestors.TestMaterialAttestor{} - c := &attestors.TestCommandRunAttestor{} - p := &attestors.TestProductAttestor{} - s := &Provenance{} + g := attestors.NewTestGitAttestor() + gh := attestors.NewTestGitHubAttestor() + m := attestors.NewTestMaterialAttestor() + c := attestors.NewTestCommandRunAttestor() + p := attestors.NewTestProductAttestor() + s := New() ctx, err := attestation.NewContext([]attestation.Attestor{g, gh, m, c, p, s}) if err != nil { t.Errorf("error creating attestation context: %s", err) } - err = s.Attest(ctx) + err = ctx.RunAttestors() if err != nil { t.Errorf("error attesting: %s", err.Error()) } diff --git a/go.mod b/go.mod index 32ef58fe..e2362349 100644 --- a/go.mod +++ b/go.mod @@ -11,9 +11,9 @@ require ( github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 github.com/edwarnicke/gitoid v0.0.0-20220710194850-1be5bfda1f9d github.com/go-git/go-git/v5 v5.11.0 - github.com/in-toto/attestation v1.0.1 github.com/go-jose/go-jose/v3 v3.0.3 github.com/in-toto/archivista v0.4.0 + github.com/in-toto/attestation v1.0.1 github.com/jellydator/ttlcache/v3 v3.1.1 github.com/mattn/go-isatty v0.0.20 github.com/mitchellh/go-homedir v1.1.0 @@ -23,7 +23,7 @@ require ( github.com/spiffe/go-spiffe/v2 v2.1.7 github.com/stretchr/testify v1.8.4 go.step.sm/crypto v0.43.1 - golang.org/x/sys v0.17.0 + golang.org/x/sys v0.18.0 google.golang.org/api v0.167.0 google.golang.org/grpc v1.62.1 gopkg.in/square/go-jose.v2 v2.6.0 @@ -98,11 +98,11 @@ require ( go.opentelemetry.io/otel/sdk v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/goleak v1.3.0 // indirect - golang.org/x/mod v0.15.0 // indirect + golang.org/x/mod v0.16.0 // indirect golang.org/x/oauth2 v0.17.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.18.0 // indirect + golang.org/x/tools v0.19.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c // indirect @@ -136,9 +136,10 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/yashtewari/glob-intersection v0.2.0 // indirect github.com/zeebo/errs v1.3.0 // indirect - golang.org/x/crypto v0.20.0 // indirect - golang.org/x/net v0.21.0 // indirect - golang.org/x/term v0.17.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 + golang.org/x/net v0.22.0 // indirect + golang.org/x/term v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/genproto v0.0.0-20240221002015-b0ce06bbee7c // indirect google.golang.org/protobuf v1.33.0 diff --git a/go.sum b/go.sum index 6e113181..cd6e0690 100644 --- a/go.sum +++ b/go.sum @@ -188,8 +188,6 @@ github.com/google/go-containerregistry v0.19.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/in-toto/attestation v1.0.1 h1:DgX1XuBkryTpj1Piq8AiMK3CMfEcec3Qv6+Ku+uI3WY= -github.com/in-toto/attestation v1.0.1/go.mod h1:hCR5COCuENh5+VfojEkJnt7caOymbEgvyZdKifD6pOw= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -205,6 +203,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0Q github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/in-toto/archivista v0.4.0 h1:5g79iqmyXblnnwuD+768lrEbeoE0V5H7URYJFnr0p4I= github.com/in-toto/archivista v0.4.0/go.mod h1:HgqAu7az0Ql0Jf844Paf0Ji5PdUMKxO5JIBh4hOjMs8= +github.com/in-toto/attestation v1.0.1 h1:DgX1XuBkryTpj1Piq8AiMK3CMfEcec3Qv6+Ku+uI3WY= +github.com/in-toto/attestation v1.0.1/go.mod h1:hCR5COCuENh5+VfojEkJnt7caOymbEgvyZdKifD6pOw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jellydator/ttlcache/v3 v3.1.1 h1:RCgYJqo3jgvhl+fEWvjNW8thxGWsgxi+TPhRir1Y9y8= @@ -372,7 +372,11 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= +golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -382,6 +386,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -402,6 +408,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= @@ -433,6 +441,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -441,6 +451,8 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -468,6 +480,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= +golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= +golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/attestors/commandrun.go b/internal/attestors/commandrun.go index f3cdc065..d7d255af 100644 --- a/internal/attestors/commandrun.go +++ b/internal/attestors/commandrun.go @@ -25,12 +25,12 @@ var ( ) type TestCommandRunAttestor struct { - comAtt commandrun.CommandRunAttestor + comAtt commandrun.CommandRun } -func (t *TestCommandRunAttestor) New() *TestCommandRunAttestor { - att := &commandrun.CommandRun{} - return &TestCommandRunAttestor{comAtt: att} +func NewTestCommandRunAttestor() *TestCommandRunAttestor { + att := commandrun.New() + return &TestCommandRunAttestor{comAtt: *att} } func (t *TestCommandRunAttestor) Name() string { @@ -49,6 +49,10 @@ func (t *TestCommandRunAttestor) Attest(ctx *attestation.AttestationContext) err return nil } +func (t *TestCommandRunAttestor) Data() *commandrun.CommandRun { + return &t.comAtt +} + func (t *TestCommandRunAttestor) CommandRuns() map[string]cryptoutil.DigestSet { return nil } diff --git a/internal/attestors/git.go b/internal/attestors/git.go index 099871de..bb47e37c 100644 --- a/internal/attestors/git.go +++ b/internal/attestors/git.go @@ -25,12 +25,12 @@ var ( ) type TestGitAttestor struct { - gitAtt git.GitAttestor + gitAtt git.Attestor } -func (t *TestGitAttestor) New() *TestGitAttestor { - att := &git.Attestor{} - return &TestGitAttestor{gitAtt: att} +func NewTestGitAttestor() *TestGitAttestor { + att := git.New() + return &TestGitAttestor{gitAtt: *att} } func (t *TestGitAttestor) Name() string { @@ -49,6 +49,10 @@ func (t *TestGitAttestor) Attest(ctx *attestation.AttestationContext) error { return nil } +func (t *TestGitAttestor) Data() *git.Attestor { + return &t.gitAtt +} + func (t *TestGitAttestor) Subjects() map[string]cryptoutil.DigestSet { return nil } diff --git a/internal/attestors/github.go b/internal/attestors/github.go index c4e62eaa..ee8b733f 100644 --- a/internal/attestors/github.go +++ b/internal/attestors/github.go @@ -17,6 +17,7 @@ package attestors import ( "github.com/in-toto/go-witness/attestation" "github.com/in-toto/go-witness/attestation/github" + "github.com/in-toto/go-witness/attestation/jwt" "github.com/in-toto/go-witness/cryptoutil" ) @@ -25,12 +26,14 @@ var ( ) type TestGitHubAttestor struct { - githubAtt github.GitHubAttestor + githubAtt github.Attestor } -func (t *TestGitHubAttestor) New() *TestGitHubAttestor { - att := &github.Attestor{} - return &TestGitHubAttestor{githubAtt: att} +func NewTestGitHubAttestor() *TestGitHubAttestor { + att := github.New() + att.JWT = jwt.New() + att.JWT.Claims["sha"] = "sha123abc" + return &TestGitHubAttestor{githubAtt: *att} } func (t *TestGitHubAttestor) Name() string { @@ -49,6 +52,10 @@ func (t *TestGitHubAttestor) Attest(ctx *attestation.AttestationContext) error { return nil } +func (t *TestGitHubAttestor) Data() *github.Attestor { + return &t.githubAtt +} + func (t *TestGitHubAttestor) Subjects() map[string]cryptoutil.DigestSet { return nil } diff --git a/internal/attestors/material.go b/internal/attestors/material.go index 1d7ac6f0..227976a6 100644 --- a/internal/attestors/material.go +++ b/internal/attestors/material.go @@ -28,8 +28,8 @@ type TestMaterialAttestor struct { matAtt material.MaterialAttestor } -func (t *TestMaterialAttestor) New() *TestMaterialAttestor { - att := &material.Attestor{} +func NewTestMaterialAttestor() *TestMaterialAttestor { + att := material.New() return &TestMaterialAttestor{matAtt: att} } diff --git a/internal/attestors/product.go b/internal/attestors/product.go index d6f96452..93299bd7 100644 --- a/internal/attestors/product.go +++ b/internal/attestors/product.go @@ -28,8 +28,8 @@ type TestProductAttestor struct { prodAtt product.ProductAttestor } -func (t *TestProductAttestor) New() *TestProductAttestor { - att := &product.Attestor{} +func NewTestProductAttestor() *TestProductAttestor { + att := product.New() return &TestProductAttestor{prodAtt: att} } From 61e81650f84b8020cc22493ec3afd2c8cfa15f41 Mon Sep 17 00:00:00 2001 From: chaosinthecrd Date: Thu, 4 Apr 2024 15:47:26 +0100 Subject: [PATCH 23/33] added changes --- attestation/context.go | 23 ++++++++++++++++++++++- attestation/link/link.go | 2 +- attestation/slsa/slsa.go | 13 ++++++++----- run.go | 2 +- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/attestation/context.go b/attestation/context.go index bef8e7bc..3dd70284 100644 --- a/attestation/context.go +++ b/attestation/context.go @@ -92,6 +92,9 @@ type AttestationContext struct { completedAttestors []CompletedAttestor products map[string]Product materials map[string]cryptoutil.DigestSet + stepName string + startTime time.Time + endTime time.Time } type Product struct { @@ -99,7 +102,7 @@ type Product struct { Digest cryptoutil.DigestSet `json:"digest"` } -func NewContext(attestors []Attestor, opts ...AttestationContextOption) (*AttestationContext, error) { +func NewContext(stepName string, attestors []Attestor, opts ...AttestationContextOption) (*AttestationContext, error) { wd, err := os.Getwd() if err != nil { return nil, err @@ -112,6 +115,7 @@ func NewContext(attestors []Attestor, opts ...AttestationContextOption) (*Attest hashes: []cryptoutil.DigestValue{{Hash: crypto.SHA256}, {Hash: crypto.SHA256, GitOID: true}, {Hash: crypto.SHA1, GitOID: true}}, materials: make(map[string]cryptoutil.DigestSet), products: make(map[string]Product), + stepName: stepName, } for _, opt := range opts { @@ -136,7 +140,12 @@ func (ctx *AttestationContext) RunAttestors() error { } order := runTypeOrder() + ctx.startTime = time.Now() for _, k := range order { + // We want to set the endTime before the PostProduct attestors run, as some (e.g., SLSA) needs to know the endTime. + if k == PostProductRunType { + ctx.endTime = time.Now() + } log.Debugf("Starting %s attestors...", k.String()) for _, att := range attestors[k] { log.Infof("Starting %v attestor...", att.Name()) @@ -209,6 +218,18 @@ func (ctx *AttestationContext) Products() map[string]Product { return out } +func (ctx *AttestationContext) StepName() string { + return ctx.stepName +} + +func (ctx *AttestationContext) StartTime() time.Time { + return ctx.startTime +} + +func (ctx *AttestationContext) EndTime() time.Time { + return ctx.endTime +} + func (ctx *AttestationContext) addMaterials(materialer Materialer) { newMats := materialer.Materials() for k, v := range newMats { diff --git a/attestation/link/link.go b/attestation/link/link.go index 40b86816..25aac535 100644 --- a/attestation/link/link.go +++ b/attestation/link/link.go @@ -99,7 +99,7 @@ func (l *Link) Export() bool { } func (l *Link) Attest(ctx *attestation.AttestationContext) error { - l.PbLink.Name = "stepNameHere" + l.PbLink.Name = ctx.StepName() for _, attestor := range ctx.CompletedAttestors() { switch name := attestor.Attestor.Name(); name { case commandrun.Name: diff --git a/attestation/slsa/slsa.go b/attestation/slsa/slsa.go index f8abf7c1..ebf70bdf 100644 --- a/attestation/slsa/slsa.go +++ b/attestation/slsa/slsa.go @@ -18,7 +18,6 @@ import ( "encoding/json" "fmt" "strings" - "time" prov "github.com/in-toto/attestation/go/predicates/provenance/v1" v1 "github.com/in-toto/attestation/go/v1" @@ -32,6 +31,7 @@ import ( "github.com/in-toto/go-witness/attestation/oci" "github.com/in-toto/go-witness/attestation/product" "github.com/in-toto/go-witness/cryptoutil" + "github.com/in-toto/go-witness/log" "github.com/in-toto/go-witness/registry" "golang.org/x/exp/maps" "google.golang.org/protobuf/types/known/structpb" @@ -196,13 +196,16 @@ func (p *Provenance) Attest(ctx *attestation.AttestationContext) error { if err != nil { return err } - // We have start and finish time at the collection level, how do we access it here? - p.PbProvenance.RunDetails.Metadata.StartedOn = timestamppb.New(time.Now()) - p.PbProvenance.RunDetails.Metadata.FinishedOn = timestamppb.New(time.Now()) + + log.Info("start time is", ctx.StartTime()) + log.Info("end time is", ctx.EndTime()) + p.PbProvenance.RunDetails.Metadata.StartedOn = timestamppb.New(ctx.StartTime()) + p.PbProvenance.RunDetails.Metadata.FinishedOn = timestamppb.New(ctx.EndTime()) // Product Attestors case product.ProductName: - maps.Copy(p.products, attestor.Attestor.(product.ProductAttestor).Products()) + p.products = make(map[string]attestation.Product) + maps.Copy(p.products, ctx.Products()) // Post Attestors case oci.Name: diff --git a/run.go b/run.go index bb0c8083..2f5f3bde 100644 --- a/run.go +++ b/run.go @@ -95,7 +95,7 @@ func run(stepName string, signer cryptoutil.Signer, opts []RunOption) ([]RunResu return result, err } - runCtx, err := attestation.NewContext(ro.attestors, ro.attestationOpts...) + runCtx, err := attestation.NewContext(stepName, ro.attestors, ro.attestationOpts...) if err != nil { return result, fmt.Errorf("failed to create attestation context: %w", err) } From b8923d2737e420f75adfbea79ff03069282a0af8 Mon Sep 17 00:00:00 2001 From: chaosinthecrd Date: Thu, 4 Apr 2024 19:20:57 +0100 Subject: [PATCH 24/33] adding changes to merge into main PR --- attestation/aws-iid/aws-iid_test.go | 5 ++--- attestation/context.go | 6 ++++-- attestation/environment/environment_test.go | 2 +- attestation/factory.go | 5 +++++ attestation/git/git_test.go | 4 ++-- attestation/link/link.go | 16 +------------- attestation/maven/maven_test.go | 2 +- attestation/oci/oci_test.go | 2 +- attestation/product/product_test.go | 6 +++--- attestation/slsa/slsa.go | 12 +++++------ attestation/slsa/slsa_test.go | 2 +- run.go | 24 ++++----------------- 12 files changed, 31 insertions(+), 55 deletions(-) diff --git a/attestation/aws-iid/aws-iid_test.go b/attestation/aws-iid/aws-iid_test.go index bcda075b..dd4bf7b1 100644 --- a/attestation/aws-iid/aws-iid_test.go +++ b/attestation/aws-iid/aws-iid_test.go @@ -112,11 +112,10 @@ func TestAttestor_Attest(t *testing.T) { conf: conf, } - ctx, err := attestation.NewContext([]attestation.Attestor{a}) + ctx, err := attestation.NewContext("test", []attestation.Attestor{a}) require.NoError(t, err) err = a.Attest(ctx) require.NoError(t, err) - } func TestAttestor_getIID(t *testing.T) { @@ -154,7 +153,7 @@ func TestAttestor_Subjects(t *testing.T) { conf: conf, } - ctx, err := attestation.NewContext([]attestation.Attestor{a}) + ctx, err := attestation.NewContext("test", []attestation.Attestor{a}) require.NoError(t, err) err = a.Attest(ctx) require.NoError(t, err) diff --git a/attestation/context.go b/attestation/context.go index 3dd70284..bfe1e886 100644 --- a/attestation/context.go +++ b/attestation/context.go @@ -93,8 +93,10 @@ type AttestationContext struct { products map[string]Product materials map[string]cryptoutil.DigestSet stepName string - startTime time.Time - endTime time.Time + // startTime is the time that the attestation process started + startTime time.Time + // endTime is the time that the attestation process ended (except PostProductRunType, as this runs after the fact) + endTime time.Time } type Product struct { diff --git a/attestation/environment/environment_test.go b/attestation/environment/environment_test.go index b958cd32..8a4eddc8 100644 --- a/attestation/environment/environment_test.go +++ b/attestation/environment/environment_test.go @@ -24,7 +24,7 @@ import ( func TestEnvironment(t *testing.T) { attestor := New() - ctx, err := attestation.NewContext([]attestation.Attestor{attestor}) + ctx, err := attestation.NewContext("test", []attestation.Attestor{attestor}) require.NoError(t, err) t.Setenv("AWS_ACCESS_KEY_ID", "super secret") diff --git a/attestation/factory.go b/attestation/factory.go index a78bfabb..9b237380 100644 --- a/attestation/factory.go +++ b/attestation/factory.go @@ -56,6 +56,11 @@ type Producer interface { Products() map[string]Product } +// Exporter allows attestors to export their attestations for separation from the collection. +type Exporter interface { + Export() bool +} + // BackReffer allows attestors to indicate which of their subjects are good candidates // to find related attestations. For example the git attestor's commit hash subject // is a good candidate to find all attestation collections that also refer to a specific diff --git a/attestation/git/git_test.go b/attestation/git/git_test.go index 30c06944..0c1ce744 100644 --- a/attestation/git/git_test.go +++ b/attestation/git/git_test.go @@ -49,7 +49,7 @@ func TestRunWorksWithCommits(t *testing.T) { _, dir, cleanup := createTestRepo(t, true) defer cleanup() - ctx, err := attestation.NewContext([]attestation.Attestor{attestor}, attestation.WithWorkingDir(dir)) + ctx, err := attestation.NewContext("test", []attestation.Attestor{attestor}, attestation.WithWorkingDir(dir)) require.NoError(t, err, "Expected no error from NewContext") err = ctx.RunAttestors() @@ -146,7 +146,7 @@ func TestRunWorksWithoutCommits(t *testing.T) { _, dir, cleanup := createTestRepo(t, false) defer cleanup() - ctx, err := attestation.NewContext([]attestation.Attestor{attestor}, attestation.WithWorkingDir(dir)) + ctx, err := attestation.NewContext("test", []attestation.Attestor{attestor}, attestation.WithWorkingDir(dir)) require.NoError(t, err, "Expected no error from NewContext") err = ctx.RunAttestors() diff --git a/attestation/link/link.go b/attestation/link/link.go index 25aac535..97b38d27 100644 --- a/attestation/link/link.go +++ b/attestation/link/link.go @@ -26,7 +26,6 @@ import ( "github.com/in-toto/go-witness/attestation/material" "github.com/in-toto/go-witness/attestation/product" "github.com/in-toto/go-witness/cryptoutil" - "github.com/in-toto/go-witness/registry" "google.golang.org/protobuf/types/known/structpb" ) @@ -48,19 +47,6 @@ var ( func init() { attestation.RegisterAttestation(Name, Type, RunType, func() attestation.Attestor { return New() }, - registry.BoolConfigOption( - "export", - "Export the link attestation to its own file", - defaultExport, - func(a attestation.Attestor, export bool) (attestation.Attestor, error) { - linkAttestor, ok := a.(*Link) - if !ok { - return a, fmt.Errorf("unexpected attestor type: %T is not a link attestor", a) - } - WithExport(export)(linkAttestor) - return linkAttestor, nil - }, - ), ) } @@ -95,7 +81,7 @@ func (l *Link) RunType() attestation.RunType { } func (l *Link) Export() bool { - return l.export + return true } func (l *Link) Attest(ctx *attestation.AttestationContext) error { diff --git a/attestation/maven/maven_test.go b/attestation/maven/maven_test.go index 8e67ccd8..84815c79 100644 --- a/attestation/maven/maven_test.go +++ b/attestation/maven/maven_test.go @@ -67,7 +67,7 @@ func TestMaven(t *testing.T) { } t.Run(test.name, func(t *testing.T) { - ctx, err := attestation.NewContext([]attestation.Attestor{}, attestation.WithWorkingDir(workingDir)) + ctx, err := attestation.NewContext("test", []attestation.Attestor{}, attestation.WithWorkingDir(workingDir)) require.NoError(t, err) a := New(WithPom(p)) require.NoError(t, a.Attest(ctx)) diff --git a/attestation/oci/oci_test.go b/attestation/oci/oci_test.go index fd64e74f..4068e2d9 100644 --- a/attestation/oci/oci_test.go +++ b/attestation/oci/oci_test.go @@ -108,7 +108,7 @@ func TestAttestor_Attest(t *testing.T) { Digest: tarDigest, } - ctx, err := attestation.NewContext([]attestation.Attestor{testProducter{testProductSet}, a}) + ctx, err := attestation.NewContext("test", []attestation.Attestor{testProducter{testProductSet}, a}) if err != nil { t.Fatal(err) } diff --git a/attestation/product/product_test.go b/attestation/product/product_test.go index cc30a30a..4923081f 100644 --- a/attestation/product/product_test.go +++ b/attestation/product/product_test.go @@ -65,7 +65,7 @@ func TestAttestorAttest(t *testing.T) { testDigestSet := make(map[string]cryptoutil.DigestSet) testDigestSet["test"] = testDigest a.baseArtifacts = testDigestSet - ctx, err := attestation.NewContext([]attestation.Attestor{a}) + ctx, err := attestation.NewContext("test", []attestation.Attestor{a}) require.NoError(t, err) require.NoError(t, a.Attest(ctx)) } @@ -174,7 +174,7 @@ func TestIncludeExcludeGlobs(t *testing.T) { } t.Run("default include all", func(t *testing.T) { - ctx, err := attestation.NewContext([]attestation.Attestor{}, attestation.WithWorkingDir(workingDir)) + ctx, err := attestation.NewContext("test", []attestation.Attestor{}, attestation.WithWorkingDir(workingDir)) require.NoError(t, err) a := New() require.NoError(t, a.Attest(ctx)) @@ -183,7 +183,7 @@ func TestIncludeExcludeGlobs(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - ctx, err := attestation.NewContext([]attestation.Attestor{}, attestation.WithWorkingDir(workingDir)) + ctx, err := attestation.NewContext("test", []attestation.Attestor{}, attestation.WithWorkingDir(workingDir)) require.NoError(t, err) a := New() WithIncludeGlob(test.includeGlob)(a) diff --git a/attestation/slsa/slsa.go b/attestation/slsa/slsa.go index ebf70bdf..3bb7e5b1 100644 --- a/attestation/slsa/slsa.go +++ b/attestation/slsa/slsa.go @@ -31,7 +31,6 @@ import ( "github.com/in-toto/go-witness/attestation/oci" "github.com/in-toto/go-witness/attestation/product" "github.com/in-toto/go-witness/cryptoutil" - "github.com/in-toto/go-witness/log" "github.com/in-toto/go-witness/registry" "golang.org/x/exp/maps" "google.golang.org/protobuf/types/known/structpb" @@ -106,7 +105,7 @@ func (p *Provenance) RunType() attestation.RunType { } func (p *Provenance) Export() bool { - return p.export + return true } func (p *Provenance) Attest(ctx *attestation.AttestationContext) error { @@ -197,15 +196,16 @@ func (p *Provenance) Attest(ctx *attestation.AttestationContext) error { return err } - log.Info("start time is", ctx.StartTime()) - log.Info("end time is", ctx.EndTime()) p.PbProvenance.RunDetails.Metadata.StartedOn = timestamppb.New(ctx.StartTime()) p.PbProvenance.RunDetails.Metadata.FinishedOn = timestamppb.New(ctx.EndTime()) // Product Attestors case product.ProductName: - p.products = make(map[string]attestation.Product) - maps.Copy(p.products, ctx.Products()) + if p.products == nil { + p.products = ctx.Products() + } else { + maps.Copy(p.products, ctx.Products()) + } // Post Attestors case oci.Name: diff --git a/attestation/slsa/slsa_test.go b/attestation/slsa/slsa_test.go index ea8a7445..90fa2725 100644 --- a/attestation/slsa/slsa_test.go +++ b/attestation/slsa/slsa_test.go @@ -93,7 +93,7 @@ func TestAttest(t *testing.T) { p := attestors.NewTestProductAttestor() s := New() - ctx, err := attestation.NewContext([]attestation.Attestor{g, gh, m, c, p, s}) + ctx, err := attestation.NewContext("test", []attestation.Attestor{g, gh, m, c, p, s}) if err != nil { t.Errorf("error creating attestation context: %s", err) } diff --git a/run.go b/run.go index 2f5f3bde..132d277f 100644 --- a/run.go +++ b/run.go @@ -23,8 +23,6 @@ import ( "github.com/in-toto/go-witness/attestation" "github.com/in-toto/go-witness/attestation/environment" "github.com/in-toto/go-witness/attestation/git" - "github.com/in-toto/go-witness/attestation/link" - "github.com/in-toto/go-witness/attestation/slsa" "github.com/in-toto/go-witness/cryptoutil" "github.com/in-toto/go-witness/dsse" "github.com/in-toto/go-witness/intoto" @@ -108,29 +106,15 @@ func run(stepName string, signer cryptoutil.Signer, opts []RunOption) ([]RunResu for _, r := range runCtx.CompletedAttestors() { if r.Error != nil { errs = append(errs, r.Error) - } else if r.Attestor.Name() == link.Name { - // TODO: Find a better way to set stepName - r.Attestor.(*link.Link).PbLink.Name = ro.stepName - - // TODO: Add Exporter interface to attestors - if r.Attestor.(*link.Link).Export() { - if subjecter, ok := r.Attestor.(attestation.Subjecter); ok { - linkEnvelope, err := createAndSignEnvelope(r.Attestor, r.Attestor.Type(), subjecter.Subjects(), dsse.SignWithSigners(ro.signer), dsse.SignWithTimestampers(ro.timestampers...)) - if err != nil { - return result, fmt.Errorf("failed to sign envelope: %w", err) - } - result = append(result, RunResult{SignedEnvelope: linkEnvelope, AttestorName: r.Attestor.Name()}) - } - } - } else if r.Attestor.Name() == slsa.Name { + } else { // TODO: Add Exporter interface to attestors - if r.Attestor.(*slsa.Provenance).Export() { + if _, ok := r.Attestor.(attestation.Exporter); ok { if subjecter, ok := r.Attestor.(attestation.Subjecter); ok { - linkEnvelope, err := createAndSignEnvelope(r.Attestor, r.Attestor.Type(), subjecter.Subjects(), dsse.SignWithSigners(ro.signer), dsse.SignWithTimestampers(ro.timestampers...)) + envelope, err := createAndSignEnvelope(r.Attestor, r.Attestor.Type(), subjecter.Subjects(), dsse.SignWithSigners(ro.signer), dsse.SignWithTimestampers(ro.timestampers...)) if err != nil { return result, fmt.Errorf("failed to sign envelope: %w", err) } - result = append(result, RunResult{SignedEnvelope: linkEnvelope, AttestorName: r.Attestor.Name()}) + result = append(result, RunResult{SignedEnvelope: envelope, AttestorName: r.Attestor.Name()}) } } } From 4d86ee92e061270b989a10be1cdd2cdc8c6e1432 Mon Sep 17 00:00:00 2001 From: Tom Meadows Date: Thu, 4 Apr 2024 21:09:43 +0100 Subject: [PATCH 25/33] Link attestor proposed changes (#204) * unmarshal the time in the attestation collection correctly (#203) * add StepName to AttestorContext * use CollectionAttestion to properly set start/end times --------- Signed-off-by: John Kjell Co-authored-by: Cole Kennedy Co-authored-by: Cole Co-authored-by: John Kjell --- attestation/aws-iid/aws-iid_test.go | 5 ++-- attestation/collection.go | 4 ++++ attestation/context.go | 8 ++++++- attestation/environment/environment_test.go | 2 +- attestation/factory.go | 5 ++++ attestation/git/git_test.go | 4 ++-- attestation/link/link.go | 18 ++------------ attestation/maven/maven_test.go | 2 +- attestation/oci/oci_test.go | 2 +- attestation/product/product_test.go | 6 ++--- attestation/slsa/slsa.go | 15 +++++++----- attestation/slsa/slsa_test.go | 2 +- run.go | 26 ++++----------------- 13 files changed, 43 insertions(+), 56 deletions(-) diff --git a/attestation/aws-iid/aws-iid_test.go b/attestation/aws-iid/aws-iid_test.go index bcda075b..dd4bf7b1 100644 --- a/attestation/aws-iid/aws-iid_test.go +++ b/attestation/aws-iid/aws-iid_test.go @@ -112,11 +112,10 @@ func TestAttestor_Attest(t *testing.T) { conf: conf, } - ctx, err := attestation.NewContext([]attestation.Attestor{a}) + ctx, err := attestation.NewContext("test", []attestation.Attestor{a}) require.NoError(t, err) err = a.Attest(ctx) require.NoError(t, err) - } func TestAttestor_getIID(t *testing.T) { @@ -154,7 +153,7 @@ func TestAttestor_Subjects(t *testing.T) { conf: conf, } - ctx, err := attestation.NewContext([]attestation.Attestor{a}) + ctx, err := attestation.NewContext("test", []attestation.Attestor{a}) require.NoError(t, err) err = a.Attest(ctx) require.NoError(t, err) diff --git a/attestation/collection.go b/attestation/collection.go index 9add8895..ab084e30 100644 --- a/attestation/collection.go +++ b/attestation/collection.go @@ -65,6 +65,8 @@ func (c *CollectionAttestation) UnmarshalJSON(data []byte) error { proposed := struct { Type string `json:"type"` Attestation json.RawMessage `json:"attestation"` + StartTime time.Time `json:"starttime"` + EndTime time.Time `json:"endtime"` }{} if err := json.Unmarshal(data, &proposed); err != nil { @@ -83,6 +85,8 @@ func (c *CollectionAttestation) UnmarshalJSON(data []byte) error { c.Type = proposed.Type c.Attestation = newAttest + c.StartTime = proposed.StartTime + c.EndTime = proposed.EndTime return nil } diff --git a/attestation/context.go b/attestation/context.go index bef8e7bc..12ecfea0 100644 --- a/attestation/context.go +++ b/attestation/context.go @@ -92,6 +92,7 @@ type AttestationContext struct { completedAttestors []CompletedAttestor products map[string]Product materials map[string]cryptoutil.DigestSet + stepName string } type Product struct { @@ -99,7 +100,7 @@ type Product struct { Digest cryptoutil.DigestSet `json:"digest"` } -func NewContext(attestors []Attestor, opts ...AttestationContextOption) (*AttestationContext, error) { +func NewContext(stepName string, attestors []Attestor, opts ...AttestationContextOption) (*AttestationContext, error) { wd, err := os.Getwd() if err != nil { return nil, err @@ -112,6 +113,7 @@ func NewContext(attestors []Attestor, opts ...AttestationContextOption) (*Attest hashes: []cryptoutil.DigestValue{{Hash: crypto.SHA256}, {Hash: crypto.SHA256, GitOID: true}, {Hash: crypto.SHA1, GitOID: true}}, materials: make(map[string]cryptoutil.DigestSet), products: make(map[string]Product), + stepName: stepName, } for _, opt := range opts { @@ -209,6 +211,10 @@ func (ctx *AttestationContext) Products() map[string]Product { return out } +func (ctx *AttestationContext) StepName() string { + return ctx.stepName +} + func (ctx *AttestationContext) addMaterials(materialer Materialer) { newMats := materialer.Materials() for k, v := range newMats { diff --git a/attestation/environment/environment_test.go b/attestation/environment/environment_test.go index b958cd32..8a4eddc8 100644 --- a/attestation/environment/environment_test.go +++ b/attestation/environment/environment_test.go @@ -24,7 +24,7 @@ import ( func TestEnvironment(t *testing.T) { attestor := New() - ctx, err := attestation.NewContext([]attestation.Attestor{attestor}) + ctx, err := attestation.NewContext("test", []attestation.Attestor{attestor}) require.NoError(t, err) t.Setenv("AWS_ACCESS_KEY_ID", "super secret") diff --git a/attestation/factory.go b/attestation/factory.go index a78bfabb..9b237380 100644 --- a/attestation/factory.go +++ b/attestation/factory.go @@ -56,6 +56,11 @@ type Producer interface { Products() map[string]Product } +// Exporter allows attestors to export their attestations for separation from the collection. +type Exporter interface { + Export() bool +} + // BackReffer allows attestors to indicate which of their subjects are good candidates // to find related attestations. For example the git attestor's commit hash subject // is a good candidate to find all attestation collections that also refer to a specific diff --git a/attestation/git/git_test.go b/attestation/git/git_test.go index 30c06944..0c1ce744 100644 --- a/attestation/git/git_test.go +++ b/attestation/git/git_test.go @@ -49,7 +49,7 @@ func TestRunWorksWithCommits(t *testing.T) { _, dir, cleanup := createTestRepo(t, true) defer cleanup() - ctx, err := attestation.NewContext([]attestation.Attestor{attestor}, attestation.WithWorkingDir(dir)) + ctx, err := attestation.NewContext("test", []attestation.Attestor{attestor}, attestation.WithWorkingDir(dir)) require.NoError(t, err, "Expected no error from NewContext") err = ctx.RunAttestors() @@ -146,7 +146,7 @@ func TestRunWorksWithoutCommits(t *testing.T) { _, dir, cleanup := createTestRepo(t, false) defer cleanup() - ctx, err := attestation.NewContext([]attestation.Attestor{attestor}, attestation.WithWorkingDir(dir)) + ctx, err := attestation.NewContext("test", []attestation.Attestor{attestor}, attestation.WithWorkingDir(dir)) require.NoError(t, err, "Expected no error from NewContext") err = ctx.RunAttestors() diff --git a/attestation/link/link.go b/attestation/link/link.go index 40b86816..97b38d27 100644 --- a/attestation/link/link.go +++ b/attestation/link/link.go @@ -26,7 +26,6 @@ import ( "github.com/in-toto/go-witness/attestation/material" "github.com/in-toto/go-witness/attestation/product" "github.com/in-toto/go-witness/cryptoutil" - "github.com/in-toto/go-witness/registry" "google.golang.org/protobuf/types/known/structpb" ) @@ -48,19 +47,6 @@ var ( func init() { attestation.RegisterAttestation(Name, Type, RunType, func() attestation.Attestor { return New() }, - registry.BoolConfigOption( - "export", - "Export the link attestation to its own file", - defaultExport, - func(a attestation.Attestor, export bool) (attestation.Attestor, error) { - linkAttestor, ok := a.(*Link) - if !ok { - return a, fmt.Errorf("unexpected attestor type: %T is not a link attestor", a) - } - WithExport(export)(linkAttestor) - return linkAttestor, nil - }, - ), ) } @@ -95,11 +81,11 @@ func (l *Link) RunType() attestation.RunType { } func (l *Link) Export() bool { - return l.export + return true } func (l *Link) Attest(ctx *attestation.AttestationContext) error { - l.PbLink.Name = "stepNameHere" + l.PbLink.Name = ctx.StepName() for _, attestor := range ctx.CompletedAttestors() { switch name := attestor.Attestor.Name(); name { case commandrun.Name: diff --git a/attestation/maven/maven_test.go b/attestation/maven/maven_test.go index 8e67ccd8..84815c79 100644 --- a/attestation/maven/maven_test.go +++ b/attestation/maven/maven_test.go @@ -67,7 +67,7 @@ func TestMaven(t *testing.T) { } t.Run(test.name, func(t *testing.T) { - ctx, err := attestation.NewContext([]attestation.Attestor{}, attestation.WithWorkingDir(workingDir)) + ctx, err := attestation.NewContext("test", []attestation.Attestor{}, attestation.WithWorkingDir(workingDir)) require.NoError(t, err) a := New(WithPom(p)) require.NoError(t, a.Attest(ctx)) diff --git a/attestation/oci/oci_test.go b/attestation/oci/oci_test.go index fd64e74f..4068e2d9 100644 --- a/attestation/oci/oci_test.go +++ b/attestation/oci/oci_test.go @@ -108,7 +108,7 @@ func TestAttestor_Attest(t *testing.T) { Digest: tarDigest, } - ctx, err := attestation.NewContext([]attestation.Attestor{testProducter{testProductSet}, a}) + ctx, err := attestation.NewContext("test", []attestation.Attestor{testProducter{testProductSet}, a}) if err != nil { t.Fatal(err) } diff --git a/attestation/product/product_test.go b/attestation/product/product_test.go index cc30a30a..4923081f 100644 --- a/attestation/product/product_test.go +++ b/attestation/product/product_test.go @@ -65,7 +65,7 @@ func TestAttestorAttest(t *testing.T) { testDigestSet := make(map[string]cryptoutil.DigestSet) testDigestSet["test"] = testDigest a.baseArtifacts = testDigestSet - ctx, err := attestation.NewContext([]attestation.Attestor{a}) + ctx, err := attestation.NewContext("test", []attestation.Attestor{a}) require.NoError(t, err) require.NoError(t, a.Attest(ctx)) } @@ -174,7 +174,7 @@ func TestIncludeExcludeGlobs(t *testing.T) { } t.Run("default include all", func(t *testing.T) { - ctx, err := attestation.NewContext([]attestation.Attestor{}, attestation.WithWorkingDir(workingDir)) + ctx, err := attestation.NewContext("test", []attestation.Attestor{}, attestation.WithWorkingDir(workingDir)) require.NoError(t, err) a := New() require.NoError(t, a.Attest(ctx)) @@ -183,7 +183,7 @@ func TestIncludeExcludeGlobs(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - ctx, err := attestation.NewContext([]attestation.Attestor{}, attestation.WithWorkingDir(workingDir)) + ctx, err := attestation.NewContext("test", []attestation.Attestor{}, attestation.WithWorkingDir(workingDir)) require.NoError(t, err) a := New() WithIncludeGlob(test.includeGlob)(a) diff --git a/attestation/slsa/slsa.go b/attestation/slsa/slsa.go index f8abf7c1..32da8e9c 100644 --- a/attestation/slsa/slsa.go +++ b/attestation/slsa/slsa.go @@ -18,7 +18,6 @@ import ( "encoding/json" "fmt" "strings" - "time" prov "github.com/in-toto/attestation/go/predicates/provenance/v1" v1 "github.com/in-toto/attestation/go/v1" @@ -106,7 +105,7 @@ func (p *Provenance) RunType() attestation.RunType { } func (p *Provenance) Export() bool { - return p.export + return true } func (p *Provenance) Attest(ctx *attestation.AttestationContext) error { @@ -196,13 +195,17 @@ func (p *Provenance) Attest(ctx *attestation.AttestationContext) error { if err != nil { return err } - // We have start and finish time at the collection level, how do we access it here? - p.PbProvenance.RunDetails.Metadata.StartedOn = timestamppb.New(time.Now()) - p.PbProvenance.RunDetails.Metadata.FinishedOn = timestamppb.New(time.Now()) + + p.PbProvenance.RunDetails.Metadata.StartedOn = timestamppb.New(attestor.StartTime) + p.PbProvenance.RunDetails.Metadata.FinishedOn = timestamppb.New(attestor.EndTime) // Product Attestors case product.ProductName: - maps.Copy(p.products, attestor.Attestor.(product.ProductAttestor).Products()) + if p.products == nil { + p.products = ctx.Products() + } else { + maps.Copy(p.products, ctx.Products()) + } // Post Attestors case oci.Name: diff --git a/attestation/slsa/slsa_test.go b/attestation/slsa/slsa_test.go index ea8a7445..90fa2725 100644 --- a/attestation/slsa/slsa_test.go +++ b/attestation/slsa/slsa_test.go @@ -93,7 +93,7 @@ func TestAttest(t *testing.T) { p := attestors.NewTestProductAttestor() s := New() - ctx, err := attestation.NewContext([]attestation.Attestor{g, gh, m, c, p, s}) + ctx, err := attestation.NewContext("test", []attestation.Attestor{g, gh, m, c, p, s}) if err != nil { t.Errorf("error creating attestation context: %s", err) } diff --git a/run.go b/run.go index bb0c8083..132d277f 100644 --- a/run.go +++ b/run.go @@ -23,8 +23,6 @@ import ( "github.com/in-toto/go-witness/attestation" "github.com/in-toto/go-witness/attestation/environment" "github.com/in-toto/go-witness/attestation/git" - "github.com/in-toto/go-witness/attestation/link" - "github.com/in-toto/go-witness/attestation/slsa" "github.com/in-toto/go-witness/cryptoutil" "github.com/in-toto/go-witness/dsse" "github.com/in-toto/go-witness/intoto" @@ -95,7 +93,7 @@ func run(stepName string, signer cryptoutil.Signer, opts []RunOption) ([]RunResu return result, err } - runCtx, err := attestation.NewContext(ro.attestors, ro.attestationOpts...) + runCtx, err := attestation.NewContext(stepName, ro.attestors, ro.attestationOpts...) if err != nil { return result, fmt.Errorf("failed to create attestation context: %w", err) } @@ -108,29 +106,15 @@ func run(stepName string, signer cryptoutil.Signer, opts []RunOption) ([]RunResu for _, r := range runCtx.CompletedAttestors() { if r.Error != nil { errs = append(errs, r.Error) - } else if r.Attestor.Name() == link.Name { - // TODO: Find a better way to set stepName - r.Attestor.(*link.Link).PbLink.Name = ro.stepName - - // TODO: Add Exporter interface to attestors - if r.Attestor.(*link.Link).Export() { - if subjecter, ok := r.Attestor.(attestation.Subjecter); ok { - linkEnvelope, err := createAndSignEnvelope(r.Attestor, r.Attestor.Type(), subjecter.Subjects(), dsse.SignWithSigners(ro.signer), dsse.SignWithTimestampers(ro.timestampers...)) - if err != nil { - return result, fmt.Errorf("failed to sign envelope: %w", err) - } - result = append(result, RunResult{SignedEnvelope: linkEnvelope, AttestorName: r.Attestor.Name()}) - } - } - } else if r.Attestor.Name() == slsa.Name { + } else { // TODO: Add Exporter interface to attestors - if r.Attestor.(*slsa.Provenance).Export() { + if _, ok := r.Attestor.(attestation.Exporter); ok { if subjecter, ok := r.Attestor.(attestation.Subjecter); ok { - linkEnvelope, err := createAndSignEnvelope(r.Attestor, r.Attestor.Type(), subjecter.Subjects(), dsse.SignWithSigners(ro.signer), dsse.SignWithTimestampers(ro.timestampers...)) + envelope, err := createAndSignEnvelope(r.Attestor, r.Attestor.Type(), subjecter.Subjects(), dsse.SignWithSigners(ro.signer), dsse.SignWithTimestampers(ro.timestampers...)) if err != nil { return result, fmt.Errorf("failed to sign envelope: %w", err) } - result = append(result, RunResult{SignedEnvelope: linkEnvelope, AttestorName: r.Attestor.Name()}) + result = append(result, RunResult{SignedEnvelope: envelope, AttestorName: r.Attestor.Name()}) } } } From 450a30660b460aca1604b173843e9965d20206a8 Mon Sep 17 00:00:00 2001 From: John Kjell Date: Sat, 6 Apr 2024 16:45:59 -0500 Subject: [PATCH 26/33] Passing SLSA Attest tests for GitHub and GitLab Signed-off-by: John Kjell --- attestation/environment/environment.go | 9 + attestation/gitlab/gitlab.go | 5 + attestation/oci/oci.go | 12 ++ attestation/slsa/slsa.go | 45 +++-- attestation/slsa/slsa_test.go | 229 +++++++++++++++++-------- internal/attestors/environment.go | 53 ++++++ internal/attestors/github.go | 1 - internal/attestors/gitlab.go | 12 +- internal/attestors/oci.go | 62 +++++++ 9 files changed, 339 insertions(+), 89 deletions(-) create mode 100644 internal/attestors/environment.go create mode 100644 internal/attestors/oci.go diff --git a/attestation/environment/environment.go b/attestation/environment/environment.go index bb3e3d96..7afd4072 100644 --- a/attestation/environment/environment.go +++ b/attestation/environment/environment.go @@ -35,6 +35,15 @@ var ( _ attestation.Attestor = &Attestor{} ) +type EnvironmentAttestor interface { + // Attestor + Name() string + Type() string + RunType() attestation.RunType + Attest(ctx *attestation.AttestationContext) error + Data() *Attestor +} + func init() { attestation.RegisterAttestation(Name, Type, RunType, func() attestation.Attestor { return New() diff --git a/attestation/gitlab/gitlab.go b/attestation/gitlab/gitlab.go index 50b7bbac..133a4223 100644 --- a/attestation/gitlab/gitlab.go +++ b/attestation/gitlab/gitlab.go @@ -47,6 +47,7 @@ type GitLabAttestor interface { Type() string RunType() attestation.RunType Attest(ctx *attestation.AttestationContext) error + Data() *Attestor // Subjecter Subjects() map[string]cryptoutil.DigestSet @@ -131,6 +132,10 @@ func (a *Attestor) Attest(ctx *attestation.AttestationContext) error { return nil } +func (a *Attestor) Data() *Attestor { + return a +} + func (a *Attestor) Subjects() map[string]cryptoutil.DigestSet { subjects := make(map[string]cryptoutil.DigestSet) hashes := []cryptoutil.DigestValue{{Hash: crypto.SHA256}} diff --git a/attestation/oci/oci.go b/attestation/oci/oci.go index 853b0580..34b895b9 100644 --- a/attestation/oci/oci.go +++ b/attestation/oci/oci.go @@ -44,8 +44,20 @@ const ( var ( _ attestation.Attestor = &Attestor{} _ attestation.Subjecter = &Attestor{} + _ OCIAttestor = &Attestor{} ) +type OCIAttestor interface { + // Attestor + Name() string + Type() string + RunType() attestation.RunType + Attest(ctx *attestation.AttestationContext) error + + // Subjector + Subjects() map[string]cryptoutil.DigestSet +} + func init() { attestation.RegisterAttestation(Name, Type, RunType, func() attestation.Attestor { return New() diff --git a/attestation/slsa/slsa.go b/attestation/slsa/slsa.go index 32da8e9c..6db3e92e 100644 --- a/attestation/slsa/slsa.go +++ b/attestation/slsa/slsa.go @@ -85,6 +85,7 @@ func WithExport(export bool) Option { type Provenance struct { PbProvenance prov.Provenance products map[string]attestation.Product + subjects map[string]cryptoutil.DigestSet export bool } @@ -123,7 +124,7 @@ func (p *Provenance) Attest(ctx *attestation.AttestationContext) error { switch name := attestor.Attestor.Name(); name { // Pre-material Attestors case environment.Name: - envs := attestor.Attestor.(*environment.Attestor).Variables + envs := attestor.Attestor.(environment.EnvironmentAttestor).Data().Variables pbEnvs := make(map[string]interface{}, len(envs)) for name, value := range envs { pbEnvs[name] = value @@ -152,26 +153,26 @@ func (p *Provenance) Attest(ctx *attestation.AttestationContext) error { digest := make(map[string]string) digest["sha1"] = gh.Data().JWT.Claims["sha"].(string) - p.PbProvenance.BuildDefinition.ResolvedDependencies = append( - p.PbProvenance.BuildDefinition.ResolvedDependencies, - &v1.ResourceDescriptor{ - Name: gh.Data().ProjectUrl, - Digest: digest, - }) + // p.PbProvenance.BuildDefinition.ResolvedDependencies = append( + // p.PbProvenance.BuildDefinition.ResolvedDependencies, + // &v1.ResourceDescriptor{ + // Name: gh.Data().ProjectUrl, + // Digest: digest, + // }) case gitlab.Name: - gl := attestor.Attestor.(*gitlab.Attestor) + gl := attestor.Attestor.(gitlab.GitLabAttestor) p.PbProvenance.RunDetails.Builder.Id = GLCBuilderId - p.PbProvenance.RunDetails.Metadata.InvocationId = gl.PipelineUrl + p.PbProvenance.RunDetails.Metadata.InvocationId = gl.Data().PipelineUrl digest := make(map[string]string) - digest["sha1"] = gl.JWT.Claims["sha"].(string) + digest["sha1"] = gl.Data().JWT.Claims["sha"].(string) - p.PbProvenance.BuildDefinition.ResolvedDependencies = append( - p.PbProvenance.BuildDefinition.ResolvedDependencies, - &v1.ResourceDescriptor{ - Name: gl.ProjectUrl, - Digest: digest, - }) + // p.PbProvenance.BuildDefinition.ResolvedDependencies = append( + // p.PbProvenance.BuildDefinition.ResolvedDependencies, + // &v1.ResourceDescriptor{ + // Name: gl.Data().ProjectUrl, + // Digest: digest, + // }) // Material Attestors case material.Name: @@ -207,9 +208,19 @@ func (p *Provenance) Attest(ctx *attestation.AttestationContext) error { maps.Copy(p.products, ctx.Products()) } + if p.subjects == nil { + p.subjects = attestor.Attestor.(attestation.Subjecter).Subjects() + } else { + maps.Copy(p.subjects, attestor.Attestor.(attestation.Subjecter).Subjects()) + } + // Post Attestors case oci.Name: - maps.Copy(p.products, attestor.Attestor.(product.ProductAttestor).Products()) + if p.subjects == nil { + p.subjects = attestor.Attestor.(attestation.Subjecter).Subjects() + } else { + maps.Copy(p.subjects, attestor.Attestor.(attestation.Subjecter).Subjects()) + } } } diff --git a/attestation/slsa/slsa_test.go b/attestation/slsa/slsa_test.go index 90fa2725..d043648b 100644 --- a/attestation/slsa/slsa_test.go +++ b/attestation/slsa/slsa_test.go @@ -15,11 +15,15 @@ package slsa import ( + "bytes" + "crypto" + "encoding/json" "testing" "github.com/in-toto/go-witness/attestation" "github.com/in-toto/go-witness/cryptoutil" "github.com/in-toto/go-witness/internal/attestors" + "google.golang.org/protobuf/types/known/timestamppb" ) func TestName(t *testing.T) { @@ -61,7 +65,7 @@ func TestExport(t *testing.T) { func TestUnmarshalJSON(t *testing.T) { provenance := New() - if err := provenance.UnmarshalJSON([]byte(testProvenanceJSON)); err != nil { + if err := provenance.UnmarshalJSON([]byte(testGHProvJSON)); err != nil { t.Errorf("unexpected error: %s", err) } } @@ -75,7 +79,7 @@ func TestUnmarshalBadJSON(t *testing.T) { func TestMarshalJSON(t *testing.T) { provenance := New() - if err := provenance.UnmarshalJSON([]byte(testProvenanceJSON)); err != nil { + if err := provenance.UnmarshalJSON([]byte(testGHProvJSON)); err != nil { t.Errorf("unexpected error: %s", err) } @@ -86,21 +90,89 @@ func TestMarshalJSON(t *testing.T) { } func TestAttest(t *testing.T) { + // Setup Env + e := attestors.NewTestEnvironmentAttestor() + e.Data().Variables = map[string]string{ + "SHELL": "/bin/zsh", + "TERM": "xterm-256color", + "TERM_PROGRAM": "iTerm.app", + } + + // Setup Git g := attestors.NewTestGitAttestor() + g.Data().CommitDigest = cryptoutil.DigestSet{ + {Hash: crypto.SHA1, GitOID: false}: "abc123", + } + g.Data().Remotes = []string{"git@github.com:in-toto/witness.git"} + + // Setup GitHub gh := attestors.NewTestGitHubAttestor() + gh.Data().JWT.Claims["sha"] = "abc123" + gh.Data().PipelineUrl = "https://github.com/testifysec/swf/actions/runs/7879307166" + + // Setup GitLab + gl := attestors.NewTestGitLabAttestor() + gl.Data().JWT.Claims["sha"] = "abc123" + gl.Data().PipelineUrl = "https://github.com/testifysec/swf/actions/runs/7879307166" + + // Setup Materials m := attestors.NewTestMaterialAttestor() + + // Setup CommandRun c := attestors.NewTestCommandRunAttestor() + c.Data().Cmd = []string{"touch", "test.txt"} + + // Setup Products p := attestors.NewTestProductAttestor() - s := New() - ctx, err := attestation.NewContext("test", []attestation.Attestor{g, gh, m, c, p, s}) - if err != nil { - t.Errorf("error creating attestation context: %s", err) + // Setup OCI + o := attestors.NewTestOCIAttestor() + + var tests = []struct { + name string + attestors []attestation.Attestor + expectedJson string + }{ + {"github", []attestation.Attestor{e, g, gh, m, c, p, o}, testGHProvJSON}, + {"gitlab", []attestation.Attestor{e, g, gl, m, c, p, o}, testGLProvJSON}, } - err = ctx.RunAttestors() - if err != nil { - t.Errorf("error attesting: %s", err.Error()) + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Logf("Running test %s", test.name) + s := New() + + ctx, err := attestation.NewContext("test", append(test.attestors, s)) + if err != nil { + t.Errorf("error creating attestation context: %s", err) + } + + err = ctx.RunAttestors() + if err != nil { + t.Errorf("error attesting: %s", err.Error()) + } + + // TODO: We don't have a way to mock out times on attestor runs + // Set attestor times manually to match testProvenanceJSON + s.PbProvenance.RunDetails.Metadata.StartedOn = ×tamppb.Timestamp{ + Seconds: 1711199861, + Nanos: 560152000, + } + s.PbProvenance.RunDetails.Metadata.FinishedOn = ×tamppb.Timestamp{ + Seconds: 1711199861, + Nanos: 560152000, + } + + var prov []byte + if prov, err = json.MarshalIndent(s, "", " "); err != nil { + t.Errorf("unexpected error: %s", err) + } + + testJson := []byte(test.expectedJson) + if !bytes.Equal(prov, testJson) { + t.Errorf("expected \n%s\n, got \n%s\n", testJson, prov) + } + }) } } @@ -130,7 +202,7 @@ func TestSubjects(t *testing.T) { func setupProvenance(t *testing.T) *Provenance { provenance := New() - if err := provenance.UnmarshalJSON([]byte(testProvenanceJSON)); err != nil { + if err := provenance.UnmarshalJSON([]byte(testGHProvJSON)); err != nil { t.Errorf("unexpected error: %s", err) } @@ -165,61 +237,82 @@ func TestRegistration(t *testing.T) { } -const testProvenanceJSON = ` -{ - "type": "https://slsa.dev/provenance/v1.0", - "attestation": { - "build_definition": { - "build_type": "https://witness.dev/slsa-build@v0.1", - "external_parameters": { - "command": "touch test.txt" - }, - "internal_parameters": { - "env": { - "COLORFGBG": "7;0", - "COLORTERM": "truecolor", - "COMMAND_MODE": "unix2003", - "SHELL": "/bin/zsh", - "SHLVL": "1", - "TERM": "xterm-256color", - "TERM_PROGRAM": "iTerm.app", - "TERM_PROGRAM_VERSION": "3.4.23", - "TERM_SESSION_ID": "w0t1p0:8939AC72-EB13-417F-9500-DD193C48127E", - "TMPDIR": "/var/folders/qy/kpkfp9r140s08yk29dccpx540000gn/T/", - "XPC_FLAGS": "0x0", - "XPC_SERVICE_NAME": "0", - "_": "/opt/homebrew/bin/go", - "_P9K_SSH_TTY": "/dev/ttys005", - "_P9K_TTY": "/dev/ttys005", - "__CFBundleIdentifier": "com.googlecode.iterm2", - "__CF_USER_TEXT_ENCODING": "0x1F5:0x0:0x0" - } - }, - "resolved_dependencies": [ - { - "name": "git@github.com:in-toto/witness.git", - "digest": { - "sha1": "51d0fa68cb991b7d3979df491e05fbf7765d6d1c" - } - } - ] - }, - "run_details": { - "builder": { - "id": "https://witness.dev/witness-github-action@v0.1" - }, - "metadata": { - "invocation_id": "gha-workflow-ref", - "started_on": { - "seconds": 1711199861, - "nanos": 560152000 - }, - "finished_on": { - "seconds": 1711199861, - "nanos": 560152000 - } - } - } - } -} -` +const testGHProvJSON = `{ + "build_definition": { + "build_type": "https://witness.dev/slsa-build@v0.1", + "external_parameters": { + "command": "touch test.txt" + }, + "internal_parameters": { + "env": { + "SHELL": "/bin/zsh", + "TERM": "xterm-256color", + "TERM_PROGRAM": "iTerm.app" + } + }, + "resolved_dependencies": [ + { + "name": "git@github.com:in-toto/witness.git", + "digest": { + "sha1": "abc123" + } + } + ] + }, + "run_details": { + "builder": { + "id": "https://witness.dev/witness-github-action-builder@v0.1" + }, + "metadata": { + "invocation_id": "https://github.com/testifysec/swf/actions/runs/7879307166", + "started_on": { + "seconds": 1711199861, + "nanos": 560152000 + }, + "finished_on": { + "seconds": 1711199861, + "nanos": 560152000 + } + } + } +}` + +const testGLProvJSON = `{ + "build_definition": { + "build_type": "https://witness.dev/slsa-build@v0.1", + "external_parameters": { + "command": "touch test.txt" + }, + "internal_parameters": { + "env": { + "SHELL": "/bin/zsh", + "TERM": "xterm-256color", + "TERM_PROGRAM": "iTerm.app" + } + }, + "resolved_dependencies": [ + { + "name": "git@github.com:in-toto/witness.git", + "digest": { + "sha1": "abc123" + } + } + ] + }, + "run_details": { + "builder": { + "id": "https://witness.dev/witness-gitlab-component-builder@v0.1" + }, + "metadata": { + "invocation_id": "https://github.com/testifysec/swf/actions/runs/7879307166", + "started_on": { + "seconds": 1711199861, + "nanos": 560152000 + }, + "finished_on": { + "seconds": 1711199861, + "nanos": 560152000 + } + } + } +}` diff --git a/internal/attestors/environment.go b/internal/attestors/environment.go new file mode 100644 index 00000000..9ea5298a --- /dev/null +++ b/internal/attestors/environment.go @@ -0,0 +1,53 @@ +// Copyright 2024 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package attestors + +import ( + "github.com/in-toto/go-witness/attestation" + "github.com/in-toto/go-witness/attestation/environment" +) + +var ( + _ environment.EnvironmentAttestor = &TestEnvironmentAttestor{} +) + +type TestEnvironmentAttestor struct { + environmentAtt environment.Attestor +} + +func NewTestEnvironmentAttestor() *TestEnvironmentAttestor { + att := environment.New() + return &TestEnvironmentAttestor{environmentAtt: *att} +} + +func (t *TestEnvironmentAttestor) Name() string { + return t.environmentAtt.Name() +} + +func (t *TestEnvironmentAttestor) Type() string { + return t.environmentAtt.Type() +} + +func (t *TestEnvironmentAttestor) RunType() attestation.RunType { + return t.environmentAtt.RunType() +} + +func (t *TestEnvironmentAttestor) Attest(ctx *attestation.AttestationContext) error { + return nil +} + +func (t *TestEnvironmentAttestor) Data() *environment.Attestor { + return &t.environmentAtt +} diff --git a/internal/attestors/github.go b/internal/attestors/github.go index ee8b733f..38286083 100644 --- a/internal/attestors/github.go +++ b/internal/attestors/github.go @@ -32,7 +32,6 @@ type TestGitHubAttestor struct { func NewTestGitHubAttestor() *TestGitHubAttestor { att := github.New() att.JWT = jwt.New() - att.JWT.Claims["sha"] = "sha123abc" return &TestGitHubAttestor{githubAtt: *att} } diff --git a/internal/attestors/gitlab.go b/internal/attestors/gitlab.go index 334e53b3..0127a1f6 100644 --- a/internal/attestors/gitlab.go +++ b/internal/attestors/gitlab.go @@ -17,6 +17,7 @@ package attestors import ( "github.com/in-toto/go-witness/attestation" "github.com/in-toto/go-witness/attestation/gitlab" + "github.com/in-toto/go-witness/attestation/jwt" "github.com/in-toto/go-witness/cryptoutil" ) @@ -25,11 +26,12 @@ var ( ) type TestGitLabAttestor struct { - gitlabAtt gitlab.GitLabAttestor + gitlabAtt gitlab.Attestor } -func (t *TestGitLabAttestor) New() *TestGitLabAttestor { - att := &gitlab.Attestor{} +func NewTestGitLabAttestor() *TestGitLabAttestor { + att := gitlab.Attestor{} + att.JWT = jwt.New() return &TestGitLabAttestor{gitlabAtt: att} } @@ -49,6 +51,10 @@ func (t *TestGitLabAttestor) Attest(ctx *attestation.AttestationContext) error { return nil } +func (t *TestGitLabAttestor) Data() *gitlab.Attestor { + return &t.gitlabAtt +} + func (t *TestGitLabAttestor) Subjects() map[string]cryptoutil.DigestSet { return nil } diff --git a/internal/attestors/oci.go b/internal/attestors/oci.go new file mode 100644 index 00000000..6f92841e --- /dev/null +++ b/internal/attestors/oci.go @@ -0,0 +1,62 @@ +// Copyright 2024 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package attestors + +import ( + "github.com/in-toto/go-witness/attestation" + "github.com/in-toto/go-witness/attestation/oci" + "github.com/in-toto/go-witness/cryptoutil" +) + +var ( + _ oci.OCIAttestor = &TestOCIAttestor{} +) + +type TestOCIAttestor struct { + ociAtt oci.Attestor +} + +func NewTestOCIAttestor() *TestOCIAttestor { + att := oci.New() + return &TestOCIAttestor{ociAtt: *att} +} + +func (t *TestOCIAttestor) Name() string { + return t.ociAtt.Name() +} + +func (t *TestOCIAttestor) Type() string { + return t.ociAtt.Type() +} + +func (t *TestOCIAttestor) RunType() attestation.RunType { + return t.ociAtt.RunType() +} + +func (t *TestOCIAttestor) Attest(ctx *attestation.AttestationContext) error { + return nil +} + +func (t *TestOCIAttestor) Data() *oci.Attestor { + return &t.ociAtt +} + +func (t *TestOCIAttestor) Subjects() map[string]cryptoutil.DigestSet { + return nil +} + +func (t *TestOCIAttestor) BackRefs() map[string]cryptoutil.DigestSet { + return nil +} From 33f3905991292ab807f8b2ce9e26377659fa4a9a Mon Sep 17 00:00:00 2001 From: John Kjell Date: Sat, 6 Apr 2024 18:05:35 -0500 Subject: [PATCH 27/33] Clean up Signed-off-by: John Kjell --- attestation/environment/environment.go | 5 +++++ attestation/github/github.go | 4 ++-- run.go | 2 -- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/attestation/environment/environment.go b/attestation/environment/environment.go index 7afd4072..8def421c 100644 --- a/attestation/environment/environment.go +++ b/attestation/environment/environment.go @@ -33,6 +33,7 @@ const ( // doesn't implement the expected interfaces. var ( _ attestation.Attestor = &Attestor{} + _ EnvironmentAttestor = &Attestor{} ) type EnvironmentAttestor interface { @@ -110,6 +111,10 @@ func (a *Attestor) Attest(ctx *attestation.AttestationContext) error { return nil } +func (a *Attestor) Data() *Attestor { + return a +} + // splitVariable splits a string representing an environment variable in the format of // "KEY=VAL" and returns the key and val separately. func splitVariable(v string) (key, val string) { diff --git a/attestation/github/github.go b/attestation/github/github.go index 33080b18..02a0aea2 100644 --- a/attestation/github/github.go +++ b/attestation/github/github.go @@ -73,10 +73,10 @@ func init() { }) } -// ErrNotGitlab is an error type that indicates the environment is not a github ci job. +// ErrNotGitHub is an error type that indicates the environment is not a github ci job. type ErrNotGitHub struct{} -// Error returns the error message for ErrNotGitlab. +// Error returns the error message for ErrNotGitHub. func (e ErrNotGitHub) Error() string { return "not in a github ci job" } diff --git a/run.go b/run.go index 132d277f..52f66a7e 100644 --- a/run.go +++ b/run.go @@ -63,7 +63,6 @@ type RunResult struct { AttestorName string } -// Should this be deprecated? // Deprecated: Use RunWithExports instead func Run(stepName string, signer cryptoutil.Signer, opts ...RunOption) (RunResult, error) { results, err := run(stepName, signer, opts) @@ -107,7 +106,6 @@ func run(stepName string, signer cryptoutil.Signer, opts []RunOption) ([]RunResu if r.Error != nil { errs = append(errs, r.Error) } else { - // TODO: Add Exporter interface to attestors if _, ok := r.Attestor.(attestation.Exporter); ok { if subjecter, ok := r.Attestor.(attestation.Subjecter); ok { envelope, err := createAndSignEnvelope(r.Attestor, r.Attestor.Type(), subjecter.Subjects(), dsse.SignWithSigners(ro.signer), dsse.SignWithTimestampers(ro.timestampers...)) From dba3c390b8c5e45d8f5f79e13ef0362a9771f0fd Mon Sep 17 00:00:00 2001 From: John Kjell Date: Sun, 7 Apr 2024 11:11:00 -0500 Subject: [PATCH 28/33] Add attestation test for link attestor Signed-off-by: John Kjell --- attestation/link/link.go | 8 +-- attestation/link/link_test.go | 99 +++++++++++++++++++++++----------- internal/attestors/material.go | 12 +++-- 3 files changed, 80 insertions(+), 39 deletions(-) diff --git a/attestation/link/link.go b/attestation/link/link.go index 97b38d27..1bcc7c4a 100644 --- a/attestation/link/link.go +++ b/attestation/link/link.go @@ -89,9 +89,9 @@ func (l *Link) Attest(ctx *attestation.AttestationContext) error { for _, attestor := range ctx.CompletedAttestors() { switch name := attestor.Attestor.Name(); name { case commandrun.Name: - l.PbLink.Command = attestor.Attestor.(*commandrun.CommandRun).Cmd + l.PbLink.Command = attestor.Attestor.(commandrun.CommandRunAttestor).Data().Cmd case material.Name: - mats := attestor.Attestor.(*material.Attestor).Materials() + mats := attestor.Attestor.(material.MaterialAttestor).Materials() for name, digestSet := range mats { digests, _ := digestSet.ToNameMap() l.PbLink.Materials = append(l.PbLink.Materials, &v1.ResourceDescriptor{ @@ -100,7 +100,7 @@ func (l *Link) Attest(ctx *attestation.AttestationContext) error { }) } case environment.Name: - envs := attestor.Attestor.(*environment.Attestor).Variables + envs := attestor.Attestor.(environment.EnvironmentAttestor).Data().Variables pbEnvs := make(map[string]interface{}, len(envs)) for name, value := range envs { pbEnvs[name] = value @@ -112,7 +112,7 @@ func (l *Link) Attest(ctx *attestation.AttestationContext) error { return err } case product.ProductName: - l.products = attestor.Attestor.(*product.Attestor).Products() + l.products = attestor.Attestor.(product.ProductAttestor).Products() } } return nil diff --git a/attestation/link/link_test.go b/attestation/link/link_test.go index c91f0547..8c8a27e4 100644 --- a/attestation/link/link_test.go +++ b/attestation/link/link_test.go @@ -15,10 +15,14 @@ package link import ( + "bytes" + "crypto" + "encoding/json" "testing" "github.com/in-toto/go-witness/attestation" "github.com/in-toto/go-witness/cryptoutil" + "github.com/in-toto/go-witness/internal/attestors" ) func TestName(t *testing.T) { @@ -85,15 +89,48 @@ func TestMarshalJSON(t *testing.T) { } func TestAttest(t *testing.T) { - link := New() - // var attestorData []attestation.CompletedAttestor - // ctx := attestation.AttestationContext{ - // completedAttestors: attestorData, - // } + // Setup Env + e := attestors.NewTestEnvironmentAttestor() + e.Data().Variables = map[string]string{ + "COLORFGBG": "7;0", + "COLORTERM": "truecolor", + } + + // Setup Materials + m := attestors.NewTestMaterialAttestor() + materials := make(map[string]cryptoutil.DigestSet) + materials["test2"] = cryptoutil.DigestSet{{Hash: crypto.SHA256, GitOID: false}: "a53d0741798b287c6dd7afa64aee473f305e65d3f49463bb9d7408ec3b12bf5f"} + materials["test1"] = cryptoutil.DigestSet{{Hash: crypto.SHA256, GitOID: false}: "a53d0741798b287c6dd7afa64aee473f305e65d3f49463bb9d7408ec3b12bf5f"} + m.SetMaterials(materials) + + // Setup CommandRun + c := attestors.NewTestCommandRunAttestor() + c.Data().Cmd = []string{"touch", "test.txt"} + + // Setup Products + p := attestors.NewTestProductAttestor() + + l := New() + + ctx, err := attestation.NewContext("test", []attestation.Attestor{e, m, c, p, l}) + if err != nil { + t.Errorf("error creating attestation context: %s", err) + } - if err := link.Attest(&attestation.AttestationContext{}); err != nil { + err = ctx.RunAttestors() + if err != nil { + t.Errorf("error attesting: %s", err.Error()) + } + + var linkJson []byte + if linkJson, err = json.MarshalIndent(l, "", " "); err != nil { t.Errorf("unexpected error: %s", err) } + + testJson := []byte(testLinkJSON) + if !bytes.Equal(linkJson, testJson) { + t.Errorf("expected \n%s\n, got \n%s\n", testJson, linkJson) + } } func TestSubjects(t *testing.T) { @@ -157,30 +194,28 @@ func TestRegistration(t *testing.T) { } -const testLinkJSON = ` -{ - "name": "test", - "command": [ - "touch", - "test.txt" - ], - "materials": [ - { - "name": "test1", - "digest": { - "sha256": "a53d0741798b287c6dd7afa64aee473f305e65d3f49463bb9d7408ec3b12bf5f" - } - }, - { - "name": "test2", - "digest": { - "sha256": "a53d0741798b287c6dd7afa64aee473f305e65d3f49463bb9d7408ec3b12bf5f" - } +const testLinkJSON = `{ + "name": "test", + "command": [ + "touch", + "test.txt" + ], + "materials": [ + { + "name": "test2", + "digest": { + "sha256": "a53d0741798b287c6dd7afa64aee473f305e65d3f49463bb9d7408ec3b12bf5f" } - ], - "environment": { - "COLORFGBG": "7;0", - "COLORTERM": "truecolor" - } -} -` + }, + { + "name": "test1", + "digest": { + "sha256": "a53d0741798b287c6dd7afa64aee473f305e65d3f49463bb9d7408ec3b12bf5f" + } + } + ], + "environment": { + "COLORFGBG": "7;0", + "COLORTERM": "truecolor" + } +}` diff --git a/internal/attestors/material.go b/internal/attestors/material.go index 227976a6..57fc66b0 100644 --- a/internal/attestors/material.go +++ b/internal/attestors/material.go @@ -25,12 +25,14 @@ var ( ) type TestMaterialAttestor struct { - matAtt material.MaterialAttestor + matAtt *material.Attestor + materials map[string]cryptoutil.DigestSet } func NewTestMaterialAttestor() *TestMaterialAttestor { att := material.New() - return &TestMaterialAttestor{matAtt: att} + mat := make(map[string]cryptoutil.DigestSet) + return &TestMaterialAttestor{matAtt: att, materials: mat} } func (t *TestMaterialAttestor) Name() string { @@ -50,5 +52,9 @@ func (t *TestMaterialAttestor) Attest(ctx *attestation.AttestationContext) error } func (t *TestMaterialAttestor) Materials() map[string]cryptoutil.DigestSet { - return nil + return t.materials +} + +func (t *TestMaterialAttestor) SetMaterials(mats map[string]cryptoutil.DigestSet) { + t.materials = mats } From 4e37a0480f95ede29f948ef6cba66c77549283eb Mon Sep 17 00:00:00 2001 From: John Kjell Date: Tue, 9 Apr 2024 20:35:52 -0500 Subject: [PATCH 29/33] Add data function for git interface and remove unused code Signed-off-by: John Kjell --- attestation/git/git.go | 5 +++++ attestation/slsa/slsa.go | 14 -------------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/attestation/git/git.go b/attestation/git/git.go index ef8e7aad..3b39df29 100644 --- a/attestation/git/git.go +++ b/attestation/git/git.go @@ -39,6 +39,7 @@ var ( _ attestation.Attestor = &Attestor{} _ attestation.Subjecter = &Attestor{} _ attestation.BackReffer = &Attestor{} + _ GitAttestor = &Attestor{} ) type GitAttestor interface { @@ -243,6 +244,10 @@ func (a *Attestor) Attest(ctx *attestation.AttestationContext) error { return nil } +func (a *Attestor) Data() *Attestor { + return a +} + func (a *Attestor) Subjects() map[string]cryptoutil.DigestSet { subjects := make(map[string]cryptoutil.DigestSet) hashes := []cryptoutil.DigestValue{{Hash: crypto.SHA256}} diff --git a/attestation/slsa/slsa.go b/attestation/slsa/slsa.go index 6db3e92e..9a1645b2 100644 --- a/attestation/slsa/slsa.go +++ b/attestation/slsa/slsa.go @@ -153,13 +153,6 @@ func (p *Provenance) Attest(ctx *attestation.AttestationContext) error { digest := make(map[string]string) digest["sha1"] = gh.Data().JWT.Claims["sha"].(string) - // p.PbProvenance.BuildDefinition.ResolvedDependencies = append( - // p.PbProvenance.BuildDefinition.ResolvedDependencies, - // &v1.ResourceDescriptor{ - // Name: gh.Data().ProjectUrl, - // Digest: digest, - // }) - case gitlab.Name: gl := attestor.Attestor.(gitlab.GitLabAttestor) p.PbProvenance.RunDetails.Builder.Id = GLCBuilderId @@ -167,13 +160,6 @@ func (p *Provenance) Attest(ctx *attestation.AttestationContext) error { digest := make(map[string]string) digest["sha1"] = gl.Data().JWT.Claims["sha"].(string) - // p.PbProvenance.BuildDefinition.ResolvedDependencies = append( - // p.PbProvenance.BuildDefinition.ResolvedDependencies, - // &v1.ResourceDescriptor{ - // Name: gl.Data().ProjectUrl, - // Digest: digest, - // }) - // Material Attestors case material.Name: mats := attestor.Attestor.(material.MaterialAttestor).Materials() From bb842ee639d058c2187f664f2fedbddd134d08c7 Mon Sep 17 00:00:00 2001 From: chaosinthecrd Date: Wed, 10 Apr 2024 13:49:31 +0100 Subject: [PATCH 30/33] adding warning mesage for slsa attestor Signed-off-by: chaosinthecrd --- attestation/slsa/slsa.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/attestation/slsa/slsa.go b/attestation/slsa/slsa.go index 9a1645b2..2ad8a9c2 100644 --- a/attestation/slsa/slsa.go +++ b/attestation/slsa/slsa.go @@ -31,6 +31,7 @@ import ( "github.com/in-toto/go-witness/attestation/oci" "github.com/in-toto/go-witness/attestation/product" "github.com/in-toto/go-witness/cryptoutil" + "github.com/in-toto/go-witness/log" "github.com/in-toto/go-witness/registry" "golang.org/x/exp/maps" "google.golang.org/protobuf/types/known/structpb" @@ -210,6 +211,11 @@ func (p *Provenance) Attest(ctx *attestation.AttestationContext) error { } } + // NOTE: We want to warn users that they can use the github and gitlab attestors to enrich their provenance + if p.PbProvenance.RunDetails.Builder.Id == DefaultBuilderId { + log.Warn("No build system attestor invoked. Consider using github or gitlab attestors (if appropriate) to enrich your SLSA provenance") + } + var err error p.PbProvenance.BuildDefinition.InternalParameters, err = structpb.NewStruct(internalParameters) if err != nil { From ec4f58ae2cfb57b04058d8e00c3a4900a03a83b8 Mon Sep 17 00:00:00 2001 From: John Kjell Date: Fri, 19 Apr 2024 09:49:43 -0500 Subject: [PATCH 31/33] Try to gracefully handle gitlab jwt Signed-off-by: John Kjell --- attestation/gitlab/gitlab.go | 6 ++++-- attestation/slsa/slsa.go | 19 ++++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/attestation/gitlab/gitlab.go b/attestation/gitlab/gitlab.go index 133a4223..f4be947b 100644 --- a/attestation/gitlab/gitlab.go +++ b/attestation/gitlab/gitlab.go @@ -107,13 +107,15 @@ func (a *Attestor) Attest(ctx *attestation.AttestationContext) error { } a.CIServerUrl = os.Getenv("CI_SERVER_URL") - jwksUrl := fmt.Sprintf("%s/-/jwks", a.CIServerUrl) - jwtString := os.Getenv("CI_JOB_JWT") + jwksUrl := fmt.Sprintf("%s/oauth/discovery/keys", a.CIServerUrl) + jwtString := os.Getenv("ID_TOKEN") if jwtString != "" { a.JWT = jwt.New(jwt.WithToken(jwtString), jwt.WithJWKSUrl(jwksUrl)) if err := a.JWT.Attest(ctx); err != nil { return err } + } else { + log.Warn("(attestation/gitlab) no jwt token found in environment") } a.CIConfigPath = os.Getenv("CI_CONFIG_PATH") diff --git a/attestation/slsa/slsa.go b/attestation/slsa/slsa.go index 2ad8a9c2..e3563ddd 100644 --- a/attestation/slsa/slsa.go +++ b/attestation/slsa/slsa.go @@ -152,6 +152,12 @@ func (p *Provenance) Attest(ctx *attestation.AttestationContext) error { p.PbProvenance.RunDetails.Builder.Id = GHABuilderId p.PbProvenance.RunDetails.Metadata.InvocationId = gh.Data().PipelineUrl digest := make(map[string]string) + + if gh.Data().JWT == nil { + log.Warn("No JWT found in GitHub attestor") + continue + } + digest["sha1"] = gh.Data().JWT.Claims["sha"].(string) case gitlab.Name: @@ -159,7 +165,18 @@ func (p *Provenance) Attest(ctx *attestation.AttestationContext) error { p.PbProvenance.RunDetails.Builder.Id = GLCBuilderId p.PbProvenance.RunDetails.Metadata.InvocationId = gl.Data().PipelineUrl digest := make(map[string]string) - digest["sha1"] = gl.Data().JWT.Claims["sha"].(string) + + if gl.Data().JWT == nil { + log.Warn("No JWT found in GitLab attestor") + continue + } + + sha, found := gl.Data().JWT.Claims["sha"] + if found { + digest["sha1"] = sha.(string) + } else { + log.Warn("No SHA found in GitLab JWT") + } // Material Attestors case material.Name: From 0f6805d016773739fac206720cda679b57bec1b3 Mon Sep 17 00:00:00 2001 From: chaosinthecrd Date: Wed, 8 May 2024 15:52:43 +0100 Subject: [PATCH 32/33] ran go mod tidy Signed-off-by: chaosinthecrd --- go.mod | 9 +++++---- go.sum | 4 ---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 6048e0c2..fc82b410 100644 --- a/go.mod +++ b/go.mod @@ -101,11 +101,12 @@ require ( go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/goleak v1.3.0 // indirect golang.org/x/mod v0.16.0 // indirect + golang.org/x/oauth2 v0.19.0 // indirect + golang.org/x/sync v0.7.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.19.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 // indirect gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect gopkg.in/inf.v0 v0.9.1 // indirect k8s.io/klog/v2 v2.120.1 // indirect @@ -136,8 +137,8 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/yashtewari/glob-intersection v0.2.0 // indirect github.com/zeebo/errs v1.3.0 // indirect - golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 golang.org/x/crypto v0.22.0 // indirect + golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 golang.org/x/net v0.24.0 // indirect golang.org/x/term v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect diff --git a/go.sum b/go.sum index 6eec4489..607c6c1d 100644 --- a/go.sum +++ b/go.sum @@ -379,8 +379,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -468,8 +466,6 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= -golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 86d4e2208397a85fe000b39baae0ec7920d870d0 Mon Sep 17 00:00:00 2001 From: chaosinthecrd Date: Wed, 8 May 2024 17:09:43 +0100 Subject: [PATCH 33/33] ensuring link and slsa attestation exporting is optional Signed-off-by: chaosinthecrd --- attestation/link/link.go | 16 +++++++++++++++- attestation/slsa/slsa.go | 4 ++-- run.go | 7 ++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/attestation/link/link.go b/attestation/link/link.go index 1bcc7c4a..8b9852d4 100644 --- a/attestation/link/link.go +++ b/attestation/link/link.go @@ -26,6 +26,7 @@ import ( "github.com/in-toto/go-witness/attestation/material" "github.com/in-toto/go-witness/attestation/product" "github.com/in-toto/go-witness/cryptoutil" + "github.com/in-toto/go-witness/registry" "google.golang.org/protobuf/types/known/structpb" ) @@ -47,6 +48,19 @@ var ( func init() { attestation.RegisterAttestation(Name, Type, RunType, func() attestation.Attestor { return New() }, + registry.BoolConfigOption( + "export", + "Export the Link predicate in its own attestation", + defaultExport, + func(a attestation.Attestor, export bool) (attestation.Attestor, error) { + linkAttestor, ok := a.(*Link) + if !ok { + return a, fmt.Errorf("unexpected attestor type: %T is not a Link provenance attestor", a) + } + WithExport(export)(linkAttestor) + return linkAttestor, nil + }, + ), ) } @@ -81,7 +95,7 @@ func (l *Link) RunType() attestation.RunType { } func (l *Link) Export() bool { - return true + return l.export } func (l *Link) Attest(ctx *attestation.AttestationContext) error { diff --git a/attestation/slsa/slsa.go b/attestation/slsa/slsa.go index e3563ddd..bbf2b9e0 100644 --- a/attestation/slsa/slsa.go +++ b/attestation/slsa/slsa.go @@ -61,7 +61,7 @@ func init() { func() attestation.Attestor { return New() }, registry.BoolConfigOption( "export", - "Export the SLSA provenance attestation to its own file", + "Export the SLSA provenance predicate in its own attestation", defaultExport, func(a attestation.Attestor, export bool) (attestation.Attestor, error) { slsaAttestor, ok := a.(*Provenance) @@ -107,7 +107,7 @@ func (p *Provenance) RunType() attestation.RunType { } func (p *Provenance) Export() bool { - return true + return p.export } func (p *Provenance) Attest(ctx *attestation.AttestationContext) error { diff --git a/run.go b/run.go index 52f66a7e..fd6d0397 100644 --- a/run.go +++ b/run.go @@ -26,6 +26,7 @@ import ( "github.com/in-toto/go-witness/cryptoutil" "github.com/in-toto/go-witness/dsse" "github.com/in-toto/go-witness/intoto" + "github.com/in-toto/go-witness/log" "github.com/in-toto/go-witness/timestamp" ) @@ -106,7 +107,11 @@ func run(stepName string, signer cryptoutil.Signer, opts []RunOption) ([]RunResu if r.Error != nil { errs = append(errs, r.Error) } else { - if _, ok := r.Attestor.(attestation.Exporter); ok { + if exporter, ok := r.Attestor.(attestation.Exporter); ok { + if !exporter.Export() { + log.Debugf("%s attestor not configured to be exported as its own attestation", r.Attestor.Name()) + continue + } if subjecter, ok := r.Attestor.(attestation.Subjecter); ok { envelope, err := createAndSignEnvelope(r.Attestor, r.Attestor.Type(), subjecter.Subjects(), dsse.SignWithSigners(ro.signer), dsse.SignWithTimestampers(ro.timestampers...)) if err != nil {