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

TEP-84: PipelineRun Attestations #436

Merged
merged 1 commit into from
Sep 20, 2022
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
3 changes: 2 additions & 1 deletion cmd/controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package main
import (
"flag"

"github.com/tektoncd/chains/pkg/reconciler/pipelinerun"
"github.com/tektoncd/chains/pkg/reconciler/taskrun"
"knative.dev/pkg/injection"
"knative.dev/pkg/injection/sharedmain"
Expand All @@ -40,5 +41,5 @@ func main() {
flag.Parse()
ctx := injection.WithNamespaceScope(signals.NewContext(), *namespace)

sharedmain.MainWithContext(ctx, "watcher", taskrun.NewController)
sharedmain.MainWithContext(ctx, "watcher", taskrun.NewController, pipelinerun.NewController)
}
116 changes: 78 additions & 38 deletions pkg/artifacts/signable.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ import (
"github.com/google/go-containerregistry/pkg/name"
"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"
"go.uber.org/zap"
"k8s.io/apimachinery/pkg/util/sets"
)

type Signable interface {
ExtractObjects(tr *v1beta1.TaskRun) []interface{}
ExtractObjects(obj objects.TektonObject) []interface{}
StorageBackend(cfg config.Config) sets.String
Signer(cfg config.Config) string
PayloadFormat(cfg config.Config) formats.PayloadType
Expand All @@ -42,13 +43,14 @@ type TaskRunArtifact struct {
}

func (ta *TaskRunArtifact) Key(obj interface{}) string {
tr := obj.(*v1beta1.TaskRun)
return "taskrun-" + string(tr.UID)
tro := obj.(*objects.TaskRunObject)
return "taskrun-" + string(tro.UID)
}

func (ta *TaskRunArtifact) ExtractObjects(tr *v1beta1.TaskRun) []interface{} {
return []interface{}{tr}
func (ta *TaskRunArtifact) ExtractObjects(obj objects.TektonObject) []interface{} {
return []interface{}{obj}
}

func (ta *TaskRunArtifact) Type() string {
return "tekton"
}
Expand All @@ -69,6 +71,40 @@ func (ta *TaskRunArtifact) Enabled(cfg config.Config) bool {
return cfg.Artifacts.TaskRuns.Enabled()
}

type PipelineRunArtifact struct {
Logger *zap.SugaredLogger
}

func (pa *PipelineRunArtifact) Key(obj interface{}) string {
pro := obj.(*objects.PipelineRunObject)
return "pipelinerun-" + string(pro.UID)
}

func (pa *PipelineRunArtifact) ExtractObjects(obj objects.TektonObject) []interface{} {
return []interface{}{obj}
}

func (pa *PipelineRunArtifact) Type() string {
// TODO: Is this right?
return "tekton-pipeline-run"
}

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) Signer(cfg config.Config) string {
return cfg.Artifacts.PipelineRuns.Signer
}

func (pa *PipelineRunArtifact) Enabled(cfg config.Config) bool {
return cfg.Artifacts.PipelineRuns.Enabled()
}

type OCIArtifact struct {
Logger *zap.SugaredLogger
}
Expand All @@ -86,49 +122,53 @@ type StructuredSignable struct {
Digest string
}

func (oa *OCIArtifact) ExtractObjects(tr *v1beta1.TaskRun) []interface{} {
imageResourceNames := map[string]*image{}
if tr.Status.TaskSpec != nil && tr.Status.TaskSpec.Resources != nil {
for _, output := range tr.Status.TaskSpec.Resources.Outputs {
if output.Type == v1beta1.PipelineResourceTypeImage {
imageResourceNames[output.Name] = &image{}
func (oa *OCIArtifact) ExtractObjects(obj objects.TektonObject) []interface{} {
objs := []interface{}{}

// TODO: Not applicable to PipelineRuns, should look into a better way to separate this out
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not blocking for this PR (because I do want to get this in), but something we could do is add additional methods to the TektonObject interface -

e.g.

func GetOCI() []name.Digest{}

then PipelineRuns and TaskRuns could implement these separately and this just becomes a thin wrapper around the impl methods.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that would be much cleaner.

if tr, ok := obj.GetObject().(*v1beta1.TaskRun); ok {
imageResourceNames := map[string]*image{}
if tr.Status.TaskSpec != nil && tr.Status.TaskSpec.Resources != nil {
for _, output := range tr.Status.TaskSpec.Resources.Outputs {
if output.Type == v1beta1.PipelineResourceTypeImage {
imageResourceNames[output.Name] = &image{}
}
}
}
}

for _, rr := range tr.Status.ResourcesResult {
img, ok := imageResourceNames[rr.ResourceName]
if !ok {
continue
}
// We have a result for an image!
if rr.Key == "url" {
img.url = rr.Value
} else if rr.Key == "digest" {
img.digest = rr.Value
for _, rr := range tr.Status.ResourcesResult {
img, ok := imageResourceNames[rr.ResourceName]
if !ok {
continue
}
// We have a result for an image!
if rr.Key == "url" {
img.url = rr.Value
} else if rr.Key == "digest" {
img.digest = rr.Value
}
}
}

objs := []interface{}{}
for _, image := range imageResourceNames {
dgst, err := name.NewDigest(fmt.Sprintf("%s@%s", image.url, image.digest))
if err != nil {
oa.Logger.Error(err)
continue
for _, image := range imageResourceNames {
dgst, err := name.NewDigest(fmt.Sprintf("%s@%s", image.url, image.digest))
if err != nil {
oa.Logger.Error(err)
continue
}
objs = append(objs, dgst)
}
objs = append(objs, dgst)
}

// Now check TaskResults
resultImages := ExtractOCIImagesFromResults(tr, oa.Logger)
resultImages := ExtractOCIImagesFromResults(obj, oa.Logger)
objs = append(objs, resultImages...)

return objs
}

func ExtractOCIImagesFromResults(tr *v1beta1.TaskRun, logger *zap.SugaredLogger) []interface{} {
ss := extractTargetFromResults(tr, "IMAGE_URL", "IMAGE_DIGEST", logger)
func ExtractOCIImagesFromResults(obj objects.TektonObject, logger *zap.SugaredLogger) []interface{} {
objs := []interface{}{}
ss := extractTargetFromResults(obj, "IMAGE_URL", "IMAGE_DIGEST", logger)
for _, s := range ss {
if s == nil || s.Digest == "" || s.URI == "" {
continue
Expand All @@ -142,7 +182,7 @@ func ExtractOCIImagesFromResults(tr *v1beta1.TaskRun, logger *zap.SugaredLogger)
objs = append(objs, dgst)
}
// look for a comma separated list of images
for _, key := range tr.Status.TaskRunResults {
for _, key := range obj.GetResults() {
if key.Name != "IMAGES" {
continue
}
Expand All @@ -166,9 +206,9 @@ func ExtractOCIImagesFromResults(tr *v1beta1.TaskRun, logger *zap.SugaredLogger)
}

// ExtractSignableTargetFromResults extracts signable targets that aim to generate intoto provenance as materials within TaskRun results and store them as StructuredSignable.
func ExtractSignableTargetFromResults(tr *v1beta1.TaskRun, logger *zap.SugaredLogger) []*StructuredSignable {
func ExtractSignableTargetFromResults(obj objects.TektonObject, logger *zap.SugaredLogger) []*StructuredSignable {
objs := []*StructuredSignable{}
ss := extractTargetFromResults(tr, "ARTIFACT_URI", "ARTIFACT_DIGEST", logger)
ss := extractTargetFromResults(obj, "ARTIFACT_URI", "ARTIFACT_DIGEST", logger)
// Only add it if we got both the signable URI and digest.
for _, s := range ss {
if s == nil || s.Digest == "" || s.URI == "" {
Expand All @@ -190,10 +230,10 @@ func (s *StructuredSignable) FullRef() string {
return fmt.Sprintf("%s@%s", s.URI, s.Digest)
}

func extractTargetFromResults(tr *v1beta1.TaskRun, identifierSuffix string, digestSuffix string, logger *zap.SugaredLogger) map[string]*StructuredSignable {
func extractTargetFromResults(obj objects.TektonObject, identifierSuffix string, digestSuffix string, logger *zap.SugaredLogger) map[string]*StructuredSignable {
ss := map[string]*StructuredSignable{}

for _, res := range tr.Status.TaskRunResults {
for _, res := range obj.GetResults() {
if strings.HasSuffix(res.Name, identifierSuffix) {
marker := strings.TrimSuffix(res.Name, identifierSuffix)
if v, ok := ss[marker]; ok {
Expand Down
47 changes: 31 additions & 16 deletions pkg/artifacts/signable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/go-containerregistry/pkg/name"
"github.com/tektoncd/chains/pkg/chains/objects"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
logtesting "knative.dev/pkg/logging/testing"
)

Expand All @@ -38,12 +40,15 @@ func TestOCIArtifact_ExtractObjects(t *testing.T) {

tests := []struct {
name string
tr *v1beta1.TaskRun
obj objects.TektonObject
want []interface{}
}{
{
name: "one image",
tr: &v1beta1.TaskRun{
obj: objects.NewTaskRunObject(&v1beta1.TaskRun{
TypeMeta: metav1.TypeMeta{
Kind: "TaskRun",
},
Status: v1beta1.TaskRunStatus{
TaskRunStatusFields: v1beta1.TaskRunStatusFields{
ResourcesResult: []v1beta1.PipelineResourceResult{
Expand Down Expand Up @@ -72,12 +77,15 @@ func TestOCIArtifact_ExtractObjects(t *testing.T) {
},
},
},
},
}),
want: []interface{}{createDigest(t, "gcr.io/foo/bar@sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b5")},
},
{
name: "two images",
tr: &v1beta1.TaskRun{
obj: objects.NewTaskRunObject(&v1beta1.TaskRun{
TypeMeta: metav1.TypeMeta{
Kind: "TaskRun",
},
Status: v1beta1.TaskRunStatus{
TaskRunStatusFields: v1beta1.TaskRunStatusFields{
ResourcesResult: []v1beta1.PipelineResourceResult{
Expand Down Expand Up @@ -122,15 +130,18 @@ func TestOCIArtifact_ExtractObjects(t *testing.T) {
},
},
},
},
}),
want: []interface{}{
createDigest(t, "gcr.io/foo/bar@sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b5"),
createDigest(t, "gcr.io/foo/baz@sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b6"),
},
},
{
name: "resource and result",
tr: &v1beta1.TaskRun{
obj: objects.NewTaskRunObject(&v1beta1.TaskRun{
TypeMeta: metav1.TypeMeta{
Kind: "TaskRun",
},
Status: v1beta1.TaskRunStatus{
TaskRunStatusFields: v1beta1.TaskRunStatusFields{
ResourcesResult: []v1beta1.PipelineResourceResult{
Expand Down Expand Up @@ -177,14 +188,17 @@ func TestOCIArtifact_ExtractObjects(t *testing.T) {
},
},
},
},
}),
want: []interface{}{
createDigest(t, "gcr.io/foo/bat@sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b4"),
createDigest(t, "gcr.io/foo/bar@sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b5")},
},
{
name: "extra",
tr: &v1beta1.TaskRun{
obj: objects.NewTaskRunObject(&v1beta1.TaskRun{
TypeMeta: metav1.TypeMeta{
Kind: "TaskRun",
},
Status: v1beta1.TaskRunStatus{
TaskRunStatusFields: v1beta1.TaskRunStatusFields{
TaskRunResults: []v1beta1.TaskRunResult{
Expand Down Expand Up @@ -233,11 +247,11 @@ func TestOCIArtifact_ExtractObjects(t *testing.T) {
},
},
},
},
}),
want: []interface{}{createDigest(t, "gcr.io/foo/bar@sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b5")},
}, {
name: "images",
tr: &v1beta1.TaskRun{
obj: objects.NewTaskRunObject(&v1beta1.TaskRun{
Status: v1beta1.TaskRunStatus{
TaskRunStatusFields: v1beta1.TaskRunStatusFields{
TaskRunResults: []v1beta1.TaskRunResult{
Expand All @@ -248,14 +262,14 @@ func TestOCIArtifact_ExtractObjects(t *testing.T) {
},
},
},
},
}),
want: []interface{}{
createDigest(t, "gcr.io/foo/bar@sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b5"),
createDigest(t, "gcr.io/baz/bar@sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b6"),
},
}, {
name: "images-newline",
tr: &v1beta1.TaskRun{
obj: objects.NewTaskRunObject(&v1beta1.TaskRun{
Status: v1beta1.TaskRunStatus{
TaskRunStatusFields: v1beta1.TaskRunStatusFields{
TaskRunResults: []v1beta1.TaskRunResult{
Expand All @@ -266,7 +280,7 @@ func TestOCIArtifact_ExtractObjects(t *testing.T) {
},
},
},
},
}),
want: []interface{}{
createDigest(t, "gcr.io/foo/bar@sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b5"),
createDigest(t, "gcr.io/baz/bar@sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b6"),
Expand All @@ -279,7 +293,7 @@ func TestOCIArtifact_ExtractObjects(t *testing.T) {
oa := &OCIArtifact{
Logger: logger,
}
got := oa.ExtractObjects(tt.tr)
got := oa.ExtractObjects(tt.obj)
sort.Slice(got, func(i, j int) bool {
a := got[i].(name.Digest)
b := got[j].(name.Digest)
Expand Down Expand Up @@ -309,12 +323,13 @@ func TestExtractOCIImagesFromResults(t *testing.T) {
},
},
}
obj := objects.NewTaskRunObject(tr)
want := []interface{}{
createDigest(t, fmt.Sprintf("img1@%s", digest1)),
createDigest(t, fmt.Sprintf("img2@%s", digest2)),
createDigest(t, fmt.Sprintf("img3@%s", digest1)),
}
got := ExtractOCIImagesFromResults(tr, logtesting.TestLogger(t))
got := ExtractOCIImagesFromResults(obj, logtesting.TestLogger(t))
sort.Slice(got, func(i, j int) bool {
a := got[i].(name.Digest)
b := got[j].(name.Digest)
Expand Down Expand Up @@ -354,7 +369,7 @@ func TestExtractSignableTargetFromResults(t *testing.T) {
{URI: "projects/test-project/locations/us-west4/repositories/test-repo/mavenArtifacts/a.b.c:d:1.0-jre", Digest: digest4},
{URI: "projects/test-project/locations/us-west4/repositories/test-repo/mavenArtifacts/empty_prefix", Digest: digest1},
}
got := ExtractSignableTargetFromResults(tr, logtesting.TestLogger(t))
got := ExtractSignableTargetFromResults(objects.NewTaskRunObject(tr), logtesting.TestLogger(t))
sort.Slice(got, func(i, j int) bool {
return got[i].URI < got[j].URI
})
Expand Down
Loading