Skip to content

Commit

Permalink
Add labels and annotations to attestation (#692)
Browse files Browse the repository at this point in the history
Labels and annotations are added to the PipelineRun and TaskRun
attestations in `.predicate.invocation.environment`. Additionally, for
PipelineRun attestations, each task also contains its own
`.invocation.environment` with the corresponding labels and annotations.

Fixes #591

Signed-off-by: Luiz Carvalho <[email protected]>
  • Loading branch information
lcarva authored Feb 3, 2023
1 parent 0a06881 commit 82c213c
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 5 deletions.
14 changes: 14 additions & 0 deletions docs/intoto.md
Original file line number Diff line number Diff line change
Expand Up @@ -455,3 +455,17 @@ results:
digest: sha256@89dedecaca1b85346600c7db9939a4fe090a42ez
```

### Invocation Environment

TaskRun attestations include the annotations and labels of the underlying TaskRun resource. The
same is true for PipelineRun attestations. These are included under the path
`.predicate.invocation.environment.annotations` and `.predicate.invocation.environment.labels`
repectively.

PipelineRun attestations also include the annotations and labels of the TaskRuns used at
`.predicate.buildConfig.tasks[].invocation.environment`.

The following annotations are always excluded:

* `kubectl.kubernetes.io/last-applied-configuration`
* Annotations starting with `chains.tekton.dev/`
28 changes: 27 additions & 1 deletion pkg/chains/formats/slsa/attest/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (

slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
Expand Down Expand Up @@ -55,7 +57,7 @@ func Step(step *v1beta1.Step, stepState *v1beta1.StepState) StepAttestation {
return attestation
}

func Invocation(source *v1beta1.ConfigSource, params []v1beta1.Param, paramSpecs []v1beta1.ParamSpec) slsa.ProvenanceInvocation {
func Invocation(source *v1beta1.ConfigSource, params []v1beta1.Param, paramSpecs []v1beta1.ParamSpec, meta metav1.Object) slsa.ProvenanceInvocation {
i := slsa.ProvenanceInvocation{
ConfigSource: convertConfigSource(source),
}
Expand All @@ -74,6 +76,30 @@ func Invocation(source *v1beta1.ConfigSource, params []v1beta1.Param, paramSpecs
}

i.Parameters = iParams

environment := map[string]map[string]string{}

annotations := map[string]string{}
for name, value := range meta.GetAnnotations() {
// Ignore annotations that are not relevant to provenance information
if name == corev1.LastAppliedConfigAnnotation || strings.HasPrefix(name, "chains.tekton.dev/") {
continue
}
annotations[name] = value
}
if len(annotations) > 0 {
environment["annotations"] = annotations
}

labels := meta.GetLabels()
if len(labels) > 0 {
environment["labels"] = labels
}

if len(environment) > 0 {
i.Environment = environment
}

return i
}

Expand Down
18 changes: 18 additions & 0 deletions pkg/chains/formats/slsa/v1/intotoite6_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ func TestTaskRunCreatePayload1(t *testing.T) {
"CHAINS-GIT_COMMIT": {Type: "string", StringVal: "sha:taskrun"},
"CHAINS-GIT_URL": {Type: "string", StringVal: "https://git.test.com"},
},
Environment: map[string]map[string]string{
"labels": {"tekton.dev/pipelineTask": "build"},
},
},
Builder: slsa.ProvenanceBuilder{
ID: "test_builder-1",
Expand Down Expand Up @@ -245,6 +248,9 @@ func TestPipelineRunCreatePayload(t *testing.T) {
"revision": {Type: "string", StringVal: ""},
"url": {Type: "string", StringVal: "https://git.test.com"},
},
Environment: map[string]map[string]string{
"labels": {"tekton.dev/pipelineTask": "git-clone"},
},
},
Results: []v1beta1.TaskRunResult{
{
Expand Down Expand Up @@ -313,6 +319,9 @@ func TestPipelineRunCreatePayload(t *testing.T) {
"CHAINS-GIT_URL": {Type: "string", StringVal: "https://git.test.com"},
"IMAGE": {Type: "string", StringVal: "test.io/test/image"},
},
Environment: map[string]map[string]string{
"labels": {"tekton.dev/pipelineTask": "build"},
},
},
Results: []v1beta1.TaskRunResult{
{
Expand Down Expand Up @@ -457,6 +466,9 @@ func TestPipelineRunCreatePayloadChildRefs(t *testing.T) {
"revision": {Type: "string", StringVal: ""},
"url": {Type: "string", StringVal: "https://git.test.com"},
},
Environment: map[string]map[string]string{
"labels": {"tekton.dev/pipelineTask": "git-clone"},
},
},
Results: []v1beta1.TaskRunResult{
{
Expand Down Expand Up @@ -525,6 +537,9 @@ func TestPipelineRunCreatePayloadChildRefs(t *testing.T) {
"CHAINS-GIT_URL": {Type: "string", StringVal: "https://git.test.com"},
"IMAGE": {Type: "string", StringVal: "test.io/test/image"},
},
Environment: map[string]map[string]string{
"labels": {"tekton.dev/pipelineTask": "build"},
},
},
Results: []v1beta1.TaskRunResult{
{
Expand Down Expand Up @@ -616,6 +631,9 @@ func TestTaskRunCreatePayload2(t *testing.T) {
"revision": {Type: "string"},
"url": {Type: "string", StringVal: "https://git.test.com"},
},
Environment: map[string]map[string]string{
"labels": {"tekton.dev/pipelineTask": "git-clone"},
},
},
BuildType: "tekton.dev/v1beta1/TaskRun",
BuildConfig: taskrun.BuildConfig{
Expand Down
4 changes: 2 additions & 2 deletions pkg/chains/formats/slsa/v1/pipelinerun/pipelinerun.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func invocation(pro *objects.PipelineRunObject) slsa.ProvenanceInvocation {
if p := pro.Status.Provenance; p != nil {
source = p.ConfigSource
}
return attest.Invocation(source, pro.Spec.Params, paramSpecs)
return attest.Invocation(source, pro.Spec.Params, paramSpecs, pro.GetObjectMeta())
}

func buildConfig(pro *objects.PipelineRunObject, logger *zap.SugaredLogger) BuildConfig {
Expand Down Expand Up @@ -153,7 +153,7 @@ func buildConfig(pro *objects.PipelineRunObject, logger *zap.SugaredLogger) Buil
FinishedOn: tr.Status.CompletionTime.Time.UTC(),
Status: getStatus(tr.Status.Conditions),
Steps: steps,
Invocation: attest.Invocation(source, params, paramSpecs),
Invocation: attest.Invocation(source, params, paramSpecs, &tr.ObjectMeta),
Results: tr.Status.TaskRunResults,
}

Expand Down
16 changes: 16 additions & 0 deletions pkg/chains/formats/slsa/v1/pipelinerun/provenance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ func TestBuildConfig(t *testing.T) {
"revision": {Type: "string", StringVal: ""},
"url": {Type: "string", StringVal: "https://git.test.com"},
},
Environment: map[string]map[string]string{
"labels": {"tekton.dev/pipelineTask": "git-clone"},
},
},
Results: []v1beta1.TaskRunResult{
{
Expand Down Expand Up @@ -184,6 +187,9 @@ func TestBuildConfig(t *testing.T) {
"CHAINS-GIT_URL": {Type: "string", StringVal: "https://git.test.com"},
"IMAGE": {Type: "string", StringVal: "test.io/test/image"},
},
Environment: map[string]map[string]string{
"labels": {"tekton.dev/pipelineTask": "build"},
},
},
Results: []v1beta1.TaskRunResult{
{
Expand Down Expand Up @@ -300,6 +306,11 @@ func TestBuildConfigTaskOrder(t *testing.T) {
"url": {Type: "string", StringVal: "https://git.test.com"},
"revision": {Type: "string", StringVal: ""},
},
Environment: map[string]map[string]string{
"labels": {
"tekton.dev/pipelineTask": "git-clone",
},
},
},
Results: []v1beta1.TaskRunResult{
{
Expand Down Expand Up @@ -370,6 +381,11 @@ func TestBuildConfigTaskOrder(t *testing.T) {
"CHAINS-GIT_URL": {Type: "string", StringVal: "https://git.test.com"},
"IMAGE": {Type: "string", StringVal: "test.io/test/image"},
},
Environment: map[string]map[string]string{
"labels": {
"tekton.dev/pipelineTask": "build",
},
},
},
Results: []v1beta1.TaskRunResult{
{
Expand Down
18 changes: 18 additions & 0 deletions pkg/chains/formats/slsa/v1/taskrun/provenance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ func TestInvocation(t *testing.T) {
kind: TaskRun
metadata:
uid: my-uid
annotations:
ann1: ann-one
ann2: ann-two
kubectl.kubernetes.io/last-applied-configuration: ignored
chains.tekton.dev/any: "ignored"
labels:
label1: label-one
label2: label-two
spec:
params:
- name: my-param
Expand Down Expand Up @@ -166,6 +174,16 @@ status:
"my-default-empty-string-param": {Type: "string", StringVal: ""},
"my-default-empty-array-param": {Type: "array", ArrayVal: []string{}},
},
Environment: map[string]map[string]string{
"annotations": {
"ann1": "ann-one",
"ann2": "ann-two",
},
"labels": {
"label1": "label-one",
"label2": "label-two",
},
},
}

got := invocation(objects.NewTaskRunObject(taskRun))
Expand Down
2 changes: 1 addition & 1 deletion pkg/chains/formats/slsa/v1/taskrun/taskrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func invocation(tro *objects.TaskRunObject) slsa.ProvenanceInvocation {
if p := tro.Status.Provenance; p != nil {
source = p.ConfigSource
}
return attest.Invocation(source, tro.Spec.Params, paramSpecs)
return attest.Invocation(source, tro.Spec.Params, paramSpecs, tro.GetObjectMeta())
}

func metadata(tro *objects.TaskRunObject) *slsa.ProvenanceMetadata {
Expand Down
27 changes: 26 additions & 1 deletion test/examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ import (
"time"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
intoto "github.com/in-toto/in-toto-golang/in_toto"
slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
"github.com/secure-systems-lab/go-securesystemslib/dsse"

"github.com/ghodss/yaml"
Expand Down Expand Up @@ -129,7 +131,15 @@ func runInTotoFormatterTests(ctx context.Context, t *testing.T, ns string, c *cl
}
expected := expectedProvenance(t, path, completed)

if diff := cmp.Diff(expected, gotProvenance, OptSortMaterial); diff != "" {
opts := []cmp.Option{
// Annotations and labels may contain release specific information. Ignore
// those to avoid brittle tests.
cmpopts.IgnoreFields(slsa.ProvenanceInvocation{}, "Environment"),
cmpopts.IgnoreMapEntries(ignoreEnvironmentAnnotationsAndLabels),
OptSortMaterial,
}

if diff := cmp.Diff(expected, gotProvenance, opts...); diff != "" {
t.Errorf("provenance dont match: -want +got: %s", diff)
}

Expand Down Expand Up @@ -375,3 +385,18 @@ func pipelineRunFromExample(t *testing.T, ns, example string) objects.TektonObje
pr.Namespace = ns
return objects.NewPipelineRunObject(pr)
}

func ignoreEnvironmentAnnotationsAndLabels(key string, value any) bool {
if key != "environment" {
return false
}
// There are multiple maps with the key "environment", so we must carefully
// choose the right one.
switch v := value.(type) {
case map[string]any:
_, hasAnnotations := v["annotations"]
_, hasLabels := v["labels"]
return hasAnnotations || hasLabels
}
return false
}

0 comments on commit 82c213c

Please sign in to comment.