diff --git a/pkg/attestation/crafter/api/attestation/v1/crafting_state.go b/pkg/attestation/crafter/api/attestation/v1/crafting_state.go index bac3dbd9e..a531b7a05 100644 --- a/pkg/attestation/crafter/api/attestation/v1/crafting_state.go +++ b/pkg/attestation/crafter/api/attestation/v1/crafting_state.go @@ -25,10 +25,10 @@ import ( "strings" v1 "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" + "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/materials/attestation" "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/materials/jacoco" materialsjunit "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/materials/junit" intoto "github.com/in-toto/attestation/go/v1" - "github.com/secure-systems-lab/go-securesystemslib/dsse" "google.golang.org/protobuf/types/known/structpb" ) @@ -110,9 +110,10 @@ func (m *Attestation_Material) GetEvaluableContent(value string) ([]byte, error) // special case for ATTESTATION materials, the statement needs to be extracted from the dsse wrapper. if m.MaterialType == v1.CraftingSchema_Material_ATTESTATION { - var envelope dsse.Envelope - if err := json.Unmarshal(rawMaterial, &envelope); err != nil { - return nil, fmt.Errorf("failed to unmarshal attestation material: %w", err) + // support both DSSE envelope and Sigstore bundle + envelope, err := attestation.ExtractDSSEEnvelope(rawMaterial) + if err != nil { + return nil, fmt.Errorf("failed to extract DSSE envelope: %w", err) } rawMaterial, err = envelope.DecodeB64Payload() diff --git a/pkg/attestation/crafter/materials/attestation.go b/pkg/attestation/crafter/materials/attestation.go index b8d634940..6c69b8942 100644 --- a/pkg/attestation/crafter/materials/attestation.go +++ b/pkg/attestation/crafter/materials/attestation.go @@ -17,18 +17,18 @@ package materials import ( "context" - "encoding/json" "fmt" "os" - "path" schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" - "github.com/chainloop-dev/chainloop/pkg/attestation" api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" + attestation2 "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/materials/attestation" "github.com/chainloop-dev/chainloop/pkg/attestation/renderer/chainloop" "github.com/chainloop-dev/chainloop/pkg/casclient" + intoto "github.com/in-toto/attestation/go/v1" + "github.com/in-toto/in-toto-golang/in_toto" "github.com/rs/zerolog" - "github.com/secure-systems-lab/go-securesystemslib/dsse" + "google.golang.org/protobuf/encoding/protojson" ) type AttestationCrafter struct { @@ -54,37 +54,32 @@ func (i *AttestationCrafter) Craft(ctx context.Context, artifactPath string) (*a if err != nil { return nil, fmt.Errorf("artifact file cannot be read: %w", err) } - var dsseEnvelope dsse.Envelope - if err := json.Unmarshal(data, &dsseEnvelope); err != nil { - return nil, fmt.Errorf("artifact is not a valid DSEE Envelope: %w", err) - } - predicate, err := chainloop.ExtractPredicate(&dsseEnvelope) + // extract the DSSE envelope (from the bundle if needed) + dsseEnvelope, err := attestation2.ExtractDSSEEnvelope(data) if err != nil { - return nil, fmt.Errorf("the provided file does not seem to be a chainloop-generated attestation: %w", err) + return nil, fmt.Errorf("failed to parse the provided file as a DSSE envelope: %w", err) } - // regenerate the json from the parsed data, just to remove any formating from the incoming json and preserve - // the digest - jsonContent, _, err := attestation.JSONEnvelopeWithDigest(&dsseEnvelope) - if err != nil { - return nil, fmt.Errorf("creating CAS payload: %w", err) + // run some validations + if dsseEnvelope.PayloadType != in_toto.PayloadType { + return nil, fmt.Errorf("the payload %q is not of type in-toto", dsseEnvelope.PayloadType) } - // Create a temp file with this content and upload to the CAS - dir := os.TempDir() - filename := fmt.Sprintf("%s-%s-attestation.json", predicate.GetMetadata().Name, predicate.GetMetadata().WorkflowRunID) - - file, err := os.Create(path.Join(dir, filename)) + dec, err := dsseEnvelope.DecodeB64Payload() if err != nil { - return nil, fmt.Errorf("failed to create temp file: %w", err) + return nil, fmt.Errorf("failed to decode the DSSE payload: %w", err) } - defer file.Close() - defer os.Remove(file.Name()) - if _, err := file.Write(jsonContent); err != nil { - return nil, fmt.Errorf("failed to write JSON payload: %w", err) + // decode the intoto payload + var intotoStatement intoto.Statement + if err = protojson.Unmarshal(dec, &intotoStatement); err != nil { + return nil, fmt.Errorf("failed to parse the DSSE payload as an in-toto statement: %w", err) + } + // check if the statement predicate is of expected type + if intotoStatement.PredicateType != chainloop.PredicateTypeV02 { + return nil, fmt.Errorf("the provided predicate is not a valid chainloop attestation: found=%q", intotoStatement.PredicateType) } - return uploadAndCraft(ctx, i.input, i.backend, file.Name(), i.logger) + return uploadAndCraft(ctx, i.input, i.backend, artifactPath, i.logger) } diff --git a/pkg/attestation/crafter/materials/attestation/attestation.go b/pkg/attestation/crafter/materials/attestation/attestation.go new file mode 100644 index 000000000..9f538c965 --- /dev/null +++ b/pkg/attestation/crafter/materials/attestation/attestation.go @@ -0,0 +1,42 @@ +// +// Copyright 2025 The Chainloop Authors. +// +// 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 attestation + +import ( + "encoding/json" + "fmt" + + "github.com/chainloop-dev/chainloop/pkg/attestation" + "github.com/secure-systems-lab/go-securesystemslib/dsse" + protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" + "google.golang.org/protobuf/encoding/protojson" +) + +func ExtractDSSEEnvelope(data []byte) (*dsse.Envelope, error) { + var dsseEnvelope dsse.Envelope + var bundle protobundle.Bundle + if err := protojson.Unmarshal(data, &bundle); err == nil && bundle.GetMediaType() != "" { + // if it has a media type, we can confirm it's a bundle + env := attestation.DSSEEnvelopeFromBundle(&bundle) + dsseEnvelope = *env + } else { + // try to parse it as a DSSE envelope + if err = json.Unmarshal(data, &dsseEnvelope); err != nil { + return nil, fmt.Errorf("failed to parse the provided file as a DSSE envelope: %w", err) + } + } + return &dsseEnvelope, nil +} diff --git a/pkg/attestation/crafter/materials/attestation_test.go b/pkg/attestation/crafter/materials/attestation_test.go index 6654b1748..769586508 100644 --- a/pkg/attestation/crafter/materials/attestation_test.go +++ b/pkg/attestation/crafter/materials/attestation_test.go @@ -18,6 +18,7 @@ package materials_test import ( "context" + "path" "testing" "code.cloudfoundry.org/bytefmt" @@ -94,42 +95,76 @@ func TestInvalidAttestation(t *testing.T) { t.Run("wrong in-toto statement", func(_ *testing.T) { // Invalid payload _, err := crafter.Craft(context.TODO(), "./testdata/attestation-invalid-intoto.json") - assert.Contains(err.Error(), "un-marshaling predicate") + assert.Contains(err.Error(), "failed to parse the DSSE payload") }) } func TestAttestationCraft(t *testing.T) { - assert := assert.New(t) - schema := &contractAPI.CraftingSchema_Material{ - Name: "test", - Type: contractAPI.CraftingSchema_Material_ATTESTATION, + var testCases = []struct { + name string + filePath string + digest string + expectErr bool + }{ + { + name: "DSSE envelope", + filePath: "./testdata/attestation-dsse.json", + digest: "sha256:3911ab20e43d801d35459c53168f6cba66d50af99dcc9e12aeb84a95c0d231df", + }, + { + name: "Sigstore bundle", + filePath: "./testdata/attestation-bundle.json", + digest: "sha256:fa7165a16cc1efdd24457a12dda613bbbfc903d3a2538a5ce8779b157d39b04c", + }, + { + name: "Invalid payload type", + filePath: "./testdata/attestation-dsse-invalidtype.json", + expectErr: true, + }, } + assert := assert.New(t) l := zerolog.Nop() - // Mock uploader - uploader := mUploader.NewUploader(t) - uploader.On("UploadFile", context.TODO(), mock.Anything). - Return(&casclient.UpDownStatus{ - Digest: "deadbeef", - Filename: "attestation.json", - }, nil) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + schema := &contractAPI.CraftingSchema_Material{ + Name: "test", + Type: contractAPI.CraftingSchema_Material_ATTESTATION, + } - backend := &casclient.CASBackend{Uploader: uploader} + // Mock uploader + uploader := mUploader.NewUploader(t) + if !tc.expectErr { + uploader.On("UploadFile", context.Background(), mock.Anything). + Return(&casclient.UpDownStatus{ + Digest: "deadbeef", + Filename: "attestation.json", + }, nil) + } - crafter, err := materials.NewAttestationCrafter(schema, backend, &l) - require.NoError(t, err) + backend := &casclient.CASBackend{Uploader: uploader} - got, err := crafter.Craft(context.TODO(), "./testdata/attestation.json") - assert.NoError(err) - assert.Equal(contractAPI.CraftingSchema_Material_ATTESTATION.String(), got.MaterialType.String()) - assert.True(got.UploadedToCas) + crafter, err := materials.NewAttestationCrafter(schema, backend, &l) + require.NoError(t, err) - // The result includes the digest reference - assert.Equal("test", got.GetArtifact().Id) - assert.Equal("sha256:30f98082cf71a990787755b360443711735de4041f27bf4a49d61bb8e6f29e92", got.GetArtifact().Digest) + got, err := crafter.Craft(context.Background(), tc.filePath) + if tc.expectErr { + assert.Error(err) + return + } + assert.NoError(err) + assert.Equal(contractAPI.CraftingSchema_Material_ATTESTATION.String(), got.MaterialType.String()) + assert.True(got.UploadedToCas) + + // The result includes the name and digest reference + assert.Equal(path.Base(tc.filePath), got.GetArtifact().GetName()) + assert.Equal(tc.digest, got.GetArtifact().GetDigest()) + + uploader.AssertExpectations(t) + }) + } - uploader.AssertExpectations(t) } func TestAttestationCraftInline(t *testing.T) { @@ -146,7 +181,7 @@ func TestAttestationCraftInline(t *testing.T) { crafter, err := materials.NewAttestationCrafter(schema, backend, &l) require.NoError(t, err) - got, err := crafter.Craft(context.TODO(), "./testdata/attestation.json") + got, err := crafter.Craft(context.TODO(), "./testdata/attestation-dsse.json") assert.NoError(err) assert.NotNil(got) @@ -160,7 +195,7 @@ func TestAttestationCraftInline(t *testing.T) { crafter, err := materials.NewAttestationCrafter(schema, backend, &l) require.NoError(t, err) - _, err = crafter.Craft(context.TODO(), "./testdata/attestation.json") + _, err = crafter.Craft(context.TODO(), "./testdata/attestation-dsse.json") assert.Error(err) }) } diff --git a/pkg/attestation/crafter/materials/slsaprovenance_test.go b/pkg/attestation/crafter/materials/slsaprovenance_test.go index 4c0d849cc..ce5986dd6 100644 --- a/pkg/attestation/crafter/materials/slsaprovenance_test.go +++ b/pkg/attestation/crafter/materials/slsaprovenance_test.go @@ -52,7 +52,7 @@ func TestInvalidSLSAProvenance(t *testing.T) { t.Run("is not a sigstore bundle but a DSSE envelope", func(_ *testing.T) { // Invalid payload - _, err := crafter.Craft(context.TODO(), "./testdata/attestation.json") + _, err := crafter.Craft(context.TODO(), "./testdata/attestation-dsse.json") assert.Contains(err.Error(), "failed to unmarshal bundle") }) diff --git a/pkg/attestation/crafter/materials/testdata/attestation-bundle.json b/pkg/attestation/crafter/materials/testdata/attestation-bundle.json new file mode 100644 index 000000000..317b6f1b7 --- /dev/null +++ b/pkg/attestation/crafter/materials/testdata/attestation-bundle.json @@ -0,0 +1 @@ +{"mediaType":"application/vnd.dev.sigstore.bundle+json;version=0.3", "verificationMaterial":{"certificate":{"rawBytes":"MIIDtzCCAZ+gAwIBAgIUExUKwhEfwQO1JugYpz7hLoY0Nh0wDQYJKoZIhvcNAQELBQAwXjELMAkGA1UEBhMCRVMxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAoMCUNoYWlubG9vcDESMBAGA1UECwwJY2hhaW5sb29wMRIwEAYDVQQDDAljaGFpbmxvb3AwHhcNMjUwNDMwMTQwNDMzWhcNMjUwNDMwMTQxNDMzWjAvMS0wKwYDVQQKEyQxOGMzZjc4Mi00OTM2LTQ2MzAtYWI4ZC0yMGI1MTEzNjY2OTkwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT/qneSDx6m5z/jbYcOwZc5sq8Hdl2zhjLXHZn0PAm+I1SAaarmpefoXvNxa6kkR1xomQBhSLdUeX1RWTGjw51po2cwZTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHQYDVR0OBBYEFK8ii3t6Pzv8ZtEz0ZZnJ8djo/14MB8GA1UdIwQYMBaAFKDk5Tdk8eOOMf/+GDw+62HNH6XKMA0GCSqGSIb3DQEBCwUAA4ICAQCH9wCAEemv7UPnXw+EkF1yx/83Q5GghOy0EvTHPxLqUltlbcDXyH34nogTgSZcl5bvWP/sQei1p0k76s6ZdwF2+5NUCHI+B4WcO7fyNMZZ+e1QT9mIc58TW17NmIGBXMBh1cjsEWXwlwpaghCtbRw+A6L8xwsfw4ocV1b8DRk855JrzQJmRZu0x5nnVcvp4CS6vI0W6QPva6t9Hyh9g1+5Pz49kw84vfKXsc428BXQTwEaLMRG89oYY4ERLva1XQYwL9z+U0F59wVRgcVCSyOL7rabQicfLjrer44R2yJ6BpSkHIncctTK1F9y8LVUcUVdWOKYc2vTYHT8bt41cSHnGCMYrrbEy1/IkbdqbuE2V3pEe0h0p6c6YQESAiipHqlJJOogw/WNlkeeFkvZRwc+jDAGRhC3GdbwPQO146A6NVekDj6b+Tw7dWV7Rqc35D6V78FrXxZ5/MrpN53Lkb4wPczln6t7AmM5/M57/z4NWxA3iny3HgEoqI/J4plGJJ+rjYyjF4jC51axoF6Pp2yAgx0/DwN4pgAmsNNH3oIiHGyy6CpBQ6LN9rrlgCE7iANaCdiipPnxLRJ99cQSBXVrvrENyNM+v1quRBWNJCHj2Jvy7LoPtB3z8Ik1/HtLiMPbkkplAX12Pi0IXCEJnJNM+DB5ebxW8qd1oxQ8dnBouA=="}}, "dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCAic3ViamVjdCI6W3sibmFtZSI6ImNoYWlubG9vcC53b3JrZmxvdy5teXdmIiwgImRpZ2VzdCI6eyJzaGEyNTYiOiJiODI1ZTRjYzc5ZjcwNDcyZGNlMWQ0MjM1ODkzYTk4N2Q3YzA3Y2ZlNzlmN2RiM2YxYjcwNDVhNjUwNzBmM2Y5In19LCB7Im5hbWUiOiJnaXQuaGVhZCIsICJkaWdlc3QiOnsic2hhMSI6ImJmODY3NWEzODQwNTM4MzU4NGI4YzQ2OWRmODAzYTk0NWQ3NWQzN2YifSwgImFubm90YXRpb25zIjp7ImF1dGhvci5lbWFpbCI6ImppcGFyaXNAY2hhaW5sb29wLmRldiIsICJhdXRob3IubmFtZSI6Ikpvc2UgSS4gUGFyaXMiLCAiZGF0ZSI6IjIwMjUtMDQtMzBUMTM6NDY6NDFaIiwgIm1lc3NhZ2UiOiJleHBvc2Ugc2lnbmluZyBvcHRpb25zIGluIGF0dGVzdGF0aW9uXG5cblNpZ25lZC1vZmYtYnk6IEpvc2UgSS4gUGFyaXMgPGppcGFyaXNAY2hhaW5sb29wLmRldj5cbiIsICJyZW1vdGVzIjpbeyJuYW1lIjoib3JpZ2luIiwgInVybCI6ImdpdEBnaXRodWIuY29tOmppcGFyaXMvY2hhaW5sb29wLmdpdCJ9LCB7Im5hbWUiOiJ1cHN0cmVhbSIsICJ1cmwiOiJnaXRAZ2l0aHViLmNvbTpjaGFpbmxvb3AtZGV2L2NoYWlubG9vcC5naXQifSwgeyJuYW1lIjoibWlndWUiLCAidXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL21pZ21hcnRyaS9jaGFpbmxvb3AvIn0sIHsibmFtZSI6ImphdmkiLCAidXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2phdmlybG4vY2hhaW5sb29wIn1dLCAic2lnbmF0dXJlIjoiLS0tLS1CRUdJTiBQR1AgU0lHTkFUVVJFLS0tLS1cblxuaVFJekJBQUJDQUFkRmlFRWpINTFITVdrQ05ZMmRoWndvSXR5Q2srbTN0NEZBbWdTS2NnQUNna1FvSXR5Q2srbVxuM3Q3azB3Ly9UelluVVdNSElDVW9OeXhiMDVuaXo0Q04vRlgrSkhPZmFjb0FkaWJIaTNaV1ZIdnNmZ3NEd3FZelxuTmliaGp0ejB5VUpKL0VMaVdudlp6QWJqY0ZEczlrM3hNVndzQXZiNUIxYkdxTjFBMTdXM2p2T2tKVm9OZ2pNYlxuRitkMytDeExVaVdyOXgyNnc3MjFxZHBDUU9TUlBTTEI3TE5NZWVXbyszU0hyM2duY2VKV0JqQTVZS1RIeTRGMlxuRTE3cTVBc2N3azhZdTFoMFBuR2pDcm1WZU0wTmtNalY2a2JIS0hiL2tsL3c2MTlrZ1FNbzkwWjZDb0FDcXAwQ1xuYWJpMUpIbWZMOWJiaVk4MkxJZlZNRk1wZVovaTlYYjJrbUtWdDNnTWRmNWJvK1owYWkzdlJTeEl6N1JmR0s5VFxuUGJjTjE5aVd0NHJvMFZLMWJmQnJPWC9wNndjemF4cGhLcE5Sc0FoWWZUbFFGaEczU3RGWUhtb3Q2UFlqdXdaeFxuN0VhTUFlTkdGSWJ0NEFGb1MzK3hmQUI2L1JnQTFNczJ0cVhrd2ZZc21yTlNHaW90ZHNyejE2cWZrdUJLc1NvRFxueGRIV1lCNXd5OWp5V3FoRkdiOVhhYWhQVTB3czlxd09KcVlJYUtidnI2Z2pJMFIxTTBkV0tja1dVMnB3cGRRVlxucXFvTlo0YkIwNnQ2WHFFbkZFMmtrcnVEMEtwaW9oUit4YU9oM1U2OCtlSkU4L0pObUdxUDQ4Vzh5UU0yUW1NYlxuWm9WdmZUcHk0amY4UmNxR052TUhmQitMaVAySVc0VHNEWFYrUVlYZko2MU15cjBFVHdvOEZaNUdvVzZlZDNXcVxudHpzUnp4RzVjd2c5QlZiSXk2bzMwUlVTRmRYdCtIdFVCQWVFaUtuUHl3RXRlR2tGLytZPVxuPUdrVW5cbi0tLS0tRU5EIFBHUCBTSUdOQVRVUkUtLS0tLVxuIn19XSwgInByZWRpY2F0ZVR5cGUiOiJjaGFpbmxvb3AuZGV2L2F0dGVzdGF0aW9uL3YwLjIiLCAicHJlZGljYXRlIjp7ImJ1aWxkVHlwZSI6ImNoYWlubG9vcC5kZXYvd29ya2Zsb3dydW4vdjAuMSIsICJidWlsZGVyIjp7ImlkIjoiY2hhaW5sb29wLmRldi9jbGkvZGV2QHNoYTI1Njo5MDk1OTBhZjAxZTA0ZDM2ZDg2MWY1Y2Y0ZWI1ZDg4YTE2OWZlOGQ0ZGY5MDU3YmUwYjBhMTY1ODZkYzQyNTQzIn0sICJtYXRlcmlhbHMiOlt7ImFubm90YXRpb25zIjp7ImNoYWlubG9vcC5tYXRlcmlhbC5jYXMiOnRydWUsICJjaGFpbmxvb3AubWF0ZXJpYWwubmFtZSI6Im1hdGVyaWFsLTE3NDYwMjE3ODQxNjkwNTkwMDAiLCAiY2hhaW5sb29wLm1hdGVyaWFsLnR5cGUiOiJBUlRJRkFDVCJ9LCAiZGlnZXN0Ijp7InNoYTI1NiI6ImE4N2FhNTE2OThlMzJkNzcwYzIyMTRmZmVjNzllYjdiZGRlMzFjZGZkN2YyMTM1ZjgxZDNlOTU4ODM0ZGJkYTUifSwgIm5hbWUiOiJidW5kbGUuanNvbiJ9XSwgIm1ldGFkYXRhIjp7ImNvbnRyYWN0TmFtZSI6Im15cHJvamVjdC1teXdmIiwgImNvbnRyYWN0VmVyc2lvbiI6Ijk3IiwgImZpbmlzaGVkQXQiOiIyMDI1LTA0LTMwVDE0OjA0OjMxLjkwNTkwMVoiLCAiaW5pdGlhbGl6ZWRBdCI6IjIwMjUtMDQtMzBUMTQ6MDI6NDcuNzM3MzAyWiIsICJuYW1lIjoibXl3ZiIsICJvcmdhbml6YXRpb24iOiJteS1vcmciLCAicHJvamVjdCI6Im15cHJvamVjdCIsICJwcm9qZWN0VmVyc2lvbiI6InYxLjAuMC1yYy4xMyIsICJwcm9qZWN0VmVyc2lvblByZXJlbGVhc2UiOnRydWUsICJ0ZWFtIjoiIiwgIndvcmtmbG93SUQiOiI5NWYxYjU5Ny0yMGUxLTQ5OGMtYjkzMS1mYzZkNzMzMTg2MGUiLCAid29ya2Zsb3dSdW5JRCI6ImUxY2ExM2NhLTliOGEtNGM2OC1iMTc2LTBmNzZiMjE5YWI1NSJ9LCAicG9saWN5QXR0QmxvY2tlZCI6ZmFsc2UsICJwb2xpY3lCbG9ja0J5cGFzc0VuYWJsZWQiOmZhbHNlLCAicG9saWN5Q2hlY2tCbG9ja2luZ1N0cmF0ZWd5IjoiQURWSVNPUlkiLCAicG9saWN5RXZhbHVhdGlvbnMiOnsiQ0hBSU5MT09QLkFUVEVTVEFUSU9OIjpbeyJhbm5vdGF0aW9ucyI6eyJjYXRlZ29yeSI6InNlY3VyaXR5LCBDVkUifSwgImRlc2NyaXB0aW9uIjoiQ2hlY2tzIHRoYXQgYSBzdXBwb3J0ZWQgU0NBIHNjYW4gbWF0ZXJpYWwgaXMgcHJlc2VudCBpbiB0aGUgYXR0ZXN0YXRpb24iLCAibmFtZSI6InZ1bG5lcmFiaWxpdHktc2Nhbi1wcmVzZW50IiwgInBvbGljeVJlZmVyZW5jZSI6eyJhbm5vdGF0aW9ucyI6eyJuYW1lIjoidnVsbmVyYWJpbGl0eS1zY2FuLXByZXNlbnQiLCAib3JnYW5pemF0aW9uIjoiIn0sICJkaWdlc3QiOnsic2hhMjU2IjoiMTEzOWJlMjVjOTEzYmY0OTg4NmIyOGU1NGJlZTEwMmRlZTJmODk5MjYyZTk2NTIxOGI1MDNjZWIzYWQ3OWE3ZSJ9LCAibmFtZSI6InZ1bG5lcmFiaWxpdHktc2Nhbi1wcmVzZW50IiwgInVyaSI6ImNoYWlubG9vcDovL2xvY2FsaG9zdDo4MDAyL3Z1bG5lcmFiaWxpdHktc2Nhbi1wcmVzZW50In0sICJza2lwcGVkIjpmYWxzZSwgInR5cGUiOiJBVFRFU1RBVElPTiIsICJ2aW9sYXRpb25zIjpbeyJtZXNzYWdlIjoibWlzc2luZyBTQ0Egc2NhbiBtYXRlcmlhbCIsICJzdWJqZWN0IjoidnVsbmVyYWJpbGl0eS1zY2FuLXByZXNlbnQifV19XX0sICJwb2xpY3lIYXNWaW9sYXRpb25zIjp0cnVlLCAicnVubmVyRW52aXJvbm1lbnQiOiJ1bmtub3duIiwgInJ1bm5lclR5cGUiOiJSVU5ORVJfVFlQRV9VTlNQRUNJRklFRCIsICJzaWduaW5nQ0EiOiJmaWxlQ0EifX0=", "payloadType":"application/vnd.in-toto+json", "signatures":[{"sig":"MEYCIQCg+mkg/6LbqytMIlfJVXUCo90CMhEH7Y4RdAMQBh4dBgIhAP9LEi8fVbIDVWS4ImT0o0rwrENMVLqv1f0H8OzOsND/"}]}} \ No newline at end of file diff --git a/pkg/attestation/crafter/materials/testdata/attestation-dsse-invalidtype.json b/pkg/attestation/crafter/materials/testdata/attestation-dsse-invalidtype.json new file mode 100644 index 000000000..f43083286 --- /dev/null +++ b/pkg/attestation/crafter/materials/testdata/attestation-dsse-invalidtype.json @@ -0,0 +1,10 @@ +{ + "payloadType": "application/vnd.othertype+json", + "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiY2hhaW5sb29wLndvcmtmbG93LmNoYWlubG9vcC1jb2RlcWwiLCJkaWdlc3QiOnsic2hhMjU2IjoiYmNmNzlhNDgwMTFiNTEyZTliYjJlZmM5OWU5NGRhOGU0ODBmNWVjNWMwODg2NDNhN2JmZDJlODVhMmZmMTVlMSJ9fSx7Im5hbWUiOiJnaXQuaGVhZCIsImRpZ2VzdCI6eyJzaGExIjoiZWQ3YTY3ZDkwNjBjMjNhYzFmNjFmYWFjMDZjMjFjOGE5YTAzYzM1NyJ9LCJhbm5vdGF0aW9ucyI6eyJhdXRob3IuZW1haWwiOiJqYXZpZXJAY2hhaW5sb29wLmRldiIsImF1dGhvci5uYW1lIjoiSmF2aWVyIFJvZHLDrWd1ZXoiLCJkYXRlIjoiMjAyNC0wNC0yOVQwODo1ODozMVoiLCJtZXNzYWdlIjoiZmVhdChjaSk6IEFkZHMgY2hhaW5sb29wIHRvIGNvZGVxbCBwaXBlbGluZSAoIzcxMSlcblxuU2lnbmVkLW9mZi1ieTogSmF2aWVyIFJvZHJpZ3VleiA8amF2aWVyQGNoYWlubG9vcC5kZXY+IiwicmVtb3RlcyI6W3sibmFtZSI6Im9yaWdpbiIsInVybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9jaGFpbmxvb3AtZGV2L2NoYWlubG9vcCJ9XX19LHsibmFtZSI6ImdvLnNhcmlmIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImQ5YTFlMmNiMTY4NGUxNDY1MjhkZjgzNWQyMjAwN2MyYWI2ZjMzMzgzNzdhY2RlYzlkZTEwYjhkMDdlYzkxNmQifSwiYW5ub3RhdGlvbnMiOnsiY2hhaW5sb29wLm1hdGVyaWFsLmNhcyI6dHJ1ZSwiY2hhaW5sb29wLm1hdGVyaWFsLm5hbWUiOiJzYXJpZi1yZXN1bHRzIiwiY2hhaW5sb29wLm1hdGVyaWFsLnR5cGUiOiJTQVJJRiJ9fV0sInByZWRpY2F0ZVR5cGUiOiJjaGFpbmxvb3AuZGV2L2F0dGVzdGF0aW9uL3YwLjIiLCJwcmVkaWNhdGUiOnsiYnVpbGRUeXBlIjoiY2hhaW5sb29wLmRldi93b3JrZmxvd3J1bi92MC4xIiwiYnVpbGRlciI6eyJpZCI6ImNoYWlubG9vcC5kZXYvY2xpLzAuODMuMEBzaGEyNTY6MGU4ODNhMmI5NGM3OTJiZWU0ZDc3YTI1OGFmM2ExOTIxZDQxNjliNWUwZGI0MTk1NzA5YzhkZjIwZGFjYjEyNiJ9LCJlbnYiOnsiR0lUSFVCX0FDVE9SIjoiamF2aXJsbiIsIkdJVEhVQl9SRUYiOiJyZWZzL2hlYWRzL21haW4iLCJHSVRIVUJfUkVQT1NJVE9SWSI6ImNoYWlubG9vcC1kZXYvY2hhaW5sb29wIiwiR0lUSFVCX1JFUE9TSVRPUllfT1dORVIiOiJjaGFpbmxvb3AtZGV2IiwiR0lUSFVCX1JVTl9JRCI6Ijg4NzU5MzQwNzEiLCJHSVRIVUJfU0hBIjoiZWQ3YTY3ZDkwNjBjMjNhYzFmNjFmYWFjMDZjMjFjOGE5YTAzYzM1NyIsIlJVTk5FUl9OQU1FIjoiR2l0SHViIEFjdGlvbnMgNDgzIiwiUlVOTkVSX09TIjoiTGludXgifSwibWF0ZXJpYWxzIjpbeyJhbm5vdGF0aW9ucyI6eyJjaGFpbmxvb3AubWF0ZXJpYWwuY2FzIjp0cnVlLCJjaGFpbmxvb3AubWF0ZXJpYWwubmFtZSI6InNhcmlmLXJlc3VsdHMiLCJjaGFpbmxvb3AubWF0ZXJpYWwudHlwZSI6IlNBUklGIn0sImRpZ2VzdCI6eyJzaGEyNTYiOiJkOWExZTJjYjE2ODRlMTQ2NTI4ZGY4MzVkMjIwMDdjMmFiNmYzMzM4Mzc3YWNkZWM5ZGUxMGI4ZDA3ZWM5MTZkIn0sIm5hbWUiOiJnby5zYXJpZiJ9XSwibWV0YWRhdGEiOnsiZmluaXNoZWRBdCI6IjIwMjQtMDQtMjlUMDk6MDI6NDQuMzE2ODg5OTIzWiIsImluaXRpYWxpemVkQXQiOiIyMDI0LTA0LTI5VDA4OjU4OjUzLjc0NjgxNzYzNFoiLCJuYW1lIjoiY2hhaW5sb29wLWNvZGVxbCIsIm9yZ2FuaXphdGlvbiI6InJlYWQtb25seS1kZW1vIiwicHJvamVjdCI6ImNoYWlubG9vcCIsInRlYW0iOiJjb3JlIiwid29ya2Zsb3dJRCI6IjIyMzg5N2U1LTE0YmItNGQyYy1hZjA5LWY1NTM4ODMwYzdkYiIsIndvcmtmbG93UnVuSUQiOiJlMjRhNTMzYy0wZTYwLTQ4NGMtOTI1MC05NzZhZjI1NGE1MDEifSwicnVubmVyVHlwZSI6IkdJVEhVQl9BQ1RJT04iLCJydW5uZXJVUkwiOiJodHRwczovL2dpdGh1Yi5jb20vY2hhaW5sb29wLWRldi9jaGFpbmxvb3AvYWN0aW9ucy9ydW5zLzg4NzU5MzQwNzEifX0=", + "signatures": [ + { + "keyid": "", + "sig": "MEQCIHn+fRwh804+Hhd1CeFSrIvg6+QWagyXjrqb6LZxnufqAiAml2fu5UNufJJTnSRWwS6+P3Nu/4LoqF8uxElGT+WGnA==" + } + ] +} diff --git a/pkg/attestation/crafter/materials/testdata/attestation.json b/pkg/attestation/crafter/materials/testdata/attestation-dsse.json similarity index 100% rename from pkg/attestation/crafter/materials/testdata/attestation.json rename to pkg/attestation/crafter/materials/testdata/attestation-dsse.json diff --git a/pkg/policies/policies.go b/pkg/policies/policies.go index 22f091277..4ebbe8cd2 100644 --- a/pkg/policies/policies.go +++ b/pkg/policies/policies.go @@ -81,13 +81,17 @@ func (pv *PolicyVerifier) VerifyMaterial(ctx context.Context, material *v12.Atte return nil, NewPolicyError(err) } - for _, attachment := range attachments { - // Load material content - subject, err := material.GetEvaluableContent(artifactPath) - if err != nil { - return nil, NewPolicyError(err) - } + if len(attachments) == 0 { + return result, nil + } + // Load material content + subject, err := material.GetEvaluableContent(artifactPath) + if err != nil { + return nil, NewPolicyError(err) + } + + for _, attachment := range attachments { ev, err := pv.evaluatePolicyAttachment(ctx, attachment, subject, &evalOpts{kind: material.MaterialType, name: material.GetId()}, )