Skip to content

Commit 5335cda

Browse files
authored
feat(controlplane): upload attestation to CAS (#308)
Signed-off-by: Miguel A. Cabrera Minagorri <[email protected]>
1 parent 126f47b commit 5335cda

File tree

4 files changed

+98
-1
lines changed

4 files changed

+98
-1
lines changed

app/controlplane/cmd/wire_gen.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//
2+
// Copyright 2023 The Chainloop Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package biz
17+
18+
import (
19+
"bytes"
20+
"context"
21+
"encoding/json"
22+
"fmt"
23+
"io"
24+
25+
"github.com/chainloop-dev/chainloop/internal/servicelogger"
26+
"github.com/go-kratos/kratos/v2/log"
27+
28+
cr_v1 "github.com/google/go-containerregistry/pkg/v1"
29+
"github.com/secure-systems-lab/go-securesystemslib/dsse"
30+
)
31+
32+
type AttestationUseCase struct {
33+
logger *log.Helper
34+
CASClient
35+
}
36+
37+
func NewAttestationUseCase(client CASClient, logger log.Logger) *AttestationUseCase {
38+
if logger == nil {
39+
logger = log.NewStdLogger(io.Discard)
40+
}
41+
42+
return &AttestationUseCase{
43+
logger: servicelogger.ScopedHelper(logger, "biz/attestation"),
44+
CASClient: client,
45+
}
46+
}
47+
48+
func (uc *AttestationUseCase) UploadToCAS(ctx context.Context, envelope *dsse.Envelope, secretID, workflowRunID string) (*cr_v1.Hash, error) {
49+
filename := fmt.Sprintf("attestation-%s.json", workflowRunID)
50+
jsonContent, err := json.Marshal(envelope)
51+
if err != nil {
52+
return nil, fmt.Errorf("marshaling the envelope: %w", err)
53+
}
54+
55+
h, _, err := cr_v1.SHA256(bytes.NewBuffer(jsonContent))
56+
if err != nil {
57+
return nil, fmt.Errorf("calculating the digest: %w", err)
58+
}
59+
60+
if err := uc.CASClient.Upload(ctx, secretID, bytes.NewBuffer(jsonContent), filename, h.String()); err != nil {
61+
return nil, fmt.Errorf("uploading to CAS: %w", err)
62+
}
63+
64+
return &h, nil
65+
}

app/controlplane/internal/biz/biz.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ var ProviderSet = wire.NewSet(
3131
NewIntegrationUseCase,
3232
NewMembershipUseCase,
3333
NewCASClientUseCase,
34+
NewAttestationUseCase,
3435
NewWorkflowRunExpirerUseCase,
3536
wire.Struct(new(NewIntegrationUseCaseOpts), "*"),
3637
wire.Struct(new(NewUserUseCaseParams), "*"),

app/controlplane/internal/service/attestation.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import (
2020
"encoding/json"
2121
"fmt"
2222
"sort"
23+
"time"
2324

25+
"github.com/cenkalti/backoff/v4"
2426
cpAPI "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1"
2527
"github.com/chainloop-dev/chainloop/app/controlplane/internal/biz"
2628
"github.com/chainloop-dev/chainloop/app/controlplane/internal/dispatcher"
@@ -46,6 +48,7 @@ type AttestationService struct {
4648
integrationUseCase *biz.IntegrationUseCase
4749
integrationDispatcher *dispatcher.FanOutDispatcher
4850
casCredsUseCase *biz.CASCredentialsUseCase
51+
attestationUseCase *biz.AttestationUseCase
4952
}
5053

5154
type NewAttestationServiceOpts struct {
@@ -56,6 +59,7 @@ type NewAttestationServiceOpts struct {
5659
CredsReader credentials.Reader
5760
IntegrationUseCase *biz.IntegrationUseCase
5861
CasCredsUseCase *biz.CASCredentialsUseCase
62+
AttestationUC *biz.AttestationUseCase
5963
FanoutDispatcher *dispatcher.FanOutDispatcher
6064
Opts []NewOpt
6165
}
@@ -71,6 +75,7 @@ func NewAttestationService(opts *NewAttestationServiceOpts) *AttestationService
7175
integrationUseCase: opts.IntegrationUseCase,
7276
casCredsUseCase: opts.CasCredsUseCase,
7377
integrationDispatcher: opts.FanoutDispatcher,
78+
attestationUseCase: opts.AttestationUC,
7479
}
7580
}
7681

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

190195
// We currently only support one backend per workflowRun
191-
secretName := wRun.CASBackends[0].SecretName
196+
casBackend := wRun.CASBackends[0]
197+
198+
// If we have an external CAS backend, we will push there the attestation
199+
if !casBackend.Inline {
200+
b := backoff.NewExponentialBackOff()
201+
b.MaxElapsedTime = 1 * time.Minute
202+
err := backoff.Retry(
203+
func() error {
204+
// reset context
205+
ctx := context.Background()
206+
d, err := s.attestationUseCase.UploadToCAS(ctx, envelope, casBackend.SecretName, req.WorkflowRunId)
207+
if err != nil {
208+
return err
209+
}
210+
211+
s.log.Infow("msg", "attestation uploaded to CAS", "digest", d, "runID", req.WorkflowRunId)
212+
return nil
213+
}, b)
214+
215+
if err != nil {
216+
_ = sl.LogAndMaskErr(err, s.log)
217+
}
218+
}
219+
220+
secretName := casBackend.SecretName
192221

193222
// Run integrations dispatcher
194223
go func() {

0 commit comments

Comments
 (0)