Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add PipelineRun support to storage/gcs #971

Merged
merged 1 commit into from
Oct 27, 2023
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
192 changes: 140 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,33 @@ 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{}
_ api.Storer[*v1beta1.TaskRun, *in_toto.Statement] = &TaskRunStorer{}
_ api.Storer[*v1beta1.PipelineRun, *in_toto.Statement] = &PipelineRunStorer{}
)

// 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 +241,73 @@ 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) {
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())
}
prefix := fmt.Sprintf("taskrun-%s-%s/%s", tr.GetNamespace(), tr.GetName(), key)
prefix := fmt.Sprintf("%s-%s-%s/%s", "taskrun", tr.GetNamespace(), tr.GetName(), key)

return store(ctx, s.writer, prefix,
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
key := s.key
if key == "" {
key = string(pr.GetUID())
}
prefix := fmt.Sprintf("%s-%s-%s/%s", "pipelinerun", pr.GetNamespace(), pr.GetName(), key)

return store(ctx, s.writer, prefix,
req.Bundle.Signature, req.Bundle.Content, req.Bundle.Cert, req.Bundle.Chain)
}

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

// 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
Loading