Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 2 additions & 40 deletions app/controlplane/internal/service/attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (
"time"

"github.com/cenkalti/backoff/v4"
"github.com/in-toto/in-toto-golang/in_toto"

cpAPI "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1"
contractAPI "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
Expand Down Expand Up @@ -295,36 +294,13 @@ func (s *AttestationService) GetUploadCreds(ctx context.Context, _ *cpAPI.Attest
return &cpAPI.AttestationServiceGetUploadCredsResponse{Result: &cpAPI.AttestationServiceGetUploadCredsResponse_Result{Token: t}}, nil
}

func extractPredicate(envelope *dsse.Envelope) (*renderer.ChainloopProvenancePredicateV1, error) {
decodedPayload, err := envelope.DecodeB64Payload()
if err != nil {
return nil, err
}

statement := &in_toto.Statement{}
if err := json.Unmarshal(decodedPayload, statement); err != nil {
return nil, fmt.Errorf("un-marshaling predicate: %w", err)
}

var predicate *renderer.ChainloopProvenancePredicateV1
if statement.PredicateType == renderer.ChainloopPredicateTypeV1 {
if predicate, err = extractPredicateV1(statement); err != nil {
return nil, fmt.Errorf("extracting predicate: %w", err)
}
} else {
return nil, errors.InternalServer("internal error", "predicate type not supported")
}

return predicate, nil
}

func bizAttestationToPb(att *biz.Attestation) (*cpAPI.AttestationItem, error) {
encodedAttestation, err := json.Marshal(att.Envelope)
if err != nil {
return nil, err
}

predicate, err := extractPredicate(att.Envelope)
predicate, err := renderer.ExtractPredicate(att.Envelope)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -358,20 +334,6 @@ func extractMaterials(in []*renderer.ChainloopProvenanceMaterial) []*cpAPI.Attes
return res
}

func extractPredicateV1(statement *in_toto.Statement) (*renderer.ChainloopProvenancePredicateV1, error) {
jsonPredicate, err := json.Marshal(statement.Predicate)
if err != nil {
return nil, fmt.Errorf("un-marshaling predicate: %w", err)
}

predicate := &renderer.ChainloopProvenancePredicateV1{}
if err := json.Unmarshal(jsonPredicate, predicate); err != nil {
return nil, fmt.Errorf("un-marshaling predicate: %w", err)
}

return predicate, nil
}

type uploadSBOMToDepTrackOpts struct {
envelope *dsse.Envelope
orgID, workflowID string
Expand Down Expand Up @@ -410,7 +372,7 @@ func uploadSBOMsToDependencyTrack(opts *uploadSBOMToDepTrackOpts) error {
return nil
}

predicate, err := extractPredicate(opts.envelope)
predicate, err := renderer.ExtractPredicate(opts.envelope)
if err != nil {
return err
}
Expand Down
42 changes: 42 additions & 0 deletions internal/attestation/renderer/chainloop.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

v1 "github.com/chainloop-dev/chainloop/app/cli/api/attestation/v1"
"github.com/in-toto/in-toto-golang/in_toto"
"github.com/secure-systems-lab/go-securesystemslib/dsse"

schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
slsacommon "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
Expand Down Expand Up @@ -213,3 +214,44 @@ func outputChainloopMaterials(att *v1.Attestation, onlyOutput bool) []*Chainloop

return res
}

// Extract the Chainloop attestation predicate from an encoded DSSE envelope
func ExtractPredicate(envelope *dsse.Envelope) (*ChainloopProvenancePredicateV1, error) {
decodedPayload, err := envelope.DecodeB64Payload()
if err != nil {
return nil, err
}

// 1 - Extract the in-toto statement
statement := &in_toto.Statement{}
if err := json.Unmarshal(decodedPayload, statement); err != nil {
return nil, fmt.Errorf("un-marshaling predicate: %w", err)
}

// 2 - Extract the Chainloop predicate from the in-toto statement
var predicate *ChainloopProvenancePredicateV1
switch statement.PredicateType {
case ChainloopPredicateTypeV1:
if predicate, err = extractPredicateV1(statement); err != nil {
return nil, fmt.Errorf("extracting predicate: %w", err)
}
default:
return nil, fmt.Errorf("unsupported predicate type: %s", statement.PredicateType)
}

return predicate, nil
}

func extractPredicateV1(statement *in_toto.Statement) (*ChainloopProvenancePredicateV1, error) {
jsonPredicate, err := json.Marshal(statement.Predicate)
if err != nil {
return nil, fmt.Errorf("un-marshaling predicate: %w", err)
}

predicate := &ChainloopProvenancePredicateV1{}
if err := json.Unmarshal(jsonPredicate, predicate); err != nil {
return nil, fmt.Errorf("un-marshaling predicate: %w", err)
}

return predicate, nil
}
96 changes: 96 additions & 0 deletions internal/attestation/renderer/chainloop_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//
// Copyright 2023 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 renderer

import (
"encoding/json"
"fmt"
"os"
"testing"

"github.com/secure-systems-lab/go-securesystemslib/dsse"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestExtractPredicate(t *testing.T) {
testCases := []struct {
name string
envelopePath string
predicatePath string
wantErr bool
}{
{
name: "valid envelope",
envelopePath: "testdata/valid.envelope.json",
predicatePath: "testdata/valid.predicate.json",
wantErr: false,
},
{
name: "unknown source attestation",
envelopePath: "testdata/unknown.envelope.json",
wantErr: true,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
envelope, err := testEnvelope(tc.envelopePath)
require.NoError(t, err)

got, err := ExtractPredicate(envelope)
if tc.wantErr {
assert.Error(t, err)
return
}

want, err := testPredicate(tc.predicatePath)
require.NoError(t, err)

assert.NoError(t, err)
assert.Equal(t, want, got)
})
}
}

func testEnvelope(filePath string) (*dsse.Envelope, error) {
var envelope dsse.Envelope
content, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}

err = json.Unmarshal(content, &envelope)
if err != nil {
return nil, err
}

return &envelope, nil
}

func testPredicate(path string) (*ChainloopProvenancePredicateV1, error) {
var predicate ChainloopProvenancePredicateV1
content, err := os.ReadFile(path)
if err != nil {
return nil, err
}

if err := json.Unmarshal(content, &predicate); err != nil {
return nil, fmt.Errorf("un-marshaling predicate: %w", err)
}

return &predicate, nil
}
10 changes: 10 additions & 0 deletions internal/attestation/renderer/testdata/unknown.envelope.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"payloadType": "application/vnd.in-toto+json",
"payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJ1bmtub3duLmNvbXBhbnkvYXR0ZXN0YXRpb24vdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJjaGFpbmxvb3AuZGV2L3dvcmtmbG93L2ZvbyIsImRpZ2VzdCI6eyJzaGEyNTYiOiIzMGE2ZTQ3NzAyMTQ4ZWE4NTUzYjNlOGZhYjA1OTUyYzMyOTdhNzMwM2EwZWY4MjA1ODAxM2U4NTg0NGFjYmQzIn19XSwicHJlZGljYXRlIjp7Im1ldGFkYXRhIjp7Im5hbWUiOiJmb28iLCJwcm9qZWN0IjoiYmFyIiwidGVhbSI6IiIsImluaXRpYWxpemVkQXQiOiIyMDIzLTAzLTIyVDEwOjE1OjQwLjczNTUzMTg0MVoiLCJmaW5pc2hlZEF0IjoiMjAyMy0wMy0yMlQxMToxNjoxOS45Njg0ODg4MzcrMDE6MDAiLCJ3b3JrZmxvd1J1bklEIjoiYTc3ZDhlNGUtNDE1My00ZWVlLTk4ZWQtN2E2MDg4OTNmMzFiIiwid29ya2Zsb3dJRCI6Ijk0YTgwOTcyLTA0YjAtNDIxNS1hZDk4LWE3NGQwODI2M2Y5ZiJ9LCJidWlsZGVyIjp7ImlkIjoiY2hhaW5sb29wLmRldi9jbGkvZGV2QHNoYTI1Njo2M2I2MDAxZjczY2Y3ZDI4ZGVjMzc3MjBlOGUwYjJmNzc5YTY5MDE4ZTgwYmRhY2JhNzY2ZDAxYjU5MDEwOThiIn0sImJ1aWxkVHlwZSI6ImNoYWlubG9vcC5kZXYvd29ya2Zsb3dydW4vdjAuMSIsInJ1bm5lclR5cGUiOiJSVU5ORVJfVFlQRV9VTlNQRUNJRklFRCJ9fQ==",
"signatures": [
{
"keyid": "",
"sig": "MEQCICuT3+A0ub9e3VJ/wjPv+oPTnwUR1AT3IKlXyqUXcgphAiAOeX1hUNYM+rGl6NE1sbTwqag6unuuXceAZF3aNCJ9YQ=="
}
]
}
10 changes: 10 additions & 0 deletions internal/attestation/renderer/testdata/valid.envelope.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"payloadType": "application/vnd.in-toto+json",
"payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJjaGFpbmxvb3AuZGV2L2F0dGVzdGF0aW9uL3YwLjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiY2hhaW5sb29wLmRldi93b3JrZmxvdy9idWlsZC1hbmQtcmVsZWFzZSIsImRpZ2VzdCI6eyJzaGEyNTYiOiI0YzQ2YTYzOWU5ZDM5NWJhYjMyODk1NDk1YjNkODExODZmNzhlNTMxMGFjM2RhODU0Mzk0ZjgxOWExMDMxYzk3In19LHsibmFtZSI6ImdoY3IuaW8vY2hhaW5sb29wLWRldi9pbnRlZ3JhdGlvbi1kZW1vIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImUwZDgxNzk5OTFkZDczNWJhZjA5NjE5MDFiMzM0NzZhNzZhMGYzMDBiYzRlYTA3ZTNkN2FlN2MyNGUxNDcxOTMifX1dLCJwcmVkaWNhdGUiOnsibWV0YWRhdGEiOnsibmFtZSI6ImJ1aWxkLWFuZC1yZWxlYXNlIiwicHJvamVjdCI6ImludGVncmF0aW9uLWRlbW8iLCJ0ZWFtIjoiIiwiaW5pdGlhbGl6ZWRBdCI6IjIwMjMtMDMtMTNUMjM6MzU6MzYuOTgzMTM4OTE5WiIsImZpbmlzaGVkQXQiOiIyMDIzLTAzLTEzVDIzOjM2OjM2Ljc0OTMyNTEwM1oiLCJ3b3JrZmxvd1J1bklEIjoiOTEwOTNmNmUtMzdjMC00ZTAyLWFmNWQtYzk4NTk1OTZkMTI1Iiwid29ya2Zsb3dJRCI6IjM3MDIyYjJmLTM0YzMtNGY0Ny05ZmQ3LTUxNGI0YTdiYWFhZCJ9LCJtYXRlcmlhbHMiOlt7Im5hbWUiOiJiaW5hcnkiLCJ0eXBlIjoiQVJUSUZBQ1QiLCJtYXRlcmlhbCI6eyJzbHNhIjp7InVyaSI6ImludGVncmF0aW9uLWRlbW9fMC4wLjM5X2xpbnV4X2FtZDY0LnRhci5neiIsImRpZ2VzdCI6eyJzaGEyNTYiOiJiMTU1Y2RmYzMyOGIyNzNjNGI3NDFjMDhiM2I4NGFjNDQxYjA1NjJjYTUxODkzZjIzNDk1YjM1YWJmODllYTg3In19fX0seyJuYW1lIjoiaW1hZ2UiLCJ0eXBlIjoiQ09OVEFJTkVSX0lNQUdFIiwibWF0ZXJpYWwiOnsic2xzYSI6eyJ1cmkiOiJnaGNyLmlvL2NoYWlubG9vcC1kZXYvaW50ZWdyYXRpb24tZGVtbyIsImRpZ2VzdCI6eyJzaGEyNTYiOiJlMGQ4MTc5OTkxZGQ3MzViYWYwOTYxOTAxYjMzNDc2YTc2YTBmMzAwYmM0ZWEwN2UzZDdhZTdjMjRlMTQ3MTkzIn19fX0seyJuYW1lIjoic2JvbSIsInR5cGUiOiJTQk9NX0NZQ0xPTkVEWF9KU09OIiwibWF0ZXJpYWwiOnsic2xzYSI6eyJ1cmkiOiJzYm9tLmN5Y2xvbmVkeC5qc29uIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImI1MGYzODk2MWNjMmU5N2QwOTAzZjQ2ODNhNDBlMjUyOGY3ZjZjOWQzODJlOGM2MDQ4YjAzNjNhZjk1YjcwODAifX19fV0sImJ1aWxkZXIiOnsiaWQiOiJjaGFpbmxvb3AuZGV2L2NsaS8wLjguOTJAc2hhMjU2OmZhMDFjNmNkMTA0ODAzZWQ1YjFlODBlMjE2YzFjN2Y2MjQ0YjIxY2VmODVjYTQ4YmI2OTMwN2JhMTcxM2U2MTkifSwiYnVpbGRUeXBlIjoiY2hhaW5sb29wLmRldi93b3JrZmxvd3J1bi92MC4xIiwiZW52Ijp7IkdJVEhVQl9BQ1RPUiI6Im1pZ21hcnRyaSIsIkdJVEhVQl9SRUYiOiJyZWZzL3RhZ3MvdjAuMC4zOSIsIkdJVEhVQl9SRVBPU0lUT1JZIjoiY2hhaW5sb29wLWRldi9pbnRlZ3JhdGlvbi1kZW1vIiwiR0lUSFVCX1JFUE9TSVRPUllfT1dORVIiOiJjaGFpbmxvb3AtZGV2IiwiR0lUSFVCX1JVTl9JRCI6IjQ0MTA1NDMzNjUiLCJHSVRIVUJfU0hBIjoiMGFjY2M5MzkyZmIxZjliMjU4MTY3YzE4ZmZhMGFlYjYyNjk3M2YxYyIsIlJVTk5FUl9OQU1FIjoiSG9zdGVkIEFnZW50IiwiUlVOTkVSX09TIjoiTGludXgifSwicnVubmVyVHlwZSI6IkdJVEhVQl9BQ1RJT04iLCJydW5uZXJVUkwiOiJodHRwczovL2dpdGh1Yi5jb20vY2hhaW5sb29wLWRldi9pbnRlZ3JhdGlvbi1kZW1vL2FjdGlvbnMvcnVucy80NDEwNTQzMzY1In19",
"signatures": [
{
"keyid": "",
"sig": "MEQCIBlSdPt604OSyDMF4vjY8DoKQ6uxc1NADtay0q4Ii4f7AiA4KxaiIc1HPSi0a0bJv3l0V/wR2aPvxsDDfmAEd+LFTA=="
}
]
}
65 changes: 65 additions & 0 deletions internal/attestation/renderer/testdata/valid.predicate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"buildType": "chainloop.dev/workflowrun/v0.1",
"builder": {
"id": "chainloop.dev/cli/0.8.92@sha256:fa01c6cd104803ed5b1e80e216c1c7f6244b21cef85ca48bb69307ba1713e619"
},
"env": {
"GITHUB_ACTOR": "migmartri",
"GITHUB_REF": "refs/tags/v0.0.39",
"GITHUB_REPOSITORY": "chainloop-dev/integration-demo",
"GITHUB_REPOSITORY_OWNER": "chainloop-dev",
"GITHUB_RUN_ID": "4410543365",
"GITHUB_SHA": "0accc9392fb1f9b258167c18ffa0aeb626973f1c",
"RUNNER_NAME": "Hosted Agent",
"RUNNER_OS": "Linux"
},
"materials": [
{
"material": {
"slsa": {
"digest": {
"sha256": "b155cdfc328b273c4b741c08b3b84ac441b0562ca51893f23495b35abf89ea87"
},
"uri": "integration-demo_0.0.39_linux_amd64.tar.gz"
}
},
"name": "binary",
"type": "ARTIFACT"
},
{
"material": {
"slsa": {
"digest": {
"sha256": "e0d8179991dd735baf0961901b33476a76a0f300bc4ea07e3d7ae7c24e147193"
},
"uri": "ghcr.io/chainloop-dev/integration-demo"
}
},
"name": "image",
"type": "CONTAINER_IMAGE"
},
{
"material": {
"slsa": {
"digest": {
"sha256": "b50f38961cc2e97d0903f4683a40e2528f7f6c9d382e8c6048b0363af95b7080"
},
"uri": "sbom.cyclonedx.json"
}
},
"name": "sbom",
"type": "SBOM_CYCLONEDX_JSON"
}
],
"metadata": {
"finishedAt": "2023-03-13T23:36:36.749325103Z",
"initializedAt": "2023-03-13T23:35:36.983138919Z",
"name": "build-and-release",
"project": "integration-demo",
"team": "",
"workflowID": "37022b2f-34c3-4f47-9fd7-514b4a7baaad",
"workflowRunID": "91093f6e-37c0-4e02-af5d-c9859596d125"
},
"runnerType": "GITHUB_ACTION",
"runnerURL": "https://github.com/chainloop-dev/integration-demo/actions/runs/4410543365"
}