From 3554a49c67222b878e621a155e5db29f3d7f0adc Mon Sep 17 00:00:00 2001 From: Roberto Scolaro Date: Thu, 11 May 2023 13:27:55 +0000 Subject: [PATCH] new(tests): added new tests Signed-off-by: Roberto Scolaro --- cmd/artifact/install/install_suite_test.go | 84 ++++ cmd/artifact/install/install_test.go | 420 ++++++++++++++++++ cmd/registry/auth/basic/basic_suite_test.go | 132 ++++++ cmd/registry/auth/basic/basic_test.go | 136 ++++++ cmd/registry/auth/oauth/oauth.go | 4 +- cmd/registry/auth/oauth/oauth_suite_test.go | 121 +++++ cmd/registry/auth/oauth/oauth_test.go | 234 ++++++++++ cmd/registry/pull/pull.go | 8 +- cmd/registry/pull/pull_suite_test.go | 97 ++++ cmd/registry/pull/pull_test.go | 299 +++++++++++++ .../push/push_suite_test.go} | 13 +- cmd/{ => registry/push}/push_test.go | 2 +- cmd/root_test.go | 32 -- go.mod | 13 +- go.sum | 92 ++++ pkg/test/registry.go | 248 +++++++++++ 16 files changed, 1889 insertions(+), 46 deletions(-) create mode 100644 cmd/artifact/install/install_suite_test.go create mode 100644 cmd/artifact/install/install_test.go 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} (90%) 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 000000000..89899235a --- /dev/null +++ b/cmd/artifact/install/install_suite_test.go @@ -0,0 +1,84 @@ +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" +) + +const ( + rulesfiletgz = "../../../pkg/test/data/rules.tar.gz" + rulesfileyaml = "../../../pkg/test/data/rules.yaml" + plugintgz = "../../../pkg/test/data/plugin.tar.gz" +) + +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()) +}) + +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 000000000..be52691ac --- /dev/null +++ b/cmd/artifact/install/install_test.go @@ -0,0 +1,420 @@ +// 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" + "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 // 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:lll // no need to check for line length. +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 +` + +var correctIndexConfig = `indexes: +- name: falcosecurity + url: https://falcosecurity.github.io/falcoctl/index.yaml +` + +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))) + }) +} + +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() { + args = []string{artifactCmd, installCmd} + }) + installAssertFailedBehavior(artifactInstallUsage, "ERRO: no artifacts to install, please configure artifacts or pass them as arguments to this command") + }) + + When("unreachable registry", func() { + BeforeEach(func() { + args = []string{artifactCmd, installCmd, "noregistry/testrules","--plain-http"} + }) + installAssertFailedBehavior(artifactInstallUsage,"ERRO: unable to connect to remote registry \"noregistry\": "+ + "Get \"http://noregistry/v2/\": dial tcp: lookup noregistry: no such host") + }) + + When("invalid repository", func() { + newReg := registry + "/wrong:latest" + BeforeEach(func() { + args = []string{artifactCmd, installCmd, newReg, "--plain-http"} + }) + 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, 0644) + + // 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, 0644) + + // 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, 0644) + + // 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, 0555) + Expect(err).To(BeNil()) + baseDir := GinkgoT().TempDir() + configFilePath := baseDir +"/config.yaml" + content := []byte(correctIndexConfig) + err := os.WriteFile(configFilePath, content, 0644) + + // 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, 0644) + + // 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, 0555) + Expect(err).To(BeNil()) + baseDir := GinkgoT().TempDir() + configFilePath := baseDir +"/config.yaml" + content := []byte(correctIndexConfig) + err := os.WriteFile(configFilePath, content, 0644) + + // 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, 0644) + + // 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/registry/auth/basic/basic_suite_test.go b/cmd/registry/auth/basic/basic_suite_test.go new file mode 100644 index 000000000..b1d68cd3e --- /dev/null +++ b/cmd/registry/auth/basic/basic_suite_test.go @@ -0,0 +1,132 @@ +// 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" + "fmt" + "net/http" + "os" + "path/filepath" + "testing" + "time" + "crypto/tls" + + "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" +) + +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))), 0644) + 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()) +}) + +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 000000000..bec1ca4f3 --- /dev/null +++ b/cmd/registry/auth/basic/basic_test.go @@ -0,0 +1,136 @@ +// 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/creack/pty" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" + _ "github.com/distribution/distribution/v3/registry/auth/htpasswd" + + "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 // 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:lll // no need to check for line length. +var registryAuthBasicHelp = `Login to an OCI registry to push and pull artifacts` + +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))) + }) +} + +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 d19a23d90..db591c27f 100644 --- a/cmd/registry/auth/oauth/oauth.go +++ b/cmd/registry/auth/oauth/oauth.go @@ -31,11 +31,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 000000000..cd2e94270 --- /dev/null +++ b/cmd/registry/auth/oauth/oauth_suite_test.go @@ -0,0 +1,121 @@ +// 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" +) + +const ( + rulesfiletgz = "../pkg/test/data/rules.tar.gz" + rulesfileyaml = "../pkg/test/data/rules.yaml" + plugintgz = "../pkg/test/data/plugin.tar.gz" +) + +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, 0755) + 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() { + testutils.StartOAuthServer(context.Background(), oauthPort) + }() + + // 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()) +}) + +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 000000000..425c1622d --- /dev/null +++ b/cmd/registry/auth/oauth/oauth_test.go @@ -0,0 +1,234 @@ +// 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" + "gopkg.in/yaml.v3" + "os" + "regexp" + "io/ioutil" + + . "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"` +} + +var correctIndexConfig = `indexes: +- name: falcosecurity + url: https://falcosecurity.github.io/falcoctl/index.yaml +` + +//nolint:lll // 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:lll // no need to check for line length. +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 +` + +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))) + }) +} + +var registryAuthOAuthTests = Describe("auth", func(){ + var ( + config Config + ) + + const ( + // Used as flags for all the test cases. + registryCmd = "registry" + authCmd = "auth" + oauthCmd = "oauth" + 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, 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, 0644) + 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: oauth2: cannot fetch 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, 0644) + 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: oauth2: cannot fetch token: 401 Unauthorized`) + }) + }) + + 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, 0644) + 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() { + data, err := ioutil.ReadFile(configFilePath) + Expect(err).To(BeNil()) + + oauthList := []OAuth{ + OAuth{ + Registry: registry, + ClientSecret: "999999", + ClientID: "000000", + TokerURL: fmt.Sprintf("http://localhost:%d/token",oauthPort), + }, + } + + correctConfig := Config{ + Registry: Registry{ + Auth: Auth{ + OAuth: oauthList, + }, + }, + } + + err = yaml.Unmarshal(data, &config) + Expect(err).To(BeNil()) + + Expect(config).Should(Equal(correctConfig)) + }) + }) + + }) +}) diff --git a/cmd/registry/pull/pull.go b/cmd/registry/pull/pull.go index 4a9b51f31..99e1282b2 100644 --- a/cmd/registry/pull/pull.go +++ b/cmd/registry/pull/pull.go @@ -31,13 +31,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 000000000..74409ac2a --- /dev/null +++ b/cmd/registry/pull/pull_suite_test.go @@ -0,0 +1,97 @@ +// 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" +) + +const ( + rulesfiletgz = "../../../pkg/test/data/rules.tar.gz" + rulesfileyaml = "../../../pkg/test/data/rules.yaml" + plugintgz = "../../../pkg/test/data/plugin.tar.gz" +) + +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()) +}) + +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 000000000..0ecbe6053 --- /dev/null +++ b/cmd/registry/pull/pull_test.go @@ -0,0 +1,299 @@ +// 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" + "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 // 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:lll // no need to check for line length. +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 +` + +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))) + }) +} + +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() { + args = []string{registryCmd, pullCmd, "noregistry/testrules","--plain-http"} + }) + pullAssertFailedBehavior(registryPullUsage, "ERRO: an error occurred while creating the puller for registry "+ + "noregistry: unable to connect to remote "+ + "registry \"noregistry\": Get \"http://noregistry/v2/\": dial tcp: lookup noregistry") + }) + + When("invalid repository", func() { + newReg := registry + "/wrong:latest" + BeforeEach(func() { + args = []string{registryCmd, pullCmd, newReg, "--plain-http"} + }) + pullAssertFailedBehavior(registryPullUsage, fmt.Sprintf("ERRO: %s: not found", newReg)) + }) + + When("unwritable --dest-dir", func() { + BeforeEach(func() { + destDir = GinkgoT().TempDir() + err = os.Chmod(destDir, 0555) + 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} + }) + + 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 push artifact failed to create file %s/config: open %s/config: permission denied\n" + + "ERRO: unable to pull artifact generic-repo with %s tag from repo %s: failed to create file %s/config: open %s/config: permission denied", + destDir,destDir,tag,artName,destDir,destDir) + 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() { + baseDir := GinkgoT().TempDir() + err = os.Chmod(baseDir, 0555) + 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} + }) + + 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() { + 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} + }) + + 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() { + ref = repoAndTag + args = []string{registryCmd, pullCmd, ref, "--plain-http"} + }) + + It("check that fails and the usage is not printed", func() { + expectedError := fmt.Sprintf("ERRO: cannot extract registry name from ref \"%s\"", 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() { + args = []string{registryCmd, pullCmd, newReg, "--plain-http"} + }) + 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() { + 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"} + }) + + 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 90% rename from cmd/cmd_suite_test.go rename to cmd/registry/push/push_suite_test.go index 17990b340..f1c498570 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" @@ -35,9 +35,9 @@ import ( ) 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" ) var ( @@ -49,15 +49,16 @@ var ( port int orasRegistry *remote.Registry configFile string + err error + args []string ) func TestPush(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") + RunSpecs(t, "Push Suite") } var _ = BeforeSuite(func() { 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 d7421448e..4bf63c30d 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" diff --git a/cmd/root_test.go b/cmd/root_test.go deleted file mode 100644 index c0e191898..000000000 --- a/cmd/root_test.go +++ /dev/null @@ -1,32 +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("registry command", func() { - Context("push subcommand", func() { - var _ = registryPushTests - }) - }) -}) diff --git a/go.mod b/go.mod index 82b74d5b0..19a83d002 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,8 @@ require ( github.com/distribution/distribution/v3 v3.0.0-20220907155224-78b9c98c5c31 github.com/docker/cli v20.10.17+incompatible github.com/docker/docker v20.10.24+incompatible + github.com/go-oauth2/oauth2/v4 v4.5.2 + github.com/golang-jwt/jwt v3.2.2+incompatible github.com/mitchellh/mapstructure v1.4.1 github.com/onsi/ginkgo/v2 v2.9.2 github.com/onsi/gomega v1.27.4 @@ -17,6 +19,7 @@ require ( github.com/spf13/cobra v1.5.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.8.1 + golang.org/x/crypto v0.1.0 gopkg.in/yaml.v3 v3.0.1 gotest.tools v2.2.0+incompatible oras.land/oras-go/v2 v2.2.0 @@ -54,6 +57,7 @@ require ( github.com/gomodule/redigo v1.8.2 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect + github.com/google/uuid v1.1.2 // indirect github.com/gookit/color v1.5.0 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/mux v1.8.0 // indirect @@ -77,11 +81,18 @@ require ( github.com/spf13/cast v1.3.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect + github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 // indirect + github.com/tidwall/buntdb v1.1.2 // indirect + github.com/tidwall/gjson v1.12.1 // indirect + github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e // indirect + github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 // indirect github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940 // indirect github.com/yvasiyarov/gorelic v0.0.7 // indirect github.com/yvasiyarov/newrelic_platform_go v0.0.0-20160601141957-9c099fbc30e9 // indirect - golang.org/x/crypto v0.1.0 // indirect golang.org/x/text v0.8.0 // indirect golang.org/x/tools v0.7.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 765ca60a3..5d60e3ac9 100644 --- a/go.sum +++ b/go.sum @@ -55,11 +55,15 @@ github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/O github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= 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/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -123,12 +127,18 @@ 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.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 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.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +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 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +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-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -141,6 +151,9 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +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-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-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= @@ -149,6 +162,9 @@ github.com/gofrs/uuid v4.3.0+incompatible h1:CaSVZxm5B+7o45rtab4jC2G37WGYX1zQfuU github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 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/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -197,6 +213,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -215,6 +233,8 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -227,6 +247,8 @@ github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH 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 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= @@ -251,8 +273,11 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +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/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -268,10 +293,13 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 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/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +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/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.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U= +github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE= @@ -293,7 +321,9 @@ github.com/lithammer/fuzzysearch v1.1.5/go.mod h1:1R1LRNk7yKid1BaQkmuLQaHruxcC4H github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= @@ -316,10 +346,18 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +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/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +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/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -383,7 +421,9 @@ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUA github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -421,8 +461,45 @@ github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8 github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E= +github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= +github.com/tidwall/buntdb v1.1.2 h1:noCrqQXL9EKMtcdwJcmuVKSEjqu1ua99RHHgbLTEHRo= +github.com/tidwall/buntdb v1.1.2/go.mod h1:xAzi36Hir4FarpSHyfuZ6JzPJdjRZ8QlLZSntE2mqlI= +github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= +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 h1:5NSYaAdrnblKByzd7XByQEJVT8+9v0W/tIY0Oo4OwrE= +github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M= +github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= +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.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e h1:+NL1GDIUOKxVfbp2KoJQD9cTQ6dyP2co9q4yzmT9FZo= +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 h1:Otn9S136ELckZ3KKDyCkxapfufrqDqwmGjcHfAyXRrE= +github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ= +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 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4= +github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/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 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +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/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.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -455,6 +532,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -494,6 +572,7 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -518,6 +597,7 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -531,6 +611,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -564,6 +646,7 @@ golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -576,13 +659,16 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20200106162015-b016eb3dc98e/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= @@ -593,6 +679,7 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/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-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -615,7 +702,9 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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-20220114195835-da31bd327af9/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -812,8 +901,10 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -824,6 +915,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/test/registry.go b/pkg/test/registry.go index 573259bc5..01db5f3ef 100644 --- a/pkg/test/registry.go +++ b/pkg/test/registry.go @@ -16,12 +16,47 @@ 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" + "strings" + "time" + + 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" "github.com/distribution/distribution/v3/configuration" "github.com/distribution/distribution/v3/registry" ) +const ( + maxRequests int = 15 + expiryTime = 10 * time.Second +) + +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 +73,89 @@ func FreePort() (int, error) { return l.Addr().(*net.TCPAddr).Port, nil } +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: %v", 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: %v", 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: %v", err) + } + + certPath := path.Join(tmpDir, name+".pem") + certOut, err := os.Create(certPath) + if err != nil { + return nil, fmt.Errorf("failed to create pem: %v", err) + } + if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { + return nil, fmt.Errorf("failed to write data to %s: %v", certPath, err) + } + if err := certOut.Close(); err != nil { + return nil, fmt.Errorf("error closing %s: %v", certPath, err) + } + + pkPath := path.Join(tmpDir, "key.pem") + keyOut, err := os.OpenFile(pkPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) + if err != nil { + return nil, fmt.Errorf("failed to open %s for writing: %v", tmpDir, err) + } + privBytes, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + return nil, fmt.Errorf("unable to marshal private key: %v", 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: %v", err) + } + if err := keyOut.Close(); err != nil { + return nil, fmt.Errorf("error closing %s: %v", 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 +171,133 @@ func StartRegistry(ctx context.Context, cfg *configuration.Configuration) error // Start serving in goroutine and listen for stop signal in main thread return reg.ListenAndServe() } + +func StartOAuthServer(ctx context.Context, port int) error { + + manager := manage.NewDefaultManager() + // token memory store + manager.MustTokenStorage(store.NewMemoryTokenStore()) + + // client memory store + clientStore := store.NewClientStore() + clientStore.Set("000000", &models.Client{ + ID: "000000", + Secret: "999999", + Domain: "http://localhost:3000/callback", + UserID: "user", + }) + 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) + } + }) + + http.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) { + srv.HandleTokenRequest(w, r) + }) + + http.HandleFunc("/hitme", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("ok hit")) + }) + + // 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) + } + + if duration := ti.GetAccessExpiresIn(); duration <= 0 { + http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) + } + }) + + authServerHost := fmt.Sprintf(":%d",port) + log.Fatal(http.ListenAndServe(authServerHost, nil)) + return nil +} + +// formatRequest generates ascii representation of a request +func formatRequest(r *http.Request) string { + // Create return string + var request []string + // Add the request string + url := fmt.Sprintf("%v %v %v", r.Method, r.URL, r.Proto) + request = append(request, url) + // Add the host + request = append(request, fmt.Sprintf("Host: %v", r.Host)) + // Loop through headers + for name, headers := range r.Header { + name = strings.ToLower(name) + for _, h := range headers { + request = append(request, fmt.Sprintf("%v: %v", name, h)) + } + } + + // If this is a POST, add post data + if r.Method == "POST" { + r.ParseForm() + request = append(request, "\n") + request = append(request, r.Form.Encode()) + } + + request = append(request, "---------------------------------") + // Return the request as a string + return strings.Join(request, "\n") +}