Skip to content

Commit

Permalink
feat(controlplane): upload attestation to CAS (#308)
Browse files Browse the repository at this point in the history
Signed-off-by: Miguel A. Cabrera Minagorri <devgorri@gmail.com>
  • Loading branch information
miguelaeh committed Aug 22, 2023
1 parent 126f47b commit 5335cda
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 1 deletion.
2 changes: 2 additions & 0 deletions app/controlplane/cmd/wire_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 65 additions & 0 deletions app/controlplane/internal/biz/attestation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//
// 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 (
"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 AttestationUseCase struct {
logger *log.Helper
CASClient
}

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) 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
}
1 change: 1 addition & 0 deletions app/controlplane/internal/biz/biz.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ var ProviderSet = wire.NewSet(
NewIntegrationUseCase,
NewMembershipUseCase,
NewCASClientUseCase,
NewAttestationUseCase,
NewWorkflowRunExpirerUseCase,
wire.Struct(new(NewIntegrationUseCaseOpts), "*"),
wire.Struct(new(NewUserUseCaseParams), "*"),
Expand Down
31 changes: 30 additions & 1 deletion app/controlplane/internal/service/attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ 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"
"github.com/chainloop-dev/chainloop/app/controlplane/internal/dispatcher"
Expand All @@ -46,6 +48,7 @@ type AttestationService struct {
integrationUseCase *biz.IntegrationUseCase
integrationDispatcher *dispatcher.FanOutDispatcher
casCredsUseCase *biz.CASCredentialsUseCase
attestationUseCase *biz.AttestationUseCase
}

type NewAttestationServiceOpts struct {
Expand All @@ -56,6 +59,7 @@ type NewAttestationServiceOpts struct {
CredsReader credentials.Reader
IntegrationUseCase *biz.IntegrationUseCase
CasCredsUseCase *biz.CASCredentialsUseCase
AttestationUC *biz.AttestationUseCase
FanoutDispatcher *dispatcher.FanOutDispatcher
Opts []NewOpt
}
Expand All @@ -71,6 +75,7 @@ func NewAttestationService(opts *NewAttestationServiceOpts) *AttestationService
integrationUseCase: opts.IntegrationUseCase,
casCredsUseCase: opts.CasCredsUseCase,
integrationDispatcher: opts.FanoutDispatcher,
attestationUseCase: opts.AttestationUC,
}
}

Expand Down Expand Up @@ -188,7 +193,31 @@ func (s *AttestationService) Store(ctx context.Context, req *cpAPI.AttestationSe
}

// We currently only support one backend per workflowRun
secretName := wRun.CASBackends[0].SecretName
casBackend := wRun.CASBackends[0]

// If we have an external CAS backend, we will push there the attestation
if !casBackend.Inline {
b := backoff.NewExponentialBackOff()
b.MaxElapsedTime = 1 * time.Minute
err := backoff.Retry(
func() error {
// reset context
ctx := context.Background()
d, err := s.attestationUseCase.UploadToCAS(ctx, envelope, casBackend.SecretName, req.WorkflowRunId)
if err != nil {
return err
}

s.log.Infow("msg", "attestation uploaded to CAS", "digest", d, "runID", req.WorkflowRunId)
return nil
}, b)

if err != nil {
_ = sl.LogAndMaskErr(err, s.log)
}
}

secretName := casBackend.SecretName

// Run integrations dispatcher
go func() {
Expand Down

0 comments on commit 5335cda

Please sign in to comment.