From 840e717b7249bca49a4cfc57feeb9ed7bb8563f8 Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Sun, 15 Oct 2023 01:25:34 +0100 Subject: [PATCH] Add CLI flag for OCIRepo verify secret Signed-off-by: Matheus Pimenta --- cmd/flux/create_source_oci.go | 37 ++++++++---- cmd/flux/create_source_oci_test.go | 10 ++++ .../oci/export_with_verify_secret.golden | 15 +++++ internal/flags/source_oci_verify_provider.go | 58 +++++++++++++++++++ .../flags/source_oci_verify_provider_test.go | 48 +++++++++++++++ 5 files changed, 158 insertions(+), 10 deletions(-) create mode 100644 cmd/flux/testdata/oci/export_with_verify_secret.golden create mode 100644 internal/flags/source_oci_verify_provider.go create mode 100644 internal/flags/source_oci_verify_provider_test.go diff --git a/cmd/flux/create_source_oci.go b/cmd/flux/create_source_oci.go index 5d6a5f95..388ab18c 100644 --- a/cmd/flux/create_source_oci.go +++ b/cmd/flux/create_source_oci.go @@ -51,16 +51,18 @@ var createSourceOCIRepositoryCmd = &cobra.Command{ } type sourceOCIRepositoryFlags struct { - url string - tag string - semver string - digest string - secretRef string - serviceAccount string - certSecretRef string - ignorePaths []string - provider flags.SourceOCIProvider - insecure bool + url string + tag string + semver string + digest string + secretRef string + serviceAccount string + certSecretRef string + verifyProvider flags.SourceOCIVerifyProvider + verifySecretRef string + ignorePaths []string + provider flags.SourceOCIProvider + insecure bool } var sourceOCIRepositoryArgs = newSourceOCIFlags() @@ -80,6 +82,8 @@ func init() { createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.secretRef, "secret-ref", "", "the name of the Kubernetes image pull secret (type 'kubernetes.io/dockerconfigjson')") createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.serviceAccount, "service-account", "", "the name of the Kubernetes service account that refers to an image pull secret") createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.certSecretRef, "cert-ref", "", "the name of a secret to use for TLS certificates") + createSourceOCIRepositoryCmd.Flags().Var(&sourceOCIRepositoryArgs.verifyProvider, "verify-provider", sourceOCIRepositoryArgs.verifyProvider.Description()) + createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.verifySecretRef, "verify-secret-ref", "", "the name of a secret to use for signature verification") createSourceOCIRepositoryCmd.Flags().StringSliceVar(&sourceOCIRepositoryArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore resources (can specify multiple paths with commas: path1,path2)") createSourceOCIRepositoryCmd.Flags().BoolVar(&sourceOCIRepositoryArgs.insecure, "insecure", false, "for when connecting to a non-TLS registries over plain HTTP") @@ -156,6 +160,19 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error { } } + if provider := sourceOCIRepositoryArgs.verifyProvider.String(); provider != "" { + repository.Spec.Verify = &sourcev1.OCIRepositoryVerification{ + Provider: provider, + } + if secretName := sourceOCIRepositoryArgs.verifySecretRef; secretName != "" { + repository.Spec.Verify.SecretRef = &meta.LocalObjectReference{ + Name: secretName, + } + } + } else if sourceOCIRepositoryArgs.verifySecretRef != "" { + return fmt.Errorf("a verification provider must be specified when a secret is specified") + } + if createArgs.export { return printExport(exportOCIRepository(repository)) } diff --git a/cmd/flux/create_source_oci_test.go b/cmd/flux/create_source_oci_test.go index 8972b813..743632a6 100644 --- a/cmd/flux/create_source_oci_test.go +++ b/cmd/flux/create_source_oci_test.go @@ -36,6 +36,11 @@ func TestCreateSourceOCI(t *testing.T) { args: "create source oci podinfo", assertFunc: assertError("url is required"), }, + { + name: "verify provider not specified", + args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --verify-secret-ref=cosign-pub", + assertFunc: assertError("a verification provider must be specified when a secret is specified"), + }, { name: "export manifest", args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m --export", @@ -46,6 +51,11 @@ func TestCreateSourceOCI(t *testing.T) { args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m --secret-ref=creds --export", assertFunc: assertGoldenFile("./testdata/oci/export_with_secret.golden"), }, + { + name: "export manifest with verify secret", + args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m --verify-provider=cosign --verify-secret-ref=cosign-pub --export", + assertFunc: assertGoldenFile("./testdata/oci/export_with_verify_secret.golden"), + }, } for _, tt := range tests { diff --git a/cmd/flux/testdata/oci/export_with_verify_secret.golden b/cmd/flux/testdata/oci/export_with_verify_secret.golden new file mode 100644 index 00000000..d7e94aad --- /dev/null +++ b/cmd/flux/testdata/oci/export_with_verify_secret.golden @@ -0,0 +1,15 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: OCIRepository +metadata: + name: podinfo + namespace: flux-system +spec: + interval: 10m0s + ref: + tag: 6.3.5 + url: oci://ghcr.io/stefanprodan/manifests/podinfo + verify: + provider: cosign + secretRef: + name: cosign-pub diff --git a/internal/flags/source_oci_verify_provider.go b/internal/flags/source_oci_verify_provider.go new file mode 100644 index 00000000..acd57a9d --- /dev/null +++ b/internal/flags/source_oci_verify_provider.go @@ -0,0 +1,58 @@ +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package flags + +import ( + "fmt" + "strings" + + "github.com/fluxcd/flux2/v2/internal/utils" +) + +var supportedSourceOCIVerifyProviders = []string{ + "cosign", +} + +type SourceOCIVerifyProvider string + +func (p *SourceOCIVerifyProvider) String() string { + return string(*p) +} + +func (p *SourceOCIVerifyProvider) Set(str string) error { + if strings.TrimSpace(str) == "" { + return fmt.Errorf("no source OCI verify provider given, please specify %s", + p.Description()) + } + if !utils.ContainsItemString(supportedSourceOCIVerifyProviders, str) { + return fmt.Errorf("source OCI verify provider '%s' is not supported, must be one of: %v", + str, strings.Join(supportedSourceOCIVerifyProviders, ", ")) + } + *p = SourceOCIVerifyProvider(str) + return nil +} + +func (p *SourceOCIVerifyProvider) Type() string { + return "sourceOCIVerifyProvider" +} + +func (p *SourceOCIVerifyProvider) Description() string { + return fmt.Sprintf( + "the OCI verify provider name to use for signature verification, available options are: (%s)", + strings.Join(supportedSourceOCIVerifyProviders, ", "), + ) +} diff --git a/internal/flags/source_oci_verify_provider_test.go b/internal/flags/source_oci_verify_provider_test.go new file mode 100644 index 00000000..e8966521 --- /dev/null +++ b/internal/flags/source_oci_verify_provider_test.go @@ -0,0 +1,48 @@ +//go:build !e2e +// +build !e2e + +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package flags + +import ( + "testing" +) + +func TestSourceOCIVerifyProvider_Set(t *testing.T) { + tests := []struct { + name string + str string + expect string + expectErr bool + }{ + {"supported", "cosign", "cosign", false}, + {"unsupported", "unsupported", "", true}, + {"empty", "", "", true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var s SourceOCIVerifyProvider + if err := s.Set(tt.str); (err != nil) != tt.expectErr { + t.Errorf("Set() error = %v, expectErr %v", err, tt.expectErr) + } + if str := s.String(); str != tt.expect { + t.Errorf("Set() = %v, expect %v", str, tt.expect) + } + }) + } +}