From bf33de5b484511bde7fd74b49080cfd8db527f5c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:05:46 +0100 Subject: [PATCH 1/3] feat(deps): bump github.com/theupdateframework/go-tuf/v2 (#186) Bumps [github.com/theupdateframework/go-tuf/v2](https://github.com/theupdateframework/go-tuf) from 2.0.1 to 2.0.2. - [Release notes](https://github.com/theupdateframework/go-tuf/releases) - [Changelog](https://github.com/theupdateframework/go-tuf/blob/master/.goreleaser.yaml) - [Commits](https://github.com/theupdateframework/go-tuf/compare/v2.0.1...v2.0.2) --- updated-dependencies: - dependency-name: github.com/theupdateframework/go-tuf/v2 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> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5c19a45..9ed47db 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.9 github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.9 github.com/stretchr/testify v1.9.0 - github.com/theupdateframework/go-tuf/v2 v2.0.1 + github.com/theupdateframework/go-tuf/v2 v2.0.2 google.golang.org/api v0.199.0 sigs.k8s.io/yaml v1.4.0 ) diff --git a/go.sum b/go.sum index de79dd0..dfad388 100644 --- a/go.sum +++ b/go.sum @@ -581,8 +581,8 @@ github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gt github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI= github.com/theupdateframework/go-tuf v0.7.0/go.mod h1:uEB7WSY+7ZIugK6R1hiBMBjQftaFzn7ZCDJcp1tCUug= -github.com/theupdateframework/go-tuf/v2 v2.0.1 h1:11p9tXpq10KQEujxjcIjDSivMKCMLguls7erXHZnxJQ= -github.com/theupdateframework/go-tuf/v2 v2.0.1/go.mod h1:baB22nBHeHBCeuGZcIlctNq4P61PcOdyARlplg5xmLA= +github.com/theupdateframework/go-tuf/v2 v2.0.2 h1:PyNnjV9BJNzN1ZE6BcWK+5JbF+if370jjzO84SS+Ebo= +github.com/theupdateframework/go-tuf/v2 v2.0.2/go.mod h1:baB22nBHeHBCeuGZcIlctNq4P61PcOdyARlplg5xmLA= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= From d58ce0c6000ca6c799d872c163fd81c5a54f231a Mon Sep 17 00:00:00 2001 From: mrjoelkamp Date: Mon, 7 Oct 2024 13:34:04 -0500 Subject: [PATCH 2/3] feat: add reference wrapper for envelope --- attestation/attestation.go | 15 ++++++++++++--- attestation/layout.go | 21 +++++++++++++++------ attestation/mock.go | 4 ++-- attestation/referrers.go | 4 ++-- attestation/registry.go | 2 +- attestation/resolver.go | 2 +- attestation/types.go | 11 +++++++++++ policy/policy_test.go | 8 ++++---- policy/rego_test.go | 2 +- verify_test.go | 4 ++-- 10 files changed, 51 insertions(+), 22 deletions(-) diff --git a/attestation/attestation.go b/attestation/attestation.go index 952ebd2..7f040cf 100644 --- a/attestation/attestation.go +++ b/attestation/attestation.go @@ -316,8 +316,8 @@ func buildImageFromLayers(layers []*Layer, manifest *v1.Descriptor, subject *v1. return newImg, nil } -func ExtractEnvelopes(manifest *Manifest, predicateType string) ([]*Envelope, error) { - var envs []*Envelope +func ExtractEnvelopes(manifest *Manifest, predicateType string) ([]*EnvelopeReference, error) { + var envs []*EnvelopeReference dsseMediaType, err := DSSEMediaType(predicateType) if err != nil { return nil, fmt.Errorf("failed to get DSSE media type for predicate '%s': %w", predicateType, err) @@ -333,11 +333,20 @@ func ExtractEnvelopes(manifest *Manifest, predicateType string) ([]*Envelope, er return nil, fmt.Errorf("failed to get layer contents: %w", err) } defer reader.Close() - env := new(Envelope) + env := new(EnvelopeReference) err = json.NewDecoder(reader).Decode(&env) if err != nil { return nil, fmt.Errorf("failed to decode envelope: %w", err) } + var uri string + if len(manifest.OriginalDescriptor.URLs) > 0 { + uri = manifest.OriginalDescriptor.URLs[0] + } + env.ResourceDescriptor = &ResourceDescriptor{ + MediaType: string(mt), + Digest: map[string]string{manifest.OriginalDescriptor.Digest.Algorithm: manifest.OriginalDescriptor.Digest.Hex}, + URI: uri, + } envs = append(envs, env) } } diff --git a/attestation/layout.go b/attestation/layout.go index 24aaa7b..94a72f3 100644 --- a/attestation/layout.go +++ b/attestation/layout.go @@ -45,8 +45,8 @@ func (r *LayoutResolver) fetchManifest() (*Manifest, error) { return r.Manifest, nil } -func (r *LayoutResolver) Attestations(_ context.Context, predicateType string) ([]*Envelope, error) { - var envs []*Envelope +func (r *LayoutResolver) Attestations(_ context.Context, predicateType string) ([]*EnvelopeReference, error) { + var envs []*EnvelopeReference dsseMediaType, err := DSSEMediaType(predicateType) if err != nil { return nil, fmt.Errorf("failed to get DSSE media type for predicate '%s': %w", predicateType, err) @@ -60,17 +60,26 @@ func (r *LayoutResolver) Attestations(_ context.Context, predicateType string) ( if mts != dsseMediaType { continue } - env := new(Envelope) + env := new(EnvelopeReference) // parse layer blob as json - r, err := attestationLayer.Layer.Uncompressed() + layer, err := attestationLayer.Layer.Uncompressed() if err != nil { return nil, fmt.Errorf("failed to get layer contents: %w", err) } - defer r.Close() - err = json.NewDecoder(r).Decode(env) + defer layer.Close() + err = json.NewDecoder(layer).Decode(env) if err != nil { return nil, fmt.Errorf("failed to decode envelope: %w", err) } + var uri string + if len(r.Manifest.OriginalDescriptor.URLs) > 0 { + uri = r.Manifest.OriginalDescriptor.URLs[0] + } + env.ResourceDescriptor = &ResourceDescriptor{ + MediaType: string(mt), + Digest: map[string]string{r.Manifest.OriginalDescriptor.Digest.Algorithm: r.Manifest.OriginalDescriptor.Digest.Hex}, + URI: uri, + } envs = append(envs, env) } return envs, nil diff --git a/attestation/mock.go b/attestation/mock.go index 977e087..98dacc9 100644 --- a/attestation/mock.go +++ b/attestation/mock.go @@ -12,14 +12,14 @@ import ( var _ oci.ImageDetailsResolver = MockResolver{} type MockResolver struct { - Envs []*Envelope + Envs []*EnvelopeReference Image string PlatformFn func() (*v1.Platform, error) DescriptorFn func() (*v1.Descriptor, error) ImangeNameFn func() (string, error) } -func (r MockResolver) Attestations(_ context.Context, _ string) ([]*Envelope, error) { +func (r MockResolver) Attestations(_ context.Context, _ string) ([]*EnvelopeReference, error) { return r.Envs, nil } diff --git a/attestation/referrers.go b/attestation/referrers.go index a804dfe..4c109ed 100644 --- a/attestation/referrers.go +++ b/attestation/referrers.go @@ -109,12 +109,12 @@ func (r *ReferrersResolver) resolveAttestations(ctx context.Context, predicateTy return aManifests, nil } -func (r *ReferrersResolver) Attestations(ctx context.Context, predicateType string) ([]*Envelope, error) { +func (r *ReferrersResolver) Attestations(ctx context.Context, predicateType string) ([]*EnvelopeReference, error) { manifests, err := r.resolveAttestations(ctx, predicateType) if err != nil { return nil, fmt.Errorf("failed to resolve attestations: %w", err) } - var envs []*Envelope + var envs []*EnvelopeReference for _, attest := range manifests { es, err := ExtractEnvelopes(attest, predicateType) if err != nil { diff --git a/attestation/registry.go b/attestation/registry.go index c65b94e..73639d3 100644 --- a/attestation/registry.go +++ b/attestation/registry.go @@ -24,7 +24,7 @@ func NewRegistryResolver(src *oci.RegistryImageDetailsResolver) (*RegistryResolv }, nil } -func (r *RegistryResolver) Attestations(ctx context.Context, predicateType string) ([]*Envelope, error) { +func (r *RegistryResolver) Attestations(ctx context.Context, predicateType string) ([]*EnvelopeReference, error) { if r.Manifest == nil { attest, err := FetchManifest(ctx, r.Identifier, r.ImageSpec.Platform) if err != nil { diff --git a/attestation/resolver.go b/attestation/resolver.go index 0cc73b0..7eae516 100644 --- a/attestation/resolver.go +++ b/attestation/resolver.go @@ -8,5 +8,5 @@ import ( type Resolver interface { oci.ImageDetailsResolver - Attestations(ctx context.Context, mediaType string) ([]*Envelope, error) + Attestations(ctx context.Context, mediaType string) ([]*EnvelopeReference, error) } diff --git a/attestation/types.go b/attestation/types.go index a912ca5..2da43db 100644 --- a/attestation/types.go +++ b/attestation/types.go @@ -67,6 +67,17 @@ type Extension struct { Ext *DockerDSSEExtension `json:"ext"` } +type EnvelopeReference struct { + *Envelope + ResourceDescriptor *ResourceDescriptor +} + +type ResourceDescriptor struct { + MediaType string `json:"mediaType"` + Digest map[string]string `json:"digest"` + URI string `json:"uri"` +} + type AnnotatedStatement struct { OCIDescriptor *v1.Descriptor InTotoStatement *intoto.Statement diff --git a/policy/policy_test.go b/policy/policy_test.go index 455e447..c2d43c6 100644 --- a/policy/policy_test.go +++ b/policy/policy_test.go @@ -20,13 +20,13 @@ import ( "github.com/stretchr/testify/require" ) -func loadAttestation(t *testing.T, path string) *attestation.Envelope { +func loadAttestation(t *testing.T, path string) *attestation.EnvelopeReference { ex, err := os.ReadFile(path) if err != nil { t.Fatal(err) } - env := new(attestation.Envelope) + env := new(attestation.EnvelopeReference) err = json.Unmarshal(ex, env) if err != nil { t.Fatal(err) @@ -44,7 +44,7 @@ func TestRegoEvaluator_Evaluate(t *testing.T) { require.NoError(t, err) re := policy.NewRegoEvaluator(true, verifier) defaultResolver := attestation.MockResolver{ - Envs: []*attestation.Envelope{loadAttestation(t, ExampleAttestation)}, + Envs: []*attestation.EnvelopeReference{loadAttestation(t, ExampleAttestation)}, } defaultPlatform, err := v1.ParsePlatform("linux/amd64") require.NoError(t, err) @@ -122,7 +122,7 @@ func TestLoadingMappings(t *testing.T) { func TestCreateAttestationResolver(t *testing.T) { mockResolver := attestation.MockResolver{ - Envs: []*attestation.Envelope{}, + Envs: []*attestation.EnvelopeReference{}, } layoutResolver := &attestation.LayoutResolver{} registryResolver := &oci.RegistryImageDetailsResolver{} diff --git a/policy/rego_test.go b/policy/rego_test.go index 47cf304..5f44083 100644 --- a/policy/rego_test.go +++ b/policy/rego_test.go @@ -83,7 +83,7 @@ func (r *NullAttestationResolver) ImageDescriptor(_ context.Context) (*v1.Descri return nil, nil } -func (r *NullAttestationResolver) Attestations(_ context.Context, _ string) ([]*attestation.Envelope, error) { +func (r *NullAttestationResolver) Attestations(_ context.Context, _ string) ([]*attestation.EnvelopeReference, error) { r.called = true return nil, nil } diff --git a/verify_test.go b/verify_test.go index af241e9..d7e0ff6 100644 --- a/verify_test.go +++ b/verify_test.go @@ -37,11 +37,11 @@ func TestVerifyAttestations(t *testing.T) { ex, err := os.ReadFile(ExampleAttestation) assert.NoError(t, err) - env := new(attestation.Envelope) + env := new(attestation.EnvelopeReference) err = json.Unmarshal(ex, env) assert.NoError(t, err) resolver := &attestation.MockResolver{ - Envs: []*attestation.Envelope{env}, + Envs: []*attestation.EnvelopeReference{env}, } testCases := []struct { From a686de72fdb95060e253dded68816599286ae163 Mon Sep 17 00:00:00 2001 From: mrjoelkamp Date: Mon, 7 Oct 2024 13:36:30 -0500 Subject: [PATCH 3/3] feat: add input atts to result summary --- attestation/types.go | 4 ++-- attestation/vsa.go | 19 +++++++------------ policy/types.go | 9 +++++---- test/testdata/local-policy-pass/policy.rego | 6 ++++++ verify.go | 1 + verify_test.go | 10 +++++++++- 6 files changed, 30 insertions(+), 19 deletions(-) diff --git a/attestation/types.go b/attestation/types.go index 2da43db..043312d 100644 --- a/attestation/types.go +++ b/attestation/types.go @@ -69,13 +69,13 @@ type Extension struct { type EnvelopeReference struct { *Envelope - ResourceDescriptor *ResourceDescriptor + ResourceDescriptor *ResourceDescriptor `json:"resourceDescriptor"` } type ResourceDescriptor struct { MediaType string `json:"mediaType"` Digest map[string]string `json:"digest"` - URI string `json:"uri"` + URI string `json:"uri,omitempty"` } type AnnotatedStatement struct { diff --git a/attestation/vsa.go b/attestation/vsa.go index e580601..8ebd398 100644 --- a/attestation/vsa.go +++ b/attestation/vsa.go @@ -12,13 +12,13 @@ const ( ) type VSAPredicate struct { - Verifier VSAVerifier `json:"verifier"` - TimeVerified string `json:"timeVerified"` - ResourceURI string `json:"resourceUri"` - Policy VSAPolicy `json:"policy"` - InputAttestations []VSAInputAttestation `json:"inputAttestations,omitempty"` - VerificationResult string `json:"verificationResult"` - VerifiedLevels []string `json:"verifiedLevels"` + Verifier VSAVerifier `json:"verifier"` + TimeVerified string `json:"timeVerified"` + ResourceURI string `json:"resourceUri"` + Policy VSAPolicy `json:"policy"` + InputAttestations []ResourceDescriptor `json:"inputAttestations,omitempty"` + VerificationResult string `json:"verificationResult"` + VerifiedLevels []string `json:"verifiedLevels"` } type VSAVerifier struct { @@ -31,11 +31,6 @@ type VSAPolicy struct { DownloadLocation string `json:"downloadLocation,omitempty"` } -type VSAInputAttestation struct { - Digest map[string]string `json:"digest"` - MediaType string `json:"mediaType"` -} - func ToVSAResourceURI(sub intoto.Subject) (string, error) { // parse purl purl, err := packageurl.FromString(sub.Name) diff --git a/policy/types.go b/policy/types.go index d9d2873..59a0bfb 100644 --- a/policy/types.go +++ b/policy/types.go @@ -8,10 +8,11 @@ import ( ) type Summary struct { - Subjects []intoto.Subject `json:"subjects"` - SLSALevels []string `json:"slsa_levels"` - Verifier string `json:"verifier"` - PolicyURI string `json:"policy_uri"` + Subjects []intoto.Subject `json:"subjects"` + Inputs []attestation.ResourceDescriptor `json:"input_attestations"` + SLSALevels []string `json:"slsa_levels"` + Verifier string `json:"verifier"` + PolicyURI string `json:"policy_uri"` } type Violation struct { diff --git a/test/testdata/local-policy-pass/policy.rego b/test/testdata/local-policy-pass/policy.rego index 71a3b82..a013cf9 100644 --- a/test/testdata/local-policy-pass/policy.rego +++ b/test/testdata/local-policy-pass/policy.rego @@ -37,11 +37,17 @@ subjects contains subject if { some subject in statement.subject } +inputs contains desc if { + some att in atts + desc := att.resourceDescriptor +} + result := { "success": true, "violations": set(), "summary": { "subjects": subjects, + "input_attestations": inputs, "slsa_levels": ["SLSA_BUILD_LEVEL_3"], "verifier": "docker-official-images", "policy_uri": "https://docker.com/official/policy/v0.1", diff --git a/verify.go b/verify.go index deb0476..04ad31d 100644 --- a/verify.go +++ b/verify.go @@ -189,6 +189,7 @@ func toVerificationResult(p *policy.Policy, input *policy.Input, result *policy. Policy: vsaPolicy, VerificationResult: outcomeStr, VerifiedLevels: result.Summary.SLSALevels, + InputAttestations: result.Summary.Inputs, }, }, }, nil diff --git a/verify_test.go b/verify_test.go index d7e0ff6..dac83ef 100644 --- a/verify_test.go +++ b/verify_test.go @@ -124,7 +124,15 @@ func TestVSA(t *testing.T) { assert.Equal(t, PassPolicyDir+"/policy.rego", attestationPredicate.Policy.DownloadLocation) assert.Equal(t, "https://docker.com/official/policy/v0.1", attestationPredicate.Policy.URI) // this is the digest of the policy file - assert.Equal(t, map[string]string{"sha256": "ae71defe3b9ecebdf4f939a396b68884d0cba3c2c9d78ce5e64146d9487b0ade"}, attestationPredicate.Policy.Digest) + assert.Equal(t, map[string]string{"sha256": "fe1d4973f3521009a3adec206946e12aae935a2aceeb1e01f52b5d4cb9de79a5"}, attestationPredicate.Policy.Digest) + assert.Greater(t, len(attestationPredicate.InputAttestations), 0) + for _, input := range attestationPredicate.InputAttestations { + require.NotEmpty(t, input.Digest) + digest, ok := input.Digest["sha256"] + assert.True(t, ok) + assert.NotEmpty(t, digest) + assert.Contains(t, []string{"application/vnd.in-toto.provenance+dsse", "application/vnd.in-toto.spdx+dsse"}, input.MediaType) + } } func TestVerificationFailure(t *testing.T) {