Skip to content

Commit

Permalink
Set ConfigSource in clusterresolver
Browse files Browse the repository at this point in the history
Related to tektoncd#5522

Prior, a field named Source was introduced to ResolutionRequest status
to record the source where the remote resource came from. And the
individual resolvers need to implement the Source function to set the
correct source value. But the method in clusterresolver returns a nil value.

Now, we return correct source value with the 3 subfields: url, digest and entrypoint
- url: [Kubernetes CRD namespace-scoped resource URI](https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-uris) appended with UID.
Example: /apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE/NAME@UID.
- digest: sha256 checksum of the in-cluster resource
- entrypoint: ***empty*** because the path is already available in url field.

Signed-off-by: Chuang Wang <chuangw@google.com>
  • Loading branch information
chuangw6 committed Nov 7, 2022
1 parent d310355 commit 65aecfd
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 6 deletions.
54 changes: 54 additions & 0 deletions docs/cluster-resolver.md
Expand Up @@ -74,6 +74,60 @@ spec:
value: namespace-containing-pipeline
```

## `ResolutionRequest` Status
`ResolutionRequest.Status.Source` field captures the source where the remote resource came from. It includes the 3 subfields: `url`, `digest` and `entrypoint`.
- `url`: [Kubernetes CRD namespace-scoped resource URI](https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-uris) appended with UID i.e. `/apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE/NAME@UID`
- `digest`: sha256 checksum of the content in the in-cluster resource's spec field. The reason why it's the checksum of the spec content rather than the whole object is because the metadata of in-cluster resources might be modified i.e. annotations. Therefore, the checksum of the spec content should be sufficient for source verifiers to verify if things have been changed maliciously even though the metadata is modified with good intentions.
- `entrypoint`: ***empty*** because the path information is already available in the url field.

Example:
- TaskRun Resolution

```yaml
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
name: cluster-demo
spec:
taskRef:
resolver: cluster
params:
- name: kind
value: task
- name: name
value: a-simple-task
- name: namespace
value: default
```


- `ResolutionRequest`
```yaml
apiVersion: resolution.tekton.dev/v1beta1
kind: ResolutionRequest
metadata:
labels:
resolution.tekton.dev/type: cluster
name: cluster-7a04be6baa3eeedd232542036b7f3b2d
namespace: default
ownerReferences: ...
spec:
params:
- name: kind
value: task
- name: name
value: a-simple-task
- name: namespace
value: default
status:
annotations: ...
conditions: ...
data: xxx
source:
digest:
sha256: 245b1aa918434cc8195b4d4d026f2e43df09199e2ed31d4dfd9c2cbea1c7ce54
uri: /apis/tekton.dev/v1beta1/namespaces/default/task/a-simple-task@3b82d8c4-f89e-47ea-a49d-3be0dca4c038
```
---

Except as otherwise noted, the content of this page is licensed under the
Expand Down
50 changes: 44 additions & 6 deletions pkg/resolution/resolver/cluster/resolver.go
Expand Up @@ -18,11 +18,14 @@ package cluster

import (
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"strings"

resolverconfig "github.com/tektoncd/pipeline/pkg/apis/config/resolver"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
pipelinev1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
clientset "github.com/tektoncd/pipeline/pkg/client/clientset/versioned"
pipelineclient "github.com/tektoncd/pipeline/pkg/client/injection/client"
Expand Down Expand Up @@ -101,6 +104,8 @@ func (r *Resolver) Resolve(ctx context.Context, origParams []pipelinev1beta1.Par
}

var data []byte
var spec []byte
var uid string
groupVersion := pipelinev1beta1.SchemeGroupVersion.String()

switch params[KindParam] {
Expand All @@ -110,35 +115,51 @@ func (r *Resolver) Resolve(ctx context.Context, origParams []pipelinev1beta1.Par
logger.Infof("failed to load task %s from namespace %s: %v", params[NameParam], params[NamespaceParam], err)
return nil, err
}
uid = string(task.UID)
task.Kind = "Task"
task.APIVersion = groupVersion
data, err = yaml.Marshal(task)
if err != nil {
logger.Infof("failed to marshal task %s from namespace %s: %v", params[NameParam], params[NamespaceParam], err)
return nil, err
}

spec, err = yaml.Marshal(task.Spec)
if err != nil {
logger.Infof("failed to marshal the spec of the task %s from namespace %s: %v", params[NameParam], params[NamespaceParam], err)
return nil, err
}
case "pipeline":
pipeline, err := r.pipelineClientSet.TektonV1beta1().Pipelines(params[NamespaceParam]).Get(ctx, params[NameParam], metav1.GetOptions{})
if err != nil {
logger.Infof("failed to load pipeline %s from namespace %s: %v", params[NameParam], params[NamespaceParam], err)
return nil, err
}
uid = string(pipeline.UID)
pipeline.Kind = "Pipeline"
pipeline.APIVersion = groupVersion
data, err = yaml.Marshal(pipeline)
if err != nil {
logger.Infof("failed to marshal pipeline %s from namespace %s: %v", params[NameParam], params[NamespaceParam], err)
return nil, err
}

spec, err = yaml.Marshal(pipeline.Spec)
if err != nil {
logger.Infof("failed to marshal the spec of the pipeline %s from namespace %s: %v", params[NameParam], params[NamespaceParam], err)
return nil, err
}
default:
logger.Infof("unknown or invalid resource kind %s", params[KindParam])
return nil, fmt.Errorf("unknown or invalid resource kind %s", params[KindParam])
}

return &ResolvedClusterResource{
Content: data,
Name: params[NameParam],
Namespace: params[NamespaceParam],
Content: data,
Spec: spec,
Name: params[NameParam],
Namespace: params[NamespaceParam],
ResourceURI: fmt.Sprintf("/apis/%s/namespaces/%s/%s/%s@%s", groupVersion, params[NamespaceParam], params[KindParam], params[NameParam], uid),
}, nil
}

Expand All @@ -161,9 +182,17 @@ func (r *Resolver) isDisabled(ctx context.Context) bool {
// ResolvedClusterResource implements framework.ResolvedResource and returns
// the resolved file []byte data and an annotation map for any metadata.
type ResolvedClusterResource struct {
Content []byte
Name string
// Content is the actual resolved resource data.
Content []byte
// Spec is the data in the resolved task/pipeline CRD spec.
Spec []byte
// Name is the resolved resource name in the cluster
Name string
// Namespace is the namespace in the cluster under which the resolved resource was created.
Namespace string
// ResourceURI is in the format of namespace-scoped resources API.
// https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-uris
ResourceURI string
}

var _ framework.ResolvedResource = &ResolvedClusterResource{}
Expand All @@ -184,7 +213,16 @@ func (r *ResolvedClusterResource) Annotations() map[string]string {
// Source is the source reference of the remote data that records where the remote
// file came from including the url, digest and the entrypoint.
func (r ResolvedClusterResource) Source() *pipelinev1beta1.ConfigSource {
return nil
h := sha256.New()
h.Write(r.Spec)
sha256CheckSum := hex.EncodeToString(h.Sum(nil))

return &v1beta1.ConfigSource{
URI: r.ResourceURI,
Digest: map[string]string{
"sha256": sha256CheckSum,
},
}
}

func populateParamsWithDefaults(ctx context.Context, origParams []pipelinev1beta1.Param) (map[string]string, error) {
Expand Down
42 changes: 42 additions & 0 deletions pkg/resolution/resolver/cluster/resolver_test.go
Expand Up @@ -19,7 +19,9 @@ package cluster

import (
"context"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
"testing"
"time"
Expand Down Expand Up @@ -190,6 +192,7 @@ func TestResolve(t *testing.T) {
Name: "example-task",
Namespace: "task-ns",
ResourceVersion: "00002",
UID: "a123",
},
TypeMeta: metav1.TypeMeta{
Kind: string(pipelinev1beta1.NamespacedTaskKind),
Expand All @@ -207,12 +210,17 @@ func TestResolve(t *testing.T) {
if err != nil {
t.Fatalf("couldn't marshal task: %v", err)
}
taskSpec, err := yaml.Marshal(exampleTask.Spec)
if err != nil {
t.Fatalf("couldn't marshal task spec: %v", err)
}

examplePipeline := &pipelinev1beta1.Pipeline{
ObjectMeta: metav1.ObjectMeta{
Name: "example-pipeline",
Namespace: defaultNS,
ResourceVersion: "00001",
UID: "b123",
},
TypeMeta: metav1.TypeMeta{
Kind: "Pipeline",
Expand All @@ -232,6 +240,10 @@ func TestResolve(t *testing.T) {
if err != nil {
t.Fatalf("couldn't marshal pipeline: %v", err)
}
pipelineSpec, err := yaml.Marshal(examplePipeline.Spec)
if err != nil {
t.Fatalf("couldn't marshal pipeline spec: %v", err)
}

testCases := []struct {
name string
Expand All @@ -252,6 +264,12 @@ func TestResolve(t *testing.T) {
Status: duckv1.Status{},
ResolutionRequestStatusFields: v1beta1.ResolutionRequestStatusFields{
Data: base64.StdEncoding.Strict().EncodeToString(taskAsYAML),
Source: &pipelinev1beta1.ConfigSource{
URI: "/apis/tekton.dev/v1beta1/namespaces/task-ns/task/example-task@a123",
Digest: map[string]string{
"sha256": sha256CheckSum(taskSpec),
},
},
},
},
}, {
Expand All @@ -263,6 +281,12 @@ func TestResolve(t *testing.T) {
Status: duckv1.Status{},
ResolutionRequestStatusFields: v1beta1.ResolutionRequestStatusFields{
Data: base64.StdEncoding.Strict().EncodeToString(pipelineAsYAML),
Source: &pipelinev1beta1.ConfigSource{
URI: "/apis/tekton.dev/v1beta1/namespaces/pipeline-ns/pipeline/example-pipeline@b123",
Digest: map[string]string{
"sha256": sha256CheckSum(pipelineSpec),
},
},
},
},
}, {
Expand All @@ -273,6 +297,12 @@ func TestResolve(t *testing.T) {
Status: duckv1.Status{},
ResolutionRequestStatusFields: v1beta1.ResolutionRequestStatusFields{
Data: base64.StdEncoding.Strict().EncodeToString(pipelineAsYAML),
Source: &pipelinev1beta1.ConfigSource{
URI: "/apis/tekton.dev/v1beta1/namespaces/pipeline-ns/pipeline/example-pipeline@b123",
Digest: map[string]string{
"sha256": sha256CheckSum(pipelineSpec),
},
},
},
},
}, {
Expand All @@ -283,6 +313,12 @@ func TestResolve(t *testing.T) {
Status: duckv1.Status{},
ResolutionRequestStatusFields: v1beta1.ResolutionRequestStatusFields{
Data: base64.StdEncoding.Strict().EncodeToString(taskAsYAML),
Source: &pipelinev1beta1.ConfigSource{
URI: "/apis/tekton.dev/v1beta1/namespaces/task-ns/task/example-task@a123",
Digest: map[string]string{
"sha256": sha256CheckSum(taskSpec),
},
},
},
},
}, {
Expand Down Expand Up @@ -453,3 +489,9 @@ func createRequest(kind, name, namespace string) *v1beta1.ResolutionRequest {
func resolverContext() context.Context {
return frtesting.ContextWithClusterResolverEnabled(context.Background())
}

func sha256CheckSum(input []byte) string {
h := sha256.New()
h.Write(input)
return hex.EncodeToString(h.Sum(nil))
}

0 comments on commit 65aecfd

Please sign in to comment.