From 6db08511ff5ebfeefb4a23ab9b6472e9bd36d913 Mon Sep 17 00:00:00 2001 From: Yongxuan Zhang Date: Wed, 17 Jan 2024 18:43:48 +0000 Subject: [PATCH] support v1 for sign and verofy command This commit adds v1 support for sign and verify. Signed-off-by: Yongxuan Zhang yongxuanzhang@google.com --- pkg/cmd/pipeline/sign.go | 13 ++++- pkg/cmd/pipeline/sign_test.go | 70 +++++++++++++++--------- pkg/cmd/pipeline/testdata/signed-v1.yaml | 25 +++++++++ pkg/cmd/pipeline/verify.go | 15 ++++- pkg/cmd/pipeline/verify_test.go | 28 ++++++++-- pkg/cmd/task/sign.go | 12 +++- pkg/cmd/task/sign_test.go | 67 +++++++++++++++-------- pkg/cmd/task/testdata/signed-v1.yaml | 26 +++++++++ pkg/cmd/task/verify.go | 16 +++++- pkg/cmd/task/verify_test.go | 30 +++++++--- pkg/trustedresources/sign.go | 29 +++++++--- pkg/trustedresources/sign_test.go | 5 +- 12 files changed, 255 insertions(+), 81 deletions(-) create mode 100644 pkg/cmd/pipeline/testdata/signed-v1.yaml create mode 100644 pkg/cmd/task/testdata/signed-v1.yaml diff --git a/pkg/cmd/pipeline/sign.go b/pkg/cmd/pipeline/sign.go index 2b233037c4..d007cebb4b 100644 --- a/pkg/cmd/pipeline/sign.go +++ b/pkg/cmd/pipeline/sign.go @@ -22,7 +22,9 @@ import ( "github.com/spf13/cobra" "github.com/tektoncd/cli/pkg/cli" "github.com/tektoncd/cli/pkg/trustedresources" + v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" cliopts "k8s.io/cli-runtime/pkg/genericclioptions" "sigs.k8s.io/yaml" ) @@ -31,6 +33,7 @@ type signOptions struct { keyfile string kmsKey string targetFile string + apiVersion string } func signCommand() *cobra.Command { @@ -70,7 +73,13 @@ or using kms return err } - crd := &v1beta1.Pipeline{} + var crd metav1.Object + if opts.apiVersion == "v1beta1" { + crd = &v1beta1.Pipeline{} + } else { + crd = &v1.Pipeline{} + } + if err := yaml.Unmarshal(b, &crd); err != nil { return fmt.Errorf("error unmarshalling Pipeline: %v", err) } @@ -87,7 +96,7 @@ or using kms c.Flags().StringVarP(&opts.keyfile, "key-file", "K", "", "Key file") c.Flags().StringVarP(&opts.kmsKey, "kms-key", "m", "", "KMS key url") c.Flags().StringVarP(&opts.targetFile, "file-name", "f", "", "Fle name of the signed pipeline, using the original file name will overwrite the file") - + c.Flags().StringVarP(&opts.apiVersion, "version", "v", "v1", "apiVersion of the Pipeline to be signed") return c } diff --git a/pkg/cmd/pipeline/sign_test.go b/pkg/cmd/pipeline/sign_test.go index da32355e7c..f4e8201945 100644 --- a/pkg/cmd/pipeline/sign_test.go +++ b/pkg/cmd/pipeline/sign_test.go @@ -16,6 +16,7 @@ package pipeline import ( "context" + "fmt" "os" "path/filepath" "testing" @@ -28,37 +29,52 @@ import ( func TestSign(t *testing.T) { ctx := context.Background() p := &test.Params{} - - task := Command(p) - + pipeline := Command(p) os.Setenv("PRIVATE_PASSWORD", "1234") - tmpDir := t.TempDir() - targetFile := filepath.Join(tmpDir, "signed.yaml") - out, err := test.ExecuteCommand(task, "sign", "testdata/pipeline.yaml", "-K", "testdata/cosign.key", "-f", targetFile) - if err != nil { - t.Errorf("Unexpected error: %v", err) - } - expected := "*Warning*: This is an experimental command, it's usage and behavior can change in the next release(s)\nPipeline testdata/pipeline.yaml is signed successfully \n" - test.AssertOutput(t, expected, out) - // verify the signed task - verifier, err := cosignsignature.LoadPublicKey(ctx, "testdata/cosign.pub") - if err != nil { - t.Errorf("error getting verifier from key file: %v", err) - } + testcases := []struct { + name string + taskFile string + apiVersion string + }{{ + name: "sign and verify v1beta1 Pipeline", + taskFile: "testdata/pipeline.yaml", + apiVersion: "v1beta1", + }, { + name: "sign and verify v1 Pipeline", + taskFile: "testdata/pipeline-v1.yaml", + apiVersion: "v1", + }} + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + tmpDir := t.TempDir() + targetFile := filepath.Join(tmpDir, "signed.yaml") + out, err := test.ExecuteCommand(pipeline, "sign", tc.taskFile, "-K", "testdata/cosign.key", "-f", targetFile, "-v", tc.apiVersion) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + expected := fmt.Sprintf("*Warning*: This is an experimental command, it's usage and behavior can change in the next release(s)\nPipeline %s is signed successfully \n", tc.taskFile) + test.AssertOutput(t, expected, out) - signed, err := os.ReadFile(targetFile) - if err != nil { - t.Fatalf("error reading file: %v", err) - } + // verify the signed task + verifier, err := cosignsignature.LoadPublicKey(ctx, "testdata/cosign.pub") + if err != nil { + t.Errorf("error getting verifier from key file: %v", err) + } - target, signature, err := trustedresources.UnmarshalCRD(signed, "Pipeline") - if err != nil { - t.Fatalf("error unmarshalling crd: %v", err) - } + signed, err := os.ReadFile(targetFile) + if err != nil { + t.Fatalf("error reading file: %v", err) + } - if err := trustedresources.VerifyInterface(target, verifier, signature); err != nil { - t.Fatalf("VerifyInterface get error: %v", err) - } + target, signature, err := trustedresources.UnmarshalCRD(signed, "Pipeline", tc.apiVersion) + if err != nil { + t.Fatalf("error unmarshalling crd: %v", err) + } + if err := trustedresources.VerifyInterface(target, verifier, signature); err != nil { + t.Fatalf("VerifyInterface get error: %v", err) + } + }) + } } diff --git a/pkg/cmd/pipeline/testdata/signed-v1.yaml b/pkg/cmd/pipeline/testdata/signed-v1.yaml new file mode 100644 index 0000000000..c04d4518d0 --- /dev/null +++ b/pkg/cmd/pipeline/testdata/signed-v1.yaml @@ -0,0 +1,25 @@ +apiVersion: tekton.dev/v1 +kind: Pipeline +metadata: + annotations: + tekton.dev/signature: MEUCIQD3tcptnk2F+9ru5gNUi91K2NPe59Dk28lwaHEQzScnOQIgL+KpDuGBf67FHGrh34cZRHVmPuYzOzPUbmvealAJPvE= + creationTimestamp: null + name: test-pipeline +spec: + tasks: + - name: build-skaffold-web + params: + - name: pathToDockerFile + value: Dockerfile + - name: pathToContext + value: /workspace/docker-source/examples/microservices/leeroy-web + taskRef: + name: build-docker-image-from-git-source + - name: deploy-web + params: + - name: path + value: /workspace/source/examples/microservices/leeroy-web/kubernetes/deployment.yaml + - name: yamlPathToImage + value: spec.template.spec.containers[0].image + taskRef: + name: deploy-using-kubectl diff --git a/pkg/cmd/pipeline/verify.go b/pkg/cmd/pipeline/verify.go index 0a1977b6ef..ff173483eb 100644 --- a/pkg/cmd/pipeline/verify.go +++ b/pkg/cmd/pipeline/verify.go @@ -22,14 +22,17 @@ import ( "github.com/spf13/cobra" "github.com/tektoncd/cli/pkg/cli" "github.com/tektoncd/cli/pkg/trustedresources" + v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" cliopts "k8s.io/cli-runtime/pkg/genericclioptions" "sigs.k8s.io/yaml" ) type verifyOptions struct { - keyfile string - kmsKey string + keyfile string + kmsKey string + apiVersion string } func verifyCommand() *cobra.Command { @@ -68,7 +71,12 @@ or using kms return err } - crd := &v1beta1.Pipeline{} + var crd metav1.Object + if opts.apiVersion == "v1beta1" { + crd = &v1beta1.Pipeline{} + } else { + crd = &v1.Pipeline{} + } if err := yaml.Unmarshal(b, &crd); err != nil { log.Fatalf("error unmarshalling Pipeline: %v", err) return err @@ -85,5 +93,6 @@ or using kms f.AddFlags(c) c.Flags().StringVarP(&opts.keyfile, "key-file", "K", "", "Key file") c.Flags().StringVarP(&opts.kmsKey, "kms-key", "m", "", "KMS key url") + c.Flags().StringVarP(&opts.apiVersion, "version", "v", "v1", "apiVersion of the Pipeline to be verified") return c } diff --git a/pkg/cmd/pipeline/verify_test.go b/pkg/cmd/pipeline/verify_test.go index d4e5b4592a..af5ced6648 100644 --- a/pkg/cmd/pipeline/verify_test.go +++ b/pkg/cmd/pipeline/verify_test.go @@ -15,6 +15,7 @@ package pipeline import ( + "fmt" "os" "testing" @@ -28,10 +29,27 @@ func TestVerify(t *testing.T) { os.Setenv("PRIVATE_PASSWORD", "1234") - out, err := test.ExecuteCommand(pipeline, "verify", "testdata/signed.yaml", "-K", "testdata/cosign.pub") - if err != nil { - t.Errorf("Unexpected error: %v", err) + testcases := []struct { + name string + taskFile string + apiVersion string + }{{ + name: "verify v1beta1 Pipeline", + taskFile: "testdata/signed.yaml", + apiVersion: "v1beta1", + }, { + name: "verify v1 Pipeline", + taskFile: "testdata/signed-v1.yaml", + apiVersion: "v1", + }} + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + out, err := test.ExecuteCommand(pipeline, "verify", tc.taskFile, "-K", "testdata/cosign.pub", "-v", tc.apiVersion) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + expected := fmt.Sprintf("*Warning*: This is an experimental command, it's usage and behavior can change in the next release(s)\nPipeline %s passes verification \n", tc.taskFile) + test.AssertOutput(t, expected, out) + }) } - expected := "*Warning*: This is an experimental command, it's usage and behavior can change in the next release(s)\nPipeline testdata/signed.yaml passes verification \n" - test.AssertOutput(t, expected, out) } diff --git a/pkg/cmd/task/sign.go b/pkg/cmd/task/sign.go index 3c7ca0eb21..9881f30685 100644 --- a/pkg/cmd/task/sign.go +++ b/pkg/cmd/task/sign.go @@ -22,7 +22,9 @@ import ( "github.com/spf13/cobra" "github.com/tektoncd/cli/pkg/cli" "github.com/tektoncd/cli/pkg/trustedresources" + v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" cliopts "k8s.io/cli-runtime/pkg/genericclioptions" "sigs.k8s.io/yaml" ) @@ -36,6 +38,7 @@ type signOptions struct { keyfile string kmsKey string targetFile string + apiVersion string } func signCommand() *cobra.Command { @@ -74,8 +77,13 @@ or using kms log.Fatalf("error reading file: %v", err) return err } + var crd metav1.Object + if opts.apiVersion == "v1beta1" { + crd = &v1beta1.Task{} + } else { + crd = &v1.Task{} + } - crd := &v1beta1.Task{} if err := yaml.Unmarshal(b, &crd); err != nil { return fmt.Errorf("error unmarshalling Task: %v", err) } @@ -91,6 +99,6 @@ or using kms c.Flags().StringVarP(&opts.keyfile, "key-file", "K", "", "Key file") c.Flags().StringVarP(&opts.kmsKey, "kms-key", "m", "", "KMS key url") c.Flags().StringVarP(&opts.targetFile, "file-name", "f", "", "file name of the signed task, using the original file name will overwrite the file") - + c.Flags().StringVarP(&opts.apiVersion, "version", "v", "v1", "apiVersion of the Task to be signed") return c } diff --git a/pkg/cmd/task/sign_test.go b/pkg/cmd/task/sign_test.go index 351c9ad810..b1a87f549e 100644 --- a/pkg/cmd/task/sign_test.go +++ b/pkg/cmd/task/sign_test.go @@ -16,6 +16,7 @@ package task import ( "context" + "fmt" "os" "path/filepath" "testing" @@ -32,32 +33,50 @@ func TestSign(t *testing.T) { task := Command(p) os.Setenv("PRIVATE_PASSWORD", "1234") - tmpDir := t.TempDir() - targetFile := filepath.Join(tmpDir, "signed.yaml") - out, err := test.ExecuteCommand(task, "sign", "testdata/task.yaml", "-K", "testdata/cosign.key", "-f", targetFile) - if err != nil { - t.Errorf("Unexpected error: %v", err) - } - expected := "*Warning*: This is an experimental command, it's usage and behavior can change in the next release(s)\nTask testdata/task.yaml is signed successfully \n" - test.AssertOutput(t, expected, out) - // verify the signed task - verifier, err := cosignsignature.LoadPublicKey(ctx, "testdata/cosign.pub") - if err != nil { - t.Errorf("error getting verifier from key file: %v", err) - } + testcases := []struct { + name string + taskFile string + apiVersion string + }{{ + name: "sign and verify v1beta1 Task", + taskFile: "testdata/task.yaml", + apiVersion: "v1beta1", + }, { + name: "sign and verify v1 Task", + taskFile: "testdata/task-v1.yaml", + apiVersion: "v1", + }} - signed, err := os.ReadFile(targetFile) - if err != nil { - t.Fatalf("error reading file: %v", err) - } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + tmpDir := t.TempDir() + targetFile := filepath.Join(tmpDir, "signed.yaml") + out, err := test.ExecuteCommand(task, "sign", tc.taskFile, "-K", "testdata/cosign.key", "-f", targetFile, "-v", tc.apiVersion) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + expected := fmt.Sprintf("*Warning*: This is an experimental command, it's usage and behavior can change in the next release(s)\nTask %s is signed successfully \n", tc.taskFile) + test.AssertOutput(t, expected, out) - target, signature, err := trustedresources.UnmarshalCRD(signed, "Task") - if err != nil { - t.Fatalf("error unmarshalling crd: %v", err) - } - if err := trustedresources.VerifyInterface(target, verifier, signature); err != nil { - t.Fatalf("VerifyTaskOCIBundle get error: %v", err) - } + // verify the signed task + verifier, err := cosignsignature.LoadPublicKey(ctx, "testdata/cosign.pub") + if err != nil { + t.Errorf("error getting verifier from key file: %v", err) + } + + signed, err := os.ReadFile(targetFile) + if err != nil { + t.Fatalf("error reading file: %v", err) + } + target, signature, err := trustedresources.UnmarshalCRD(signed, "Task", tc.apiVersion) + if err != nil { + t.Fatalf("error unmarshalling crd: %v", err) + } + if err := trustedresources.VerifyInterface(target, verifier, signature); err != nil { + t.Fatalf("VerifyInterface get error: %v", err) + } + }) + } } diff --git a/pkg/cmd/task/testdata/signed-v1.yaml b/pkg/cmd/task/testdata/signed-v1.yaml new file mode 100644 index 0000000000..da7d074401 --- /dev/null +++ b/pkg/cmd/task/testdata/signed-v1.yaml @@ -0,0 +1,26 @@ +apiVersion: tekton.dev/v1 +kind: Task +metadata: + annotations: + tekton.dev/signature: MEUCIESVeQ7mC8hxSLrmcQhVsnc0ErIjZ9NLaKv2MifSrHYtAiEA1KfI181TCK2eJ8XH5fboBvbr2/YBVGqlgrkS7vfY9mw= + creationTimestamp: null + name: task-v1 +spec: + params: + - name: foobar + type: string + results: + - name: url + steps: + - computeResources: {} + env: + - name: PARAM_URL + value: $(params.foobar) + image: alpine + name: build-sources + script: | + #!/bin/sh + + printf "%s" "${PARAM_URL}" > "$(results.url.path)" + workspaces: + - name: temporary diff --git a/pkg/cmd/task/verify.go b/pkg/cmd/task/verify.go index 55407d74a3..642f04aa9d 100644 --- a/pkg/cmd/task/verify.go +++ b/pkg/cmd/task/verify.go @@ -22,14 +22,17 @@ import ( "github.com/spf13/cobra" "github.com/tektoncd/cli/pkg/cli" "github.com/tektoncd/cli/pkg/trustedresources" + v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" cliopts "k8s.io/cli-runtime/pkg/genericclioptions" "sigs.k8s.io/yaml" ) type verifyOptions struct { - keyfile string - kmsKey string + keyfile string + kmsKey string + apiVersion string } func verifyCommand() *cobra.Command { @@ -68,7 +71,13 @@ or using kms return err } - crd := &v1beta1.Task{} + var crd metav1.Object + if opts.apiVersion == "v1beta1" { + crd = &v1beta1.Task{} + } else { + crd = &v1.Task{} + } + if err := yaml.Unmarshal(b, &crd); err != nil { log.Fatalf("error unmarshalling Task: %v", err) return err @@ -85,5 +94,6 @@ or using kms f.AddFlags(c) c.Flags().StringVarP(&opts.keyfile, "key-file", "K", "", "Key file") c.Flags().StringVarP(&opts.kmsKey, "kms-key", "m", "", "KMS key url") + c.Flags().StringVarP(&opts.apiVersion, "version", "v", "v1", "apiVersion of the Task to be verified") return c } diff --git a/pkg/cmd/task/verify_test.go b/pkg/cmd/task/verify_test.go index c465556655..2c6354daf5 100644 --- a/pkg/cmd/task/verify_test.go +++ b/pkg/cmd/task/verify_test.go @@ -15,6 +15,7 @@ package task import ( + "fmt" "os" "testing" @@ -23,15 +24,30 @@ import ( func TestVerify(t *testing.T) { p := &test.Params{} - task := Command(p) - os.Setenv("PRIVATE_PASSWORD", "1234") - out, err := test.ExecuteCommand(task, "verify", "testdata/signed.yaml", "-K", "testdata/cosign.pub") - if err != nil { - t.Errorf("Unexpected error: %v", err) + testcases := []struct { + name string + taskFile string + apiVersion string + }{{ + name: "verify v1beta1 Task", + taskFile: "testdata/signed.yaml", + apiVersion: "v1beta1", + }, { + name: "verify v1 Task", + taskFile: "testdata/signed-v1.yaml", + apiVersion: "v1", + }} + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + out, err := test.ExecuteCommand(task, "verify", tc.taskFile, "-K", "testdata/cosign.pub", "-v", tc.apiVersion) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + expected := fmt.Sprintf("*Warning*: This is an experimental command, it's usage and behavior can change in the next release(s)\nTask %s passes verification \n", tc.taskFile) + test.AssertOutput(t, expected, out) + }) } - expected := "*Warning*: This is an experimental command, it's usage and behavior can change in the next release(s)\nTask testdata/signed.yaml passes verification \n" - test.AssertOutput(t, expected, out) } diff --git a/pkg/trustedresources/sign.go b/pkg/trustedresources/sign.go index 4977fa90c0..b0d140b29f 100644 --- a/pkg/trustedresources/sign.go +++ b/pkg/trustedresources/sign.go @@ -29,6 +29,7 @@ import ( "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/kms" + v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" "golang.org/x/term" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -114,20 +115,34 @@ func signInterface(signer signature.Signer, i interface{}) ([]byte, error) { } // UnmarshalCRD will get the task/pipeline from buffer and extract the signature. -func UnmarshalCRD(buf []byte, kind string) (metav1.Object, []byte, error) { +func UnmarshalCRD(buf []byte, kind string, version string) (metav1.Object, []byte, error) { var resource metav1.Object var signature []byte switch kind { case "Task": - resource = &v1beta1.Task{} - if err := yaml.Unmarshal(buf, &resource); err != nil { - return nil, nil, err + if version == "v1beta1" { + resource = &v1beta1.Task{} + if err := yaml.Unmarshal(buf, &resource); err != nil { + return nil, nil, err + } + } else { + resource = &v1.Task{} + if err := yaml.Unmarshal(buf, &resource); err != nil { + return nil, nil, err + } } case "Pipeline": - resource = &v1beta1.Pipeline{} - if err := yaml.Unmarshal(buf, &resource); err != nil { - return nil, nil, err + if version == "v1beta1" { + resource = &v1beta1.Pipeline{} + if err := yaml.Unmarshal(buf, &resource); err != nil { + return nil, nil, err + } + } else { + resource = &v1.Pipeline{} + if err := yaml.Unmarshal(buf, &resource); err != nil { + return nil, nil, err + } } } annotations := resource.GetAnnotations() diff --git a/pkg/trustedresources/sign_test.go b/pkg/trustedresources/sign_test.go index 421c7eaada..741ef52dcc 100644 --- a/pkg/trustedresources/sign_test.go +++ b/pkg/trustedresources/sign_test.go @@ -78,16 +78,19 @@ func TestSign(t *testing.T) { resource metav1.Object kind string targetFile string + apiVersion string }{{ name: "Task Sign and pass verification", resource: getTask(), kind: "Task", targetFile: "signed-task.yaml", + apiVersion: "v1beta1", }, { name: "Pipeline Sign and pass verification", resource: getPipeline(), kind: "Pipeline", targetFile: "signed-pipeline.yaml", + apiVersion: "v1beta1", }, } @@ -101,7 +104,7 @@ func TestSign(t *testing.T) { t.Fatalf("error reading file: %v", err) } - target, signature, err := UnmarshalCRD(signed, tc.kind) + target, signature, err := UnmarshalCRD(signed, tc.kind, tc.apiVersion) if err != nil { t.Fatalf("error unmarshalling crd: %v", err) }