Skip to content

Commit

Permalink
Generalize format loading. (#603)
Browse files Browse the repository at this point in the history
* Generalize format loading.

This refactors how formatters are initialized and configured to be more
uniform. This allows for formatter providers to be more easily slotted
in / configured without needing to make modifications for downstream
signers. The ultimate goal for this change is to allow for "type
aliasing" of formatters, where the same formatter may be referenced by
different names to allow for supporting new formats in a backwards
compatible way (i.e. letting slsa be an alias for intoto or slsa0.2,
etc.)

This follows a similar pattern to sigstore KMS providers - https://github.com/sigstore/sigstore/tree/main/pkg/signature/kms

As part of this change, the way to initialize a formatter is
standardized. To support this, the intoto logger that was previously
configured during initialization is now taken from the context.

BREAKING CHANGES:
- formats.PayloadType is now defined in config rather than formats. The
  semantics behind this value are still the same.
- CreatePayload now takes in a context.
- InTotoIte6.NewFormatter no longer takes in a logger.
- formats.AllFormatters is removed. Use formats.GetPayloader instead.

Signed-off-by: Billy Lynch <billy@chainguard.dev>

* Add formatter loaders to controllers.

Signed-off-by: Billy Lynch <billy@chainguard.dev>
  • Loading branch information
wlynch authored Dec 16, 2022
1 parent 34161cd commit 7897fa4
Show file tree
Hide file tree
Showing 14 changed files with 146 additions and 104 deletions.
15 changes: 7 additions & 8 deletions pkg/artifacts/signable.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"github.com/google/go-containerregistry/pkg/name"
slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
"github.com/opencontainers/go-digest"
"github.com/tektoncd/chains/pkg/chains/formats"
"github.com/tektoncd/chains/pkg/chains/objects"
"github.com/tektoncd/chains/pkg/config"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
Expand All @@ -38,7 +37,7 @@ type Signable interface {
ExtractObjects(obj objects.TektonObject) []interface{}
StorageBackend(cfg config.Config) sets.String
Signer(cfg config.Config) string
PayloadFormat(cfg config.Config) formats.PayloadType
PayloadFormat(cfg config.Config) config.PayloadType
// FullKey returns the full identifier for a signable artifact.
// - For OCI artifact, it is the full representation in the format of `<NAME>@sha256:<DIGEST>`.
// - For TaskRun/PipelineRun artifact, it is `<GROUP>-<VERSION>-<KIND>-<UID>`
Expand Down Expand Up @@ -80,8 +79,8 @@ func (ta *TaskRunArtifact) StorageBackend(cfg config.Config) sets.String {
return cfg.Artifacts.TaskRuns.StorageBackend
}

func (ta *TaskRunArtifact) PayloadFormat(cfg config.Config) formats.PayloadType {
return formats.PayloadType(cfg.Artifacts.TaskRuns.Format)
func (ta *TaskRunArtifact) PayloadFormat(cfg config.Config) config.PayloadType {
return config.PayloadType(cfg.Artifacts.TaskRuns.Format)
}

func (ta *TaskRunArtifact) Signer(cfg config.Config) string {
Expand Down Expand Up @@ -122,8 +121,8 @@ func (pa *PipelineRunArtifact) StorageBackend(cfg config.Config) sets.String {
return cfg.Artifacts.PipelineRuns.StorageBackend
}

func (pa *PipelineRunArtifact) PayloadFormat(cfg config.Config) formats.PayloadType {
return formats.PayloadType(cfg.Artifacts.PipelineRuns.Format)
func (pa *PipelineRunArtifact) PayloadFormat(cfg config.Config) config.PayloadType {
return config.PayloadType(cfg.Artifacts.PipelineRuns.Format)
}

func (pa *PipelineRunArtifact) Signer(cfg config.Config) string {
Expand Down Expand Up @@ -392,8 +391,8 @@ func (oa *OCIArtifact) StorageBackend(cfg config.Config) sets.String {
return cfg.Artifacts.OCI.StorageBackend
}

func (oa *OCIArtifact) PayloadFormat(cfg config.Config) formats.PayloadType {
return formats.PayloadType(cfg.Artifacts.OCI.Format)
func (oa *OCIArtifact) PayloadFormat(cfg config.Config) config.PayloadType {
return config.PayloadType(cfg.Artifacts.OCI.Format)
}

func (oa *OCIArtifact) Signer(cfg config.Config) string {
Expand Down
21 changes: 21 additions & 0 deletions pkg/chains/formats/all/all.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2022 The Tekton 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 all

import (
_ "github.com/tektoncd/chains/pkg/chains/formats/intotoite6"
_ "github.com/tektoncd/chains/pkg/chains/formats/simple"
_ "github.com/tektoncd/chains/pkg/chains/formats/tekton"
)
43 changes: 34 additions & 9 deletions pkg/chains/formats/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,45 @@ limitations under the License.

package formats

import (
"context"
"fmt"

"github.com/tektoncd/chains/pkg/config"
)

// Payloader is an interface to generate a chains Payload from a TaskRun
type Payloader interface {
CreatePayload(obj interface{}) (interface{}, error)
Type() PayloadType
CreatePayload(ctx context.Context, obj interface{}) (interface{}, error)
Type() config.PayloadType
Wrap() bool
}

type PayloadType string

// If you update this, remember to update AllFormatters
const (
PayloadTypeTekton PayloadType = "tekton"
PayloadTypeSimpleSigning PayloadType = "simplesigning"
PayloadTypeInTotoIte6 PayloadType = "in-toto"
PayloadTypeTekton config.PayloadType = "tekton"
PayloadTypeSimpleSigning config.PayloadType = "simplesigning"
PayloadTypeInTotoIte6 config.PayloadType = "in-toto"
)

var (
payloaderMap = map[config.PayloadType]PayloaderInit{}
)

var AllFormatters = []PayloadType{PayloadTypeTekton, PayloadTypeSimpleSigning, PayloadTypeInTotoIte6}
// PayloaderInit initializes a new Payloader instance for the given config.
type PayloaderInit func(config.Config) (Payloader, error)

// RegisterPayloader registers the PayloaderInit func for the given type.
// This is suitable to be calling during init() to register Payloader types.
func RegisterPayloader(key config.PayloadType, init PayloaderInit) {
payloaderMap[key] = init
}

// GetPayloader returns a new Payloader of the given type.
// If no Payloader is registered for the type, an error is returned.
func GetPayloader(key config.PayloadType, cfg config.Config) (Payloader, error) {
fn, ok := payloaderMap[key]
if !ok {
return nil, fmt.Errorf("payloader %q not found", key)
}
return fn(cfg)
}
24 changes: 16 additions & 8 deletions pkg/chains/formats/intotoite6/intotoite6.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,51 @@ limitations under the License.
package intotoite6

import (
"context"
"fmt"

"github.com/tektoncd/chains/pkg/chains/formats"
"github.com/tektoncd/chains/pkg/chains/formats/intotoite6/pipelinerun"
"github.com/tektoncd/chains/pkg/chains/formats/intotoite6/taskrun"
"github.com/tektoncd/chains/pkg/chains/objects"
"github.com/tektoncd/chains/pkg/config"
"go.uber.org/zap"
"knative.dev/pkg/logging"
)

const (
PayloadTypeInTotoIte6 = formats.PayloadTypeInTotoIte6
)

func init() {
formats.RegisterPayloader(PayloadTypeInTotoIte6, NewFormatter)
}

type InTotoIte6 struct {
builderID string
logger *zap.SugaredLogger
}

func NewFormatter(cfg config.Config, logger *zap.SugaredLogger) (formats.Payloader, error) {
func NewFormatter(cfg config.Config) (formats.Payloader, error) {
return &InTotoIte6{
builderID: cfg.Builder.ID,
logger: logger,
}, nil
}

func (i *InTotoIte6) Wrap() bool {
return true
}

func (i *InTotoIte6) CreatePayload(obj interface{}) (interface{}, error) {
func (i *InTotoIte6) CreatePayload(ctx context.Context, obj interface{}) (interface{}, error) {
logger := logging.FromContext(ctx)
switch v := obj.(type) {
case *objects.TaskRunObject:
return taskrun.GenerateAttestation(i.builderID, v, i.logger)
return taskrun.GenerateAttestation(i.builderID, v, logger)
case *objects.PipelineRunObject:
return pipelinerun.GenerateAttestation(i.builderID, v, i.logger)
return pipelinerun.GenerateAttestation(i.builderID, v, logger)
default:
return nil, fmt.Errorf("intoto does not support type: %s", v)
}
}

func (i *InTotoIte6) Type() formats.PayloadType {
func (i *InTotoIte6) Type() config.PayloadType {
return formats.PayloadTypeInTotoIte6
}
35 changes: 22 additions & 13 deletions pkg/chains/formats/intotoite6/intotoite6_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ var e1BuildStart = time.Unix(1617011400, 0)
var e1BuildFinished = time.Unix(1617011415, 0)

func TestTaskRunCreatePayload1(t *testing.T) {
ctx := logtesting.TestContextWithLogger(t)

tr, err := objectloader.TaskRunFromFile("testdata/taskrun1.json")
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -113,9 +115,9 @@ func TestTaskRunCreatePayload1(t *testing.T) {
},
},
}
i, _ := NewFormatter(cfg, logtesting.TestLogger(t))
i, _ := NewFormatter(cfg)

got, err := i.CreatePayload(objects.NewTaskRunObject(tr))
got, err := i.CreatePayload(ctx, objects.NewTaskRunObject(tr))

if err != nil {
t.Errorf("unexpected error: %s", err.Error())
Expand All @@ -126,6 +128,7 @@ func TestTaskRunCreatePayload1(t *testing.T) {
}

func TestPipelineRunCreatePayload(t *testing.T) {
ctx := logtesting.TestContextWithLogger(t)
pr, err := objectloader.PipelineRunFromFile("testdata/pipelinerun1.json")
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -316,9 +319,9 @@ func TestPipelineRunCreatePayload(t *testing.T) {
pro.AppendTaskRun(tr1)
pro.AppendTaskRun(tr2)

i, _ := NewFormatter(cfg, logtesting.TestLogger(t))
i, _ := NewFormatter(cfg)

got, err := i.CreatePayload(pro)
got, err := i.CreatePayload(ctx, pro)
if err != nil {
t.Errorf("unexpected error: %s", err.Error())
}
Expand All @@ -327,6 +330,7 @@ func TestPipelineRunCreatePayload(t *testing.T) {
}
}
func TestPipelineRunCreatePayloadChildRefs(t *testing.T) {
ctx := logtesting.TestContextWithLogger(t)
pr, err := objectloader.PipelineRunFromFile("testdata/pipelinerun-childrefs.json")
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -512,8 +516,8 @@ func TestPipelineRunCreatePayloadChildRefs(t *testing.T) {
pro.AppendTaskRun(tr1)
pro.AppendTaskRun(tr2)

i, _ := NewFormatter(cfg, logtesting.TestLogger(t))
got, err := i.CreatePayload(pro)
i, _ := NewFormatter(cfg)
got, err := i.CreatePayload(ctx, pro)
if err != nil {
t.Errorf("unexpected error: %s", err.Error())
}
Expand All @@ -523,6 +527,7 @@ func TestPipelineRunCreatePayloadChildRefs(t *testing.T) {
}

func TestTaskRunCreatePayload2(t *testing.T) {
ctx := logtesting.TestContextWithLogger(t)
tr, err := objectloader.TaskRunFromFile("testdata/taskrun2.json")
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -578,8 +583,8 @@ func TestTaskRunCreatePayload2(t *testing.T) {
},
},
}
i, _ := NewFormatter(cfg, logtesting.TestLogger(t))
got, err := i.CreatePayload(objects.NewTaskRunObject(tr))
i, _ := NewFormatter(cfg)
got, err := i.CreatePayload(ctx, objects.NewTaskRunObject(tr))

if err != nil {
t.Errorf("unexpected error: %s", err.Error())
Expand All @@ -590,6 +595,8 @@ func TestTaskRunCreatePayload2(t *testing.T) {
}

func TestMultipleSubjects(t *testing.T) {
ctx := logtesting.TestContextWithLogger(t)

tr, err := objectloader.TaskRunFromFile("testdata/taskrun-multiple-subjects.json")
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -641,8 +648,8 @@ func TestMultipleSubjects(t *testing.T) {
},
}

i, _ := NewFormatter(cfg, logtesting.TestLogger(t))
got, err := i.CreatePayload(objects.NewTaskRunObject(tr))
i, _ := NewFormatter(cfg)
got, err := i.CreatePayload(ctx, objects.NewTaskRunObject(tr))
if err != nil {
t.Errorf("unexpected error: %s", err.Error())
}
Expand All @@ -658,7 +665,7 @@ func TestNewFormatter(t *testing.T) {
ID: "testid",
},
}
f, err := NewFormatter(cfg, logtesting.TestLogger(t))
f, err := NewFormatter(cfg)
if f == nil {
t.Error("Failed to create formatter")
}
Expand All @@ -669,15 +676,17 @@ func TestNewFormatter(t *testing.T) {
}

func TestCreatePayloadError(t *testing.T) {
ctx := logtesting.TestContextWithLogger(t)

cfg := config.Config{
Builder: config.BuilderConfig{
ID: "testid",
},
}
f, _ := NewFormatter(cfg, logtesting.TestLogger(t))
f, _ := NewFormatter(cfg)

t.Run("Invalid type", func(t *testing.T) {
p, err := f.CreatePayload("not a task ref")
p, err := f.CreatePayload(ctx, "not a task ref")

if p != nil {
t.Errorf("Unexpected payload")
Expand Down
19 changes: 14 additions & 5 deletions pkg/chains/formats/simple/simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,32 @@ limitations under the License.
package simple

import (
"context"
"fmt"

"github.com/sigstore/sigstore/pkg/signature/payload"
"github.com/tektoncd/chains/pkg/chains/formats"
"github.com/tektoncd/chains/pkg/config"

"github.com/google/go-containerregistry/pkg/name"
)

const (
PayloadTypeSimpleSigning = formats.PayloadTypeSimpleSigning
)

func init() {
formats.RegisterPayloader(PayloadTypeSimpleSigning, NewFormatter)
}

// SimpleSigning is a formatter that uses the RedHat simple signing format
// https://www.redhat.com/en/blog/container-image-signing
type SimpleSigning struct {
}
type SimpleSigning struct{}

type SimpleContainerImage payload.SimpleContainerImage

// CreatePayload implements the Payloader interface.
func (i *SimpleSigning) CreatePayload(obj interface{}) (interface{}, error) {
func (i *SimpleSigning) CreatePayload(ctx context.Context, obj interface{}) (interface{}, error) {
switch v := obj.(type) {
case name.Digest:
format := NewSimpleStruct(v)
Expand All @@ -44,7 +53,7 @@ func (i *SimpleSigning) Wrap() bool {
return false
}

func NewFormatter() (formats.Payloader, error) {
func NewFormatter(config.Config) (formats.Payloader, error) {
return &SimpleSigning{}, nil
}

Expand All @@ -57,6 +66,6 @@ func (i SimpleContainerImage) ImageName() string {
return fmt.Sprintf("%s@%s", i.Critical.Identity.DockerReference, i.Critical.Image.DockerManifestDigest)
}

func (i *SimpleSigning) Type() formats.PayloadType {
func (i *SimpleSigning) Type() config.PayloadType {
return formats.PayloadTypeSimpleSigning
}
5 changes: 3 additions & 2 deletions pkg/chains/formats/simple/simple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ limitations under the License.
package simple

import (
"context"
"reflect"
"testing"

Expand Down Expand Up @@ -61,7 +62,7 @@ func TestSimpleSigning_CreatePayload(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
i := &SimpleSigning{}
got, err := i.CreatePayload(tt.obj)
got, err := i.CreatePayload(context.Background(), tt.obj)
if (err != nil) != tt.wantErr {
t.Errorf("SimpleSigning.CreatePayload() error = %v, wantErr %v", err, tt.wantErr)
return
Expand All @@ -81,7 +82,7 @@ func TestImageName(t *testing.T) {
obj := makeDigest(t, img)

i := &SimpleSigning{}
format, err := i.CreatePayload(obj)
format, err := i.CreatePayload(context.Background(), obj)
if err != nil {
t.Fatal(err)
}
Expand Down
Loading

0 comments on commit 7897fa4

Please sign in to comment.