From bdf565fe28d9a903953285176458c064eeaf0705 Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino Date: Sun, 18 Jun 2023 17:57:31 +0200 Subject: [PATCH 1/5] feat(controlplane): store attestation in DB Signed-off-by: Miguel Martinez Trivino --- app/controlplane/cmd/wire_gen.go | 3 - app/controlplane/internal/biz/attestation.go | 68 --------------- .../internal/biz/attestation_test.go | 82 ------------------- app/controlplane/internal/biz/biz.go | 1 - .../internal/biz/mocks/WorkflowRunRepo.go | 27 ++---- app/controlplane/internal/biz/workflowrun.go | 15 ++-- .../biz/workflowrun_integration_test.go | 18 ++-- .../internal/data/ent/migrate/schema.go | 2 +- .../internal/data/ent/mutation.go | 79 +++++++++--------- .../internal/data/ent/schema-viz.html | 2 +- .../internal/data/ent/schema/workflowrun.go | 3 +- .../internal/data/ent/workflowrun.go | 19 +++-- .../internal/data/ent/workflowrun/where.go | 12 +-- .../data/ent/workflowrun/workflowrun.go | 6 +- .../internal/data/ent/workflowrun_create.go | 13 +-- .../internal/data/ent/workflowrun_update.go | 41 +++++----- app/controlplane/internal/data/workflowrun.go | 22 ++--- .../internal/service/attestation.go | 59 ++++--------- .../internal/service/workflowrun.go | 28 +------ 19 files changed, 142 insertions(+), 358 deletions(-) delete mode 100644 app/controlplane/internal/biz/attestation_test.go diff --git a/app/controlplane/cmd/wire_gen.go b/app/controlplane/cmd/wire_gen.go index 3641d39c8..35033dc1f 100644 --- a/app/controlplane/cmd/wire_gen.go +++ b/app/controlplane/cmd/wire_gen.go @@ -82,11 +82,9 @@ func wireApp(bootstrap *conf.Bootstrap, readerWriter credentials.ReaderWriter, l cleanup() return nil, nil, err } - attestationUseCase := biz.NewAttestationUseCase(casClientUseCase, logger) newWorkflowRunServiceOpts := &service.NewWorkflowRunServiceOpts{ WorkflowRunUC: workflowRunUseCase, WorkflowUC: workflowUseCase, - AttestationUC: attestationUseCase, WorkflowContractUC: workflowContractUseCase, CredsReader: readerWriter, Opts: v2, @@ -103,7 +101,6 @@ func wireApp(bootstrap *conf.Bootstrap, readerWriter credentials.ReaderWriter, l WorkflowUC: workflowUseCase, WorkflowContractUC: workflowContractUseCase, OCIUC: ociRepositoryUseCase, - AttestationUC: attestationUseCase, CredsReader: readerWriter, IntegrationUseCase: integrationUseCase, CasCredsUseCase: casCredentialsUseCase, diff --git a/app/controlplane/internal/biz/attestation.go b/app/controlplane/internal/biz/attestation.go index ec717fcfd..4852252f5 100644 --- a/app/controlplane/internal/biz/attestation.go +++ b/app/controlplane/internal/biz/attestation.go @@ -16,77 +16,9 @@ package biz import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - - "github.com/chainloop-dev/chainloop/internal/servicelogger" - "github.com/go-kratos/kratos/v2/log" - - cr_v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/secure-systems-lab/go-securesystemslib/dsse" ) type Attestation struct { Envelope *dsse.Envelope } - -type AttestationUseCase struct { - logger *log.Helper - CASClient -} - -type AttestationRef struct { - // Sha256 is the digest of the attestation and used as reference for the CAS - Sha256 string - // Unique identifier of the secret containing the credentials to access the CAS - SecretRef string -} - -func NewAttestationUseCase(client CASClient, logger log.Logger) *AttestationUseCase { - if logger == nil { - logger = log.NewStdLogger(io.Discard) - } - - return &AttestationUseCase{ - logger: servicelogger.ScopedHelper(logger, "biz/attestation"), - CASClient: client, - } -} - -func (uc *AttestationUseCase) FetchFromStore(ctx context.Context, secretID, digest string) (*Attestation, error) { - uc.logger.Infow("msg", "downloading attestation", "digest", digest) - buf := bytes.NewBuffer(nil) - - if err := uc.CASClient.Download(ctx, secretID, buf, digest); err != nil { - return nil, fmt.Errorf("downloading from CAS: %w", err) - } - - var envelope dsse.Envelope - if err := json.Unmarshal(buf.Bytes(), &envelope); err != nil { - return nil, err - } - - return &Attestation{Envelope: &envelope}, nil -} - -func (uc *AttestationUseCase) UploadToCAS(ctx context.Context, envelope *dsse.Envelope, secretID, workflowRunID string) (*cr_v1.Hash, error) { - filename := fmt.Sprintf("attestation-%s.json", workflowRunID) - jsonContent, err := json.Marshal(envelope) - if err != nil { - return nil, fmt.Errorf("marshaling the envelope: %w", err) - } - - h, _, err := cr_v1.SHA256(bytes.NewBuffer(jsonContent)) - if err != nil { - return nil, fmt.Errorf("calculating the digest: %w", err) - } - - if err := uc.CASClient.Upload(ctx, secretID, bytes.NewBuffer(jsonContent), filename, h.String()); err != nil { - return nil, fmt.Errorf("uploading to CAS: %w", err) - } - - return &h, nil -} diff --git a/app/controlplane/internal/biz/attestation_test.go b/app/controlplane/internal/biz/attestation_test.go deleted file mode 100644 index 20265e153..000000000 --- a/app/controlplane/internal/biz/attestation_test.go +++ /dev/null @@ -1,82 +0,0 @@ -// -// 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 biz_test - -import ( - "context" - "encoding/json" - "fmt" - "io" - "testing" - - "github.com/chainloop-dev/chainloop/app/controlplane/internal/biz" - "github.com/chainloop-dev/chainloop/app/controlplane/internal/biz/mocks" - cr_v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/google/uuid" - "github.com/secure-systems-lab/go-securesystemslib/dsse" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" -) - -var runID = uuid.NewString() -var envelope = &dsse.Envelope{} - -var expectedDigest = cr_v1.Hash{Algorithm: "sha256", Hex: "f845058d865c3d4d491c9019f6afe9c543ad2cd11b31620cc512e341fb03d3d8"} - -func (s *attestationTestSuite) TestUploadToCAS() { - ctx := context.Background() - s.casClient.On( - "Upload", ctx, "my-secret", mock.Anything, - fmt.Sprintf("attestation-%s.json", runID), expectedDigest.String(), - ).Return(nil) - - gotDigest, err := s.uc.UploadToCAS(ctx, envelope, "my-secret", runID) - assert.NoError(s.T(), err) - assert.Equal(s.T(), &expectedDigest, gotDigest) -} - -func (s *attestationTestSuite) TestFetchFromStore() { - want := &biz.Attestation{Envelope: &dsse.Envelope{}} - ctx := context.Background() - s.casClient.On("Download", ctx, "my-secret", mock.Anything, expectedDigest.String()).Return(nil).Run( - func(args mock.Arguments) { - buf := args.Get(2).(io.Writer) - err := json.NewEncoder(buf).Encode(want) - require.NoError(s.T(), err) - }) - - got, err := s.uc.FetchFromStore(ctx, "my-secret", expectedDigest.String()) - assert.NoError(s.T(), err) - assert.Equal(s.T(), want, got) -} - -func TestAttestation(t *testing.T) { - suite.Run(t, new(attestationTestSuite)) -} - -func (s *attestationTestSuite) SetupTest() { - s.casClient = mocks.NewCASClient(s.T()) - s.uc = biz.NewAttestationUseCase(s.casClient, nil) -} - -// Utility struct to hold the test suite -type attestationTestSuite struct { - suite.Suite - uc *biz.AttestationUseCase - casClient *mocks.CASClient -} diff --git a/app/controlplane/internal/biz/biz.go b/app/controlplane/internal/biz/biz.go index 7affc96b8..131a95082 100644 --- a/app/controlplane/internal/biz/biz.go +++ b/app/controlplane/internal/biz/biz.go @@ -24,7 +24,6 @@ var ProviderSet = wire.NewSet( NewRootAccountUseCase, NewWorkflowRunUseCase, NewOrganizationUseCase, - NewAttestationUseCase, NewWorkflowContractUseCase, NewCASCredentialsUseCase, NewOCIRepositoryUseCase, diff --git a/app/controlplane/internal/biz/mocks/WorkflowRunRepo.go b/app/controlplane/internal/biz/mocks/WorkflowRunRepo.go index 0b52dc8ce..8bff6c97c 100644 --- a/app/controlplane/internal/biz/mocks/WorkflowRunRepo.go +++ b/app/controlplane/internal/biz/mocks/WorkflowRunRepo.go @@ -1,18 +1,3 @@ -// -// 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. - // Code generated by mockery v2.20.0. DO NOT EDIT. package mocks @@ -22,6 +7,8 @@ import ( biz "github.com/chainloop-dev/chainloop/app/controlplane/internal/biz" + dsse "github.com/secure-systems-lab/go-securesystemslib/dsse" + mock "github.com/stretchr/testify/mock" pagination "github.com/chainloop-dev/chainloop/app/controlplane/internal/pagination" @@ -201,13 +188,13 @@ func (_m *WorkflowRunRepo) MarkAsFinished(ctx context.Context, ID uuid.UUID, sta return r0 } -// SaveAttestationRef provides a mock function with given fields: ctx, ID, ref -func (_m *WorkflowRunRepo) SaveAttestationRef(ctx context.Context, ID uuid.UUID, ref *biz.AttestationRef) error { - ret := _m.Called(ctx, ID, ref) +// SaveAttestation provides a mock function with given fields: ctx, ID, att +func (_m *WorkflowRunRepo) SaveAttestation(ctx context.Context, ID uuid.UUID, att *dsse.Envelope) error { + ret := _m.Called(ctx, ID, att) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID, *biz.AttestationRef) error); ok { - r0 = rf(ctx, ID, ref) + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID, *dsse.Envelope) error); ok { + r0 = rf(ctx, ID, att) } else { r0 = ret.Error(0) } diff --git a/app/controlplane/internal/biz/workflowrun.go b/app/controlplane/internal/biz/workflowrun.go index 5dd4edc0b..e33420745 100644 --- a/app/controlplane/internal/biz/workflowrun.go +++ b/app/controlplane/internal/biz/workflowrun.go @@ -17,11 +17,11 @@ package biz import ( "context" - "errors" "io" "time" "github.com/chainloop-dev/chainloop/app/controlplane/internal/pagination" + "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/go-kratos/kratos/v2/log" "github.com/google/uuid" @@ -35,7 +35,7 @@ type WorkflowRun struct { AttestationID uuid.UUID RunURL, RunnerType string ContractVersionID uuid.UUID - AttestationRef *AttestationRef + Attestation *dsse.Envelope } type WorkflowRunWithContract struct { @@ -58,7 +58,7 @@ type WorkflowRunRepo interface { FindByID(ctx context.Context, ID uuid.UUID) (*WorkflowRun, error) FindByIDInOrg(ctx context.Context, orgID, ID uuid.UUID) (*WorkflowRun, error) MarkAsFinished(ctx context.Context, ID uuid.UUID, status WorkflowRunStatus, reason string) error - SaveAttestationRef(ctx context.Context, ID uuid.UUID, ref *AttestationRef) error + SaveAttestation(ctx context.Context, ID uuid.UUID, att *dsse.Envelope) error List(ctx context.Context, orgID, workflowID uuid.UUID, p *pagination.Options) ([]*WorkflowRun, string, error) // List the runs that have not finished and are older than a given time ListNotFinishedOlderThan(ctx context.Context, olderThan time.Time) ([]*WorkflowRun, error) @@ -198,18 +198,13 @@ func (uc *WorkflowRunUseCase) MarkAsFinished(ctx context.Context, id string, sta return uc.wfRunRepo.MarkAsFinished(ctx, runID, status, reason) } -// Store the attestation digest for the workflowrun -func (uc *WorkflowRunUseCase) AssociateAttestation(ctx context.Context, id string, ref *AttestationRef) error { - if ref == nil || ref.SecretRef == "" || ref.Sha256 == "" { - return NewErrValidation(errors.New("attestation ref is nil or invalid")) - } - +func (uc *WorkflowRunUseCase) SaveAttestation(ctx context.Context, id string, envelope *dsse.Envelope) error { runID, err := uuid.Parse(id) if err != nil { return NewErrInvalidUUID(err) } - return uc.wfRunRepo.SaveAttestationRef(ctx, runID, ref) + return uc.wfRunRepo.SaveAttestation(ctx, runID, envelope) } // List the workflowruns associated with an org and optionally filtered by a workflow diff --git a/app/controlplane/internal/biz/workflowrun_integration_test.go b/app/controlplane/internal/biz/workflowrun_integration_test.go index ce394a5e7..8d74e8367 100644 --- a/app/controlplane/internal/biz/workflowrun_integration_test.go +++ b/app/controlplane/internal/biz/workflowrun_integration_test.go @@ -22,27 +22,23 @@ import ( "github.com/chainloop-dev/chainloop/app/controlplane/internal/biz" "github.com/chainloop-dev/chainloop/app/controlplane/internal/biz/testhelpers" "github.com/google/uuid" + "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) -func (s *workflowRunIntegrationTestSuite) TestAssociateAttestation() { +func (s *workflowRunIntegrationTestSuite) TestSaveAttestation() { assert := assert.New(s.T()) ctx := context.Background() - validRef := &biz.AttestationRef{Sha256: "deadbeef", SecretRef: "secret-ref"} + + validEnvelope := &dsse.Envelope{} s.T().Run("non existing workflowRun", func(t *testing.T) { - err := s.WorkflowRun.AssociateAttestation(ctx, uuid.NewString(), validRef) + err := s.WorkflowRun.SaveAttestation(ctx, uuid.NewString(), validEnvelope) assert.Error(err) assert.True(biz.IsNotFound(err)) }) - s.T().Run("empty attestation ref", func(t *testing.T) { - err := s.WorkflowRun.AssociateAttestation(ctx, uuid.NewString(), nil) - assert.Error(err) - assert.True(biz.IsErrValidation(err)) - }) - s.T().Run("valid workflowrun", func(t *testing.T) { org, err := s.Organization.Create(ctx, "testing org") assert.NoError(err) @@ -64,13 +60,13 @@ func (s *workflowRunIntegrationTestSuite) TestAssociateAttestation() { }) assert.NoError(err) - err = s.WorkflowRun.AssociateAttestation(ctx, run.ID.String(), validRef) + err = s.WorkflowRun.SaveAttestation(ctx, run.ID.String(), validEnvelope) assert.NoError(err) // Retrieve attestation ref from storage and compare r, err := s.WorkflowRun.View(ctx, org.ID, run.ID.String()) assert.NoError(err) - assert.Equal(r.AttestationRef, validRef) + assert.Equal(r.Attestation, validEnvelope) }) } diff --git a/app/controlplane/internal/data/ent/migrate/schema.go b/app/controlplane/internal/data/ent/migrate/schema.go index 62a25f57b..6ed0652fa 100644 --- a/app/controlplane/internal/data/ent/migrate/schema.go +++ b/app/controlplane/internal/data/ent/migrate/schema.go @@ -253,7 +253,7 @@ var ( {Name: "reason", Type: field.TypeString, Nullable: true, Size: 2147483647}, {Name: "run_url", Type: field.TypeString, Nullable: true}, {Name: "runner_type", Type: field.TypeString, Nullable: true}, - {Name: "attestation_ref", Type: field.TypeJSON, Nullable: true}, + {Name: "attestation", Type: field.TypeJSON, Nullable: true}, {Name: "robot_account_workflowruns", Type: field.TypeUUID, Nullable: true}, {Name: "workflow_workflowruns", Type: field.TypeUUID, Nullable: true}, {Name: "workflow_run_contract_version", Type: field.TypeUUID, Nullable: true}, diff --git a/app/controlplane/internal/data/ent/mutation.go b/app/controlplane/internal/data/ent/mutation.go index f6d720143..fd96c30a7 100644 --- a/app/controlplane/internal/data/ent/mutation.go +++ b/app/controlplane/internal/data/ent/mutation.go @@ -25,6 +25,7 @@ import ( "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/workflowcontractversion" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/workflowrun" "github.com/google/uuid" + "github.com/secure-systems-lab/go-securesystemslib/dsse" ) const ( @@ -6882,7 +6883,7 @@ type WorkflowRunMutation struct { reason *string run_url *string runner_type *string - attestation_ref **biz.AttestationRef + attestation **dsse.Envelope clearedFields map[string]struct{} workflow *uuid.UUID clearedworkflow bool @@ -7267,53 +7268,53 @@ func (m *WorkflowRunMutation) ResetRunnerType() { delete(m.clearedFields, workflowrun.FieldRunnerType) } -// SetAttestationRef sets the "attestation_ref" field. -func (m *WorkflowRunMutation) SetAttestationRef(br *biz.AttestationRef) { - m.attestation_ref = &br +// SetAttestation sets the "attestation" field. +func (m *WorkflowRunMutation) SetAttestation(d *dsse.Envelope) { + m.attestation = &d } -// AttestationRef returns the value of the "attestation_ref" field in the mutation. -func (m *WorkflowRunMutation) AttestationRef() (r *biz.AttestationRef, exists bool) { - v := m.attestation_ref +// Attestation returns the value of the "attestation" field in the mutation. +func (m *WorkflowRunMutation) Attestation() (r *dsse.Envelope, exists bool) { + v := m.attestation if v == nil { return } return *v, true } -// OldAttestationRef returns the old "attestation_ref" field's value of the WorkflowRun entity. +// OldAttestation returns the old "attestation" field's value of the WorkflowRun entity. // If the WorkflowRun object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *WorkflowRunMutation) OldAttestationRef(ctx context.Context) (v *biz.AttestationRef, err error) { +func (m *WorkflowRunMutation) OldAttestation(ctx context.Context) (v *dsse.Envelope, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldAttestationRef is only allowed on UpdateOne operations") + return v, errors.New("OldAttestation is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldAttestationRef requires an ID field in the mutation") + return v, errors.New("OldAttestation requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldAttestationRef: %w", err) + return v, fmt.Errorf("querying old value for OldAttestation: %w", err) } - return oldValue.AttestationRef, nil + return oldValue.Attestation, nil } -// ClearAttestationRef clears the value of the "attestation_ref" field. -func (m *WorkflowRunMutation) ClearAttestationRef() { - m.attestation_ref = nil - m.clearedFields[workflowrun.FieldAttestationRef] = struct{}{} +// ClearAttestation clears the value of the "attestation" field. +func (m *WorkflowRunMutation) ClearAttestation() { + m.attestation = nil + m.clearedFields[workflowrun.FieldAttestation] = struct{}{} } -// AttestationRefCleared returns if the "attestation_ref" field was cleared in this mutation. -func (m *WorkflowRunMutation) AttestationRefCleared() bool { - _, ok := m.clearedFields[workflowrun.FieldAttestationRef] +// AttestationCleared returns if the "attestation" field was cleared in this mutation. +func (m *WorkflowRunMutation) AttestationCleared() bool { + _, ok := m.clearedFields[workflowrun.FieldAttestation] return ok } -// ResetAttestationRef resets all changes to the "attestation_ref" field. -func (m *WorkflowRunMutation) ResetAttestationRef() { - m.attestation_ref = nil - delete(m.clearedFields, workflowrun.FieldAttestationRef) +// ResetAttestation resets all changes to the "attestation" field. +func (m *WorkflowRunMutation) ResetAttestation() { + m.attestation = nil + delete(m.clearedFields, workflowrun.FieldAttestation) } // SetWorkflowID sets the "workflow" edge to the Workflow entity by id. @@ -7486,8 +7487,8 @@ func (m *WorkflowRunMutation) Fields() []string { if m.runner_type != nil { fields = append(fields, workflowrun.FieldRunnerType) } - if m.attestation_ref != nil { - fields = append(fields, workflowrun.FieldAttestationRef) + if m.attestation != nil { + fields = append(fields, workflowrun.FieldAttestation) } return fields } @@ -7509,8 +7510,8 @@ func (m *WorkflowRunMutation) Field(name string) (ent.Value, bool) { return m.RunURL() case workflowrun.FieldRunnerType: return m.RunnerType() - case workflowrun.FieldAttestationRef: - return m.AttestationRef() + case workflowrun.FieldAttestation: + return m.Attestation() } return nil, false } @@ -7532,8 +7533,8 @@ func (m *WorkflowRunMutation) OldField(ctx context.Context, name string) (ent.Va return m.OldRunURL(ctx) case workflowrun.FieldRunnerType: return m.OldRunnerType(ctx) - case workflowrun.FieldAttestationRef: - return m.OldAttestationRef(ctx) + case workflowrun.FieldAttestation: + return m.OldAttestation(ctx) } return nil, fmt.Errorf("unknown WorkflowRun field %s", name) } @@ -7585,12 +7586,12 @@ func (m *WorkflowRunMutation) SetField(name string, value ent.Value) error { } m.SetRunnerType(v) return nil - case workflowrun.FieldAttestationRef: - v, ok := value.(*biz.AttestationRef) + case workflowrun.FieldAttestation: + v, ok := value.(*dsse.Envelope) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetAttestationRef(v) + m.SetAttestation(v) return nil } return fmt.Errorf("unknown WorkflowRun field %s", name) @@ -7634,8 +7635,8 @@ func (m *WorkflowRunMutation) ClearedFields() []string { if m.FieldCleared(workflowrun.FieldRunnerType) { fields = append(fields, workflowrun.FieldRunnerType) } - if m.FieldCleared(workflowrun.FieldAttestationRef) { - fields = append(fields, workflowrun.FieldAttestationRef) + if m.FieldCleared(workflowrun.FieldAttestation) { + fields = append(fields, workflowrun.FieldAttestation) } return fields } @@ -7663,8 +7664,8 @@ func (m *WorkflowRunMutation) ClearField(name string) error { case workflowrun.FieldRunnerType: m.ClearRunnerType() return nil - case workflowrun.FieldAttestationRef: - m.ClearAttestationRef() + case workflowrun.FieldAttestation: + m.ClearAttestation() return nil } return fmt.Errorf("unknown WorkflowRun nullable field %s", name) @@ -7692,8 +7693,8 @@ func (m *WorkflowRunMutation) ResetField(name string) error { case workflowrun.FieldRunnerType: m.ResetRunnerType() return nil - case workflowrun.FieldAttestationRef: - m.ResetAttestationRef() + case workflowrun.FieldAttestation: + m.ResetAttestation() return nil } return fmt.Errorf("unknown WorkflowRun field %s", name) diff --git a/app/controlplane/internal/data/ent/schema-viz.html b/app/controlplane/internal/data/ent/schema-viz.html index a05e61093..8a4ec5088 100644 --- a/app/controlplane/internal/data/ent/schema-viz.html +++ b/app/controlplane/internal/data/ent/schema-viz.html @@ -70,7 +70,7 @@ } - const entGraph = JSON.parse("{\"nodes\":[{\"id\":\"Integration\",\"fields\":[{\"name\":\"kind\",\"type\":\"string\"},{\"name\":\"description\",\"type\":\"string\"},{\"name\":\"secret_name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"configuration\",\"type\":\"[]byte\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"}]},{\"id\":\"IntegrationAttachment\",\"fields\":[{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"configuration\",\"type\":\"[]byte\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"}]},{\"id\":\"Membership\",\"fields\":[{\"name\":\"current\",\"type\":\"bool\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"updated_at\",\"type\":\"time.Time\"}]},{\"id\":\"OCIRepository\",\"fields\":[{\"name\":\"repo\",\"type\":\"string\"},{\"name\":\"secret_name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"validation_status\",\"type\":\"biz.OCIRepoValidationStatus\"},{\"name\":\"validated_at\",\"type\":\"time.Time\"}]},{\"id\":\"Organization\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"}]},{\"id\":\"RobotAccount\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"revoked_at\",\"type\":\"time.Time\"}]},{\"id\":\"User\",\"fields\":[{\"name\":\"email\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"}]},{\"id\":\"Workflow\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"project\",\"type\":\"string\"},{\"name\":\"team\",\"type\":\"string\"},{\"name\":\"runs_count\",\"type\":\"int\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"}]},{\"id\":\"WorkflowContract\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"}]},{\"id\":\"WorkflowContractVersion\",\"fields\":[{\"name\":\"body\",\"type\":\"[]byte\"},{\"name\":\"revision\",\"type\":\"int\"},{\"name\":\"created_at\",\"type\":\"time.Time\"}]},{\"id\":\"WorkflowRun\",\"fields\":[{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"finished_at\",\"type\":\"time.Time\"},{\"name\":\"state\",\"type\":\"biz.WorkflowRunStatus\"},{\"name\":\"reason\",\"type\":\"string\"},{\"name\":\"run_url\",\"type\":\"string\"},{\"name\":\"runner_type\",\"type\":\"string\"},{\"name\":\"attestation_ref\",\"type\":\"*biz.AttestationRef\"}]}],\"edges\":[{\"from\":\"IntegrationAttachment\",\"to\":\"Integration\",\"label\":\"integration\"},{\"from\":\"IntegrationAttachment\",\"to\":\"Workflow\",\"label\":\"workflow\"},{\"from\":\"Organization\",\"to\":\"Membership\",\"label\":\"memberships\"},{\"from\":\"Organization\",\"to\":\"WorkflowContract\",\"label\":\"workflow_contracts\"},{\"from\":\"Organization\",\"to\":\"Workflow\",\"label\":\"workflows\"},{\"from\":\"Organization\",\"to\":\"OCIRepository\",\"label\":\"oci_repositories\"},{\"from\":\"Organization\",\"to\":\"Integration\",\"label\":\"integrations\"},{\"from\":\"RobotAccount\",\"to\":\"WorkflowRun\",\"label\":\"workflowruns\"},{\"from\":\"User\",\"to\":\"Membership\",\"label\":\"memberships\"},{\"from\":\"Workflow\",\"to\":\"RobotAccount\",\"label\":\"robotaccounts\"},{\"from\":\"Workflow\",\"to\":\"WorkflowRun\",\"label\":\"workflowruns\"},{\"from\":\"Workflow\",\"to\":\"WorkflowContract\",\"label\":\"contract\"},{\"from\":\"WorkflowContract\",\"to\":\"WorkflowContractVersion\",\"label\":\"versions\"},{\"from\":\"WorkflowRun\",\"to\":\"WorkflowContractVersion\",\"label\":\"contract_version\"}]}"); + const entGraph = JSON.parse("{\"nodes\":[{\"id\":\"Integration\",\"fields\":[{\"name\":\"kind\",\"type\":\"string\"},{\"name\":\"description\",\"type\":\"string\"},{\"name\":\"secret_name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"configuration\",\"type\":\"[]byte\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"}]},{\"id\":\"IntegrationAttachment\",\"fields\":[{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"configuration\",\"type\":\"[]byte\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"}]},{\"id\":\"Membership\",\"fields\":[{\"name\":\"current\",\"type\":\"bool\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"updated_at\",\"type\":\"time.Time\"}]},{\"id\":\"OCIRepository\",\"fields\":[{\"name\":\"repo\",\"type\":\"string\"},{\"name\":\"secret_name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"validation_status\",\"type\":\"biz.OCIRepoValidationStatus\"},{\"name\":\"validated_at\",\"type\":\"time.Time\"}]},{\"id\":\"Organization\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"}]},{\"id\":\"RobotAccount\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"revoked_at\",\"type\":\"time.Time\"}]},{\"id\":\"User\",\"fields\":[{\"name\":\"email\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"}]},{\"id\":\"Workflow\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"project\",\"type\":\"string\"},{\"name\":\"team\",\"type\":\"string\"},{\"name\":\"runs_count\",\"type\":\"int\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"}]},{\"id\":\"WorkflowContract\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"}]},{\"id\":\"WorkflowContractVersion\",\"fields\":[{\"name\":\"body\",\"type\":\"[]byte\"},{\"name\":\"revision\",\"type\":\"int\"},{\"name\":\"created_at\",\"type\":\"time.Time\"}]},{\"id\":\"WorkflowRun\",\"fields\":[{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"finished_at\",\"type\":\"time.Time\"},{\"name\":\"state\",\"type\":\"biz.WorkflowRunStatus\"},{\"name\":\"reason\",\"type\":\"string\"},{\"name\":\"run_url\",\"type\":\"string\"},{\"name\":\"runner_type\",\"type\":\"string\"},{\"name\":\"attestation\",\"type\":\"*dsse.Envelope\"}]}],\"edges\":[{\"from\":\"IntegrationAttachment\",\"to\":\"Integration\",\"label\":\"integration\"},{\"from\":\"IntegrationAttachment\",\"to\":\"Workflow\",\"label\":\"workflow\"},{\"from\":\"Organization\",\"to\":\"Membership\",\"label\":\"memberships\"},{\"from\":\"Organization\",\"to\":\"WorkflowContract\",\"label\":\"workflow_contracts\"},{\"from\":\"Organization\",\"to\":\"Workflow\",\"label\":\"workflows\"},{\"from\":\"Organization\",\"to\":\"OCIRepository\",\"label\":\"oci_repositories\"},{\"from\":\"Organization\",\"to\":\"Integration\",\"label\":\"integrations\"},{\"from\":\"RobotAccount\",\"to\":\"WorkflowRun\",\"label\":\"workflowruns\"},{\"from\":\"User\",\"to\":\"Membership\",\"label\":\"memberships\"},{\"from\":\"Workflow\",\"to\":\"RobotAccount\",\"label\":\"robotaccounts\"},{\"from\":\"Workflow\",\"to\":\"WorkflowRun\",\"label\":\"workflowruns\"},{\"from\":\"Workflow\",\"to\":\"WorkflowContract\",\"label\":\"contract\"},{\"from\":\"WorkflowContract\",\"to\":\"WorkflowContractVersion\",\"label\":\"versions\"},{\"from\":\"WorkflowRun\",\"to\":\"WorkflowContractVersion\",\"label\":\"contract_version\"}]}"); const nodes = new vis.DataSet((entGraph.nodes || []).map(n => ({ id: n.id, diff --git a/app/controlplane/internal/data/ent/schema/workflowrun.go b/app/controlplane/internal/data/ent/schema/workflowrun.go index 88c164aa6..c98f1ebba 100644 --- a/app/controlplane/internal/data/ent/schema/workflowrun.go +++ b/app/controlplane/internal/data/ent/schema/workflowrun.go @@ -25,6 +25,7 @@ import ( "entgo.io/ent/schema/index" "github.com/chainloop-dev/chainloop/app/controlplane/internal/biz" "github.com/google/uuid" + "github.com/secure-systems-lab/go-securesystemslib/dsse" ) // WorkflowRun holds the schema definition for the WorkflowRun entity. @@ -47,7 +48,7 @@ func (WorkflowRun) Fields() []ent.Field { field.Text("reason").Optional(), field.String("run_url").Optional(), field.String("runner_type").Optional(), - field.JSON("attestation_ref", &biz.AttestationRef{}).Optional(), + field.JSON("attestation", &dsse.Envelope{}).Optional(), } } diff --git a/app/controlplane/internal/data/ent/workflowrun.go b/app/controlplane/internal/data/ent/workflowrun.go index cc4d8588c..c008f38e8 100644 --- a/app/controlplane/internal/data/ent/workflowrun.go +++ b/app/controlplane/internal/data/ent/workflowrun.go @@ -15,6 +15,7 @@ import ( "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/workflowcontractversion" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/workflowrun" "github.com/google/uuid" + "github.com/secure-systems-lab/go-securesystemslib/dsse" ) // WorkflowRun is the model entity for the WorkflowRun schema. @@ -34,8 +35,8 @@ type WorkflowRun struct { RunURL string `json:"run_url,omitempty"` // RunnerType holds the value of the "runner_type" field. RunnerType string `json:"runner_type,omitempty"` - // AttestationRef holds the value of the "attestation_ref" field. - AttestationRef *biz.AttestationRef `json:"attestation_ref,omitempty"` + // Attestation holds the value of the "attestation" field. + Attestation *dsse.Envelope `json:"attestation,omitempty"` // Edges holds the relations/edges for other nodes in the graph. // The values are being populated by the WorkflowRunQuery when eager-loading is set. Edges WorkflowRunEdges `json:"edges"` @@ -101,7 +102,7 @@ func (*WorkflowRun) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { - case workflowrun.FieldAttestationRef: + case workflowrun.FieldAttestation: values[i] = new([]byte) case workflowrun.FieldState, workflowrun.FieldReason, workflowrun.FieldRunURL, workflowrun.FieldRunnerType: values[i] = new(sql.NullString) @@ -172,12 +173,12 @@ func (wr *WorkflowRun) assignValues(columns []string, values []any) error { } else if value.Valid { wr.RunnerType = value.String } - case workflowrun.FieldAttestationRef: + case workflowrun.FieldAttestation: if value, ok := values[i].(*[]byte); !ok { - return fmt.Errorf("unexpected type %T for field attestation_ref", values[i]) + return fmt.Errorf("unexpected type %T for field attestation", values[i]) } else if value != nil && len(*value) > 0 { - if err := json.Unmarshal(*value, &wr.AttestationRef); err != nil { - return fmt.Errorf("unmarshal field attestation_ref: %w", err) + if err := json.Unmarshal(*value, &wr.Attestation); err != nil { + return fmt.Errorf("unmarshal field attestation: %w", err) } } case workflowrun.ForeignKeys[0]: @@ -262,8 +263,8 @@ func (wr *WorkflowRun) String() string { builder.WriteString("runner_type=") builder.WriteString(wr.RunnerType) builder.WriteString(", ") - builder.WriteString("attestation_ref=") - builder.WriteString(fmt.Sprintf("%v", wr.AttestationRef)) + builder.WriteString("attestation=") + builder.WriteString(fmt.Sprintf("%v", wr.Attestation)) builder.WriteByte(')') return builder.String() } diff --git a/app/controlplane/internal/data/ent/workflowrun/where.go b/app/controlplane/internal/data/ent/workflowrun/where.go index 6cefc3d41..8da3f1b54 100644 --- a/app/controlplane/internal/data/ent/workflowrun/where.go +++ b/app/controlplane/internal/data/ent/workflowrun/where.go @@ -427,14 +427,14 @@ func RunnerTypeContainsFold(v string) predicate.WorkflowRun { return predicate.WorkflowRun(sql.FieldContainsFold(FieldRunnerType, v)) } -// AttestationRefIsNil applies the IsNil predicate on the "attestation_ref" field. -func AttestationRefIsNil() predicate.WorkflowRun { - return predicate.WorkflowRun(sql.FieldIsNull(FieldAttestationRef)) +// AttestationIsNil applies the IsNil predicate on the "attestation" field. +func AttestationIsNil() predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldIsNull(FieldAttestation)) } -// AttestationRefNotNil applies the NotNil predicate on the "attestation_ref" field. -func AttestationRefNotNil() predicate.WorkflowRun { - return predicate.WorkflowRun(sql.FieldNotNull(FieldAttestationRef)) +// AttestationNotNil applies the NotNil predicate on the "attestation" field. +func AttestationNotNil() predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldNotNull(FieldAttestation)) } // HasWorkflow applies the HasEdge predicate on the "workflow" edge. diff --git a/app/controlplane/internal/data/ent/workflowrun/workflowrun.go b/app/controlplane/internal/data/ent/workflowrun/workflowrun.go index 1f29c0abb..5bd0fbc82 100644 --- a/app/controlplane/internal/data/ent/workflowrun/workflowrun.go +++ b/app/controlplane/internal/data/ent/workflowrun/workflowrun.go @@ -27,8 +27,8 @@ const ( FieldRunURL = "run_url" // FieldRunnerType holds the string denoting the runner_type field in the database. FieldRunnerType = "runner_type" - // FieldAttestationRef holds the string denoting the attestation_ref field in the database. - FieldAttestationRef = "attestation_ref" + // FieldAttestation holds the string denoting the attestation field in the database. + FieldAttestation = "attestation" // EdgeWorkflow holds the string denoting the workflow edge name in mutations. EdgeWorkflow = "workflow" // EdgeRobotaccount holds the string denoting the robotaccount edge name in mutations. @@ -69,7 +69,7 @@ var Columns = []string{ FieldReason, FieldRunURL, FieldRunnerType, - FieldAttestationRef, + FieldAttestation, } // ForeignKeys holds the SQL foreign-keys that are owned by the "workflow_runs" diff --git a/app/controlplane/internal/data/ent/workflowrun_create.go b/app/controlplane/internal/data/ent/workflowrun_create.go index 6c8924926..e2ed3bce5 100644 --- a/app/controlplane/internal/data/ent/workflowrun_create.go +++ b/app/controlplane/internal/data/ent/workflowrun_create.go @@ -16,6 +16,7 @@ import ( "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/workflowcontractversion" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/workflowrun" "github.com/google/uuid" + "github.com/secure-systems-lab/go-securesystemslib/dsse" ) // WorkflowRunCreate is the builder for creating a WorkflowRun entity. @@ -109,9 +110,9 @@ func (wrc *WorkflowRunCreate) SetNillableRunnerType(s *string) *WorkflowRunCreat return wrc } -// SetAttestationRef sets the "attestation_ref" field. -func (wrc *WorkflowRunCreate) SetAttestationRef(br *biz.AttestationRef) *WorkflowRunCreate { - wrc.mutation.SetAttestationRef(br) +// SetAttestation sets the "attestation" field. +func (wrc *WorkflowRunCreate) SetAttestation(d *dsse.Envelope) *WorkflowRunCreate { + wrc.mutation.SetAttestation(d) return wrc } @@ -307,9 +308,9 @@ func (wrc *WorkflowRunCreate) createSpec() (*WorkflowRun, *sqlgraph.CreateSpec) _spec.SetField(workflowrun.FieldRunnerType, field.TypeString, value) _node.RunnerType = value } - if value, ok := wrc.mutation.AttestationRef(); ok { - _spec.SetField(workflowrun.FieldAttestationRef, field.TypeJSON, value) - _node.AttestationRef = value + if value, ok := wrc.mutation.Attestation(); ok { + _spec.SetField(workflowrun.FieldAttestation, field.TypeJSON, value) + _node.Attestation = value } if nodes := wrc.mutation.WorkflowIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ diff --git a/app/controlplane/internal/data/ent/workflowrun_update.go b/app/controlplane/internal/data/ent/workflowrun_update.go index 96ac3006b..d6471f24e 100644 --- a/app/controlplane/internal/data/ent/workflowrun_update.go +++ b/app/controlplane/internal/data/ent/workflowrun_update.go @@ -18,6 +18,7 @@ import ( "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/workflowcontractversion" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/workflowrun" "github.com/google/uuid" + "github.com/secure-systems-lab/go-securesystemslib/dsse" ) // WorkflowRunUpdate is the builder for updating WorkflowRun entities. @@ -127,15 +128,15 @@ func (wru *WorkflowRunUpdate) ClearRunnerType() *WorkflowRunUpdate { return wru } -// SetAttestationRef sets the "attestation_ref" field. -func (wru *WorkflowRunUpdate) SetAttestationRef(br *biz.AttestationRef) *WorkflowRunUpdate { - wru.mutation.SetAttestationRef(br) +// SetAttestation sets the "attestation" field. +func (wru *WorkflowRunUpdate) SetAttestation(d *dsse.Envelope) *WorkflowRunUpdate { + wru.mutation.SetAttestation(d) return wru } -// ClearAttestationRef clears the value of the "attestation_ref" field. -func (wru *WorkflowRunUpdate) ClearAttestationRef() *WorkflowRunUpdate { - wru.mutation.ClearAttestationRef() +// ClearAttestation clears the value of the "attestation" field. +func (wru *WorkflowRunUpdate) ClearAttestation() *WorkflowRunUpdate { + wru.mutation.ClearAttestation() return wru } @@ -295,11 +296,11 @@ func (wru *WorkflowRunUpdate) sqlSave(ctx context.Context) (n int, err error) { if wru.mutation.RunnerTypeCleared() { _spec.ClearField(workflowrun.FieldRunnerType, field.TypeString) } - if value, ok := wru.mutation.AttestationRef(); ok { - _spec.SetField(workflowrun.FieldAttestationRef, field.TypeJSON, value) + if value, ok := wru.mutation.Attestation(); ok { + _spec.SetField(workflowrun.FieldAttestation, field.TypeJSON, value) } - if wru.mutation.AttestationRefCleared() { - _spec.ClearField(workflowrun.FieldAttestationRef, field.TypeJSON) + if wru.mutation.AttestationCleared() { + _spec.ClearField(workflowrun.FieldAttestation, field.TypeJSON) } if wru.mutation.WorkflowCleared() { edge := &sqlgraph.EdgeSpec{ @@ -520,15 +521,15 @@ func (wruo *WorkflowRunUpdateOne) ClearRunnerType() *WorkflowRunUpdateOne { return wruo } -// SetAttestationRef sets the "attestation_ref" field. -func (wruo *WorkflowRunUpdateOne) SetAttestationRef(br *biz.AttestationRef) *WorkflowRunUpdateOne { - wruo.mutation.SetAttestationRef(br) +// SetAttestation sets the "attestation" field. +func (wruo *WorkflowRunUpdateOne) SetAttestation(d *dsse.Envelope) *WorkflowRunUpdateOne { + wruo.mutation.SetAttestation(d) return wruo } -// ClearAttestationRef clears the value of the "attestation_ref" field. -func (wruo *WorkflowRunUpdateOne) ClearAttestationRef() *WorkflowRunUpdateOne { - wruo.mutation.ClearAttestationRef() +// ClearAttestation clears the value of the "attestation" field. +func (wruo *WorkflowRunUpdateOne) ClearAttestation() *WorkflowRunUpdateOne { + wruo.mutation.ClearAttestation() return wruo } @@ -718,11 +719,11 @@ func (wruo *WorkflowRunUpdateOne) sqlSave(ctx context.Context) (_node *WorkflowR if wruo.mutation.RunnerTypeCleared() { _spec.ClearField(workflowrun.FieldRunnerType, field.TypeString) } - if value, ok := wruo.mutation.AttestationRef(); ok { - _spec.SetField(workflowrun.FieldAttestationRef, field.TypeJSON, value) + if value, ok := wruo.mutation.Attestation(); ok { + _spec.SetField(workflowrun.FieldAttestation, field.TypeJSON, value) } - if wruo.mutation.AttestationRefCleared() { - _spec.ClearField(workflowrun.FieldAttestationRef, field.TypeJSON) + if wruo.mutation.AttestationCleared() { + _spec.ClearField(workflowrun.FieldAttestation, field.TypeJSON) } if wruo.mutation.WorkflowCleared() { edge := &sqlgraph.EdgeSpec{ diff --git a/app/controlplane/internal/data/workflowrun.go b/app/controlplane/internal/data/workflowrun.go index 384c32d0b..42a07d3c2 100644 --- a/app/controlplane/internal/data/workflowrun.go +++ b/app/controlplane/internal/data/workflowrun.go @@ -29,6 +29,7 @@ import ( "github.com/chainloop-dev/chainloop/app/controlplane/internal/pagination" "github.com/go-kratos/kratos/v2/log" "github.com/google/uuid" + "github.com/secure-systems-lab/go-securesystemslib/dsse" "entgo.io/ent/dialect/sql" ) @@ -87,8 +88,9 @@ func (r *WorkflowRunRepo) FindByIDInOrg(ctx context.Context, orgID, id uuid.UUID return entWrToBizWr(run), nil } -func (r *WorkflowRunRepo) SaveAttestationRef(ctx context.Context, id uuid.UUID, ref *biz.AttestationRef) error { - run, err := r.data.db.WorkflowRun.UpdateOneID(id).SetAttestationRef(ref).Save(ctx) +// Save the attestation for a workflow run in the database +func (r *WorkflowRunRepo) SaveAttestation(ctx context.Context, id uuid.UUID, att *dsse.Envelope) error { + run, err := r.data.db.WorkflowRun.UpdateOneID(id).SetAttestation(att).Save(ctx) if err != nil && !ent.IsNotFound(err) { return err } else if run == nil { @@ -181,14 +183,14 @@ func (r *WorkflowRunRepo) Expire(ctx context.Context, id uuid.UUID) error { func entWrToBizWr(wr *ent.WorkflowRun) *biz.WorkflowRun { r := &biz.WorkflowRun{ - ID: wr.ID, - CreatedAt: toTimePtr(wr.CreatedAt), - FinishedAt: toTimePtr(wr.FinishedAt), - State: string(wr.State), - Reason: wr.Reason, - RunURL: wr.RunURL, - RunnerType: wr.RunnerType, - AttestationRef: wr.AttestationRef, + ID: wr.ID, + CreatedAt: toTimePtr(wr.CreatedAt), + FinishedAt: toTimePtr(wr.FinishedAt), + State: string(wr.State), + Reason: wr.Reason, + RunURL: wr.RunURL, + RunnerType: wr.RunnerType, + Attestation: wr.Attestation, } if cv := wr.Edges.ContractVersion; cv != nil { diff --git a/app/controlplane/internal/service/attestation.go b/app/controlplane/internal/service/attestation.go index 8ed450363..be3926355 100644 --- a/app/controlplane/internal/service/attestation.go +++ b/app/controlplane/internal/service/attestation.go @@ -20,9 +20,6 @@ import ( "encoding/json" "fmt" "sort" - "time" - - "github.com/cenkalti/backoff/v4" cpAPI "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1" "github.com/chainloop-dev/chainloop/app/controlplane/internal/biz" @@ -45,7 +42,6 @@ type AttestationService struct { workflowUseCase *biz.WorkflowUseCase workflowContractUseCase *biz.WorkflowContractUseCase ociUC *biz.OCIRepositoryUseCase - attestationUseCase *biz.AttestationUseCase credsReader credentials.Reader integrationUseCase *biz.IntegrationUseCase integrationDispatcher *dispatcher.FanOutDispatcher @@ -57,7 +53,6 @@ type NewAttestationServiceOpts struct { WorkflowUC *biz.WorkflowUseCase WorkflowContractUC *biz.WorkflowContractUseCase OCIUC *biz.OCIRepositoryUseCase - AttestationUC *biz.AttestationUseCase CredsReader credentials.Reader IntegrationUseCase *biz.IntegrationUseCase CasCredsUseCase *biz.CASCredentialsUseCase @@ -70,7 +65,6 @@ func NewAttestationService(opts *NewAttestationServiceOpts) *AttestationService service: newService(opts.Opts...), wrUseCase: opts.WorkflowRunUC, workflowUseCase: opts.WorkflowUC, - attestationUseCase: opts.AttestationUC, workflowContractUseCase: opts.WorkflowContractUC, ociUC: opts.OCIUC, credsReader: opts.CredsReader, @@ -158,53 +152,28 @@ func (s *AttestationService) Store(ctx context.Context, req *cpAPI.AttestationSe // Decode the envelope through json encoding but // TODO: Verify the envelope signature before storing it - // see sigstore's dsee signer/verifier helpers + // see sigstore's dsse signer/verifier helpers envelope := &dsse.Envelope{} if err := json.Unmarshal(req.Attestation, envelope); err != nil { return nil, sl.LogAndMaskErr(err, s.log) } + // Store the attestation + if err := s.wrUseCase.SaveAttestation(ctx, req.WorkflowRunId, envelope); err != nil { + return nil, sl.LogAndMaskErr(err, s.log) + } + + if err := s.wrUseCase.MarkAsFinished(ctx, req.WorkflowRunId, biz.WorkflowRunSuccess, ""); err != nil { + return nil, sl.LogAndMaskErr(err, s.log) + } + + // find the CAS oci credentials so the dispatcher can download the materials if needed repo, err := s.ociUC.FindMainRepo(context.Background(), robotAccount.OrgID) if err != nil { return nil, fmt.Errorf("failed to find main repository: %w", err) } - // TODO: Move to event bus and background processing - // https://github.com/chainloop-dev/chainloop/issues/39 - // Upload to OCI - // TODO: Move to generic dispatcher and integrations - go func() { - b := backoff.NewExponentialBackOff() - b.MaxElapsedTime = 1 * time.Minute - err := backoff.RetryNotify( - func() error { - // reset context - ctx := context.Background() - digest, err := s.attestationUseCase.UploadToCAS(ctx, envelope, repo.SecretName, req.WorkflowRunId) - if err != nil { - return err - } - - // associate the attestation stored in the CAS with the workflow run - if err := s.wrUseCase.AssociateAttestation(ctx, req.WorkflowRunId, &biz.AttestationRef{Sha256: digest.Hex, SecretRef: repo.SecretName}); err != nil { - return err - } - - s.log.Infow("msg", "attestation associated", "digest", digest, "runID", req.WorkflowRunId) - - return s.wrUseCase.MarkAsFinished(ctx, req.WorkflowRunId, biz.WorkflowRunSuccess, "") - }, - b, - func(err error, delay time.Duration) { - s.log.Warnf("error uploading attestation to CAS, retrying in %s - %s", delay, err) - }, - ) - if err != nil { - // Send a notification - _ = sl.LogAndMaskErr(err, s.log) - } - }() - + // Run integrations dispatcher go func() { if err := s.integrationDispatcher.Run(context.TODO(), &dispatcher.RunOpts{ Envelope: envelope, OrgID: robotAccount.OrgID, WorkflowID: robotAccount.WorkflowID, DownloadSecretName: repo.SecretName, WorkflowRunID: req.WorkflowRunId, @@ -275,6 +244,10 @@ func (s *AttestationService) GetUploadCreds(ctx context.Context, _ *cpAPI.Attest } func bizAttestationToPb(att *biz.Attestation) (*cpAPI.AttestationItem, error) { + if att.Envelope == nil { + return nil, nil + } + encodedAttestation, err := json.Marshal(att.Envelope) if err != nil { return nil, err diff --git a/app/controlplane/internal/service/workflowrun.go b/app/controlplane/internal/service/workflowrun.go index 2facbd659..51f80f89c 100644 --- a/app/controlplane/internal/service/workflowrun.go +++ b/app/controlplane/internal/service/workflowrun.go @@ -17,7 +17,6 @@ package service import ( "context" - "fmt" pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1" craftingpb "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" @@ -36,14 +35,12 @@ type WorkflowRunService struct { wrUseCase *biz.WorkflowRunUseCase workflowUseCase *biz.WorkflowUseCase workflowContractUseCase *biz.WorkflowContractUseCase - attestationUseCase *biz.AttestationUseCase credsReader credentials.Reader } type NewWorkflowRunServiceOpts struct { WorkflowRunUC *biz.WorkflowRunUseCase WorkflowUC *biz.WorkflowUseCase - AttestationUC *biz.AttestationUseCase WorkflowContractUC *biz.WorkflowContractUseCase CredsReader credentials.Reader Opts []NewOpt @@ -54,7 +51,6 @@ func NewWorkflowRunService(opts *NewWorkflowRunServiceOpts) *WorkflowRunService service: newService(opts.Opts...), wrUseCase: opts.WorkflowRunUC, workflowUseCase: opts.WorkflowUC, - attestationUseCase: opts.AttestationUC, workflowContractUseCase: opts.WorkflowContractUC, credsReader: opts.CredsReader, } @@ -113,25 +109,9 @@ func (s *WorkflowRunService) View(ctx context.Context, req *pb.WorkflowRunServic return nil, errors.NotFound("not found", "workflow run not found") } - var attestation *biz.Attestation - // Download the attestation if the workflow run is successful - if run.AttestationRef != nil { - attestation, err = s.attestationUseCase.FetchFromStore(ctx, run.AttestationRef.SecretRef, fmt.Sprintf("sha256:%s", run.AttestationRef.Sha256)) - if err != nil { - // NOTE: For now we don't return an error if the attestation is not found - // since we do not have a good error recovery in place for assets - // stored in the object store. - // If for some reason we can't retrieve the attestation we just return an empy attestatiation - _ = sl.LogAndMaskErr(err, s.log) - } - } - - var att *pb.AttestationItem - if attestation != nil { - att, err = bizAttestationToPb(attestation) - if err != nil { - return nil, sl.LogAndMaskErr(err, s.log) - } + attestation, err := bizAttestationToPb(&biz.Attestation{Envelope: run.Attestation}) + if err != nil { + return nil, sl.LogAndMaskErr(err, s.log) } contractVersion, err := s.workflowContractUseCase.FindVersionByID(ctx, run.ContractVersionID.String()) @@ -146,7 +126,7 @@ func (s *WorkflowRunService) View(ctx context.Context, req *pb.WorkflowRunServic wr.ContractVersion = bizWorkFlowContractVersionToPb(contractVersion) res := &pb.WorkflowRunServiceViewResponse_Result{ WorkflowRun: wr, - Attestation: att, + Attestation: attestation, } return &pb.WorkflowRunServiceViewResponse{Result: res}, nil From e151027b9e06e29581ce10e7f5187d83a6060a5a Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino Date: Sun, 18 Jun 2023 18:04:56 +0200 Subject: [PATCH 2/5] wrap attestation Signed-off-by: Miguel Martinez Trivino --- app/controlplane/internal/biz/attestation.go | 24 ------------------- app/controlplane/internal/biz/workflowrun.go | 6 ++++- app/controlplane/internal/data/workflowrun.go | 21 +++++++++------- .../internal/service/workflowrun.go | 2 +- 4 files changed, 19 insertions(+), 34 deletions(-) delete mode 100644 app/controlplane/internal/biz/attestation.go diff --git a/app/controlplane/internal/biz/attestation.go b/app/controlplane/internal/biz/attestation.go deleted file mode 100644 index 4852252f5..000000000 --- a/app/controlplane/internal/biz/attestation.go +++ /dev/null @@ -1,24 +0,0 @@ -// -// 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 biz - -import ( - "github.com/secure-systems-lab/go-securesystemslib/dsse" -) - -type Attestation struct { - Envelope *dsse.Envelope -} diff --git a/app/controlplane/internal/biz/workflowrun.go b/app/controlplane/internal/biz/workflowrun.go index e33420745..87b6fdb78 100644 --- a/app/controlplane/internal/biz/workflowrun.go +++ b/app/controlplane/internal/biz/workflowrun.go @@ -35,7 +35,11 @@ type WorkflowRun struct { AttestationID uuid.UUID RunURL, RunnerType string ContractVersionID uuid.UUID - Attestation *dsse.Envelope + Attestation *Attestation +} + +type Attestation struct { + Envelope *dsse.Envelope } type WorkflowRunWithContract struct { diff --git a/app/controlplane/internal/data/workflowrun.go b/app/controlplane/internal/data/workflowrun.go index 42a07d3c2..857a03843 100644 --- a/app/controlplane/internal/data/workflowrun.go +++ b/app/controlplane/internal/data/workflowrun.go @@ -183,14 +183,19 @@ func (r *WorkflowRunRepo) Expire(ctx context.Context, id uuid.UUID) error { func entWrToBizWr(wr *ent.WorkflowRun) *biz.WorkflowRun { r := &biz.WorkflowRun{ - ID: wr.ID, - CreatedAt: toTimePtr(wr.CreatedAt), - FinishedAt: toTimePtr(wr.FinishedAt), - State: string(wr.State), - Reason: wr.Reason, - RunURL: wr.RunURL, - RunnerType: wr.RunnerType, - Attestation: wr.Attestation, + ID: wr.ID, + CreatedAt: toTimePtr(wr.CreatedAt), + FinishedAt: toTimePtr(wr.FinishedAt), + State: string(wr.State), + Reason: wr.Reason, + RunURL: wr.RunURL, + RunnerType: wr.RunnerType, + } + + if wr.Attestation != nil { + r.Attestation = &biz.Attestation{ + Envelope: wr.Attestation, + } } if cv := wr.Edges.ContractVersion; cv != nil { diff --git a/app/controlplane/internal/service/workflowrun.go b/app/controlplane/internal/service/workflowrun.go index 51f80f89c..947861c92 100644 --- a/app/controlplane/internal/service/workflowrun.go +++ b/app/controlplane/internal/service/workflowrun.go @@ -109,7 +109,7 @@ func (s *WorkflowRunService) View(ctx context.Context, req *pb.WorkflowRunServic return nil, errors.NotFound("not found", "workflow run not found") } - attestation, err := bizAttestationToPb(&biz.Attestation{Envelope: run.Attestation}) + attestation, err := bizAttestationToPb(run.Attestation) if err != nil { return nil, sl.LogAndMaskErr(err, s.log) } From 10e85d520f39468249a651759790eca9c6217898 Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino Date: Sun, 18 Jun 2023 18:07:31 +0200 Subject: [PATCH 3/5] update docs Signed-off-by: Miguel Martinez Trivino --- app/controlplane/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controlplane/README.md b/app/controlplane/README.md index 1924f0826..e4c401c1c 100644 --- a/app/controlplane/README.md +++ b/app/controlplane/README.md @@ -23,7 +23,7 @@ The control plane has 4 main dependencies - Sensitive information provided by the user such as OCI registry credentials is sent to a secret storage backend. Currently we support [Hashicorp Vault](https://www.vaultproject.io/), [AWS Secret Manager](https://aws.amazon.com/secrets-manager/) and [GCP Secret Manager](https://cloud.google.com/secret-manager). - In addition to those third party dependencies, the control plane also has a dependency on Chainloop own [Artifact CAS](../artifact-cas). It is used to upload the received attestation to the end-user storage backend. -> NOTE: The control plane does not store attestation or artifact data, these get forwarded to the user storage backend through the Artifact CAS. +> NOTE: The control plane does not store artifact data, these get forwarded to the user storage backend through the Artifact CAS. ## Runbook From 5024a529c09cb626eedc0264ca83f8781bedcd17 Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino Date: Sun, 18 Jun 2023 18:09:41 +0200 Subject: [PATCH 4/5] update docs Signed-off-by: Miguel Martinez Trivino --- app/controlplane/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controlplane/README.md b/app/controlplane/README.md index e4c401c1c..1f416c752 100644 --- a/app/controlplane/README.md +++ b/app/controlplane/README.md @@ -23,7 +23,7 @@ The control plane has 4 main dependencies - Sensitive information provided by the user such as OCI registry credentials is sent to a secret storage backend. Currently we support [Hashicorp Vault](https://www.vaultproject.io/), [AWS Secret Manager](https://aws.amazon.com/secrets-manager/) and [GCP Secret Manager](https://cloud.google.com/secret-manager). - In addition to those third party dependencies, the control plane also has a dependency on Chainloop own [Artifact CAS](../artifact-cas). It is used to upload the received attestation to the end-user storage backend. -> NOTE: The control plane does not store artifact data, these get forwarded to the user storage backend through the Artifact CAS. +> NOTE: The control plane does not store artifacts, these get forwarded to the user storage backend (i.e OCI registry) through the Artifact CAS. ## Runbook From 1545ff45e14605afd7935c2c4080cd7988b017ff Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino Date: Sun, 18 Jun 2023 18:15:16 +0200 Subject: [PATCH 5/5] update docs Signed-off-by: Miguel Martinez Trivino --- app/controlplane/internal/biz/workflowrun_integration_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controlplane/internal/biz/workflowrun_integration_test.go b/app/controlplane/internal/biz/workflowrun_integration_test.go index 8d74e8367..4a5056387 100644 --- a/app/controlplane/internal/biz/workflowrun_integration_test.go +++ b/app/controlplane/internal/biz/workflowrun_integration_test.go @@ -66,7 +66,7 @@ func (s *workflowRunIntegrationTestSuite) TestSaveAttestation() { // Retrieve attestation ref from storage and compare r, err := s.WorkflowRun.View(ctx, org.ID, run.ID.String()) assert.NoError(err) - assert.Equal(r.Attestation, validEnvelope) + assert.Equal(r.Attestation, &biz.Attestation{Envelope: validEnvelope}) }) }