mirror of https://github.com/docker/cli.git
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>
This commit is contained in:
parent
32ff200fe6
commit
555aba27af
|
@ -2,21 +2,70 @@ package credentials
|
||||||
|
|
||||||
import "os/exec"
|
import "os/exec"
|
||||||
|
|
||||||
// DetectDefaultStore return the default credentials store for the platform if
|
// DetectDefaultStore returns the credentials store to use if no user-defined
|
||||||
// no user-defined store is passed, and the store executable is available.
|
// custom helper is passed.
|
||||||
func DetectDefaultStore(store string) string {
|
//
|
||||||
if store != "" {
|
// Some platforms define a preferred helper, in which case it attempts to look
|
||||||
|
// up the helper binary before falling back to the platform's default.
|
||||||
|
//
|
||||||
|
// If no user-defined helper is passed, and no helper is found, it returns an
|
||||||
|
// empty string, which means credentials are stored unencrypted in the CLI's
|
||||||
|
// config-file without the use of a credentials store.
|
||||||
|
func DetectDefaultStore(customStore string) string {
|
||||||
|
if customStore != "" {
|
||||||
// use user-defined
|
// use user-defined
|
||||||
return store
|
return customStore
|
||||||
}
|
}
|
||||||
|
if preferred := findPreferredHelper(); preferred != "" {
|
||||||
platformDefault := defaultCredentialsStore()
|
return preferred
|
||||||
if platformDefault == "" {
|
}
|
||||||
|
if defaultHelper == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
if _, err := exec.LookPath(remoteCredentialsPrefix + defaultHelper); err != nil {
|
||||||
if _, err := exec.LookPath(remoteCredentialsPrefix + platformDefault); err != nil {
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return platformDefault
|
return defaultHelper
|
||||||
|
}
|
||||||
|
|
||||||
|
// overridePreferred is used to override the preferred helper in tests.
|
||||||
|
var overridePreferred string
|
||||||
|
|
||||||
|
// findPreferredHelper detects whether the preferred credentials-store and
|
||||||
|
// its helper binaries are installed. It returns the name of the preferred
|
||||||
|
// store if found, otherwise returns an empty string to fall back to resolving
|
||||||
|
// the default helper.
|
||||||
|
//
|
||||||
|
// Note that the logic below is currently specific to detection needed for the
|
||||||
|
// "pass" credentials-helper on Linux (which is the only platform with a preferred
|
||||||
|
// helper). It is put in a non-platform specific file to allow running tests
|
||||||
|
// on other platforms.
|
||||||
|
func findPreferredHelper() string {
|
||||||
|
preferred := preferredHelper
|
||||||
|
if overridePreferred != "" {
|
||||||
|
preferred = overridePreferred
|
||||||
|
}
|
||||||
|
if preferred == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that the logic below is specific to detection needed for the
|
||||||
|
// "pass" credentials-helper on Linux (which is the only platform with
|
||||||
|
// a "preferred" and "default" helper. This logic may change if a similar
|
||||||
|
// order of preference is needed on other platforms.
|
||||||
|
|
||||||
|
// If we don't have the preferred helper installed, there's no need
|
||||||
|
// to check if its dependencies are installed, instead, try to
|
||||||
|
// use the default credentials-helper for this platform (if installed).
|
||||||
|
if _, err := exec.LookPath(remoteCredentialsPrefix + preferred); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect if the helper binary is present as well. This is needed for
|
||||||
|
// the "pass" credentials helper, which uses this binary.
|
||||||
|
if _, err := exec.LookPath(preferred); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return preferred
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package credentials
|
package credentials
|
||||||
|
|
||||||
func defaultCredentialsStore() string {
|
const (
|
||||||
return "osxkeychain"
|
preferredHelper = ""
|
||||||
}
|
defaultHelper = "osxkeychain"
|
||||||
|
)
|
||||||
|
|
|
@ -1,13 +1,6 @@
|
||||||
package credentials
|
package credentials
|
||||||
|
|
||||||
import (
|
const (
|
||||||
"os/exec"
|
preferredHelper = "pass"
|
||||||
|
defaultHelper = "secretservice"
|
||||||
)
|
)
|
||||||
|
|
||||||
func defaultCredentialsStore() string {
|
|
||||||
if _, err := exec.LookPath("pass"); err == nil {
|
|
||||||
return "pass"
|
|
||||||
}
|
|
||||||
|
|
||||||
return "secretservice"
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDetectDefaultStore(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
t.Setenv("PATH", tmpDir)
|
||||||
|
|
||||||
|
t.Run("none available", func(t *testing.T) {
|
||||||
|
const expected = ""
|
||||||
|
assert.Equal(t, expected, DetectDefaultStore(""))
|
||||||
|
})
|
||||||
|
t.Run("custom helper", func(t *testing.T) {
|
||||||
|
const expected = "my-custom-helper"
|
||||||
|
assert.Equal(t, expected, DetectDefaultStore(expected))
|
||||||
|
|
||||||
|
// Custom helper should be used even if the actual helper exists
|
||||||
|
createFakeHelper(t, path.Join(tmpDir, remoteCredentialsPrefix+defaultHelper))
|
||||||
|
assert.Equal(t, expected, DetectDefaultStore(expected))
|
||||||
|
})
|
||||||
|
t.Run("default", func(t *testing.T) {
|
||||||
|
createFakeHelper(t, path.Join(tmpDir, remoteCredentialsPrefix+defaultHelper))
|
||||||
|
expected := defaultHelper
|
||||||
|
assert.Equal(t, expected, DetectDefaultStore(""))
|
||||||
|
})
|
||||||
|
|
||||||
|
// On Linux, the "pass" credentials helper requires both a "pass" binary
|
||||||
|
// to be present and a "docker-credentials-pass" credentials helper to
|
||||||
|
// be installed.
|
||||||
|
t.Run("preferred helper", func(t *testing.T) {
|
||||||
|
// Create the default helper as we need it for the fallback.
|
||||||
|
createFakeHelper(t, path.Join(tmpDir, remoteCredentialsPrefix+defaultHelper))
|
||||||
|
|
||||||
|
const testPreferredHelper = "preferred"
|
||||||
|
overridePreferred = testPreferredHelper
|
||||||
|
|
||||||
|
// Use preferred helper if both binaries exist.
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
createFakeHelper(t, path.Join(tmpDir, testPreferredHelper))
|
||||||
|
createFakeHelper(t, path.Join(tmpDir, remoteCredentialsPrefix+testPreferredHelper))
|
||||||
|
expected := testPreferredHelper
|
||||||
|
assert.Equal(t, expected, DetectDefaultStore(""))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Fall back to the default helper if the preferred credentials-helper isn't installed.
|
||||||
|
t.Run("not installed", func(t *testing.T) {
|
||||||
|
createFakeHelper(t, path.Join(tmpDir, remoteCredentialsPrefix+testPreferredHelper))
|
||||||
|
expected := defaultHelper
|
||||||
|
assert.Equal(t, expected, DetectDefaultStore(""))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Similarly, fall back to the default helper if the preferred credentials-helper
|
||||||
|
// is installed, but the helper binary isn't found.
|
||||||
|
t.Run("missing helper", func(t *testing.T) {
|
||||||
|
createFakeHelper(t, path.Join(tmpDir, testPreferredHelper))
|
||||||
|
expected := defaultHelper
|
||||||
|
assert.Equal(t, expected, DetectDefaultStore(""))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFakeHelper(t *testing.T, fileName string) {
|
||||||
|
t.Helper()
|
||||||
|
assert.NilError(t, os.WriteFile(fileName, []byte("I'm a credentials-helper executable (really!)"), 0o700))
|
||||||
|
t.Cleanup(func() {
|
||||||
|
assert.NilError(t, os.RemoveAll(fileName))
|
||||||
|
})
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
package credentials
|
package credentials
|
||||||
|
|
||||||
func defaultCredentialsStore() string {
|
const (
|
||||||
return ""
|
preferredHelper = ""
|
||||||
}
|
defaultHelper = ""
|
||||||
|
)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package credentials
|
package credentials
|
||||||
|
|
||||||
func defaultCredentialsStore() string {
|
const (
|
||||||
return "wincred"
|
preferredHelper = ""
|
||||||
}
|
defaultHelper = "wincred"
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue