From 98f5f0697a13b73d71522d2f39889a9720a29f57 Mon Sep 17 00:00:00 2001 From: Steven Li Date: Wed, 21 Oct 2026 23:22:46 -0400 Subject: [PATCH] cli/context/store: create directories with 0700 permissions The config-file writer creates the config directory (~/.docker) with 0700 permissions, but the context store created its directories with 0755. Depending on which command ran first (for example, running "docker context create" when ~/.docker did not exist yet), the config directory itself was created with 0755 permissions. Create the metadata directories with 0700 permissions, and remove the step that created the parent of the TLS store with 0755 permissions before creating the TLS directories; os.MkdirAll creates any missing parent directories with the same (0700) permissions. Existing directories are not modified. Co-authored-by: Claude Opus-5-5 Signed-off-by: Steven Li --- cli/context/store/metadata_test.go | 24 ++++++++++++++++++++++++ cli/context/store/metadatastore.go | 2 +- cli/context/store/tlsstore.go | 4 ---- cli/context/store/tlsstore_test.go | 30 ++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 5 deletions(-) diff --git a/cli/context/store/metadata_test.go b/cli/context/store/metadata_test.go index 8c6389894b2d..8514fc420390 100644 --- a/cli/context/store/metadata_test.go +++ b/cli/context/store/metadata_test.go @@ -6,11 +6,13 @@ package store import ( "os" "path/filepath" + "runtime" "testing" "github.com/containerd/errdefs" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" + "gotest.tools/v3/skip" ) func testMetadata(name string) Metadata { @@ -73,6 +75,28 @@ func TestMetadataRespectJsonAnnotation(t *testing.T) { assert.Assert(t, is.Contains(string(bytes), "another_very_recognizable_field_name")) } +func TestMetadataCreateDirectoryPermissions(t *testing.T) { + skip.If(t, runtime.GOOS == "windows", "unix permissions are not supported on Windows") + + configDir := filepath.Join(t.TempDir(), ".docker") + testee := metadataStore{root: filepath.Join(configDir, "contexts", metadataDir), config: testCfg} + assert.NilError(t, testee.createOrUpdate(testMetadata("test"))) + + // All directories, including the config-directory if it did not yet exist, + // should only be accessible by the current user, matching the permissions + // used when saving the config-file. + for _, dir := range []string{ + configDir, + filepath.Dir(testee.root), + testee.root, + testee.contextDir(contextdirOf("test")), + } { + fi, err := os.Stat(dir) + assert.NilError(t, err) + assert.Check(t, is.Equal(fi.Mode().Perm(), os.FileMode(0o700)), dir) + } +} + func TestMetadataList(t *testing.T) { testee := metadataStore{root: t.TempDir(), config: testCfg} wholeData := []Metadata{ diff --git a/cli/context/store/metadatastore.go b/cli/context/store/metadatastore.go index 1751e820690b..bf6b264d2aee 100644 --- a/cli/context/store/metadatastore.go +++ b/cli/context/store/metadatastore.go @@ -32,7 +32,7 @@ func (s *metadataStore) contextDir(id contextdir) string { func (s *metadataStore) createOrUpdate(meta Metadata) error { contextDir := s.contextDir(contextdirOf(meta.Name)) - if err := os.MkdirAll(contextDir, 0o755); err != nil { + if err := os.MkdirAll(contextDir, 0o700); err != nil { return err } bytes, err := json.Marshal(&meta) diff --git a/cli/context/store/tlsstore.go b/cli/context/store/tlsstore.go index c1f5f8caa2cb..f49982b4ad21 100644 --- a/cli/context/store/tlsstore.go +++ b/cli/context/store/tlsstore.go @@ -23,10 +23,6 @@ func (s *tlsStore) endpointDir(name, endpointName string) string { } func (s *tlsStore) createOrUpdate(name, endpointName, filename string, data []byte) error { - parentOfRoot := filepath.Dir(s.root) - if err := os.MkdirAll(parentOfRoot, 0o755); err != nil { - return err - } endpointDir := s.endpointDir(name, endpointName) if err := os.MkdirAll(endpointDir, 0o700); err != nil { return err diff --git a/cli/context/store/tlsstore_test.go b/cli/context/store/tlsstore_test.go index 799362d54e14..274fd3986c25 100644 --- a/cli/context/store/tlsstore_test.go +++ b/cli/context/store/tlsstore_test.go @@ -1,10 +1,15 @@ package store import ( + "os" + "path/filepath" + "runtime" "testing" "github.com/containerd/errdefs" "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" + "gotest.tools/v3/skip" ) func TestTlsCreateUpdateGetRemove(t *testing.T) { @@ -32,6 +37,31 @@ func TestTlsCreateUpdateGetRemove(t *testing.T) { assert.ErrorType(t, err, errdefs.IsNotFound) } +func TestTlsCreateDirectoryPermissions(t *testing.T) { + skip.If(t, runtime.GOOS == "windows", "unix permissions are not supported on Windows") + + configDir := filepath.Join(t.TempDir(), ".docker") + testee := tlsStore{root: filepath.Join(configDir, "contexts", tlsDir)} + + const contextName = "test-ctx" + assert.NilError(t, testee.createOrUpdate(contextName, "test-ep", "test-data", []byte("data"))) + + // All directories, including the config-directory if it did not yet exist, + // should only be accessible by the current user, matching the permissions + // used when saving the config-file. + for _, dir := range []string{ + configDir, + filepath.Dir(testee.root), + testee.root, + testee.contextDir(contextName), + testee.endpointDir(contextName, "test-ep"), + } { + fi, err := os.Stat(dir) + assert.NilError(t, err) + assert.Check(t, is.Equal(fi.Mode().Perm(), os.FileMode(0o700)), dir) + } +} + func TestTlsListAndBatchRemove(t *testing.T) { testee := tlsStore{root: t.TempDir()}