Skip to content

Commit 555aba2

Browse files
committed
cli/config/credentials: refactor DetectDefaultStore and add tests
Refactor the DetectDefaultStore to allow testing it cross-platform, and without the actual helpers installed. This also makes a small change in the logic for detecting the preferred helper. Previously, it only detected the "helper" binary ("pass"), but would fall back to using plain-text if the pass credentials-helper was not installed. With this patch, it falls back to the platform default (secretservice), before falling back to using no credentials helper (and storing unencrypted). Signed-off-by: Sebastiaan van Stijn <[email protected]>
1 parent 32ff200 commit 555aba2

File tree

6 files changed

+147
-28
lines changed

6 files changed

+147
-28
lines changed

cli/config/credentials/default_store.go

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,70 @@ package credentials
22

33
import "os/exec"
44

5-
// DetectDefaultStore return the default credentials store for the platform if
6-
// no user-defined store is passed, and the store executable is available.
7-
func DetectDefaultStore(store string) string {
8-
if store != "" {
5+
// DetectDefaultStore returns the credentials store to use if no user-defined
6+
// custom helper is passed.
7+
//
8+
// Some platforms define a preferred helper, in which case it attempts to look
9+
// up the helper binary before falling back to the platform's default.
10+
//
11+
// If no user-defined helper is passed, and no helper is found, it returns an
12+
// empty string, which means credentials are stored unencrypted in the CLI's
13+
// config-file without the use of a credentials store.
14+
func DetectDefaultStore(customStore string) string {
15+
if customStore != "" {
916
// use user-defined
10-
return store
17+
return customStore
1118
}
19+
if preferred := findPreferredHelper(); preferred != "" {
20+
return preferred
21+
}
22+
if defaultHelper == "" {
23+
return ""
24+
}
25+
if _, err := exec.LookPath(remoteCredentialsPrefix + defaultHelper); err != nil {
26+
return ""
27+
}
28+
return defaultHelper
29+
}
30+
31+
// overridePreferred is used to override the preferred helper in tests.
32+
var overridePreferred string
1233

13-
platformDefault := defaultCredentialsStore()
14-
if platformDefault == "" {
34+
// findPreferredHelper detects whether the preferred credentials-store and
35+
// its helper binaries are installed. It returns the name of the preferred
36+
// store if found, otherwise returns an empty string to fall back to resolving
37+
// the default helper.
38+
//
39+
// Note that the logic below is currently specific to detection needed for the
40+
// "pass" credentials-helper on Linux (which is the only platform with a preferred
41+
// helper). It is put in a non-platform specific file to allow running tests
42+
// on other platforms.
43+
func findPreferredHelper() string {
44+
preferred := preferredHelper
45+
if overridePreferred != "" {
46+
preferred = overridePreferred
47+
}
48+
if preferred == "" {
49+
return ""
50+
}
51+
52+
// Note that the logic below is specific to detection needed for the
53+
// "pass" credentials-helper on Linux (which is the only platform with
54+
// a "preferred" and "default" helper. This logic may change if a similar
55+
// order of preference is needed on other platforms.
56+
57+
// If we don't have the preferred helper installed, there's no need
58+
// to check if its dependencies are installed, instead, try to
59+
// use the default credentials-helper for this platform (if installed).
60+
if _, err := exec.LookPath(remoteCredentialsPrefix + preferred); err != nil {
1561
return ""
1662
}
1763

18-
if _, err := exec.LookPath(remoteCredentialsPrefix + platformDefault); err != nil {
64+
// Detect if the helper binary is present as well. This is needed for
65+
// the "pass" credentials helper, which uses this binary.
66+
if _, err := exec.LookPath(preferred); err != nil {
1967
return ""
2068
}
21-
return platformDefault
69+
70+
return preferred
2271
}
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package credentials
22

3-
func defaultCredentialsStore() string {
4-
return "osxkeychain"
5-
}
3+
const (
4+
preferredHelper = ""
5+
defaultHelper = "osxkeychain"
6+
)
Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
package credentials
22

3-
import (
4-
"os/exec"
3+
const (
4+
preferredHelper = "pass"
5+
defaultHelper = "secretservice"
56
)
6-
7-
func defaultCredentialsStore() string {
8-
if _, err := exec.LookPath("pass"); err == nil {
9-
return "pass"
10-
}
11-
12-
return "secretservice"
13-
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package credentials
2+
3+
import (
4+
"os"
5+
"path"
6+
"testing"
7+
8+
"gotest.tools/v3/assert"
9+
)
10+
11+
func TestDetectDefaultStore(t *testing.T) {
12+
tmpDir := t.TempDir()
13+
t.Setenv("PATH", tmpDir)
14+
15+
t.Run("none available", func(t *testing.T) {
16+
const expected = ""
17+
assert.Equal(t, expected, DetectDefaultStore(""))
18+
})
19+
t.Run("custom helper", func(t *testing.T) {
20+
const expected = "my-custom-helper"
21+
assert.Equal(t, expected, DetectDefaultStore(expected))
22+
23+
// Custom helper should be used even if the actual helper exists
24+
createFakeHelper(t, path.Join(tmpDir, remoteCredentialsPrefix+defaultHelper))
25+
assert.Equal(t, expected, DetectDefaultStore(expected))
26+
})
27+
t.Run("default", func(t *testing.T) {
28+
createFakeHelper(t, path.Join(tmpDir, remoteCredentialsPrefix+defaultHelper))
29+
expected := defaultHelper
30+
assert.Equal(t, expected, DetectDefaultStore(""))
31+
})
32+
33+
// On Linux, the "pass" credentials helper requires both a "pass" binary
34+
// to be present and a "docker-credentials-pass" credentials helper to
35+
// be installed.
36+
t.Run("preferred helper", func(t *testing.T) {
37+
// Create the default helper as we need it for the fallback.
38+
createFakeHelper(t, path.Join(tmpDir, remoteCredentialsPrefix+defaultHelper))
39+
40+
const testPreferredHelper = "preferred"
41+
overridePreferred = testPreferredHelper
42+
43+
// Use preferred helper if both binaries exist.
44+
t.Run("success", func(t *testing.T) {
45+
createFakeHelper(t, path.Join(tmpDir, testPreferredHelper))
46+
createFakeHelper(t, path.Join(tmpDir, remoteCredentialsPrefix+testPreferredHelper))
47+
expected := testPreferredHelper
48+
assert.Equal(t, expected, DetectDefaultStore(""))
49+
})
50+
51+
// Fall back to the default helper if the preferred credentials-helper isn't installed.
52+
t.Run("not installed", func(t *testing.T) {
53+
createFakeHelper(t, path.Join(tmpDir, remoteCredentialsPrefix+testPreferredHelper))
54+
expected := defaultHelper
55+
assert.Equal(t, expected, DetectDefaultStore(""))
56+
})
57+
58+
// Similarly, fall back to the default helper if the preferred credentials-helper
59+
// is installed, but the helper binary isn't found.
60+
t.Run("missing helper", func(t *testing.T) {
61+
createFakeHelper(t, path.Join(tmpDir, testPreferredHelper))
62+
expected := defaultHelper
63+
assert.Equal(t, expected, DetectDefaultStore(""))
64+
})
65+
})
66+
}
67+
68+
func createFakeHelper(t *testing.T, fileName string) {
69+
t.Helper()
70+
assert.NilError(t, os.WriteFile(fileName, []byte("I'm a credentials-helper executable (really!)"), 0o700))
71+
t.Cleanup(func() {
72+
assert.NilError(t, os.RemoveAll(fileName))
73+
})
74+
}

cli/config/credentials/default_store_unsupported.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
package credentials
44

5-
func defaultCredentialsStore() string {
6-
return ""
7-
}
5+
const (
6+
preferredHelper = ""
7+
defaultHelper = ""
8+
)
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package credentials
22

3-
func defaultCredentialsStore() string {
4-
return "wincred"
5-
}
3+
const (
4+
preferredHelper = ""
5+
defaultHelper = "wincred"
6+
)

0 commit comments

Comments
 (0)