Skip to content

Commit

Permalink
add trusted resources e2e tests
Browse files Browse the repository at this point in the history
This commit adds trusted resources e2e tests to test tasks and
pipelinese verification.
  • Loading branch information
Yongxuanzhang committed Oct 31, 2022
1 parent b7055ca commit 8e8fc96
Show file tree
Hide file tree
Showing 8 changed files with 561 additions and 30 deletions.
16 changes: 9 additions & 7 deletions pkg/reconciler/pipelinerun/resources/pipelineref.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func GetPipelineFunc(ctx context.Context, k8s kubernetes.Interface, tekton clien
return nil, fmt.Errorf("failed to get keychain: %w", err)
}
resolver := oci.NewResolver(pr.Bundle, kc)
return resolvePipeline(ctx, resolver, name)
return resolvePipeline(ctx, resolver, name, k8s)
}, nil
case pr != nil && pr.Resolver != "" && requester != nil:
return func(ctx context.Context, name string) (v1beta1.PipelineObject, error) {
Expand All @@ -80,13 +80,14 @@ func GetPipelineFunc(ctx context.Context, k8s kubernetes.Interface, tekton clien
}
replacedParams := replaceParamValues(pr.Params, stringReplacements, arrayReplacements, objectReplacements)
resolver := resolution.NewResolver(requester, pipelineRun, string(pr.Resolver), "", "", replacedParams)
return resolvePipeline(ctx, resolver, name)
return resolvePipeline(ctx, resolver, name, k8s)
}, nil
default:
// Even if there is no task ref, we should try to return a local resolver.
local := &LocalPipelineRefResolver{
Namespace: namespace,
Tektonclient: tekton,
K8sclient: k8s,
}
return local.GetPipeline, nil
}
Expand All @@ -96,6 +97,7 @@ func GetPipelineFunc(ctx context.Context, k8s kubernetes.Interface, tekton clien
type LocalPipelineRefResolver struct {
Namespace string
Tektonclient clientset.Interface
K8sclient kubernetes.Interface
}

// GetPipeline will resolve a Pipeline from the local cluster using a versioned Tekton client. It will
Expand All @@ -110,7 +112,7 @@ func (l *LocalPipelineRefResolver) GetPipeline(ctx context.Context, name string)
if err != nil {
return nil, err
}
if err := verifyResolvedPipeline(ctx, pipeline); err != nil {
if err := verifyResolvedPipeline(ctx, pipeline, l.K8sclient); err != nil {
return nil, err
}
return pipeline, nil
Expand All @@ -120,7 +122,7 @@ func (l *LocalPipelineRefResolver) GetPipeline(ctx context.Context, name string)
// fetch a pipeline with given name. An error is returned if the
// resolution doesn't work or the returned data isn't a valid
// v1beta1.PipelineObject.
func resolvePipeline(ctx context.Context, resolver remote.Resolver, name string) (v1beta1.PipelineObject, error) {
func resolvePipeline(ctx context.Context, resolver remote.Resolver, name string, k8s kubernetes.Interface) (v1beta1.PipelineObject, error) {
obj, err := resolver.Get(ctx, "pipeline", name)
if err != nil {
return nil, err
Expand All @@ -130,7 +132,7 @@ func resolvePipeline(ctx context.Context, resolver remote.Resolver, name string)
return nil, fmt.Errorf("failed to convert obj %s into Pipeline", obj.GetObjectKind().GroupVersionKind().String())
}
// TODO(#5527): Consider move this function call to GetPipelineData
if err := verifyResolvedPipeline(ctx, pipelineObj); err != nil {
if err := verifyResolvedPipeline(ctx, pipelineObj, k8s); err != nil {
return nil, err
}
return pipelineObj, nil
Expand All @@ -150,10 +152,10 @@ func readRuntimeObjectAsPipeline(ctx context.Context, obj runtime.Object) (v1bet
}

// verifyResolvedPipeline verifies the resolved pipeline
func verifyResolvedPipeline(ctx context.Context, pipeline v1beta1.PipelineObject) error {
func verifyResolvedPipeline(ctx context.Context, pipeline v1beta1.PipelineObject, k8s kubernetes.Interface) error {
cfg := config.FromContextOrDefaults(ctx)
if cfg.FeatureFlags.ResourceVerificationMode == config.EnforceResourceVerificationMode || cfg.FeatureFlags.ResourceVerificationMode == config.WarnResourceVerificationMode {
if err := trustedresources.VerifyPipeline(ctx, pipeline); err != nil {
if err := trustedresources.VerifyPipeline(ctx, pipeline, k8s); err != nil {
if cfg.FeatureFlags.ResourceVerificationMode == config.EnforceResourceVerificationMode {
return trustedresources.ErrorResourceVerificationFailed
}
Expand Down
16 changes: 9 additions & 7 deletions pkg/reconciler/taskrun/resources/taskref.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func GetTaskFunc(ctx context.Context, k8s kubernetes.Interface, tekton clientset
}
resolver := oci.NewResolver(tr.Bundle, kc)

return resolveTask(ctx, resolver, name, kind)
return resolveTask(ctx, resolver, name, kind, k8s)
}, nil
case tr != nil && tr.Resolver != "" && requester != nil:
// Return an inline function that implements GetTask by calling Resolver.Get with the specified task type and
Expand All @@ -120,7 +120,7 @@ func GetTaskFunc(ctx context.Context, k8s kubernetes.Interface, tekton clientset
replacedParams = append(replacedParams, tr.Params...)
}
resolver := resolution.NewResolver(requester, owner, string(tr.Resolver), trName, namespace, replacedParams)
return resolveTask(ctx, resolver, name, kind)
return resolveTask(ctx, resolver, name, kind, k8s)
}, nil

default:
Expand All @@ -129,6 +129,7 @@ func GetTaskFunc(ctx context.Context, k8s kubernetes.Interface, tekton clientset
Namespace: namespace,
Kind: kind,
Tektonclient: tekton,
K8sclient: k8s,
}
return local.GetTask, nil
}
Expand All @@ -138,7 +139,7 @@ func GetTaskFunc(ctx context.Context, k8s kubernetes.Interface, tekton clientset
// fetch a task with given name. An error is returned if the
// remoteresource doesn't work or the returned data isn't a valid
// v1beta1.TaskObject.
func resolveTask(ctx context.Context, resolver remote.Resolver, name string, kind v1beta1.TaskKind) (v1beta1.TaskObject, error) {
func resolveTask(ctx context.Context, resolver remote.Resolver, name string, kind v1beta1.TaskKind, k8s kubernetes.Interface) (v1beta1.TaskObject, error) {
// Because the resolver will only return references with the same kind (eg ClusterTask), this will ensure we
// don't accidentally return a Task with the same name but different kind.
obj, err := resolver.Get(ctx, strings.TrimSuffix(strings.ToLower(string(kind)), "s"), name)
Expand All @@ -150,7 +151,7 @@ func resolveTask(ctx context.Context, resolver remote.Resolver, name string, kin
return nil, fmt.Errorf("failed to convert obj %s into Task", obj.GetObjectKind().GroupVersionKind().String())
}
// TODO(#5527): Consider move this function call to GetTaskData
if err := verifyResolvedTask(ctx, taskObj); err != nil {
if err := verifyResolvedTask(ctx, taskObj, k8s); err != nil {
return nil, err
}
return taskObj, nil
Expand All @@ -173,6 +174,7 @@ type LocalTaskRefResolver struct {
Namespace string
Kind v1beta1.TaskKind
Tektonclient clientset.Interface
K8sclient kubernetes.Interface
}

// GetTask will resolve either a Task or ClusterTask from the local cluster using a versioned Tekton client. It will
Expand All @@ -194,7 +196,7 @@ func (l *LocalTaskRefResolver) GetTask(ctx context.Context, name string) (v1beta
if err != nil {
return nil, err
}
if err := verifyResolvedTask(ctx, task); err != nil {
if err := verifyResolvedTask(ctx, task, l.K8sclient); err != nil {
return nil, err
}
return task, nil
Expand All @@ -206,10 +208,10 @@ func IsGetTaskErrTransient(err error) bool {
}

// verifyResolvedTask verifies the resolved task
func verifyResolvedTask(ctx context.Context, task v1beta1.TaskObject) error {
func verifyResolvedTask(ctx context.Context, task v1beta1.TaskObject, k8s kubernetes.Interface) error {
cfg := config.FromContextOrDefaults(ctx)
if cfg.FeatureFlags.ResourceVerificationMode == config.EnforceResourceVerificationMode || cfg.FeatureFlags.ResourceVerificationMode == config.WarnResourceVerificationMode {
if err := trustedresources.VerifyTask(ctx, task); err != nil {
if err := trustedresources.VerifyTask(ctx, task, k8s); err != nil {
if cfg.FeatureFlags.ResourceVerificationMode == config.EnforceResourceVerificationMode {
return trustedresources.ErrorResourceVerificationFailed
}
Expand Down
80 changes: 66 additions & 14 deletions pkg/trustedresources/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,23 @@ import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/pkg/errors"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/tektoncd/pipeline/pkg/apis/config"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)

const (
// SignatureAnnotation is the key of signature in annotation map
SignatureAnnotation = "tekton.dev/signature"
// keyReference is the prefix of secret reference
keyReference = "k8s://"
)

// VerifyInterface get the checksum of json marshalled object and verify it.
Expand All @@ -57,7 +63,7 @@ func VerifyInterface(obj interface{}, verifier signature.Verifier, signature []b
}

// VerifyTask verifies the signature and public key against task
func VerifyTask(ctx context.Context, taskObj v1beta1.TaskObject) error {
func VerifyTask(ctx context.Context, taskObj v1beta1.TaskObject, k8s kubernetes.Interface) error {
tm, signature, err := prepareObjectMeta(taskObj.TaskMetadata())
if err != nil {
return err
Expand All @@ -69,7 +75,7 @@ func VerifyTask(ctx context.Context, taskObj v1beta1.TaskObject) error {
ObjectMeta: tm,
Spec: taskObj.TaskSpec(),
}
verifiers, err := getVerifiers(ctx)
verifiers, err := getVerifiers(ctx, k8s)
if err != nil {
return err
}
Expand All @@ -82,7 +88,7 @@ func VerifyTask(ctx context.Context, taskObj v1beta1.TaskObject) error {
}

// VerifyPipeline verifies the signature and public key against pipeline
func VerifyPipeline(ctx context.Context, pipelineObj v1beta1.PipelineObject) error {
func VerifyPipeline(ctx context.Context, pipelineObj v1beta1.PipelineObject, k8s kubernetes.Interface) error {
pm, signature, err := prepareObjectMeta(pipelineObj.PipelineMetadata())
if err != nil {
return err
Expand All @@ -94,7 +100,7 @@ func VerifyPipeline(ctx context.Context, pipelineObj v1beta1.PipelineObject) err
ObjectMeta: pm,
Spec: pipelineObj.PipelineSpec(),
}
verifiers, err := getVerifiers(ctx)
verifiers, err := getVerifiers(ctx, k8s)
if err != nil {
return err
}
Expand Down Expand Up @@ -152,18 +158,16 @@ func prepareObjectMeta(in metav1.ObjectMeta) (metav1.ObjectMeta, []byte, error)
}

// getVerifiers get all verifiers from configmap
func getVerifiers(ctx context.Context) ([]signature.Verifier, error) {
func getVerifiers(ctx context.Context, k8s kubernetes.Interface) ([]signature.Verifier, error) {
cfg := config.FromContextOrDefaults(ctx)
verifiers := []signature.Verifier{}

// TODO(#5527): consider using k8s://namespace/name instead of mounting files.
for key := range cfg.TrustedResources.Keys {
v, err := verifierForKeyRef(ctx, key, crypto.SHA256)
v, err := verifierForKeyRef(ctx, key, crypto.SHA256, k8s)
if err == nil {
verifiers = append(verifiers, v)
verifiers = append(verifiers, v...)
}
}

if len(verifiers) == 0 {
return verifiers, fmt.Errorf("no public keys are founded for verification")
}
Expand All @@ -174,17 +178,65 @@ func getVerifiers(ctx context.Context) ([]signature.Verifier, error) {
// verifierForKeyRef parses the given keyRef, loads the key and returns an appropriate
// verifier using the provided hash algorithm
// TODO(#5527): consider wrap verifiers to resolver so the same verifiers are used for the same reconcile event
func verifierForKeyRef(ctx context.Context, keyRef string, hashAlgorithm crypto.Hash) (verifier signature.Verifier, err error) {
raw, err := os.ReadFile(filepath.Clean(keyRef))
if err != nil {
return nil, err
func verifierForKeyRef(ctx context.Context, keyRef string, hashAlgorithm crypto.Hash, k8s kubernetes.Interface) (verifiers []signature.Verifier, err error) {
var raw []byte
verifiers = []signature.Verifier{}
// if the ref is secret then we fetch the keys from the secrets
if strings.HasPrefix(keyRef, keyReference) {
s, err := getKeyPairSecret(ctx, keyRef, k8s)
if err != nil {
return nil, err
}
for _, raw := range s.Data {
pubKey, err := cryptoutils.UnmarshalPEMToPublicKey(raw)
if err != nil {
return nil, fmt.Errorf("pem to public key: %w", err)
}
v, _ := signature.LoadVerifier(pubKey, hashAlgorithm)
verifiers = append(verifiers, v)
}
if len(verifiers) == 0 {
return verifiers, fmt.Errorf("no public keys are founded for verification")
}
return verifiers, nil
} else {
// read the key from mounted file
raw, err = os.ReadFile(filepath.Clean(keyRef))
if err != nil {
return nil, err
}
}

// PEM encoded file.
pubKey, err := cryptoutils.UnmarshalPEMToPublicKey(raw)
if err != nil {
return nil, fmt.Errorf("pem to public key: %w", err)
}
v, _ := signature.LoadVerifier(pubKey, hashAlgorithm)
verifiers = append(verifiers, v)

return verifiers, nil
}

func getKeyPairSecret(ctx context.Context, k8sRef string, k8s kubernetes.Interface) (*v1.Secret, error) {
namespace, name, err := parseRef(k8sRef)
if err != nil {
return nil, err
}

return signature.LoadVerifier(pubKey, hashAlgorithm)
var s *v1.Secret
if s, err = k8s.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}); err != nil {
return nil, errors.Wrap(err, "checking if secret exists")
}

return s, nil
}

// the reference should be formatted as <namespace>/<secret name>
func parseRef(k8sRef string) (string, string, error) {
s := strings.Split(strings.TrimPrefix(k8sRef, keyReference), "/")
if len(s) != 2 {
return "", "", errors.New("kubernetes specification should be in the format k8s://<namespace>/<secret>")
}
return s[0], s[1], nil
}
Loading

0 comments on commit 8e8fc96

Please sign in to comment.