Skip to content

Commit

Permalink
feat: add PipelineRun support to storage/gcs
Browse files Browse the repository at this point in the history
Prior to this commit the storage/gcs client didn't support PipelineRuns, only TaskRuns which is not in parity with our other storage interfaces.  This PR adds PipelineRuns support to storage/gcs.
  • Loading branch information
aaron-prindle committed Oct 27, 2023
1 parent 3fe5c46 commit b7e05f7
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 55 deletions.
186 changes: 134 additions & 52 deletions pkg/chains/storage/gcs/gcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,11 @@ import (
const (
StorageBackendGCS = "gcs"
// taskrun-$namespace-$name/$key.<type>
SignatureNameFormat = "taskrun-%s-%s/%s.signature"
PayloadNameFormat = "taskrun-%s-%s/%s.payload"
SignatureNameFormatTaskRun = "taskrun-%s-%s/%s.signature"
PayloadNameFormatTaskRun = "taskrun-%s-%s/%s.payload"
// pipelinerun-$namespace-$name/$key.<type>
SignatureNameFormatPipelineRun = "pipelinerun-%s-%s/%s.signature"
PayloadNameFormatPipelineRun = "pipelinerun-%s-%s/%s.payload"
)

// Backend is a storage backend that stores signed payloads in the TaskRun metadata as an annotation.
Expand All @@ -60,33 +63,55 @@ func NewStorageBackend(ctx context.Context, cfg config.Config) (*Backend, error)
}

// StorePayload implements the storage.Backend interface.
//
//nolint:staticcheck
func (b *Backend) StorePayload(ctx context.Context, obj objects.TektonObject, rawPayload []byte, signature string, opts config.StorageOpts) error {
logger := logging.FromContext(ctx)

// TODO(https://github.com/tektoncd/chains/issues/852): Support PipelineRuns
tr, ok := obj.GetObject().(*v1beta1.TaskRun)
if !ok {
return fmt.Errorf("type %T not supported - supported types: [*v1beta1.TaskRun]", obj.GetObject())
}

store := &TaskRunStorer{
writer: b.writer,
key: opts.ShortKey,
}
if _, err := store.Store(ctx, &api.StoreRequest[*v1beta1.TaskRun, *in_toto.Statement]{
Object: obj,
Artifact: tr,
// We don't actually use payload - we store the raw bundle values directly.
Payload: nil,
Bundle: &signing.Bundle{
Content: rawPayload,
Signature: []byte(signature),
Cert: []byte(opts.Cert),
Chain: []byte(opts.Chain),
},
}); err != nil {
logger.Errorf("error writing to GCS: %w", err)
return err
if tr, isTaskRun := obj.GetObject().(*v1beta1.TaskRun); isTaskRun {
store := &TaskRunStorer{
writer: b.writer,
key: opts.ShortKey,
}
// TODO(https://github.com/tektoncd/chains/issues/665) currently using deprecated v1beta1 APIs until we add full v1 support
if _, err := store.Store(ctx, &api.StoreRequest[*v1beta1.TaskRun, *in_toto.Statement]{
Object: obj,
Artifact: tr,
// We don't actually use payload - we store the raw bundle values directly.
Payload: nil,
Bundle: &signing.Bundle{
Content: rawPayload,
Signature: []byte(signature),
Cert: []byte(opts.Cert),
Chain: []byte(opts.Chain),
},
}); err != nil {
logger.Errorf("error writing to GCS: %w", err)
return err
}
} else if pr, isPipelineRun := obj.GetObject().(*v1beta1.PipelineRun); isPipelineRun {
store := &PipelineRunStorer{
writer: b.writer,
key: opts.ShortKey,
}
// TODO(https://github.com/tektoncd/chains/issues/665) currently using deprecated v1beta1 APIs until we add full v1 support
if _, err := store.Store(ctx, &api.StoreRequest[*v1beta1.PipelineRun, *in_toto.Statement]{
Object: obj,
Artifact: pr,
// We don't actually use payload - we store the raw bundle values directly.
Payload: nil,
Bundle: &signing.Bundle{
Content: rawPayload,
Signature: []byte(signature),
Cert: []byte(opts.Cert),
Chain: []byte(opts.Chain),
},
}); err != nil {
logger.Errorf("error writing to GCS: %w", err)
return err
}
} else {
return fmt.Errorf("type %T not supported - supported types: [*v1beta1.TaskRun, *v1beta1.PipelineRun]", obj.GetObject())
}
return nil
}
Expand Down Expand Up @@ -121,10 +146,19 @@ func (r *reader) GetReader(ctx context.Context, object string) (io.ReadCloser, e
return r.client.Bucket(r.bucket).Object(object).NewReader(ctx)
}

//nolint:staticcheck
func (b *Backend) RetrieveSignatures(ctx context.Context, obj objects.TektonObject, opts config.StorageOpts) (map[string][]string, error) {
// TODO: Handle unsupported type gracefully
tr := obj.GetObject().(*v1beta1.TaskRun)
object := sigName(tr, opts)
var object string

switch t := obj.GetObject().(type) {
case *v1beta1.TaskRun:
object = taskRunSigName(t, opts)
case *v1beta1.PipelineRun:
object = pipelineRunSigname(t, opts)
default:
return nil, fmt.Errorf("unsupported TektonObject type: %T", t)
}

signature, err := b.retrieveObject(ctx, object)
if err != nil {
return nil, err
Expand All @@ -135,16 +169,25 @@ func (b *Backend) RetrieveSignatures(ctx context.Context, obj objects.TektonObje
return m, nil
}

//nolint:staticcheck
func (b *Backend) RetrievePayloads(ctx context.Context, obj objects.TektonObject, opts config.StorageOpts) (map[string]string, error) {
// TODO: Handle unsupported type gracefully
tr := obj.GetObject().(*v1beta1.TaskRun)
object := payloadName(tr, opts)
m := make(map[string]string)
var object string

switch t := obj.GetObject().(type) {
case *v1beta1.TaskRun:
object = taskRunPayloadName(t, opts)
case *v1beta1.PipelineRun:
object = pipelineRunPayloadName(t, opts)
default:
return nil, fmt.Errorf("unsupported TektonObject type: %T", t)
}

payload, err := b.retrieveObject(ctx, object)
if err != nil {
return nil, err
}

m := make(map[string]string)
m[object] = payload
return m, nil
}
Expand All @@ -163,20 +206,32 @@ func (b *Backend) retrieveObject(ctx context.Context, object string) (string, er
return string(payload), nil
}

func sigName(tr *v1beta1.TaskRun, opts config.StorageOpts) string {
return fmt.Sprintf(SignatureNameFormat, tr.Namespace, tr.Name, opts.ShortKey)
//nolint:staticcheck
func taskRunSigName(tr *v1beta1.TaskRun, opts config.StorageOpts) string {
return fmt.Sprintf(SignatureNameFormatTaskRun, tr.Namespace, tr.Name, opts.ShortKey)
}

//nolint:staticcheck
func taskRunPayloadName(tr *v1beta1.TaskRun, opts config.StorageOpts) string {
return fmt.Sprintf(PayloadNameFormatTaskRun, tr.Namespace, tr.Name, opts.ShortKey)
}

//nolint:staticcheck
func pipelineRunSigname(pr *v1beta1.PipelineRun, opts config.StorageOpts) string {
return fmt.Sprintf(SignatureNameFormatPipelineRun, pr.Namespace, pr.Name, opts.ShortKey)
}

func payloadName(tr *v1beta1.TaskRun, opts config.StorageOpts) string {
return fmt.Sprintf(PayloadNameFormat, tr.Namespace, tr.Name, opts.ShortKey)
//nolint:staticcheck
func pipelineRunPayloadName(pr *v1beta1.PipelineRun, opts config.StorageOpts) string {
return fmt.Sprintf(PayloadNameFormatPipelineRun, pr.Namespace, pr.Name, opts.ShortKey)
}

//nolint:staticcheck
var (
_ api.Storer[*v1beta1.TaskRun, *in_toto.Statement] = &TaskRunStorer{}
)

// TaskRunStorer stores TaskRuns in GCS.
// TODO(https://github.com/tektoncd/chains/issues/852): implement PipelineRun support (nothing in here is particularly TaskRun specific, but needs tests).
type TaskRunStorer struct {
writer gcsWriter

Expand All @@ -185,41 +240,68 @@ type TaskRunStorer struct {
key string
}

// Store stores the
// Store stores the TaskRun chains information in GCS
//
//nolint:staticcheck
func (s *TaskRunStorer) Store(ctx context.Context, req *api.StoreRequest[*v1beta1.TaskRun, *in_toto.Statement]) (*api.StoreResponse, error) {
tr := req.Artifact
return store(ctx, s.writer, "taskrun", s.key,
tr.GetNamespace(), tr.GetName(), string(tr.GetUID()),
req.Bundle.Signature, req.Bundle.Content, req.Bundle.Cert, req.Bundle.Chain)
}

// PipelineRunStorer stores PipelineRuns in GCS.
type PipelineRunStorer struct {
writer gcsWriter

// Optional key to store objects as. If not set, the object UID will be used.
// The resulting name will look like: $bucket/pipelinerun-$namespace-$name/$key.signature
key string
}

// Store stores the PipelineRun chains information in GCS
//
//nolint:staticcheck
func (s *PipelineRunStorer) Store(ctx context.Context, req *api.StoreRequest[*v1beta1.PipelineRun, *in_toto.Statement]) (*api.StoreResponse, error) {
pr := req.Artifact
return store(ctx, s.writer, "pipelinerun", s.key,
pr.GetNamespace(), pr.GetName(), string(pr.GetUID()),
req.Bundle.Signature, req.Bundle.Content, req.Bundle.Cert, req.Bundle.Chain)
}

func store(ctx context.Context, writer gcsWriter, kind, key, namespace, name, uid string,
signature, content, cert, chain []byte) (*api.StoreResponse, error) {
logger := logging.FromContext(ctx)

tr := req.Artifact
// We need multiple objects: the signature and the payload. We want to make these unique to the UID, but easy to find based on the
// name/namespace as well.
// $bucket/taskrun-$namespace-$name/$key.signature
// $bucket/taskrun-$namespace-$name/$key.payload
key := s.key
if key == "" {
key = string(tr.GetUID())
key = uid
}
prefix := fmt.Sprintf("taskrun-%s-%s/%s", tr.GetNamespace(), tr.GetName(), key)
prefix := fmt.Sprintf("%s-%s-%s/%s", kind, namespace, name, key)

// Write signature
sigName := prefix + ".signature"
logger.Infof("Storing signature at %s", sigName)
if _, err := write(ctx, s.writer, sigName, req.Bundle.Signature); err != nil {
if _, err := write(ctx, writer, sigName, signature); err != nil {
return nil, err
}

// Write payload
if _, err := write(ctx, s.writer, prefix+".payload", req.Bundle.Content); err != nil {
payloadName := prefix + ".payload"
if _, err := write(ctx, writer, payloadName, content); err != nil {
return nil, err
}

// Only write cert+chain if it is present.
if req.Bundle.Cert == nil {
if cert == nil {
return nil, nil
}
if _, err := write(ctx, s.writer, prefix+".cert", req.Bundle.Cert); err != nil {
certName := prefix + ".cert"
if _, err := write(ctx, writer, certName, cert); err != nil {
return nil, err
}
if _, err := write(ctx, s.writer, prefix+".chain", req.Bundle.Chain); err != nil {

chainName := prefix + ".chain"
if _, err := write(ctx, writer, chainName, chain); err != nil {
return nil, err
}

Expand Down
45 changes: 42 additions & 3 deletions pkg/chains/storage/gcs/gcs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ import (
rtesting "knative.dev/pkg/reconciler/testing"
)

//nolint:staticcheck
func TestBackend_StorePayload(t *testing.T) {

type args struct {
tr *v1beta1.TaskRun
pr *v1beta1.PipelineRun
signed []byte
signature string
opts config.StorageOpts
Expand All @@ -52,6 +53,13 @@ func TestBackend_StorePayload(t *testing.T) {
UID: types.UID("uid"),
},
},
pr: &v1beta1.PipelineRun{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
UID: types.UID("uid"),
},
},
signed: []byte("signed"),
signature: "signature",
opts: config.StorageOpts{ShortKey: "foo.uuid", PayloadFormat: formats.PayloadTypeSlsav1},
Expand All @@ -67,6 +75,13 @@ func TestBackend_StorePayload(t *testing.T) {
UID: types.UID("uid"),
},
},
pr: &v1beta1.PipelineRun{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
UID: types.UID("uid"),
},
},
signed: []byte("signed"),
signature: "signature",
opts: config.StorageOpts{ShortKey: "foo.uuid", PayloadFormat: formats.PayloadTypeTekton},
Expand All @@ -88,8 +103,8 @@ func TestBackend_StorePayload(t *testing.T) {
t.Errorf("Backend.StorePayload() error = %v, wantErr %v", err, tt.wantErr)
}

objectSig := sigName(tt.args.tr, tt.args.opts)
objectPayload := payloadName(tt.args.tr, tt.args.opts)
objectSig := taskRunSigName(tt.args.tr, tt.args.opts)
objectPayload := taskRunPayloadName(tt.args.tr, tt.args.opts)
got, err := b.RetrieveSignatures(ctx, trObj, tt.args.opts)
if err != nil {
t.Fatal(err)
Expand All @@ -105,6 +120,30 @@ func TestBackend_StorePayload(t *testing.T) {
if gotPayload[objectPayload] != string(tt.args.signed) {
t.Errorf("wrong signature, expected %s, got %s", tt.args.signed, gotPayload[objectPayload])
}

prObj := objects.NewPipelineRunObject(tt.args.pr)
if err := b.StorePayload(ctx, prObj, tt.args.signed, tt.args.signature, tt.args.opts); (err != nil) != tt.wantErr {
t.Errorf("Backend.StorePayload() error = %v, wantErr %v", err, tt.wantErr)
}

objectSig = pipelineRunSigname(tt.args.pr, tt.args.opts)
objectPayload = pipelineRunPayloadName(tt.args.pr, tt.args.opts)
got, err = b.RetrieveSignatures(ctx, prObj, tt.args.opts)
if err != nil {
t.Fatal(err)
}

if got[objectSig][0] != tt.args.signature {
t.Errorf("wrong signature, expected %q, got %q", tt.args.signature, got[objectSig][0])
}

gotPayload, err = b.RetrievePayloads(ctx, prObj, tt.args.opts)
if err != nil {
t.Fatal(err)
}
if gotPayload[objectPayload] != string(tt.args.signed) {
t.Errorf("wrong signature, expected %s, got %s", tt.args.signed, gotPayload[objectPayload])
}
})
}
}
Expand Down

0 comments on commit b7e05f7

Please sign in to comment.