From 73ef4fb66f3928f272e39c8aec2ae1796138ac51 Mon Sep 17 00:00:00 2001 From: Roberto Scolaro Date: Thu, 11 May 2023 13:27:55 +0000 Subject: [PATCH 1/2] new(tests): added new tests Signed-off-by: Roberto Scolaro --- cmd/artifact/install/install_suite_test.go | 101 ++++ cmd/artifact/install/install_test.go | 441 ++++++++++++++++++ cmd/index/add/add_suite_test.go | 87 ++++ cmd/{ => index/add}/add_test.go | 2 +- cmd/registry/auth/basic/basic_suite_test.go | 134 ++++++ cmd/registry/auth/basic/basic_test.go | 137 ++++++ cmd/registry/auth/oauth/oauth.go | 4 +- cmd/registry/auth/oauth/oauth_suite_test.go | 118 +++++ cmd/registry/auth/oauth/oauth_test.go | 206 ++++++++ cmd/registry/pull/pull.go | 8 +- cmd/registry/pull/pull_suite_test.go | 100 ++++ cmd/registry/pull/pull_test.go | 332 +++++++++++++ .../push/push_suite_test.go} | 15 +- cmd/{ => registry/push}/push_test.go | 9 +- cmd/root_test.go | 37 -- go.mod | 12 +- go.sum | 88 ++++ pkg/test/registry.go | 234 ++++++++++ 18 files changed, 2012 insertions(+), 53 deletions(-) create mode 100644 cmd/artifact/install/install_suite_test.go create mode 100644 cmd/artifact/install/install_test.go create mode 100644 cmd/index/add/add_suite_test.go rename cmd/{ => index/add}/add_test.go (99%) create mode 100644 cmd/registry/auth/basic/basic_suite_test.go create mode 100644 cmd/registry/auth/basic/basic_test.go create mode 100644 cmd/registry/auth/oauth/oauth_suite_test.go create mode 100644 cmd/registry/auth/oauth/oauth_test.go create mode 100644 cmd/registry/pull/pull_suite_test.go create mode 100644 cmd/registry/pull/pull_test.go rename cmd/{cmd_suite_test.go => registry/push/push_suite_test.go} (87%) rename cmd/{ => registry/push}/push_test.go (99%) delete mode 100644 cmd/root_test.go diff --git a/cmd/artifact/install/install_suite_test.go b/cmd/artifact/install/install_suite_test.go new file mode 100644 index 00000000..0f8d8f8e --- /dev/null +++ b/cmd/artifact/install/install_suite_test.go @@ -0,0 +1,101 @@ +// Copyright 2023 The Falco 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 install_test + +import ( + "context" + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/distribution/distribution/v3/configuration" + _ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" + "github.com/spf13/cobra" + "oras.land/oras-go/v2/registry/remote" + + "github.com/falcosecurity/falcoctl/cmd" + commonoptions "github.com/falcosecurity/falcoctl/pkg/options" + testutils "github.com/falcosecurity/falcoctl/pkg/test" +) + +//nolint:unused // false positive +const ( + rulesfiletgz = "../../../pkg/test/data/rules.tar.gz" + rulesfileyaml = "../../../pkg/test/data/rules.yaml" + plugintgz = "../../../pkg/test/data/plugin.tar.gz" +) + +//nolint:unused // false positive +var ( + registry string + ctx = context.Background() + output = gbytes.NewBuffer() + rootCmd *cobra.Command + opt *commonoptions.CommonOptions + port int + orasRegistry *remote.Registry + configFile string + err error + args []string +) + +func TestInstall(t *testing.T) { + var err error + RegisterFailHandler(Fail) + port, err = testutils.FreePort() + Expect(err).ToNot(HaveOccurred()) + registry = fmt.Sprintf("localhost:%d", port) + RunSpecs(t, "root suite") +} + +var _ = BeforeSuite(func() { + config := &configuration.Configuration{} + config.HTTP.Addr = fmt.Sprintf("localhost:%d", port) + // Create and configure the common options. + opt = commonoptions.NewOptions() + opt.Initialize(commonoptions.WithWriter(output)) + opt.Printer.DisableStylingf() + + // Create the oras registry. + orasRegistry, err = testutils.NewOrasRegistry(registry, true) + Expect(err).ToNot(HaveOccurred()) + + // Start the local registry. + go func() { + err := testutils.StartRegistry(context.Background(), config) + Expect(err).ToNot(BeNil()) + }() + + // Create temporary directory used to save the configuration file. + configFile, err = testutils.CreateEmptyFile("falcoctl.yaml") + Expect(err).Should(Succeed()) + +}) + +var _ = AfterSuite(func() { + configDir := filepath.Dir(configFile) + Expect(os.RemoveAll(configDir)).Should(Succeed()) +}) + +//nolint:unused // false positive +func executeRoot(args []string) error { + rootCmd.SetArgs(args) + rootCmd.SetOut(output) + return cmd.Execute(rootCmd, opt.Printer) +} diff --git a/cmd/artifact/install/install_test.go b/cmd/artifact/install/install_test.go new file mode 100644 index 00000000..810e9dd9 --- /dev/null +++ b/cmd/artifact/install/install_test.go @@ -0,0 +1,441 @@ +// Copyright 2023 The Falco 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 install_test + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" + "oras.land/oras-go/v2/registry/remote/auth" + + "github.com/falcosecurity/falcoctl/cmd" + "github.com/falcosecurity/falcoctl/pkg/oci" + "github.com/falcosecurity/falcoctl/pkg/oci/authn" + ocipusher "github.com/falcosecurity/falcoctl/pkg/oci/pusher" + out "github.com/falcosecurity/falcoctl/pkg/output" +) + +//nolint:lll,unused // no need to check for line length. +var artifactInstallUsage = `Usage: + falcoctl artifact install [ref1 [ref2 ...]] [flags] + +Flags: + --allowed-types ArtifactTypeSlice list of artifact types that can be installed. If not specified or configured, all types are allowed. + It accepts comma separated values or it can be repeated multiple times. + Examples: + --allowed-types="rulesfile,plugin" + --allowed-types=rulesfile --allowed-types=plugin + -h, --help help for install + --plain-http allows interacting with remote registry via plain http requests + --plugins-dir string directory where to install plugins. (default "/usr/share/falco/plugins") + --resolve-deps whether this command should resolve dependencies or not (default true) + --rulesfiles-dir string directory where to install rules. (default "/etc/falco") + +Global Flags: + --config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml") + --disable-styling Disable output styling such as spinners, progress bars and colors. Styling is automatically disabled if not attacched to a tty (default false) + -v, --verbose Enable verbose logs (default false) + +` + +//nolint:unused // false positive +var artifactInstallHelp = `This command allows you to install one or more given artifacts. + +Artifact references and flags are passed as arguments through: +- command line options +- environment variables +- configuration file +The arguments passed through these different modalities are prioritized in the following order: +command line options, environment variables, and finally the configuration file. This means that +if an argument is passed through multiple modalities, the value set in the command line options +will take precedence over the value set in environment variables, which will in turn take precedence +over the value set in the configuration file. +Please note that when passing multiple artifact references via an environment variable, they must be +separated by a semicolon ';'. Other arguments, if passed through environment variables, should start +with "FALCOCTL_" and be followed by the hierarchical keys used in the configuration file separated by +an underscore "_". + +A reference is either a simple name or a fully qualified reference ("/"), +optionally followed by ":" (":latest" is assumed by default when no tag is given). + +When providing just the name of the artifact, the command will search for the artifacts in +the configured index files, and if found, it will use the registry and repository specified +in the indexes. + +Example - Install "latest" tag of "k8saudit-rules" artifact by relying on index metadata: + falcoctl artifact install k8saudit-rules + +Example - Install all updates from "k8saudit-rules" 0.5.x release series: + falcoctl artifact install k8saudit-rules:0.5 + +Example - Install "cloudtrail" plugins using a fully qualified reference: + falcoctl artifact install ghcr.io/falcosecurity/plugins/ruleset/k8saudit:latest +` + +//nolint:unused // false positive +var correctIndexConfig = `indexes: +- name: falcosecurity + url: https://falcosecurity.github.io/falcoctl/index.yaml +` + +//nolint:unused // false positive +var installAssertFailedBehavior = func(usage, specificError string) { + It("check that fails and the usage is not printed", func() { + Expect(err).To(HaveOccurred()) + Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(usage))) + Expect(output).Should(gbytes.Say(regexp.QuoteMeta(specificError))) + }) +} + +//nolint:unused // false positive +var artifactInstallTests = Describe("install", func() { + var ( + pusher *ocipusher.Pusher + ref string + config ocipusher.Option + ) + + const ( + // Used as flags for all the test cases. + artifactCmd = "artifact" + installCmd = "install" + dep1 = "myplugin:1.2.3" + dep2 = "myplugin1:1.2.3|otherplugin:3.2.1" + req = "engine_version:15" + anSource = "myrepo.com/rules.git" + artifact = "generic-repo" + repo = "/" + artifact + tag = "tag" + repoAndTag = repo + ":" + tag + ) + + // Each test gets its own root command and runs it. + // The err variable is asserted by each test. + JustBeforeEach(func() { + rootCmd = cmd.New(ctx, opt) + err = executeRoot(args) + }) + + JustAfterEach(func() { + Expect(output.Clear()).ShouldNot(HaveOccurred()) + }) + + Context("help message", func() { + BeforeEach(func() { + args = []string{artifactCmd, installCmd, "--help"} + }) + + It("should match the saved one", func() { + Expect(output).Should(gbytes.Say(regexp.QuoteMeta(artifactInstallHelp))) + }) + }) + + Context("failure", func() { + var ( + tracker out.Tracker + options []ocipusher.Option + filePathsAndPlatforms ocipusher.Option + filePaths ocipusher.Option + destDir string + ) + const ( + plainHTTP = true + testPluginPlatform1 = "linux/amd64" + ) + + When("without artifact", func() { + BeforeEach(func() { + configDir := GinkgoT().TempDir() + configFile := filepath.Join(configDir, ".config") + _, err := os.Create(configFile) + Expect(err).To(BeNil()) + args = []string{artifactCmd, installCmd, "--config", configFile} + }) + installAssertFailedBehavior(artifactInstallUsage, + "ERRO: no artifacts to install, please configure artifacts or pass them as arguments to this command") + }) + + When("unreachable registry", func() { + BeforeEach(func() { + configDir := GinkgoT().TempDir() + configFile := filepath.Join(configDir, ".config") + _, err := os.Create(configFile) + Expect(err).To(BeNil()) + args = []string{artifactCmd, installCmd, "noregistry/testrules", "--plain-http", "--config", configFile} + }) + installAssertFailedBehavior(artifactInstallUsage, `ERRO: unable to fetch reference`) + }) + + When("invalid repository", func() { + newReg := registry + "/wrong:latest" + BeforeEach(func() { + configDir := GinkgoT().TempDir() + configFile := filepath.Join(configDir, ".config") + _, err := os.Create(configFile) + Expect(err).To(BeNil()) + args = []string{artifactCmd, installCmd, newReg, "--plain-http", "--config", configFile} + }) + installAssertFailedBehavior(artifactInstallUsage, fmt.Sprintf("ERRO: unable to fetch reference %q", newReg)) + }) + + When("with disallowed types (rulesfile)", func() { + BeforeEach(func() { + baseDir := GinkgoT().TempDir() + configFilePath := baseDir + "/config.yaml" + content := []byte(correctIndexConfig) + err := os.WriteFile(configFilePath, content, 0o644) + Expect(err).To(BeNil()) + + // push plugin + pusher = ocipusher.NewPusher(authn.NewClient(authn.WithCredentials(&auth.EmptyCredential)), plainHTTP, tracker) + ref = registry + repoAndTag + config = ocipusher.WithArtifactConfig(oci.ArtifactConfig{ + Name: "plugin1", + Version: "0.0.1", + }) + filePathsAndPlatforms = ocipusher.WithFilepathsAndPlatforms([]string{plugintgz}, []string{testPluginPlatform1}) + options = []ocipusher.Option{filePathsAndPlatforms, config} + result, err := pusher.Push(ctx, oci.Plugin, ref, options...) + Expect(err).To(BeNil()) + Expect(result).ToNot(BeNil()) + ref = registry + repoAndTag + Expect(err).To(BeNil()) + args = []string{artifactCmd, installCmd, ref, "--plain-http", + "--config", configFilePath, "--allowed-types", "rulesfile"} + }) + + installAssertFailedBehavior(artifactInstallUsage, "ERRO: cannot download artifact of type \"plugin\": type not permitted") + }) + + When("with disallowed types (plugin)", func() { + BeforeEach(func() { + baseDir := GinkgoT().TempDir() + configFilePath := baseDir + "/config.yaml" + content := []byte(correctIndexConfig) + err := os.WriteFile(configFilePath, content, 0o644) + Expect(err).To(BeNil()) + + // push rulesfile + pusher = ocipusher.NewPusher(authn.NewClient(authn.WithCredentials(&auth.EmptyCredential)), plainHTTP, tracker) + ref = registry + repoAndTag + config = ocipusher.WithArtifactConfig(oci.ArtifactConfig{ + Name: "rules1", + Version: "0.0.1", + }) + filePaths = ocipusher.WithFilepaths([]string{rulesfiletgz}) + options = []ocipusher.Option{filePaths, config} + result, err := pusher.Push(ctx, oci.Rulesfile, ref, options...) + Expect(err).To(BeNil()) + Expect(result).ToNot(BeNil()) + ref = registry + repoAndTag + Expect(err).To(BeNil()) + args = []string{artifactCmd, installCmd, ref, "--plain-http", + "--config", configFilePath, "--allowed-types", "plugin"} + }) + + installAssertFailedBehavior(artifactInstallUsage, "ERRO: cannot download artifact of type \"rulesfile\": type not permitted") + }) + + When("an unknown type is used", func() { + wrongType := "mywrongtype" + BeforeEach(func() { + baseDir := GinkgoT().TempDir() + configFilePath := baseDir + "/config.yaml" + content := []byte(correctIndexConfig) + err := os.WriteFile(configFilePath, content, 0o644) + Expect(err).To(BeNil()) + + // push rulesfile + pusher = ocipusher.NewPusher(authn.NewClient(authn.WithCredentials(&auth.EmptyCredential)), plainHTTP, tracker) + ref = registry + repoAndTag + config = ocipusher.WithArtifactConfig(oci.ArtifactConfig{ + Name: "rules1", + Version: "0.0.1", + }) + filePaths = ocipusher.WithFilepaths([]string{rulesfiletgz}) + options = []ocipusher.Option{filePaths, config} + result, err := pusher.Push(ctx, oci.Rulesfile, ref, options...) + Expect(err).To(BeNil()) + Expect(result).ToNot(BeNil()) + ref = registry + repoAndTag + Expect(err).To(BeNil()) + args = []string{artifactCmd, installCmd, ref, "--plain-http", + "--config", configFilePath, "--allowed-types", "plugin," + wrongType} + }) + + installAssertFailedBehavior(artifactInstallUsage, fmt.Sprintf("ERRO: invalid argument \"plugin,%s\" for \"--allowed-types\" flag: "+ + "not valid token %q: must be one of \"rulesfile\", \"plugin\"", wrongType, wrongType)) + }) + + When("--plugins-dir is not writable", func() { + BeforeEach(func() { + destDir = GinkgoT().TempDir() + err = os.Chmod(destDir, 0o555) + Expect(err).To(BeNil()) + baseDir := GinkgoT().TempDir() + configFilePath := baseDir + "/config.yaml" + content := []byte(correctIndexConfig) + err := os.WriteFile(configFilePath, content, 0o644) + Expect(err).To(BeNil()) + + // push plugin + pusher = ocipusher.NewPusher(authn.NewClient(authn.WithCredentials(&auth.EmptyCredential)), plainHTTP, tracker) + ref = registry + repoAndTag + config = ocipusher.WithArtifactConfig(oci.ArtifactConfig{ + Name: "plugin1", + Version: "0.0.1", + }) + filePathsAndPlatforms = ocipusher.WithFilepathsAndPlatforms([]string{plugintgz}, []string{testPluginPlatform1}) + options = []ocipusher.Option{filePathsAndPlatforms, config} + result, err := pusher.Push(ctx, oci.Plugin, ref, options...) + Expect(err).To(BeNil()) + Expect(result).ToNot(BeNil()) + ref = registry + repoAndTag + Expect(err).To(BeNil()) + args = []string{artifactCmd, installCmd, ref, "--plain-http", + "--config", configFilePath, "--plugins-dir", destDir} + }) + + It("check that fails and the usage is not printed", func() { + expectedError := fmt.Sprintf("ERRO: cannot use directory %q "+ + "as install destination: %s is not writable", destDir, destDir) + Expect(err).To(HaveOccurred()) + Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(artifactInstallUsage))) + Expect(output).Should(gbytes.Say(regexp.QuoteMeta(expectedError))) + }) + }) + + When("--plugins-dir is not present", func() { + BeforeEach(func() { + destDir = GinkgoT().TempDir() + err = os.Remove(destDir) + Expect(err).To(BeNil()) + baseDir := GinkgoT().TempDir() + configFilePath := baseDir + "/config.yaml" + content := []byte(correctIndexConfig) + err := os.WriteFile(configFilePath, content, 0o644) + Expect(err).To(BeNil()) + + // push plugin + pusher = ocipusher.NewPusher(authn.NewClient(authn.WithCredentials(&auth.EmptyCredential)), plainHTTP, tracker) + ref = registry + repoAndTag + config = ocipusher.WithArtifactConfig(oci.ArtifactConfig{ + Name: "plugin1", + Version: "0.0.1", + }) + filePathsAndPlatforms = ocipusher.WithFilepathsAndPlatforms([]string{plugintgz}, []string{testPluginPlatform1}) + options = []ocipusher.Option{filePathsAndPlatforms, config} + result, err := pusher.Push(ctx, oci.Plugin, ref, options...) + Expect(err).To(BeNil()) + Expect(result).ToNot(BeNil()) + ref = registry + repoAndTag + Expect(err).To(BeNil()) + args = []string{artifactCmd, installCmd, ref, "--plain-http", + "--config", configFilePath, "--plugins-dir", destDir} + }) + + It("check that fails and the usage is not printed", func() { + expectedError := fmt.Sprintf("ERRO: cannot use directory %q "+ + "as install destination: %s doesn't exists", destDir, destDir) + Expect(err).To(HaveOccurred()) + Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(artifactInstallUsage))) + Expect(output).Should(gbytes.Say(regexp.QuoteMeta(expectedError))) + }) + }) + + When("--rulesfile-dir is not writable", func() { + BeforeEach(func() { + destDir = GinkgoT().TempDir() + err = os.Chmod(destDir, 0o555) + Expect(err).To(BeNil()) + baseDir := GinkgoT().TempDir() + configFilePath := baseDir + "/config.yaml" + content := []byte(correctIndexConfig) + err := os.WriteFile(configFilePath, content, 0o644) + Expect(err).To(BeNil()) + + // push plugin + pusher = ocipusher.NewPusher(authn.NewClient(authn.WithCredentials(&auth.EmptyCredential)), plainHTTP, tracker) + ref = registry + repoAndTag + config = ocipusher.WithArtifactConfig(oci.ArtifactConfig{ + Name: "rules1", + Version: "0.0.1", + }) + filePaths = ocipusher.WithFilepaths([]string{rulesfiletgz}) + options = []ocipusher.Option{filePaths, config} + result, err := pusher.Push(ctx, oci.Rulesfile, ref, options...) + Expect(err).To(BeNil()) + Expect(result).ToNot(BeNil()) + ref = registry + repoAndTag + Expect(err).To(BeNil()) + args = []string{artifactCmd, installCmd, ref, "--plain-http", + "--config", configFilePath, "--rulesfiles-dir", destDir} + }) + + It("check that fails and the usage is not printed", func() { + expectedError := fmt.Sprintf("ERRO: cannot use directory %q "+ + "as install destination: %s is not writable", destDir, destDir) + Expect(err).To(HaveOccurred()) + Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(artifactInstallUsage))) + Expect(output).Should(gbytes.Say(regexp.QuoteMeta(expectedError))) + }) + }) + + When("not existing --plugins-dir", func() { + BeforeEach(func() { + destDir = GinkgoT().TempDir() + err = os.Remove(destDir) + Expect(err).To(BeNil()) + baseDir := GinkgoT().TempDir() + configFilePath := baseDir + "/config.yaml" + content := []byte(correctIndexConfig) + err := os.WriteFile(configFilePath, content, 0o644) + Expect(err).To(BeNil()) + + // push plugin + pusher = ocipusher.NewPusher(authn.NewClient(authn.WithCredentials(&auth.EmptyCredential)), plainHTTP, tracker) + ref = registry + repoAndTag + config = ocipusher.WithArtifactConfig(oci.ArtifactConfig{ + Name: "rules1", + Version: "0.0.1", + }) + filePathsAndPlatforms = ocipusher.WithFilepaths([]string{rulesfiletgz}) + options = []ocipusher.Option{filePaths, config} + result, err := pusher.Push(ctx, oci.Rulesfile, ref, options...) + Expect(err).To(BeNil()) + Expect(result).ToNot(BeNil()) + ref = registry + repoAndTag + Expect(err).To(BeNil()) + args = []string{artifactCmd, installCmd, ref, "--plain-http", + "--config", configFilePath, "--rulesfiles-dir", destDir} + }) + + It("check that fails and the usage is not printed", func() { + expectedError := fmt.Sprintf("ERRO: cannot use directory %q "+ + "as install destination: %s doesn't exists", destDir, destDir) + Expect(err).To(HaveOccurred()) + Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(artifactInstallUsage))) + Expect(output).Should(gbytes.Say(regexp.QuoteMeta(expectedError))) + }) + }) + + }) + +}) diff --git a/cmd/index/add/add_suite_test.go b/cmd/index/add/add_suite_test.go new file mode 100644 index 00000000..642d2616 --- /dev/null +++ b/cmd/index/add/add_suite_test.go @@ -0,0 +1,87 @@ +// Copyright 2023 The Falco 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 add_test + +import ( + "context" + "fmt" + "os" + "path/filepath" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" + "github.com/spf13/cobra" + "oras.land/oras-go/v2/registry/remote" + + "github.com/falcosecurity/falcoctl/cmd" + commonoptions "github.com/falcosecurity/falcoctl/pkg/options" + testutils "github.com/falcosecurity/falcoctl/pkg/test" +) + +//nolint:unused // false positive +const ( + rulesfiletgz = "../../../pkg/test/data/rules.tar.gz" + rulesfileyaml = "../../../pkg/test/data/rules.yaml" + plugintgz = "../../../pkg/test/data/plugin.tar.gz" +) + +//nolint:unused // false positive +var ( + registry string + ctx = context.Background() + output = gbytes.NewBuffer() + rootCmd *cobra.Command + opt *commonoptions.CommonOptions + port int + orasRegistry *remote.Registry + configFile string + err error + args []string +) + +func TestAdd(t *testing.T) { + RegisterFailHandler(Fail) + port, err = testutils.FreePort() + Expect(err).ToNot(HaveOccurred()) + registry = fmt.Sprintf("localhost:%d", port) + RunSpecs(t, "Add Suite") +} + +var _ = BeforeSuite(func() { + + // Create and configure the common options. + opt = commonoptions.NewOptions() + opt.Initialize(commonoptions.WithWriter(output)) + opt.Printer.DisableStylingf() + + // Create temporary directory used to save the configuration file. + configFile, err = testutils.CreateEmptyFile("falcoctl.yaml") + Expect(err).Should(Succeed()) + +}) + +var _ = AfterSuite(func() { + configDir := filepath.Dir(configFile) + Expect(os.RemoveAll(configDir)).Should(Succeed()) +}) + +//nolint:unused // false positive +func executeRoot(args []string) error { + rootCmd.SetArgs(args) + rootCmd.SetOut(output) + return cmd.Execute(rootCmd, opt.Printer) +} diff --git a/cmd/add_test.go b/cmd/index/add/add_test.go similarity index 99% rename from cmd/add_test.go rename to cmd/index/add/add_test.go index fd6f8c6b..be9fab64 100644 --- a/cmd/add_test.go +++ b/cmd/index/add/add_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cmd_test +package add_test import ( "regexp" diff --git a/cmd/registry/auth/basic/basic_suite_test.go b/cmd/registry/auth/basic/basic_suite_test.go new file mode 100644 index 00000000..80a1e9cf --- /dev/null +++ b/cmd/registry/auth/basic/basic_suite_test.go @@ -0,0 +1,134 @@ +// Copyright 2023 The Falco 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 basic_test + +import ( + "context" + "crypto/tls" + "fmt" + "net/http" + "os" + "path/filepath" + "testing" + "time" + + "github.com/distribution/distribution/v3/configuration" + _ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" + "github.com/spf13/cobra" + "golang.org/x/crypto/bcrypt" + + "github.com/falcosecurity/falcoctl/cmd" + commonoptions "github.com/falcosecurity/falcoctl/pkg/options" + testutils "github.com/falcosecurity/falcoctl/pkg/test" +) + +//nolint:unused // false positive +var ( + registry string + registryBasic string + ctx = context.Background() + output = gbytes.NewBuffer() + rootCmd *cobra.Command + opt *commonoptions.CommonOptions + port int + portBasic int + configFile string + err error + args []string +) + +func TestBasic(t *testing.T) { + var err error + RegisterFailHandler(Fail) + port, err = testutils.FreePort() + Expect(err).ToNot(HaveOccurred()) + portBasic, err = testutils.FreePort() + Expect(err).ToNot(HaveOccurred()) + registry = fmt.Sprintf("localhost:%d", port) + registryBasic = fmt.Sprintf("localhost:%d", portBasic) + RunSpecs(t, "Auth Basic Suite") +} + +var _ = BeforeSuite(func() { + config := &configuration.Configuration{} + config.HTTP.Addr = fmt.Sprintf("localhost:%d", port) + + testHtpasswdFileBasename := "authtest.htpasswd" + testUsername, testPassword := "username", "password" + + pwBytes, err := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost) + Expect(err).To(BeNil()) + + htpasswdPath := filepath.Join(GinkgoT().TempDir(), testHtpasswdFileBasename) + err = os.WriteFile(htpasswdPath, []byte(fmt.Sprintf("%s:%s\n", testUsername, string(pwBytes))), 0o644) + Expect(err).To(BeNil()) + + tlsConfig, err := testutils.BuildRegistryTLSConfig(GinkgoT().TempDir(), []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"}) + Expect(err).To(BeNil()) + + configBasic := &configuration.Configuration{} + configBasic.HTTP.Addr = fmt.Sprintf("localhost:%d", portBasic) + configBasic.Auth = configuration.Auth{ + "htpasswd": configuration.Parameters{ + "realm": "localhost", + "path": htpasswdPath, + }, + } + configBasic.HTTP.DrainTimeout = time.Duration(10) * time.Second + configBasic.HTTP.TLS.CipherSuites = tlsConfig.CipherSuites + configBasic.HTTP.TLS.Certificate = tlsConfig.CertificatePath + configBasic.HTTP.TLS.Key = tlsConfig.PrivateKeyPath + + http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{ + InsecureSkipVerify: true, + } + + // Create and configure the common options. + opt = commonoptions.NewOptions() + opt.Initialize(commonoptions.WithWriter(output)) + opt.Printer.DisableStylingf() + + // Start the local registry. + go func() { + err := testutils.StartRegistry(context.Background(), config) + Expect(err).ToNot(BeNil()) + }() + + // Start the local registry with basic authentication. + go func() { + err := testutils.StartRegistry(context.Background(), configBasic) + Expect(err).ToNot(BeNil()) + }() + + // Create temporary directory used to save the configuration file. + configFile, err = testutils.CreateEmptyFile("falcoctl.yaml") + Expect(err).Should(Succeed()) + +}) + +var _ = AfterSuite(func() { + configDir := filepath.Dir(configFile) + Expect(os.RemoveAll(configDir)).Should(Succeed()) +}) + +//nolint:unused // false positive +func executeRoot(args []string) error { + rootCmd.SetArgs(args) + rootCmd.SetOut(output) + return cmd.Execute(rootCmd, opt.Printer) +} diff --git a/cmd/registry/auth/basic/basic_test.go b/cmd/registry/auth/basic/basic_test.go new file mode 100644 index 00000000..d3701b2d --- /dev/null +++ b/cmd/registry/auth/basic/basic_test.go @@ -0,0 +1,137 @@ +// Copyright 2023 The Falco 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 basic_test + +import ( + "regexp" + + _ "github.com/distribution/distribution/v3/registry/auth/htpasswd" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" + + "github.com/falcosecurity/falcoctl/cmd" +) + +type Config struct { + Registry Registry `yaml:"registry"` +} + +type Registry struct { + Auth Auth `yaml:"auth"` +} + +type Auth struct { + OAuth []OAuth `yaml:"oauth"` +} + +type OAuth struct { + Registry string `yaml:"registry"` + ClientSecret string `yaml:"clientsecret"` + ClientID string `yaml:"clientid"` + TokerURL string `yaml:"tokenurl"` +} + +//nolint:lll,unused // no need to check for line length. +var registryAuthBasicUsage = `Usage: + falcoctl registry auth basic [hostname] + +Flags: + -h, --help help for basic + +Global Flags: + --config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml") + --disable-styling Disable output styling such as spinners, progress bars and colors. Styling is automatically disabled if not attacched to a tty (default false) + -v, --verbose Enable verbose logs (default false) +` + +//nolint:unused // false positive +var registryAuthBasicHelp = `Login to an OCI registry to push and pull artifacts` + +//nolint:unused // false positive +var registryAuthBasicAssertFailedBehavior = func(usage, specificError string) { + It("check that fails and the usage is not printed", func() { + Expect(err).To(HaveOccurred()) + Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(usage))) + Expect(output).Should(gbytes.Say(regexp.QuoteMeta(specificError))) + }) +} + +//nolint:unused // false positive +var registryAuthBasicTests = Describe("auth", func() { + + const ( + // Used as flags for all the test cases. + registryCmd = "registry" + authCmd = "auth" + basicCmd = "basic" + ) + + // Each test gets its own root command and runs it. + // The err variable is asserted by each test. + JustBeforeEach(func() { + rootCmd = cmd.New(ctx, opt) + err = executeRoot(args) + }) + + JustAfterEach(func() { + Expect(output.Clear()).ShouldNot(HaveOccurred()) + }) + + Context("help message", func() { + BeforeEach(func() { + args = []string{registryCmd, authCmd, basicCmd, "--help"} + }) + + It("should match the saved one", func() { + Expect(output).Should(gbytes.Say(regexp.QuoteMeta(registryAuthBasicHelp))) + }) + }) + Context("failure", func() { + + When("without hostname", func() { + BeforeEach(func() { + args = []string{registryCmd, authCmd, basicCmd} + }) + registryAuthBasicAssertFailedBehavior(registryAuthBasicUsage, + "ERRO: accepts 1 arg(s), received 0") + }) + + /* + When("wrong credentials", func() { + BeforeEach(func() { + + ptyFile, ttyFile, err := pty.Open() + Expect(err).To(BeNil()) + + os.Stdin = ttyFile + input := `username1 + password1 + ` + _, err = ptyFile.Write([]byte(input)) + Expect(err).To(BeNil()) + + http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + + args = []string{registryCmd, authCmd, basicCmd, "--config", configFile, registryBasic} + }) + + registryAuthBasicAssertFailedBehavior(registryAuthBasicUsage, + "ERRO: accepts 0 arg(s), received 0") + }) + */ + }) + +}) diff --git a/cmd/registry/auth/oauth/oauth.go b/cmd/registry/auth/oauth/oauth.go index 7d488436..1f082bef 100644 --- a/cmd/registry/auth/oauth/oauth.go +++ b/cmd/registry/auth/oauth/oauth.go @@ -30,11 +30,11 @@ const ( Client credentials will be saved in the ~/.config directory. -Example +Example falcoctl registry oauth \ --token-url="http://localhost:9096/token" \ --client-id=000000 \ - --client-secret=999999 --scopes="my-scope" \ + --client-secret=999999 --scopes="my-scope" \ hostname ` ) diff --git a/cmd/registry/auth/oauth/oauth_suite_test.go b/cmd/registry/auth/oauth/oauth_suite_test.go new file mode 100644 index 00000000..d3792835 --- /dev/null +++ b/cmd/registry/auth/oauth/oauth_suite_test.go @@ -0,0 +1,118 @@ +// Copyright 2023 The Falco 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 oauth_test + +import ( + "context" + "fmt" + "os" + "os/user" + "path/filepath" + "testing" + + "github.com/distribution/distribution/v3/configuration" + _ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" + "github.com/spf13/cobra" + "oras.land/oras-go/v2/registry/remote" + + "github.com/falcosecurity/falcoctl/cmd" + commonoptions "github.com/falcosecurity/falcoctl/pkg/options" + testutils "github.com/falcosecurity/falcoctl/pkg/test" +) + +//nolint:unused // false positive +var ( + registry string + oauthServer string + oauthPort int + ctx = context.Background() + output = gbytes.NewBuffer() + rootCmd *cobra.Command + opt *commonoptions.CommonOptions + port int + orasRegistry *remote.Registry + configFile string + err error + args []string +) + +func TestOAuth(t *testing.T) { + var err error + RegisterFailHandler(Fail) + port, err = testutils.FreePort() + Expect(err).ToNot(HaveOccurred()) + oauthPort, err = testutils.FreePort() + Expect(err).ToNot(HaveOccurred()) + registry = fmt.Sprintf("localhost:%d", port) + RunSpecs(t, "OAuth Suite") +} + +var _ = BeforeSuite(func() { + + // Get the current user's home directory + usr, err := user.Current() + Expect(err).ToNot(HaveOccurred()) + + // Construct the path for the .config directory + configDir := filepath.Join(usr.HomeDir, ".config", "falcoctl") + + // Check if the directory already exists + if _, err := os.Stat(configDir); os.IsNotExist(err) { + // Directory doesn't exist, create it + err := os.MkdirAll(configDir, 0o755) + Expect(err).ToNot(HaveOccurred()) + } + + config := &configuration.Configuration{} + config.HTTP.Addr = fmt.Sprintf("localhost:%d", port) + // Create and configure the common options. + opt = commonoptions.NewOptions() + opt.Initialize(commonoptions.WithWriter(output)) + opt.Printer.DisableStylingf() + + // Create the oras registry. + orasRegistry, err = testutils.NewOrasRegistry(registry, true) + Expect(err).ToNot(HaveOccurred()) + + // Start the local registry. + go func() { + err := testutils.StartRegistry(context.Background(), config) + Expect(err).ToNot(BeNil()) + }() + + go func() { + err := testutils.StartOAuthServer(context.Background(), oauthPort) + Expect(err).ToNot(BeNil()) + }() + + // Create temporary directory used to save the configuration file. + configFile, err = testutils.CreateEmptyFile("falcoctl.yaml") + Expect(err).Should(Succeed()) +}) + +var _ = AfterSuite(func() { + configDir := filepath.Dir(configFile) + Expect(os.RemoveAll(configDir)).Should(Succeed()) +}) + +//nolint:unused // false positive +func executeRoot(args []string) error { + rootCmd.SetArgs(args) + rootCmd.SetOut(output) + return cmd.Execute(rootCmd, opt.Printer) +} diff --git a/cmd/registry/auth/oauth/oauth_test.go b/cmd/registry/auth/oauth/oauth_test.go new file mode 100644 index 00000000..5e98708e --- /dev/null +++ b/cmd/registry/auth/oauth/oauth_test.go @@ -0,0 +1,206 @@ +// Copyright 2023 The Falco 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 oauth_test + +import ( + "fmt" + "os" + "regexp" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" + + "github.com/falcosecurity/falcoctl/cmd" +) + +type Config struct { + Registry Registry `yaml:"registry"` +} + +type Registry struct { + Auth Auth `yaml:"auth"` +} + +type Auth struct { + OAuth []OAuth `yaml:"oauth"` +} + +type OAuth struct { + Registry string `yaml:"registry"` + ClientSecret string `yaml:"clientsecret"` + ClientID string `yaml:"clientid"` + TokerURL string `yaml:"tokenurl"` +} + +//nolint:unused // false positive +var correctIndexConfig = `indexes: +- name: falcosecurity + url: https://falcosecurity.github.io/falcoctl/index.yaml +` + +//nolint:lll,unused // no need to check for line length. +var registryAuthOAuthUsage = `Usage: + falcoctl registry auth oauth [HOSTNAME] + +Flags: + --client-id string client ID of the OAuth2.0 app + --client-secret string client secret of the OAuth2.0 app + -h, --help help for oauth + --scopes strings comma separeted list of scopes for which requesting access + --token-url string token URL used to get access and refresh tokens + +Global Flags: + --config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml") + --disable-styling Disable output styling such as spinners, progress bars and colors. Styling is automatically disabled if not attacched to a tty (default false) + -v, --verbose Enable verbose logs (default false) +` + +//nolint:unused // false positive +var registryAuthOAuthHelp = `Store client credentials for later OAuth2.0 authentication + +Client credentials will be saved in the ~/.config directory. + +Example + falcoctl registry oauth \ + --token-url="http://localhost:9096/token" \ + --client-id=000000 \ + --client-secret=999999 --scopes="my-scope" \ + hostname +` + +//nolint:unused // false positive +var registryAuthOAuthAssertFailedBehavior = func(usage, specificError string) { + It("check that fails and the usage is not printed", func() { + Expect(err).To(HaveOccurred()) + Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(usage))) + Expect(output).Should(gbytes.Say(regexp.QuoteMeta(specificError))) + }) +} + +//nolint:unused // false positive +var registryAuthOAuthTests = Describe("auth", func() { + const ( + // Used as flags for all the test cases. + registryCmd = "registry" + authCmd = "auth" + oauthCmd = "oauth" + anSource = "myrepo.com/rules.git" + artifact = "generic-repo" + repo = "/" + artifact + tag = "tag" + repoAndTag = repo + ":" + tag + ) + + // Each test gets its own root command and runs it. + // The err variable is asserted by each test. + JustBeforeEach(func() { + rootCmd = cmd.New(ctx, opt) + err = executeRoot(args) + }) + + JustAfterEach(func() { + Expect(output.Clear()).ShouldNot(HaveOccurred()) + }) + + Context("help message", func() { + BeforeEach(func() { + args = []string{registryCmd, authCmd, oauthCmd, "--help"} + }) + + It("should match the saved one", func() { + Expect(output).Should(gbytes.Say(regexp.QuoteMeta(registryAuthOAuthHelp))) + }) + }) + Context("failure", func() { + + When("without hostname", func() { + BeforeEach(func() { + args = []string{registryCmd, authCmd, oauthCmd} + }) + registryAuthOAuthAssertFailedBehavior(registryAuthOAuthUsage, + "ERRO: accepts 1 arg(s), received 0") + }) + + When("wrong client id", func() { + BeforeEach(func() { + + baseDir := GinkgoT().TempDir() + configFilePath := baseDir + "/config.yaml" + content := []byte(correctIndexConfig) + err = os.WriteFile(configFilePath, content, 0o644) + Expect(err).To(BeNil()) + + args = []string{registryCmd, authCmd, oauthCmd, + "--client-id=000001", "--client-secret=999999", + "--token-url", fmt.Sprintf("http://localhost:%d/token", oauthPort), + "--config", configFilePath, + "127.0.0.1:5000", + } + }) + registryAuthOAuthAssertFailedBehavior(registryAuthOAuthUsage, + `ERRO: wrong client credentials, unable to retrieve token`) + }) + + When("wrong client secret", func() { + BeforeEach(func() { + // start the OAuthServer + baseDir := GinkgoT().TempDir() + configFilePath := baseDir + "/config.yaml" + content := []byte(correctIndexConfig) + err := os.WriteFile(configFilePath, content, 0o644) + Expect(err).To(BeNil()) + + args = []string{registryCmd, authCmd, oauthCmd, + "--client-id=000000", "--client-secret=999998", + "--token-url", fmt.Sprintf("http://localhost:%d/token", oauthPort), + "--config", configFilePath, + "127.0.0.1:5000", + } + }) + registryAuthOAuthAssertFailedBehavior(registryAuthOAuthUsage, + `ERRO: wrong client credentials, unable to retrieve token`) + }) + }) + + Context("success", func() { + var ( + configFilePath string + ) + + When("all good", func() { + BeforeEach(func() { + baseDir := GinkgoT().TempDir() + configFilePath = baseDir + "/config.yaml" + content := []byte(correctIndexConfig) + err = os.WriteFile(configFilePath, content, 0o644) + Expect(err).To(BeNil()) + + args = []string{registryCmd, authCmd, oauthCmd, + "--client-id=000000", "--client-secret=999999", + "--token-url", fmt.Sprintf("http://localhost:%d/token", oauthPort), + "--config", configFilePath, + registry, + } + }) + + It("should successed", func() { + Expect(output).Should(gbytes.Say(regexp.QuoteMeta( + `INFO: client credentials correctly saved in`))) + }) + }) + + }) +}) diff --git a/cmd/registry/pull/pull.go b/cmd/registry/pull/pull.go index 42607ea9..8234fa85 100644 --- a/cmd/registry/pull/pull.go +++ b/cmd/registry/pull/pull.go @@ -30,13 +30,13 @@ import ( const ( longPull = `Pull Falco "rulesfile" or "plugin" OCI artifacts from remote registry. -Artifact references are passed as arguments. +Artifact references are passed as arguments. -A reference is either a simple name or a fully qualified reference ("/"), +A reference is either a simple name or a fully qualified reference ("/"), optionally followed by ":" (":latest" is assumed by default when no tag is given). -When providing just the name of the artifact, the command will search for the artifacts in -the configured index files, and if found, it will use the registry and repository specified +When providing just the name of the artifact, the command will search for the artifacts in +the configured index files, and if found, it will use the registry and repository specified in the indexes. Example - Pull artifact "myplugin" for the platform where falcoctl is running (default) in the current working directory (default): diff --git a/cmd/registry/pull/pull_suite_test.go b/cmd/registry/pull/pull_suite_test.go new file mode 100644 index 00000000..c48c1e75 --- /dev/null +++ b/cmd/registry/pull/pull_suite_test.go @@ -0,0 +1,100 @@ +// Copyright 2023 The Falco 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 pull_test + +import ( + "context" + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/distribution/distribution/v3/configuration" + _ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" + "github.com/spf13/cobra" + "oras.land/oras-go/v2/registry/remote" + + "github.com/falcosecurity/falcoctl/cmd" + commonoptions "github.com/falcosecurity/falcoctl/pkg/options" + testutils "github.com/falcosecurity/falcoctl/pkg/test" +) + +//nolint:unused // false positive +const ( + rulesfiletgz = "../../../pkg/test/data/rules.tar.gz" + rulesfileyaml = "../../../pkg/test/data/rules.yaml" + plugintgz = "../../../pkg/test/data/plugin.tar.gz" +) + +//nolint:unused // false positive +var ( + registry string + ctx = context.Background() + output = gbytes.NewBuffer() + rootCmd *cobra.Command + opt *commonoptions.CommonOptions + port int + orasRegistry *remote.Registry + configFile string + err error + args []string +) + +func TestPull(t *testing.T) { + RegisterFailHandler(Fail) + port, err = testutils.FreePort() + Expect(err).ToNot(HaveOccurred()) + registry = fmt.Sprintf("localhost:%d", port) + RunSpecs(t, "Pull Suite") +} + +var _ = BeforeSuite(func() { + config := &configuration.Configuration{} + config.HTTP.Addr = fmt.Sprintf("localhost:%d", port) + // Create and configure the common options. + opt = commonoptions.NewOptions() + opt.Initialize(commonoptions.WithWriter(output)) + opt.Printer.DisableStylingf() + + // Create the oras registry. + orasRegistry, err = testutils.NewOrasRegistry(registry, true) + Expect(err).ToNot(HaveOccurred()) + + // Start the local registry. + go func() { + err := testutils.StartRegistry(context.Background(), config) + Expect(err).ToNot(BeNil()) + }() + + // Create temporary directory used to save the configuration file. + configFile, err = testutils.CreateEmptyFile("falcoctl.yaml") + Expect(err).Should(Succeed()) + +}) + +var _ = AfterSuite(func() { + configDir := filepath.Dir(configFile) + Expect(os.RemoveAll(configDir)).Should(Succeed()) +}) + +//nolint:unused // false positive +func executeRoot(args []string) error { + rootCmd.SetArgs(args) + rootCmd.SetOut(output) + return cmd.Execute(rootCmd, opt.Printer) +} diff --git a/cmd/registry/pull/pull_test.go b/cmd/registry/pull/pull_test.go new file mode 100644 index 00000000..43c51fe3 --- /dev/null +++ b/cmd/registry/pull/pull_test.go @@ -0,0 +1,332 @@ +// Copyright 2023 The Falco 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 pull_test + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" + "oras.land/oras-go/v2/registry/remote/auth" + + "github.com/falcosecurity/falcoctl/cmd" + "github.com/falcosecurity/falcoctl/pkg/oci" + "github.com/falcosecurity/falcoctl/pkg/oci/authn" + ocipusher "github.com/falcosecurity/falcoctl/pkg/oci/pusher" + out "github.com/falcosecurity/falcoctl/pkg/output" +) + +//nolint:lll,unused // no need to check for line length. +var registryPullUsage = `Usage: + falcoctl registry pull hostname/repo[:tag|@digest] [flags] + +Flags: + -o, --dest-dir string destination dir where to save the artifacts(default: current directory) + -h, --help help for pull + --plain-http allows interacting with remote registry via plain http requests + --platform stringArray os and architecture of the artifact in OS/ARCH format (only for plugins artifacts) + +Global Flags: + --config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml") + --disable-styling Disable output styling such as spinners, progress bars and colors. Styling is automatically disabled if not attacched to a tty (default false) + -v, --verbose Enable verbose logs (default false) + +` + +//nolint:unused // false positive +var registryPullHelp = `Pull Falco "rulesfile" or "plugin" OCI artifacts from remote registry. + +Artifact references are passed as arguments. + +A reference is either a simple name or a fully qualified reference ("/"), +optionally followed by ":" (":latest" is assumed by default when no tag is given). + +When providing just the name of the artifact, the command will search for the artifacts in +the configured index files, and if found, it will use the registry and repository specified +in the indexes. + +Example - Pull artifact "myplugin" for the platform where falcoctl is running (default) in the current working directory (default): + falcoctl registry pull localhost:5000/myplugin:latest + +Example - Pull artifact "myplugin" for platform "linux/arm64" in the current working directory (default): + falcoctl registry pull localhost:5000/myplugin:latest --platform linux/arm64 + +Example - Pull artifact "myplugin" for platform "linux/arm64" in "myDir" directory: + falcoctl registry pull localhost:5000/myplugin:latest --platform linux/arm64 --dest-dir=./myDir + +Example - Pull artifact "myrulesfile": + falcoctl registry pull localhost:5000/myrulesfile:latest +` + +//nolint:unused // false positive +var pullAssertFailedBehavior = func(usage, specificError string) { + It("check that fails and the usage is not printed", func() { + Expect(err).To(HaveOccurred()) + Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(usage))) + Expect(output).Should(gbytes.Say(regexp.QuoteMeta(specificError))) + }) +} + +//nolint:unused // false positive +var registryPullTests = Describe("pull", func() { + var ( + pusher *ocipusher.Pusher + ref string + config ocipusher.Option + ) + + const ( + // Used as flags for all the test cases. + registryCmd = "registry" + pullCmd = "pull" + dep1 = "myplugin:1.2.3" + dep2 = "myplugin1:1.2.3|otherplugin:3.2.1" + req = "engine_version:15" + anSource = "myrepo.com/rules.git" + artifact = "generic-repo" + repo = "/" + artifact + tag = "tag" + repoAndTag = repo + ":" + tag + ) + + // Each test gets its own root command and runs it. + // The err variable is asserted by each test. + JustBeforeEach(func() { + rootCmd = cmd.New(ctx, opt) + err = executeRoot(args) + }) + + JustAfterEach(func() { + Expect(output.Clear()).ShouldNot(HaveOccurred()) + }) + + Context("help message", func() { + BeforeEach(func() { + args = []string{registryCmd, pullCmd, "--help"} + }) + + It("should match the saved one", func() { + + Expect(output).Should(gbytes.Say(regexp.QuoteMeta(registryPullHelp))) + }) + }) + + // Here we are testing all the failure cases using both the rulesfile and plugin artifact types. + // The common logic for the artifacts is tested once using a rulesfile artifact, no need to repeat + // the same test using a plugin artifact. + Context("failure", func() { + var ( + tracker out.Tracker + options []ocipusher.Option + filePathsAndPlatforms ocipusher.Option + destDir string + ) + const ( + plainHTTP = true + testPluginPlatform1 = "linux/amd64" + ) + + When("without artifact", func() { + BeforeEach(func() { + args = []string{registryCmd, pullCmd} + }) + pullAssertFailedBehavior(registryPullUsage, "ERRO: accepts 1 arg(s), received 0") + }) + + When("unreachable registry", func() { + BeforeEach(func() { + configDir := GinkgoT().TempDir() + configFile := filepath.Join(configDir, ".config") + _, err := os.Create(configFile) + Expect(err).To(BeNil()) + args = []string{registryCmd, pullCmd, "noregistry/testrules", "--plain-http", "--config", configFile} + }) + pullAssertFailedBehavior(registryPullUsage, "ERRO: unable to connect to remote registry") + }) + + When("invalid repository", func() { + newReg := registry + "/wrong:latest" + BeforeEach(func() { + configDir := GinkgoT().TempDir() + configFile := filepath.Join(configDir, ".config") + _, err := os.Create(configFile) + Expect(err).To(BeNil()) + args = []string{registryCmd, pullCmd, newReg, "--plain-http", "--config", configFile} + }) + pullAssertFailedBehavior(registryPullUsage, fmt.Sprintf("ERRO: %s: not found", newReg)) + }) + + When("unwritable --dest-dir", func() { + BeforeEach(func() { + configDir := GinkgoT().TempDir() + configFile := filepath.Join(configDir, ".config") + _, err := os.Create(configFile) + Expect(err).To(BeNil()) + destDir = GinkgoT().TempDir() + err = os.Chmod(destDir, 0o555) + Expect(err).To(BeNil()) + pusher = ocipusher.NewPusher(authn.NewClient(authn.WithCredentials(&auth.EmptyCredential)), plainHTTP, tracker) + ref = registry + repoAndTag + config = ocipusher.WithArtifactConfig(oci.ArtifactConfig{}) + filePathsAndPlatforms = ocipusher.WithFilepathsAndPlatforms([]string{plugintgz}, []string{testPluginPlatform1}) + options = []ocipusher.Option{filePathsAndPlatforms, config} + result, err := pusher.Push(ctx, oci.Plugin, ref, options...) + Expect(err).To(BeNil()) + Expect(result).ToNot(BeNil()) + args = []string{registryCmd, pullCmd, ref, "--plain-http", + "--platform", testPluginPlatform1, "--dest-dir", destDir, + "--config", configFile, + } + }) + + It("check that fails and the usage is not printed", func() { + tmp := strings.Split(repoAndTag, "/") + artNameAndTag := tmp[len(tmp)-1] + tmp = strings.Split(artNameAndTag, ":") + artName := tmp[0] + tag := tmp[1] + expectedError := fmt.Sprintf( + "ERRO: unable to pull artifact generic-repo with %s tag from repo %s: failed to create file", + tag, artName) + Expect(err).To(HaveOccurred()) + Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(registryPullUsage))) + Expect(output).Should(gbytes.Say(regexp.QuoteMeta(expectedError))) + }) + }) + + When("--dest-dir not present (and parent not writable)", func() { + BeforeEach(func() { + configDir := GinkgoT().TempDir() + configFile := filepath.Join(configDir, ".config") + _, err := os.Create(configFile) + Expect(err).To(BeNil()) + baseDir := GinkgoT().TempDir() + err = os.Chmod(baseDir, 0o555) + Expect(err).To(BeNil()) + destDir = baseDir + "/dest" + pusher = ocipusher.NewPusher(authn.NewClient(authn.WithCredentials(&auth.EmptyCredential)), plainHTTP, tracker) + ref = registry + repoAndTag + config = ocipusher.WithArtifactConfig(oci.ArtifactConfig{}) + filePathsAndPlatforms = ocipusher.WithFilepathsAndPlatforms([]string{plugintgz}, []string{testPluginPlatform1}) + options = []ocipusher.Option{filePathsAndPlatforms, config} + result, err := pusher.Push(ctx, oci.Plugin, ref, options...) + Expect(err).To(BeNil()) + Expect(result).ToNot(BeNil()) + args = []string{registryCmd, pullCmd, ref, "--plain-http", + "--platform", testPluginPlatform1, "--dest-dir", destDir, + "--config", configFile, + } + }) + + It("check that fails and the usage is not printed", func() { + expectedError := fmt.Sprintf( + "ERRO: unable to push artifact failed to ensure directories of the target path: mkdir %s: permission denied\n"+ + "ERRO: unable to pull artifact %s with tag %s from repo %s: failed to ensure directories of the target path: mkdir %s: permission denied", + destDir, artifact, tag, artifact, destDir) + Expect(err).To(HaveOccurred()) + Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(registryPullUsage))) + Expect(output).Should(gbytes.Say(regexp.QuoteMeta(expectedError))) + }) + }) + + When("wrong digest format", func() { + wrongDigest := "sha256:06f961b802bc46ee168555f066d28f4f0e9afdf3f88174c1ee6f9de004fc30a0" + BeforeEach(func() { + configDir := GinkgoT().TempDir() + configFile := filepath.Join(configDir, ".config") + _, err := os.Create(configFile) + Expect(err).To(BeNil()) + pusher = ocipusher.NewPusher(authn.NewClient(authn.WithCredentials(&auth.EmptyCredential)), plainHTTP, tracker) + ref = registry + repoAndTag + config = ocipusher.WithArtifactConfig(oci.ArtifactConfig{}) + filePathsAndPlatforms = ocipusher.WithFilepathsAndPlatforms([]string{plugintgz}, []string{testPluginPlatform1}) + options = []ocipusher.Option{filePathsAndPlatforms, config} + result, err := pusher.Push(ctx, oci.Plugin, ref, options...) + Expect(err).To(BeNil()) + Expect(result).ToNot(BeNil()) + ref = registry + repoAndTag + "@" + wrongDigest + args = []string{registryCmd, pullCmd, ref, "--plain-http", + "--platform", testPluginPlatform1, "--config", configFile} + }) + + It("check that fails and the usage is not printed", func() { + expectedError := fmt.Sprintf("ERRO: %s: not found", registry+repo+"@"+wrongDigest) + Expect(err).To(HaveOccurred()) + Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(registryPullUsage))) + Expect(output).Should(gbytes.Say(regexp.QuoteMeta(expectedError))) + }) + }) + + When("missing repository", func() { + BeforeEach(func() { + configDir := GinkgoT().TempDir() + configFile := filepath.Join(configDir, ".config") + _, err := os.Create(configFile) + Expect(err).To(BeNil()) + ref = repoAndTag + args = []string{registryCmd, pullCmd, ref, "--plain-http", "--config", configFile} + }) + + It("check that fails and the usage is not printed", func() { + expectedError := fmt.Sprintf("ERRO: cannot extract registry name from ref %q", ref) + Expect(err).To(HaveOccurred()) + Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(registryPullUsage))) + Expect(output).Should(gbytes.Say(regexp.QuoteMeta(expectedError))) + }) + }) + + When("invalid repository", func() { + newReg := registry + "/wrong@something" + BeforeEach(func() { + configDir := GinkgoT().TempDir() + configFile := filepath.Join(configDir, ".config") + _, err := os.Create(configFile) + Expect(err).To(BeNil()) + args = []string{registryCmd, pullCmd, newReg, "--plain-http", "--config", configFile} + }) + pullAssertFailedBehavior(registryPullUsage, fmt.Sprintf("ERRO: unable to create new repository with ref %s: "+ + "invalid reference: invalid digest; invalid checksum digest format\n", newReg)) + }) + + When("invalid platform", func() { + BeforeEach(func() { + configDir := GinkgoT().TempDir() + configFile := filepath.Join(configDir, ".config") + _, err := os.Create(configFile) + Expect(err).To(BeNil()) + pusher = ocipusher.NewPusher(authn.NewClient(authn.WithCredentials(&auth.EmptyCredential)), plainHTTP, tracker) + ref = registry + repoAndTag + config = ocipusher.WithArtifactConfig(oci.ArtifactConfig{}) + filePathsAndPlatforms = ocipusher.WithFilepathsAndPlatforms([]string{plugintgz}, []string{testPluginPlatform1}) + options = []ocipusher.Option{filePathsAndPlatforms, config} + result, err := pusher.Push(ctx, oci.Plugin, ref, options...) + Expect(err).To(BeNil()) + Expect(result).ToNot(BeNil()) + ref = registry + repoAndTag + args = []string{registryCmd, pullCmd, ref, "--plain-http", + "--platform", "linux/unknown", "--config", configFile} + }) + + pullAssertFailedBehavior(registryPullUsage, "not found: no matching manifest was found in the manifest list") + }) + + }) + +}) diff --git a/cmd/cmd_suite_test.go b/cmd/registry/push/push_suite_test.go similarity index 87% rename from cmd/cmd_suite_test.go rename to cmd/registry/push/push_suite_test.go index 7051ede6..93b86c25 100644 --- a/cmd/cmd_suite_test.go +++ b/cmd/registry/push/push_suite_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cmd_test +package push_test import ( "context" @@ -34,12 +34,14 @@ import ( testutils "github.com/falcosecurity/falcoctl/pkg/test" ) +//nolint:unused // false positive const ( - rulesfiletgz = "../pkg/test/data/rules.tar.gz" - rulesfileyaml = "../pkg/test/data/rules.yaml" - plugintgz = "../pkg/test/data/plugin.tar.gz" + rulesfiletgz = "../../../pkg/test/data/rules.tar.gz" + rulesfileyaml = "../../../pkg/test/data/rules.yaml" + plugintgz = "../../../pkg/test/data/plugin.tar.gz" ) +//nolint:unused // false positive var ( registry string ctx = context.Background() @@ -49,6 +51,8 @@ var ( port int orasRegistry *remote.Registry configFile string + err error + args []string ) func TestRoot(t *testing.T) { @@ -57,7 +61,7 @@ func TestRoot(t *testing.T) { port, err = testutils.FreePort() Expect(err).ToNot(HaveOccurred()) registry = fmt.Sprintf("localhost:%d", port) - RunSpecs(t, "root suite") + RunSpecs(t, "Push Suite") } var _ = BeforeSuite(func() { @@ -88,6 +92,7 @@ var _ = AfterSuite(func() { Expect(os.RemoveAll(configDir)).Should(Succeed()) }) +//nolint:unused // false positive func executeRoot(args []string) error { rootCmd.SetArgs(args) rootCmd.SetOut(output) diff --git a/cmd/push_test.go b/cmd/registry/push/push_test.go similarity index 99% rename from cmd/push_test.go rename to cmd/registry/push/push_test.go index 3585e80c..b3bfe5e3 100644 --- a/cmd/push_test.go +++ b/cmd/registry/push/push_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cmd_test +package push_test import ( "fmt" @@ -31,7 +31,7 @@ import ( testutils "github.com/falcosecurity/falcoctl/pkg/test" ) -//nolint:lll // no need to check for line length. +//nolint:lll,unused // no need to check for line length. var registryPushUsage = `Usage: falcoctl registry push hostname/repo[:tag|@digest] file [flags] @@ -54,7 +54,7 @@ Global Flags: ` -//nolint:lll // no need to check for line length. +//nolint:lll,unused // no need to check for line length. var registryPushHelp = `Push Falco "rulesfile" or "plugin" OCI artifacts to remote registry Example - Push artifact "myplugin.tar.gz" of type "plugin" for the platform where falcoctl is running (default): @@ -108,6 +108,7 @@ Global Flags: -v, --verbose Enable verbose logs (default false) ` +//nolint:unused // false positive var pushAssertFailedBehavior = func(usage, specificError string) { It("check that fails and the usage is not printed", func() { Expect(err).To(HaveOccurred()) @@ -116,11 +117,13 @@ var pushAssertFailedBehavior = func(usage, specificError string) { }) } +//nolint:unused // false positive var randomRulesRepoName = func(registry, repo string) (string, string) { rName := fmt.Sprintf("%s-%d", repo, rand.Int()) return rName, fmt.Sprintf("%s/%s", registry, rName) } +//nolint:unused // false positive var registryPushTests = Describe("push", func() { var ( registryCmd = "registry" diff --git a/cmd/root_test.go b/cmd/root_test.go deleted file mode 100644 index bdb85777..00000000 --- a/cmd/root_test.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2023 The Falco 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 cmd_test - -import ( - . "github.com/onsi/ginkgo/v2" -) - -var ( - err error - args []string -) - -var _ = Describe("root", func() { - Describe("index command", func() { - Context("add subcommand", func() { - var _ = indexAddTests - }) - }) - Describe("registry command", func() { - Context("push subcommand", func() { - var _ = registryPushTests - }) - }) -}) diff --git a/go.mod b/go.mod index bf59f18b..5ecafc7a 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,8 @@ require ( github.com/distribution/distribution/v3 v3.0.0-20230608105614-4501a6e06d3b github.com/docker/cli v24.0.5+incompatible github.com/docker/docker v24.0.5+incompatible + github.com/go-oauth2/oauth2/v4 v4.5.2 + github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/go-containerregistry v0.16.1 github.com/mitchellh/mapstructure v1.5.0 github.com/onsi/ginkgo/v2 v2.10.0 @@ -22,6 +24,7 @@ require ( github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.16.0 + golang.org/x/crypto v0.12.0 golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 google.golang.org/api v0.138.0 gopkg.in/yaml.v3 v3.0.1 @@ -228,6 +231,14 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/thales-e-security/pool v0.0.2 // indirect github.com/theupdateframework/go-tuf v0.6.1 // indirect + github.com/tidwall/btree v1.1.0 // indirect + github.com/tidwall/buntdb v1.2.0 // indirect + github.com/tidwall/gjson v1.12.1 // indirect + github.com/tidwall/grect v0.1.4 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/tidwall/rtred v0.1.2 // indirect + github.com/tidwall/tinyqueue v0.1.1 // indirect github.com/tinylib/msgp v1.1.8 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect @@ -250,7 +261,6 @@ require ( go.uber.org/zap v1.25.0 // indirect go4.org/intern v0.0.0-20230525184215-6c62f75575cb // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect - golang.org/x/crypto v0.12.0 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/text v0.12.0 // indirect golang.org/x/time v0.3.0 // indirect diff --git a/go.sum b/go.sum index 8b6791fe..f176c408 100644 --- a/go.sum +++ b/go.sum @@ -141,6 +141,8 @@ github.com/ThalesIgnite/crypto11 v1.2.5 h1:1IiIIEqYmBvUYFeMnHqRft4bwf/O36jryEUpY github.com/ThalesIgnite/crypto11 v1.2.5/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= +github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= @@ -187,6 +189,9 @@ github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCE github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw= github.com/aliyun/credentials-go v1.3.1 h1:uq/0v7kWrxmoLGpqjx7vtQ/s03f0zR//0br/xWDTE28= github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= @@ -338,8 +343,11 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= @@ -352,6 +360,8 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8= +github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= @@ -369,6 +379,8 @@ github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-oauth2/oauth2/v4 v4.5.2 h1:CuZhD3lhGuI6aNLyUbRHXsgG2RwGRBOuCBfd4WQKqBQ= +github.com/go-oauth2/oauth2/v4 v4.5.2/go.mod h1:wk/2uLImWIa9VVQDgxz99H2GDbhmfi/9/Xr+GvkSUSQ= github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY= github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc= github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo= @@ -420,6 +432,7 @@ github.com/go-playground/validator/v10 v10.15.2 h1:Ra5cll2/eF8X0Ff2+8SMD7euo2nen github.com/go-playground/validator/v10 v10.15.2/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-rod/rod v0.114.2 h1:Qwt+vZHHnb117zc0q+XjhAJCkB01hchWSxH/raCyLb4= github.com/go-rod/rod v0.114.2/go.mod h1:aiedSEFg5DwG/fnNbUOTPMTTWX3MRj6vIs/a684Mthw= +github.com/go-session/session v3.1.2+incompatible/go.mod h1:8B3iivBQjrz/JtC68Np2T1yBBLxTan3mn/3OM0CyRt0= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= @@ -457,6 +470,9 @@ github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= @@ -522,6 +538,7 @@ github.com/google/go-containerregistry v0.16.1 h1:rUEt426sR6nyrL3gt+18ibRcvYpKYd github.com/google/go-containerregistry v0.16.1/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= github.com/google/go-github/v50 v50.2.0 h1:j2FyongEHlO9nxXLc+LP3wuBSVU9mVxfpdYUexMpIfk= github.com/google/go-github/v50 v50.2.0/go.mod h1:VBY8FB6yPIjrtKhozXv4FQupxKLS6H4m6xFZlT43q8Q= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -569,11 +586,15 @@ github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0= github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -609,6 +630,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU= github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -634,8 +657,10 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= @@ -643,6 +668,7 @@ github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0Lh github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -692,8 +718,10 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -719,6 +747,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs= +github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/mozillazg/docker-credential-acr-helper v0.3.0 h1:DVWFZ3/O8BP6Ue3iS/Olw+G07u1hCq1EOVCDZZjCIBI= github.com/mozillazg/docker-credential-acr-helper v0.3.0/go.mod h1:cZlu3tof523ujmLuiNUb6JsjtHcNA70u1jitrrdnuyA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= @@ -736,6 +766,7 @@ github.com/oleiade/reflections v1.0.1 h1:D1XO3LVEYroYskEsoSiGItp9RUxG6jWnCVvrqH0 github.com/oleiade/reflections v1.0.1/go.mod h1:rdFxbxq4QXVZWj0F+e9jqjDkc7dbp97vkRixKo2JR60= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= @@ -825,12 +856,14 @@ github.com/sassoftware/relic v7.2.1+incompatible h1:Pwyh1F3I0r4clFJXkSI8bOyJINGq github.com/sassoftware/relic v7.2.1+incompatible/go.mod h1:CWfAxv73/iLZ17rbyhIEq3K9hs5w6FpNMdUT//qR+zk= github.com/sassoftware/relic/v7 v7.5.5 h1:2ZUM6ovo3STCAp0hZnO9nQY9lOB8OyfneeYIi4YUxMU= github.com/sassoftware/relic/v7 v7.5.5/go.mod h1:NxwtWxWxlUa9as2qZi635Ye6bBT/tGnMALLq7dSfOOU= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/secure-systems-lab/go-securesystemslib v0.7.0 h1:OwvJ5jQf9LnIAS83waAjPbcMsODrTQUpJ02eNLUoxBg= github.com/secure-systems-lab/go-securesystemslib v0.7.0/go.mod h1:/2gYnlnHVQ6xeGtfIqFy7Do03K4cdCY0A/GlJLDKLHI= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= @@ -867,7 +900,9 @@ github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:s github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.1.0 h1:MkTeG1DMwsrdH7QtLXy5W+fUxWq+vmb6cLmyJ7aRtF0= github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -914,7 +949,36 @@ github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gt github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= github.com/theupdateframework/go-tuf v0.6.1 h1:6J89fGjQf7s0mLmTG7p7pO/MbKOg+bIXhaLyQdmbKuE= github.com/theupdateframework/go-tuf v0.6.1/go.mod h1:LAFusuQsFNBnEyYoTuA5zZrF7iaQ4TEgBXm8lb6Vj18= +github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= +github.com/tidwall/btree v0.3.0/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= +github.com/tidwall/btree v1.1.0 h1:5P+9WU8ui5uhmcg3SoPyTwoI0mVyZ1nps7YQzTZFkYM= +github.com/tidwall/btree v1.1.0/go.mod h1:TzIRzen6yHbibdSfK6t8QimqbUnoxUSrZfeW7Uob0q4= +github.com/tidwall/buntdb v1.1.2/go.mod h1:xAzi36Hir4FarpSHyfuZ6JzPJdjRZ8QlLZSntE2mqlI= +github.com/tidwall/buntdb v1.2.0 h1:8KOzf5Gg97DoCMSOgcwZjnM0FfROtq0fcZkPW54oGKU= +github.com/tidwall/buntdb v1.2.0/go.mod h1:XLza/dhlwzO6dc5o/KWor4kfZSt3BP8QV+77ZMKfI58= +github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= +github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= +github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= +github.com/tidwall/gjson v1.12.1 h1:ikuZsLdhr8Ws0IdROXUS1Gi4v9Z4pGqpX/CvJkxvfpo= +github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M= +github.com/tidwall/grect v0.1.0/go.mod h1:sa5O42oP6jWfTShL9ka6Sgmg3TgIK649veZe05B7+J8= +github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg= +github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q= +github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= +github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8= +github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ= +github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e/go.mod h1:/h+UnNGt0IhNNJLkGikcdcJqm66zGD/uJGMRxK/9+Ao= +github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ= +github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE= +github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw= github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= @@ -924,6 +988,12 @@ github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4= github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0= +github.com/valyala/fasthttp v1.45.0 h1:zPkkzpIn8tdHZUrVa6PzYd0i5verqiPSkgTd3bSUcpA= +github.com/valyala/fasthttp v1.45.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts= github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk= github.com/xanzy/go-gitlab v0.90.0 h1:j8ZUHfLfXdnC+B8njeNaW/kM44c1zw8fiuNj7D+qQN8= @@ -935,9 +1005,18 @@ github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3k github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ= github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns= @@ -949,6 +1028,11 @@ github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE= github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg= github.com/ysmood/leakless v0.8.0 h1:BzLrVoiwxikpgEQR0Lk8NyBN5Cit2b1z+u0mgL4ZJak= github.com/ysmood/leakless v0.8.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1021,6 +1105,7 @@ golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -1176,6 +1261,7 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1187,6 +1273,7 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1206,6 +1293,7 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/pkg/test/registry.go b/pkg/test/registry.go index 573259bc..f31e6ec6 100644 --- a/pkg/test/registry.go +++ b/pkg/test/registry.go @@ -16,12 +16,43 @@ package test import ( "context" + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "log" + "math/big" "net" + "net/http" + "os" + "path" + "path/filepath" + "strings" + "time" "github.com/distribution/distribution/v3/configuration" "github.com/distribution/distribution/v3/registry" + oaerrors "github.com/go-oauth2/oauth2/v4/errors" + "github.com/go-oauth2/oauth2/v4/generates" + "github.com/go-oauth2/oauth2/v4/manage" + "github.com/go-oauth2/oauth2/v4/models" + "github.com/go-oauth2/oauth2/v4/server" + "github.com/go-oauth2/oauth2/v4/store" + "github.com/golang-jwt/jwt" ) +// RegistryTLSConfig maintains all certificate informations. +type RegistryTLSConfig struct { + CipherSuites []string + CertificatePath string + PrivateKeyPath string + Certificate *tls.Certificate +} + // FreePort get a free port on the system by listening in a socket, // checking the bound port number and then closing the socket. func FreePort() (int, error) { @@ -38,6 +69,90 @@ func FreePort() (int, error) { return l.Addr().(*net.TCPAddr).Port, nil } +// BuildRegistryTLSConfig creates a new RegistryTLSConfig. +func BuildRegistryTLSConfig(tmpDir string, cipherSuites []string) (*RegistryTLSConfig, error) { + var priv interface{} + var pub crypto.PublicKey + var err error + + name := "cert" + + priv, err = rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, fmt.Errorf("failed to create rsa private key: %w", err) + } + rsaKey := priv.(*rsa.PrivateKey) + pub = rsaKey.Public() + + notBefore := time.Now() + notAfter := notBefore.Add(time.Minute) + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, fmt.Errorf("failed to create serial number: %w", err) + } + cert := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"registry_test"}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, + DNSNames: []string{"localhost"}, + IsCA: true, + } + derBytes, err := x509.CreateCertificate(rand.Reader, &cert, &cert, pub, priv) + if err != nil { + return nil, fmt.Errorf("failed to create certificate: %w", err) + } + + certPath := path.Join(tmpDir, name+".pem") + certOut, err := os.Create(filepath.Clean(certPath)) + if err != nil { + return nil, fmt.Errorf("failed to create pem: %w", err) + } + if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { + return nil, fmt.Errorf("failed to write data to %s: %w", certPath, err) + } + if err := certOut.Close(); err != nil { + return nil, fmt.Errorf("error closing %s: %w", certPath, err) + } + + pkPath := path.Join(tmpDir, "key.pem") + keyOut, err := os.OpenFile(filepath.Clean(pkPath), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) + if err != nil { + return nil, fmt.Errorf("failed to open %s for writing: %w", tmpDir, err) + } + privBytes, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + return nil, fmt.Errorf("unable to marshal private key: %w", err) + } + if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { + return nil, fmt.Errorf("failed to write data to key.pem: %w", err) + } + if err := keyOut.Close(); err != nil { + return nil, fmt.Errorf("error closing %s: %w", pkPath, err) + } + + tlsCert := tls.Certificate{ + Certificate: [][]byte{derBytes}, + PrivateKey: priv, + } + + tlsTestCfg := RegistryTLSConfig{ + CipherSuites: cipherSuites, + CertificatePath: certPath, + PrivateKeyPath: pkPath, + Certificate: &tlsCert, + } + + return &tlsTestCfg, nil +} + // StartRegistry starts a new OCI registry and returns it's address. func StartRegistry(ctx context.Context, cfg *configuration.Configuration) error { if cfg.Storage == nil { @@ -53,3 +168,122 @@ func StartRegistry(ctx context.Context, cfg *configuration.Configuration) error // Start serving in goroutine and listen for stop signal in main thread return reg.ListenAndServe() } + +// StartOAuthServer starts a new OAuth server. +func StartOAuthServer(ctx context.Context, port int) error { + manager := manage.NewDefaultManager() + // token memory store + manager.MustTokenStorage(store.NewMemoryTokenStore()) + + // client memory store + clientStore := store.NewClientStore() + err := clientStore.Set("000000", &models.Client{ + ID: "000000", + Secret: "999999", + Domain: "http://localhost:3000/callback", + UserID: "user", + }) + if err != nil { + return err + } + manager.MapClientStorage(clientStore) + + manager.MapAccessGenerate(generates.NewJWTAccessGenerate("", []byte("secret"), jwt.SigningMethodHS256)) + + // config used for client credentials + cfg := &manage.Config{ + AccessTokenExp: 60 * time.Second, + RefreshTokenExp: 0, + IsGenerateRefresh: false, + } + manager.SetClientTokenCfg(cfg) + + // useful to test other grant types + refreshTokenConfig := &manage.RefreshingConfig{ + AccessTokenExp: time.Second * 3, + RefreshTokenExp: time.Hour * 24, + IsGenerateRefresh: true, + IsResetRefreshTime: false, + IsRemoveAccess: false, + IsRemoveRefreshing: false, + } + manager.SetRefreshTokenCfg(refreshTokenConfig) + + srv := server.NewDefaultServer(manager) + + srv.SetClientInfoHandler(server.ClientFormHandler) + + srv.SetInternalErrorHandler(func(err error) (re *oaerrors.Response) { + log.Println("Internal Error:", err.Error()) + return + }) + + srv.SetResponseErrorHandler(func(re *oaerrors.Response) { + log.Println("Response Error:", re.Error.Error()) + }) + + srv.SetUserAuthorizationHandler(func(w http.ResponseWriter, r *http.Request) (string, error) { + return "id", nil + }) + + srv.SetPasswordAuthorizationHandler(func(ctx context.Context, clientID, username, password string) (userID string, err error) { + if clientID == "000000" && username == "username" && password == "password" { + return username, nil + } + return "", oaerrors.ErrAccessDenied + }) + + http.HandleFunc("/authorize", func(w http.ResponseWriter, r *http.Request) { + err := srv.HandleAuthorizeRequest(w, r) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + }) + + http.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) { + err := srv.HandleTokenRequest(w, r) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + }) + + http.HandleFunc("/hitme", func(w http.ResponseWriter, r *http.Request) { + _, err = w.Write([]byte("ok hit")) + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + }) + + // Token introspection endpoint + http.HandleFunc("/introspect", func(w http.ResponseWriter, r *http.Request) { + err := r.ParseForm() + if err != nil { + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return + } + + accessToken := r.FormValue("token") + accessToken = strings.TrimPrefix(accessToken, "Bearer ") + ti, err := srv.Manager.LoadAccessToken(ctx, accessToken) + if err != nil { + http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) + return + } + + if duration := ti.GetAccessExpiresIn(); duration <= 0 { + http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) + return + } + }) + + s := &http.Server{ + Addr: fmt.Sprintf(":%d", port), + ReadHeaderTimeout: 3 * time.Second, + } + + log.Fatal(s.ListenAndServe()) + return nil +} From f960637f03d82d33ca432b5d56c7757f35869d39 Mon Sep 17 00:00:00 2001 From: Roberto Scolaro Date: Wed, 30 Aug 2023 09:44:08 +0000 Subject: [PATCH 2/2] refactor: rename options dropping suffix Signed-off-by: Roberto Scolaro --- cmd/artifact/install/install_suite_test.go | 2 +- cmd/index/add/add_suite_test.go | 2 +- cmd/registry/auth/basic/basic_suite_test.go | 2 +- cmd/registry/auth/oauth/oauth_suite_test.go | 2 +- cmd/registry/pull/pull_suite_test.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/artifact/install/install_suite_test.go b/cmd/artifact/install/install_suite_test.go index 0f8d8f8e..572c4ef9 100644 --- a/cmd/artifact/install/install_suite_test.go +++ b/cmd/artifact/install/install_suite_test.go @@ -47,7 +47,7 @@ var ( ctx = context.Background() output = gbytes.NewBuffer() rootCmd *cobra.Command - opt *commonoptions.CommonOptions + opt *commonoptions.Common port int orasRegistry *remote.Registry configFile string diff --git a/cmd/index/add/add_suite_test.go b/cmd/index/add/add_suite_test.go index 642d2616..5a93bc06 100644 --- a/cmd/index/add/add_suite_test.go +++ b/cmd/index/add/add_suite_test.go @@ -45,7 +45,7 @@ var ( ctx = context.Background() output = gbytes.NewBuffer() rootCmd *cobra.Command - opt *commonoptions.CommonOptions + opt *commonoptions.Common port int orasRegistry *remote.Registry configFile string diff --git a/cmd/registry/auth/basic/basic_suite_test.go b/cmd/registry/auth/basic/basic_suite_test.go index 80a1e9cf..63bed781 100644 --- a/cmd/registry/auth/basic/basic_suite_test.go +++ b/cmd/registry/auth/basic/basic_suite_test.go @@ -44,7 +44,7 @@ var ( ctx = context.Background() output = gbytes.NewBuffer() rootCmd *cobra.Command - opt *commonoptions.CommonOptions + opt *commonoptions.Common port int portBasic int configFile string diff --git a/cmd/registry/auth/oauth/oauth_suite_test.go b/cmd/registry/auth/oauth/oauth_suite_test.go index d3792835..d8d68180 100644 --- a/cmd/registry/auth/oauth/oauth_suite_test.go +++ b/cmd/registry/auth/oauth/oauth_suite_test.go @@ -43,7 +43,7 @@ var ( ctx = context.Background() output = gbytes.NewBuffer() rootCmd *cobra.Command - opt *commonoptions.CommonOptions + opt *commonoptions.Common port int orasRegistry *remote.Registry configFile string diff --git a/cmd/registry/pull/pull_suite_test.go b/cmd/registry/pull/pull_suite_test.go index c48c1e75..ee214e12 100644 --- a/cmd/registry/pull/pull_suite_test.go +++ b/cmd/registry/pull/pull_suite_test.go @@ -47,7 +47,7 @@ var ( ctx = context.Background() output = gbytes.NewBuffer() rootCmd *cobra.Command - opt *commonoptions.CommonOptions + opt *commonoptions.Common port int orasRegistry *remote.Registry configFile string