Skip to content

Commit a378b19

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 <github@gone.nl>
1 parent e769549 commit a378b19

6 files changed

Lines changed: 135 additions & 23 deletions

File tree

cli/config/credentials/default_store.go

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,56 @@ func DetectDefaultStore(customStore string) string {
1616
// use user-defined
1717
return customStore
1818
}
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
1933

20-
platformDefault := defaultCredentialsStore()
21-
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 == "" {
2249
return ""
2350
}
2451

25-
if _, err := exec.LookPath(remoteCredentialsPrefix + platformDefault); err != nil {
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 {
2661
return ""
2762
}
28-
return platformDefault
63+
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 {
67+
return ""
68+
}
69+
70+
return preferred
2971
}
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)