From b43677c9fd7f04c1ebf8265a0b14fc8ed70e4d66 Mon Sep 17 00:00:00 2001 From: haircommander Date: Fri, 8 Jun 2018 17:56:25 -0400 Subject: Added --tls-verify functionality to podman search, with tests Signed-off-by: haircommander Closes: #932 Approved by: baude --- cmd/podman/search.go | 73 +++++++++++++++++---- docs/podman-search.1.md | 6 ++ test/e2e/libpod_suite_test.go | 33 +++++++++- test/e2e/search_test.go | 144 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 241 insertions(+), 15 deletions(-) diff --git a/cmd/podman/search.go b/cmd/podman/search.go index 803661753..a5eb580cd 100644 --- a/cmd/podman/search.go +++ b/cmd/podman/search.go @@ -2,16 +2,19 @@ package main import ( "context" + "fmt" "reflect" "strconv" "strings" "github.com/containers/image/docker" + "github.com/containers/image/types" "github.com/pkg/errors" "github.com/projectatomic/libpod/cmd/podman/formats" "github.com/projectatomic/libpod/cmd/podman/libpodruntime" "github.com/projectatomic/libpod/libpod/common" sysreg "github.com/projectatomic/libpod/pkg/registries" + "github.com/projectatomic/libpod/pkg/util" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -43,6 +46,10 @@ var ( Name: "registry", Usage: "specific registry to search", }, + cli.BoolTFlag{ + Name: "tls-verify", + Usage: "require HTTPS and verify certificates when contacting registries (default: true)", + }, } searchDescription = ` Search registries for a given image. Can search all the default registries or a specific registry. @@ -106,15 +113,9 @@ func searchCmd(c *cli.Context) error { limit: c.Int("limit"), filter: c.StringSlice("filter"), } - - var registries []string - if len(c.StringSlice("registry")) > 0 { - registries = c.StringSlice("registry") - } else { - registries, err = sysreg.GetRegistries() - if err != nil { - return errors.Wrapf(err, "error getting registries to search") - } + registries, sc, err := getSystemContextAndRegistries(c) + if err != nil { + return err } filter, err := parseSearchFilter(&opts) @@ -122,7 +123,7 @@ func searchCmd(c *cli.Context) error { return err } - return generateSearchOutput(term, registries, opts, *filter) + return generateSearchOutput(term, registries, opts, *filter, sc) } func genSearchFormat(format string) string { @@ -153,8 +154,54 @@ func (s *searchParams) headerMap() map[string]string { return values } -func getSearchOutput(term string, registries []string, opts searchOpts, filter searchFilterParams) ([]searchParams, error) { +// A wrapper for GetSystemContext and GetInsecureRegistries +// Sets up system context and active list of registries to search with +func getSystemContextAndRegistries(c *cli.Context) ([]string, *types.SystemContext, error) { sc := common.GetSystemContext("", "", false) + + // Variables for setting up Registry and TLSVerify + tlsVerify := c.BoolT("tls-verify") + forceSecure := false + + if c.IsSet("tls-verify") { + forceSecure = c.BoolT("tls-verify") + } + + var registries []string + if len(c.StringSlice("registry")) > 0 { + registries = c.StringSlice("registry") + } else { + var err error + registries, err = sysreg.GetRegistries() + if err != nil { + return nil, nil, errors.Wrapf(err, "error getting registries to search") + } + } + + // If user flagged to skip verify for HTTP connections, set System Context as such + if !tlsVerify { + // If tls-verify is set to false, allow insecure always. + sc.DockerInsecureSkipTLSVerify = true + } else if !forceSecure { + // if the user didn't allow nor disallow insecure registries, check to see if the registry is insecure + insecureRegistries, err := sysreg.GetInsecureRegistries() + if err != nil { + return nil, nil, errors.Wrapf(err, "error getting insecure registries to search") + } + + for _, reg := range insecureRegistries { + // if there are any insecure registries in registries, allow for HTTP + if util.StringInSlice(reg, registries) { + sc.DockerInsecureSkipTLSVerify = true + logrus.Info(fmt.Sprintf("%s is an insecure registry; searching with tls-verify=false", reg)) + break + } + } + } + return registries, sc, nil +} + +func getSearchOutput(term string, registries []string, opts searchOpts, filter searchFilterParams, sc *types.SystemContext) ([]searchParams, error) { // Max number of queries by default is 25 limit := maxQueries if opts.limit != 0 { @@ -222,8 +269,8 @@ func getSearchOutput(term string, registries []string, opts searchOpts, filter s return paramsArr, nil } -func generateSearchOutput(term string, registries []string, opts searchOpts, filter searchFilterParams) error { - searchOutput, err := getSearchOutput(term, registries, opts, filter) +func generateSearchOutput(term string, registries []string, opts searchOpts, filter searchFilterParams, sc *types.SystemContext) error { + searchOutput, err := getSearchOutput(term, registries, opts, filter, sc) if err != nil { return err } diff --git a/docs/podman-search.1.md b/docs/podman-search.1.md index 84a134200..432906821 100644 --- a/docs/podman-search.1.md +++ b/docs/podman-search.1.md @@ -70,6 +70,12 @@ Do not truncate the output Specific registry to search (only the given registry will be searched, not the default registries) +**--tls-verify** + +Require HTTPS and verify certificates when contacting registries (default: true). If explicitly set to true, +then tls verification will be used, If set to false then tls verification will not be used. If not specified +both insecured and default registries will be searched through, and tls will be used when possible. + ## EXAMPLES ``` diff --git a/test/e2e/libpod_suite_test.go b/test/e2e/libpod_suite_test.go index 9f59eb4a6..d06818fd3 100644 --- a/test/e2e/libpod_suite_test.go +++ b/test/e2e/libpod_suite_test.go @@ -2,6 +2,7 @@ package integration import ( "context" + "encoding/json" "fmt" "io/ioutil" "os" @@ -12,8 +13,6 @@ import ( "testing" "time" - "encoding/json" - "github.com/containers/image/copy" "github.com/containers/image/signature" "github.com/containers/image/storage" @@ -268,6 +267,36 @@ func (s *PodmanSession) OutputToStringArray() []string { return strings.Split(output, "\n") } +// ErrorGrepString takes session stderr output and behaves like grep. it returns a bool +// if successful and an array of strings on positive matches +func (s *PodmanSession) ErrorGrepString(term string) (bool, []string) { + var ( + greps []string + matches bool + ) + + for _, line := range strings.Split(s.ErrorToString(), "\n") { + if strings.Contains(line, term) { + matches = true + greps = append(greps, line) + } + } + return matches, greps +} + +// ErrorToString formats session stderr to string +func (s *PodmanSession) ErrorToString() string { + fields := strings.Fields(fmt.Sprintf("%s", s.Err.Contents())) + return strings.Join(fields, " ") +} + +// ErrorToStringArray returns the stderr output as a []string +// where each array item is a line split by newline +func (s *PodmanSession) ErrorToStringArray() []string { + output := fmt.Sprintf("%s", s.Err.Contents()) + return strings.Split(output, "\n") +} + // IsJSONOutputValid attempts to unmarshal the session buffer // and if successful, returns true, else false func (s *PodmanSession) IsJSONOutputValid() bool { diff --git a/test/e2e/search_test.go b/test/e2e/search_test.go index 5a814b139..96e1422ed 100644 --- a/test/e2e/search_test.go +++ b/test/e2e/search_test.go @@ -1,7 +1,9 @@ package integration import ( + "io/ioutil" "os" + "path/filepath" "strconv" . "github.com/onsi/ginkgo" @@ -14,13 +16,26 @@ var _ = Describe("Podman search", func() { err error podmanTest PodmanTest ) + const regFileContents = ` + [registries.search] + registries = ['localhost:5000'] + [registries.insecure] + registries = ['localhost:5000']` + + const badRegFileContents = ` + [registries.search] + registries = ['localhost:5000'] + # empty + [registries.insecure] + registries = []` BeforeEach(func() { tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) } podmanTest = PodmanCreate(tempdir) + podmanTest.RestoreAllArtifacts() }) AfterEach(func() { @@ -96,4 +111,133 @@ var _ = Describe("Podman search", func() { Expect(output[i]).To(Equal("")) } }) + + It("podman search attempts HTTP if tls-verify flag is set false", func() { + fakereg := podmanTest.Podman([]string{"run", "-d", "--name", "registry", "-p", "5000:5000", "docker.io/library/registry:2", "/entrypoint.sh", "/etc/docker/registry/config.yml"}) + fakereg.WaitWithDefaultTimeout() + Expect(fakereg.ExitCode()).To(Equal(0)) + + if !WaitContainerReady(&podmanTest, "registry", "listening on", 20, 1) { + Skip("Can not start docker registry.") + } + + search := podmanTest.Podman([]string{"search", "--registry", "localhost:5000", "fake/image:andtag", "--tls-verify=false"}) + search.WaitWithDefaultTimeout() + + // if this test succeeded, there will be no output (there is no entry named fake/image:andtag in an empty registry) + // and the exit code will be 0 + Expect(search.ExitCode()).To(Equal(0)) + Expect(search.OutputToString()).Should(BeEmpty()) + Expect(search.ErrorToString()).Should(BeEmpty()) + }) + + It("podman search in local registry", func() { + registry := podmanTest.Podman([]string{"run", "-d", "--name", "registry3", "-p", "5000:5000", "docker.io/library/registry:2", "/entrypoint.sh", "/etc/docker/registry/config.yml"}) + registry.WaitWithDefaultTimeout() + Expect(registry.ExitCode()).To(Equal(0)) + + if !WaitContainerReady(&podmanTest, "registry3", "listening on", 20, 1) { + Skip("Can not start docker registry.") + } + + push := podmanTest.Podman([]string{"push", "--tls-verify=false", "--remove-signatures", ALPINE, "localhost:5000/my-alpine"}) + push.WaitWithDefaultTimeout() + Expect(push.ExitCode()).To(Equal(0)) + search := podmanTest.Podman([]string{"search", "--registry", "localhost:5000", "my-alpine", "--tls-verify=false"}) + search.WaitWithDefaultTimeout() + + Expect(search.ExitCode()).To(Equal(0)) + Expect(search.OutputToString()).ShouldNot(BeEmpty()) + }) + + It("podman search attempts HTTP if registry is in registries.insecure and force secure is false", func() { + registry := podmanTest.Podman([]string{"run", "-d", "--name", "registry4", "-p", "5000:5000", "docker.io/library/registry:2", "/entrypoint.sh", "/etc/docker/registry/config.yml"}) + registry.WaitWithDefaultTimeout() + Expect(registry.ExitCode()).To(Equal(0)) + + if !WaitContainerReady(&podmanTest, "registry4", "listening on", 20, 1) { + Skip("Can not start docker registry.") + } + + push := podmanTest.Podman([]string{"push", "--tls-verify=false", "--remove-signatures", ALPINE, "localhost:5000/my-alpine"}) + push.WaitWithDefaultTimeout() + Expect(push.ExitCode()).To(Equal(0)) + + // registries.conf set up + regFileBytes := []byte(regFileContents) + outfile := filepath.Join(podmanTest.TempDir, "registries.conf") + os.Setenv("REGISTRIES_CONFIG_PATH", outfile) + ioutil.WriteFile(outfile, regFileBytes, 0644) + + search := podmanTest.Podman([]string{"search", "--registry", "localhost:5000", "my-alpine"}) + search.WaitWithDefaultTimeout() + + Expect(search.ExitCode()).To(Equal(0)) + match, _ := search.GrepString("my-alpine") + Expect(match).Should(BeTrue()) + Expect(search.ErrorToString()).Should(BeEmpty()) + + // cleanup + os.Setenv("REGISTRIES_CONFIG_PATH", "") + }) + + It("podman search doesn't attempt HTTP if force secure is true", func() { + registry := podmanTest.Podman([]string{"run", "-d", "-p", "5000:5000", "--name", "registry5", "registry:2"}) + registry.WaitWithDefaultTimeout() + Expect(registry.ExitCode()).To(Equal(0)) + + if !WaitContainerReady(&podmanTest, "registry5", "listening on", 20, 1) { + Skip("Can not start docker registry.") + } + push := podmanTest.Podman([]string{"push", "--tls-verify=false", "--remove-signatures", ALPINE, "localhost:5000/my-alpine"}) + push.WaitWithDefaultTimeout() + Expect(push.ExitCode()).To(Equal(0)) + + // registries.conf set up + regFileBytes := []byte(regFileContents) + outfile := filepath.Join(podmanTest.TempDir, "registries.conf") + os.Setenv("REGISTRIES_CONFIG_PATH", outfile) + ioutil.WriteFile(outfile, regFileBytes, 0644) + + search := podmanTest.Podman([]string{"search", "--registry", "localhost:5000", "my-alpine", "--tls-verify=true"}) + search.WaitWithDefaultTimeout() + + Expect(search.ExitCode()).To(Equal(0)) + Expect(search.OutputToString()).Should(BeEmpty()) + match, _ := search.ErrorGrepString("error") + Expect(match).Should(BeTrue()) + + // cleanup + os.Setenv("REGISTRIES_CONFIG_PATH", "") + }) + + It("podman search doesn't attempt HTTP if registry is not listed as insecure", func() { + registry := podmanTest.Podman([]string{"run", "-d", "-p", "5000:5000", "--name", "registry6", "registry:2"}) + registry.WaitWithDefaultTimeout() + Expect(registry.ExitCode()).To(Equal(0)) + + if !WaitContainerReady(&podmanTest, "registry6", "listening on", 20, 1) { + Skip("Can not start docker registry.") + } + push := podmanTest.Podman([]string{"push", "--tls-verify=false", "--remove-signatures", ALPINE, "localhost:5000/my-alpine"}) + push.WaitWithDefaultTimeout() + Expect(push.ExitCode()).To(Equal(0)) + + // registries.conf set up + regFileBytes := []byte(badRegFileContents) + outfile := filepath.Join(podmanTest.TempDir, "registries.conf") + os.Setenv("REGISTRIES_CONFIG_PATH", outfile) + ioutil.WriteFile(outfile, regFileBytes, 0644) + + search := podmanTest.Podman([]string{"search", "--registry", "localhost:5000", "my-alpine"}) + search.WaitWithDefaultTimeout() + + Expect(search.ExitCode()).To(Equal(0)) + Expect(search.OutputToString()).Should(BeEmpty()) + match, _ := search.ErrorGrepString("error") + Expect(match).Should(BeTrue()) + + // cleanup + os.Setenv("REGISTRIES_CONFIG_PATH", "") + }) }) -- cgit v1.2.3-54-g00ecf