From a2f0cf527bfffb7c35d8144f653cf2c299bd5f6c Mon Sep 17 00:00:00 2001 From: Chris Crone Date: Wed, 16 Sep 2020 16:35:04 +0200 Subject: [PATCH 1/3] context: Ensure import paths are valid Signed-off-by: Chris Crone (cherry picked from commit 6f49197cabdccf36e0d899e8c35a94cce5793716) Signed-off-by: Sebastiaan van Stijn --- cli/context/store/store.go | 27 +++++++++++++++++++++++---- cli/context/store/store_test.go | 4 ++-- cli/context/store/storeconfig_test.go | 16 ++++++++++++++++ 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/cli/context/store/store.go b/cli/context/store/store.go index b3cd4c54ed..5ad4d7759b 100644 --- a/cli/context/store/store.go +++ b/cli/context/store/store.go @@ -7,7 +7,6 @@ import ( "bytes" _ "crypto/sha256" // ensure ids can be computed "encoding/json" - "errors" "fmt" "io" "io/ioutil" @@ -18,6 +17,7 @@ import ( "github.com/docker/docker/errdefs" digest "github.com/opencontainers/go-digest" + "github.com/pkg/errors" ) // Store provides a context store for easily remembering endpoints configuration @@ -295,6 +295,19 @@ func Import(name string, s Writer, reader io.Reader) error { } } +func isValidFilePath(p string) error { + if p != metaFile && !strings.HasPrefix(p, "tls/") { + return errors.New("unexpected context file") + } + if path.Clean(p) != p { + return errors.New("unexpected path format") + } + if strings.Contains(p, `\`) { + return errors.New(`unexpected '\' in path`) + } + return nil +} + func importTar(name string, s Writer, reader io.Reader) error { tr := tar.NewReader(&LimitedReader{R: reader, N: maxAllowedFileSizeToImport}) tlsData := ContextTLSData{ @@ -309,10 +322,13 @@ func importTar(name string, s Writer, reader io.Reader) error { if err != nil { return err } - if hdr.Typeflag == tar.TypeDir { + if hdr.Typeflag != tar.TypeReg { // skip this entry, only taking files into account continue } + if err := isValidFilePath(hdr.Name); err != nil { + return errors.Wrap(err, hdr.Name) + } if hdr.Name == metaFile { data, err := ioutil.ReadAll(tr) if err != nil { @@ -358,10 +374,13 @@ func importZip(name string, s Writer, reader io.Reader) error { var importedMetaFile bool for _, zf := range zr.File { fi := zf.FileInfo() - if fi.IsDir() { - // skip this entry, only taking files into account + if !fi.Mode().IsRegular() { + // skip this entry, only taking regular files into account continue } + if err := isValidFilePath(zf.Name); err != nil { + return errors.Wrap(err, zf.Name) + } if zf.Name == metaFile { f, err := zf.Open() if err != nil { diff --git a/cli/context/store/store_test.go b/cli/context/store/store_test.go index df506061e5..4b412652de 100644 --- a/cli/context/store/store_test.go +++ b/cli/context/store/store_test.go @@ -175,7 +175,7 @@ func TestImportTarInvalid(t *testing.T) { var r io.Reader = source s := New(testDir, testCfg) err = Import("tarInvalid", s, r) - assert.ErrorContains(t, err, "invalid context: no metadata found") + assert.ErrorContains(t, err, "unexpected context file") } func TestImportZip(t *testing.T) { @@ -254,5 +254,5 @@ func TestImportZipInvalid(t *testing.T) { var r io.Reader = source s := New(testDir, testCfg) err = Import("zipInvalid", s, r) - assert.ErrorContains(t, err, "invalid context: no metadata found") + assert.ErrorContains(t, err, "unexpected context file") } diff --git a/cli/context/store/storeconfig_test.go b/cli/context/store/storeconfig_test.go index 7f59e3f015..e7384fb071 100644 --- a/cli/context/store/storeconfig_test.go +++ b/cli/context/store/storeconfig_test.go @@ -29,3 +29,19 @@ func TestConfigModification(t *testing.T) { assert.Equal(t, &testEP2{}, cfgCopy.endpointTypes["ep1"]()) assert.Equal(t, &testEP3{}, cfgCopy.endpointTypes["ep2"]()) } + +func TestValidFilePaths(t *testing.T) { + paths := map[string]bool{ + "tls/_/../../something": false, + "tls/../../something": false, + "../../something": false, + "/tls/absolute/unix/path": false, + `C:\tls\absolute\windows\path`: false, + "C:/tls/absolute/windows/path": false, + "tls/kubernetes/key.pem": true, + } + for p, expectedValid := range paths { + err := isValidFilePath(p) + assert.Equal(t, err == nil, expectedValid, "%q should report valid as: %v", p, expectedValid) + } +} From 8c2872d2a3c4d6ab109cc15ce002c4e32fbbd2b3 Mon Sep 17 00:00:00 2001 From: Chris Crone Date: Thu, 24 Sep 2020 16:24:24 +0200 Subject: [PATCH 2/3] context: Ensure context name is valid on import Signed-off-by: Chris Crone (cherry picked from commit 9ecc69d17e40cd0a03b6b03d8421bc415936d231) Signed-off-by: Sebastiaan van Stijn --- cli/command/context/cmd.go | 21 --------------------- cli/command/context/create.go | 2 +- cli/command/context/export.go | 2 +- cli/command/context/update.go | 2 +- cli/command/context/use.go | 3 ++- cli/context/store/store.go | 22 ++++++++++++++++++++++ cli/context/store/storeconfig_test.go | 13 +++++++++++++ 7 files changed, 40 insertions(+), 25 deletions(-) diff --git a/cli/command/context/cmd.go b/cli/command/context/cmd.go index 1b6898456d..6dce68aeaa 100644 --- a/cli/command/context/cmd.go +++ b/cli/command/context/cmd.go @@ -1,10 +1,6 @@ package context import ( - "errors" - "fmt" - "regexp" - "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/spf13/cobra" @@ -30,20 +26,3 @@ func NewContextCommand(dockerCli command.Cli) *cobra.Command { ) return cmd } - -const restrictedNamePattern = "^[a-zA-Z0-9][a-zA-Z0-9_.+-]+$" - -var restrictedNameRegEx = regexp.MustCompile(restrictedNamePattern) - -func validateContextName(name string) error { - if name == "" { - return errors.New("context name cannot be empty") - } - if name == "default" { - return errors.New(`"default" is a reserved context name`) - } - if !restrictedNameRegEx.MatchString(name) { - return fmt.Errorf("context name %q is invalid, names are validated against regexp %q", name, restrictedNamePattern) - } - return nil -} diff --git a/cli/command/context/create.go b/cli/command/context/create.go index 9d5865a70a..9e6fe0295b 100644 --- a/cli/command/context/create.go +++ b/cli/command/context/create.go @@ -137,7 +137,7 @@ func createNewContext(o *CreateOptions, stackOrchestrator command.Orchestrator, } func checkContextNameForCreation(s store.Reader, name string) error { - if err := validateContextName(name); err != nil { + if err := store.ValidateContextName(name); err != nil { return err } if _, err := s.GetMetadata(name); !store.IsErrContextDoesNotExist(err) { diff --git a/cli/command/context/export.go b/cli/command/context/export.go index fd66a5d490..6013071402 100644 --- a/cli/command/context/export.go +++ b/cli/command/context/export.go @@ -77,7 +77,7 @@ func writeTo(dockerCli command.Cli, reader io.Reader, dest string) error { // RunExport exports a Docker context func RunExport(dockerCli command.Cli, opts *ExportOptions) error { - if err := validateContextName(opts.ContextName); err != nil && opts.ContextName != command.DefaultContextName { + if err := store.ValidateContextName(opts.ContextName); err != nil && opts.ContextName != command.DefaultContextName { return err } ctxMeta, err := dockerCli.ContextStore().GetMetadata(opts.ContextName) diff --git a/cli/command/context/update.go b/cli/command/context/update.go index 9165bb3019..3c67fdd207 100644 --- a/cli/command/context/update.go +++ b/cli/command/context/update.go @@ -68,7 +68,7 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command { // RunUpdate updates a Docker context func RunUpdate(cli command.Cli, o *UpdateOptions) error { - if err := validateContextName(o.Name); err != nil { + if err := store.ValidateContextName(o.Name); err != nil { return err } s := cli.ContextStore() diff --git a/cli/command/context/use.go b/cli/command/context/use.go index 97e3a97056..a3998da69d 100644 --- a/cli/command/context/use.go +++ b/cli/command/context/use.go @@ -5,6 +5,7 @@ import ( "os" "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/context/store" "github.com/spf13/cobra" ) @@ -23,7 +24,7 @@ func newUseCommand(dockerCli command.Cli) *cobra.Command { // RunUse set the current Docker context func RunUse(dockerCli command.Cli, name string) error { - if err := validateContextName(name); err != nil && name != "default" { + if err := store.ValidateContextName(name); err != nil && name != "default" { return err } if _, err := dockerCli.ContextStore().GetMetadata(name); err != nil && name != "default" { diff --git a/cli/context/store/store.go b/cli/context/store/store.go index 5ad4d7759b..71220e9ac0 100644 --- a/cli/context/store/store.go +++ b/cli/context/store/store.go @@ -13,6 +13,7 @@ import ( "net/http" "path" "path/filepath" + "regexp" "strings" "github.com/docker/docker/errdefs" @@ -20,6 +21,10 @@ import ( "github.com/pkg/errors" ) +const restrictedNamePattern = "^[a-zA-Z0-9][a-zA-Z0-9_.+-]+$" + +var restrictedNameRegEx = regexp.MustCompile(restrictedNamePattern) + // Store provides a context store for easily remembering endpoints configuration type Store interface { Reader @@ -184,6 +189,20 @@ func (s *store) GetStorageInfo(contextName string) StorageInfo { } } +// ValidateContextName checks a context name is valid. +func ValidateContextName(name string) error { + if name == "" { + return errors.New("context name cannot be empty") + } + if name == "default" { + return errors.New(`"default" is a reserved context name`) + } + if !restrictedNameRegEx.MatchString(name) { + return fmt.Errorf("context name %q is invalid, names are validated against regexp %q", name, restrictedNamePattern) + } + return nil +} + // Export exports an existing namespace into an opaque data stream // This stream is actually a tarball containing context metadata and TLS materials, but it does // not map 1:1 the layout of the context store (don't try to restore it manually without calling store.Import) @@ -427,6 +446,9 @@ func parseMetadata(data []byte, name string) (Metadata, error) { if err := json.Unmarshal(data, &meta); err != nil { return meta, err } + if err := ValidateContextName(name); err != nil { + return Metadata{}, err + } meta.Name = name return meta, nil } diff --git a/cli/context/store/storeconfig_test.go b/cli/context/store/storeconfig_test.go index e7384fb071..4d9a3b63fb 100644 --- a/cli/context/store/storeconfig_test.go +++ b/cli/context/store/storeconfig_test.go @@ -45,3 +45,16 @@ func TestValidFilePaths(t *testing.T) { assert.Equal(t, err == nil, expectedValid, "%q should report valid as: %v", p, expectedValid) } } + +func TestValidateContextName(t *testing.T) { + names := map[string]bool{ + "../../invalid/escape": false, + "/invalid/absolute": false, + `\invalid\windows`: false, + "validname": true, + } + for n, expectedValid := range names { + err := ValidateContextName(n) + assert.Equal(t, err == nil, expectedValid, "%q should report valid as: %v", n, expectedValid) + } +} From b43b85203156095cf5e436f80dd169fff294073e Mon Sep 17 00:00:00 2001 From: Chris Crone Date: Tue, 5 Jan 2021 14:12:52 +0100 Subject: [PATCH 3/3] context: Add tarball e2e tests Signed-off-by: Chris Crone (cherry picked from commit 18f33b337d7af97ba30ab03eeeab801574208e55) Signed-off-by: Sebastiaan van Stijn --- e2e/context/context_test.go | 73 ++++++++++++++++++ e2e/context/testdata/context-ls-notls.golden | 3 + e2e/context/testdata/context-ls-tls.golden | 3 + .../testdata/test-dockerconfig-tls.tar | Bin 0 -> 8335 bytes e2e/context/testdata/test-dockerconfig.tar | Bin 0 -> 1536 bytes 5 files changed, 79 insertions(+) create mode 100644 e2e/context/testdata/context-ls-notls.golden create mode 100644 e2e/context/testdata/context-ls-tls.golden create mode 100644 e2e/context/testdata/test-dockerconfig-tls.tar create mode 100644 e2e/context/testdata/test-dockerconfig.tar diff --git a/e2e/context/context_test.go b/e2e/context/context_test.go index ce22f7b7c3..6212b98dea 100644 --- a/e2e/context/context_test.go +++ b/e2e/context/context_test.go @@ -1,8 +1,11 @@ package context import ( + "io/ioutil" + "os" "testing" + "gotest.tools/v3/assert" "gotest.tools/v3/golden" "gotest.tools/v3/icmd" ) @@ -19,3 +22,73 @@ func TestContextList(t *testing.T) { }) golden.Assert(t, result.Stdout(), "context-ls.golden") } + +func TestContextImportNoTLS(t *testing.T) { + d, _ := ioutil.TempDir("", "") + defer func() { + os.RemoveAll(d) + }() + cmd := icmd.Command("docker", "context", "import", "remote", "./testdata/test-dockerconfig.tar") + cmd.Env = append(cmd.Env, + "DOCKER_CONFIG="+d, + ) + icmd.RunCmd(cmd).Assert(t, icmd.Success) + + cmd = icmd.Command("docker", "context", "ls") + cmd.Env = append(cmd.Env, + "DOCKER_CONFIG="+d, + "KUBECONFIG=./testdata/test-kubeconfig", // Allows reuse of context-ls.golden + ) + result := icmd.RunCmd(cmd).Assert(t, icmd.Success) + golden.Assert(t, result.Stdout(), "context-ls.golden") +} + +func TestContextImportTLS(t *testing.T) { + d, _ := ioutil.TempDir("", "") + defer func() { + os.RemoveAll(d) + }() + cmd := icmd.Command("docker", "context", "import", "test", "./testdata/test-dockerconfig-tls.tar") + cmd.Env = append(cmd.Env, + "DOCKER_CONFIG="+d, + ) + icmd.RunCmd(cmd).Assert(t, icmd.Success) + + cmd = icmd.Command("docker", "context", "ls") + cmd.Env = append(cmd.Env, + "DOCKER_CONFIG="+d, + ) + result := icmd.RunCmd(cmd).Assert(t, icmd.Success) + golden.Assert(t, result.Stdout(), "context-ls-tls.golden") + + b, err := ioutil.ReadFile(d + "/contexts/tls/9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08/kubernetes/key.pem") + assert.NilError(t, err) + assert.Equal(t, string(b), `-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEArQk77K5sgrQYY6HiQ1y7AC+67HrRB36oEvR+Fq60RsFcc3cZ +xAvMkRSBPjQyskjdYY7kfykGHhfJxGKopb3cDJx3eDBxjgAniwnnOMmHVWbf8Eik +o0sNxkgzQPGq83nL3QvVxm3xgqe4nlTdR/Swoq6Pv0oaVYvPPMnaZIF89SJ/wlNT +myCs6Uq00dICi20II+M2Nw9b+EVEK4ENl+SlrsK7iuoBIh/H0ZghxOthO9J/HeBb +hmM4wcs1OonhPDYKHEaChYA7/Q3/8OBp3bAdlQJ1ziyP3ROAKHL2NwwkGZ8o8HP8 +u0ex/NAb8w5J5WNePqYQd/sqfisfNpA5VIKcEQIDAQABAoIBABLo4W2aGi2mdMve +kxV9esoobSsOuO0ywDdiFK1x5i2dT/cmWuB70Z1BOmaL2cZ2BAt3TC1BVHPRcbFO +ftOuDfAq4Tt3P9Ge3rNpH6WrEGka1voxVhyqRRUYKtG8F0yIUOkVNAV9WllG7vwO +ligY63y7yuXCuWID51/jR0SYiglXz6G4gcJKFXtugXXiLUIg08GVWkwOsrACC+hR +mhcHly1926VhN5+ozjNU/GZ1LaTuK6erBZakH5bqlN97s5rrk0ZRwk/JtnkoRRdI +cq0918Za2vqGDHZ3MqLttL52YfDXPIEJPwlFdvC/+sXK2NhUB/xY4yuliU3sY0sf +XsIvIWECgYEAwD8AnZI0hnGv8hc6zJppHFRwhrtLZ+09SJwPv5Y4wxuuk5dzNkpf +xCNo5hjSVYA1MMmWG8p/sEXo2IyCT8sWDNCn9kieTXihxRxbj88Y2qA5O4N46Zy4 +kPngjkP5PPDMkwaQQgUr9LvlWS7P6OJkH18ZN8s3QhMaKcHu9FFT44UCgYEA5mte +mMSDf9hUK3IK+yrGX62qc2H+ecXN3Zf3nehyiz+dX4ZXhBwBkwJ/mHvuAZPfoFUN +Xg6cdyWFJg9ynm45JXnDjmYPGmFLn0mP3Mje/+SbbW2fdFWHJW/maqj4uUqqgQd+ +pGNzKXq34MzDrpsqIJ7AHu3LYVMOoLAVqC7LXh0CgYEAnLF9ZfFqQH7fgvouIeBl +dgLZKOf2AUJcJheVunnN0DF67K+P55tdTTfzY0CuB6SVNivI3uQBiYKh1AdKm5ET +auSTUmlEJi8B4/BGLQQG5QOdQoXZgsgLo5cX0b1To7k9dUTvRfCDMFoKCNPgAJiu +NOfFXTWU15VMSObaRmcXciUCgYEA5e1cXwsxwUAodZX+eTXs8ArHHQ47Nl55GFeN +wufybRuUuX7AE9cyhvUmSA3aqX5a144noaTo40fwftNJZ+jLY6cGyjDzfzp5kMCC +KynSxPzlUCPkytyR2Hy6K9LjJ1rnm4vUBswqXcjUdiE+Xxz8w8JGKlbV7Q9JeHVd +lw7i5s0CgYAn9T9ySI3xCbrUa/XV/ZY2hopUdH5CDPeTd2eH+L+lctkD9nlzLrpj +qij+jaEUweymNx0uttgv02J3DYcIIvVq3RNAwORy5Mp9KasHmjbW2xq+HAq5yFOO +1ma82F5zeUl+bKqjMRCY8IVZ349VxRZtb2RVVEKyVswb7HmKp6gGbA== +-----END RSA PRIVATE KEY----- +`) +} diff --git a/e2e/context/testdata/context-ls-notls.golden b/e2e/context/testdata/context-ls-notls.golden new file mode 100644 index 0000000000..1747503305 --- /dev/null +++ b/e2e/context/testdata/context-ls-notls.golden @@ -0,0 +1,3 @@ +NAME DESCRIPTION DOCKER ENDPOINT KUBERNETES ENDPOINT ORCHESTRATOR +default * Current DOCKER_HOST based configuration unix:///var/run/docker.sock swarm +remote my remote cluster ssh://someserver https://someserver (default) kubernetes diff --git a/e2e/context/testdata/context-ls-tls.golden b/e2e/context/testdata/context-ls-tls.golden new file mode 100644 index 0000000000..f0f0d464e7 --- /dev/null +++ b/e2e/context/testdata/context-ls-tls.golden @@ -0,0 +1,3 @@ +NAME DESCRIPTION DOCKER ENDPOINT KUBERNETES ENDPOINT ORCHESTRATOR +default * Current DOCKER_HOST based configuration unix:///var/run/docker.sock swarm +test unix:///var/run/docker.sock https://kubernetes.docker.internal:6443 (default) swarm diff --git a/e2e/context/testdata/test-dockerconfig-tls.tar b/e2e/context/testdata/test-dockerconfig-tls.tar new file mode 100644 index 0000000000000000000000000000000000000000..6dc21a601421ecbbb22119a38f2b0d175e46a152 GIT binary patch literal 8335 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 literal 0 HcmV?d00001 diff --git a/e2e/context/testdata/test-dockerconfig.tar b/e2e/context/testdata/test-dockerconfig.tar new file mode 100644 index 0000000000000000000000000000000000000000..92eab23bda94661456a55c023f3c8dc4b227ff37 GIT binary patch literal 1536 zcmd^8O;5ux4DC;;a$dU@_JMk45(hpGLtGGtkh(OqO{&-(Ow;~54i#G22~FxQ_>efx z^Rr(RH$XCeM{9!dEMZ&BW`WLT#^*oPFw>jr(4q2Fr?XrJt|Z+KN~nkuSu%v!Ko6E zoFwZQS9P!83=p8$PsGaPt<{Fm6XsS`oX6gcMPe*snN6o?8$fWIp0d*I$a4s*t4DS7f_9(SM#H>9E`@%sQBaW`A?S1bP<{9 literal 0 HcmV?d00001