Skip to content

Commit

Permalink
Merge pull request #8154 from jandubois/wsl-docker-creds-store
Browse files Browse the repository at this point in the history
Wsl docker creds store
  • Loading branch information
mook-as authored Jan 30, 2025
2 parents 5fb9bf4 + 92de96c commit 9cf491f
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ export default class WindowsIntegrationManager implements IntegrationManager {
const srcPath = await this.getLinuxToolPath(distro,
path.join(paths.resources, 'linux', 'docker-cli-plugins'));
const wslHelper = await this.getLinuxToolPath(distro, executable('wsl-helper-linux'));
const args = ['wsl', 'integration', 'docker-plugin',
const args = ['wsl', 'integration', 'docker',
`--plugin-dir=${ srcPath }`, `--bin-dir=${ binDir }`, `--state=${ state }`];

if (this.settings.application?.debug) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,24 @@ import (
"github.com/spf13/viper"
)

var wslIntegrationDockerPluginViper = viper.New()
var wslIntegrationDockerViper = viper.New()

// wslIntegrationDockerPluginCmd represents the `wsl integration docker-plugin` command
var wslIntegrationDockerPluginCmd = &cobra.Command{
Use: "docker-plugin",
Short: "Commands for managing docker plugin WSL integration",
// wslIntegrationDockerCmd represents the `wsl integration docker` command
var wslIntegrationDockerCmd = &cobra.Command{
Use: "docker",
Short: "Commands for managing docker config for WSL integration",
RunE: func(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true

state := wslIntegrationDockerPluginViper.GetBool("state")
pluginDir := wslIntegrationDockerPluginViper.GetString("plugin-dir")
binDir := wslIntegrationDockerPluginViper.GetString("bin-dir")
state := wslIntegrationDockerViper.GetBool("state")
pluginDir := wslIntegrationDockerViper.GetString("plugin-dir")
binDir := wslIntegrationDockerViper.GetString("bin-dir")
homeDir, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("failed to locate home directory: %w", err)
}

if err := integration.SetupPluginDirConfig(homeDir, pluginDir, state); err != nil {
if err := integration.UpdateDockerConfig(homeDir, pluginDir, state); err != nil {
return err
}

Expand All @@ -56,15 +56,15 @@ var wslIntegrationDockerPluginCmd = &cobra.Command{
}

func init() {
wslIntegrationDockerPluginCmd.Flags().String("plugin-dir", "", "Full path to plugin directory")
wslIntegrationDockerPluginCmd.Flags().String("bin-dir", "", "Full path to bin directory to clean up deprecated links")
wslIntegrationDockerPluginCmd.Flags().Bool("state", false, "Desired state")
if err := wslIntegrationDockerPluginCmd.MarkFlagRequired("plugin-dir"); err != nil {
wslIntegrationDockerCmd.Flags().String("plugin-dir", "", "Full path to plugin directory")
wslIntegrationDockerCmd.Flags().String("bin-dir", "", "Full path to bin directory to clean up deprecated links")
wslIntegrationDockerCmd.Flags().Bool("state", false, "Desired state")
if err := wslIntegrationDockerCmd.MarkFlagRequired("plugin-dir"); err != nil {
logrus.WithError(err).Fatal("Failed to set up flags")
}
wslIntegrationDockerPluginViper.AutomaticEnv()
if err := wslIntegrationDockerPluginViper.BindPFlags(wslIntegrationDockerPluginCmd.Flags()); err != nil {
wslIntegrationDockerViper.AutomaticEnv()
if err := wslIntegrationDockerViper.BindPFlags(wslIntegrationDockerCmd.Flags()); err != nil {
logrus.WithError(err).Fatal("Failed to set up flags")
}
wslIntegrationCmd.AddCommand(wslIntegrationDockerPluginCmd)
wslIntegrationCmd.AddCommand(wslIntegrationDockerCmd)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"errors"
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"slices"
Expand All @@ -30,11 +31,14 @@ import (

const (
pluginDirsKey = "cliPluginsExtraDirs"
credsStoreKey = "credsStore"

dockerCredentialWinCredExe = "wincred.exe"
)

// SetupPluginDirConfig configures docker CLI to load plugins from the directory
// given.
func SetupPluginDirConfig(homeDir, pluginPath string, enabled bool) error {
// UpdateDockerConfig configures docker CLI to load plugins from the directory
// given. It also sets the credential helper to wincred.exe.
func UpdateDockerConfig(homeDir, pluginPath string, enabled bool) error {
configPath := filepath.Join(homeDir, ".docker", "config.json")
config := make(map[string]any)

Expand All @@ -52,6 +56,16 @@ func SetupPluginDirConfig(homeDir, pluginPath string, enabled bool) error {
}
}

replaceCredsStore := true
if credsStoreRaw, ok := config[credsStoreKey]; ok {
if credsStore, ok := credsStoreRaw.(string); ok {
replaceCredsStore = !isCredHelperWorking(credsStore)
}
}
if replaceCredsStore {
config[credsStoreKey] = dockerCredentialWinCredExe
}

var dirs []string

if dirsRaw, ok := config[pluginDirsKey]; ok {
Expand Down Expand Up @@ -109,6 +123,16 @@ func SetupPluginDirConfig(homeDir, pluginPath string, enabled bool) error {
return nil
}

// isCredHelperWorking verifies that the credential helper can be called, and doesn't need to be replaced.
func isCredHelperWorking(credsStore string) bool {
// The proprietary "desktop" helper is always replaced with the default helper.
if credsStore == "" || credsStore == "desktop" || credsStore == "desktop.exe" {
return false
}
credHelper := fmt.Sprintf("docker-credential-%s", credsStore)
return exec.Command(credHelper, "list").Run() == nil
}

// RemoveObsoletePluginSymlinks removes symlinks in the docker CLI plugin
// directory which are children of the given directory.
func RemoveObsoletePluginSymlinks(homeDir, binPath string) error {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ import (
"github.com/rancher-sandbox/rancher-desktop/src/go/wsl-helper/pkg/integration"
)

func TestSetupPluginDirConfig(t *testing.T) {
func TestUpdateDockerConfig(t *testing.T) {
t.Parallel()
t.Run("create config file", func(t *testing.T) {
homeDir := t.TempDir()
pluginPath := t.TempDir()

assert.NoError(t, integration.SetupPluginDirConfig(homeDir, pluginPath, true))
assert.NoError(t, integration.UpdateDockerConfig(homeDir, pluginPath, true))

bytes, err := os.ReadFile(path.Join(homeDir, ".docker", "config.json"))
require.NoError(t, err, "error reading docker CLI config")
Expand All @@ -45,6 +45,8 @@ func TestSetupPluginDirConfig(t *testing.T) {
value := config["cliPluginsExtraDirs"]
require.Contains(t, config, "cliPluginsExtraDirs")
require.Contains(t, value, pluginPath, "did not contain plugin path")
credStore := config["credsStore"]
require.Equal(t, credStore, "wincred.exe")
})
t.Run("update config file", func(t *testing.T) {
homeDir := t.TempDir()
Expand All @@ -54,15 +56,15 @@ func TestSetupPluginDirConfig(t *testing.T) {
existingContents := []byte(`{"credsStore": "nothing"}`)
require.NoError(t, os.WriteFile(configPath, existingContents, 0o644))

require.NoError(t, integration.SetupPluginDirConfig(homeDir, pluginPath, true))
require.NoError(t, integration.UpdateDockerConfig(homeDir, pluginPath, true))

bytes, err := os.ReadFile(path.Join(homeDir, ".docker", "config.json"))
require.NoError(t, err, "error reading docker CLI config")
var config map[string]any
require.NoError(t, json.Unmarshal(bytes, &config))

assert.Subset(t, config, map[string]any{"credsStore": "nothing"})
assert.Subset(t, config, map[string]any{"cliPluginsExtraDirs": []any{pluginPath}})
assert.Subset(t, config, map[string]any{"credsStore": "wincred.exe"})
})
t.Run("do not add multiple instances", func(t *testing.T) {
homeDir := t.TempDir()
Expand All @@ -77,7 +79,7 @@ func TestSetupPluginDirConfig(t *testing.T) {
require.NoError(t, os.WriteFile(configPath, existingContents, 0o644))
config = make(map[string]any)

require.NoError(t, integration.SetupPluginDirConfig(homeDir, pluginPath, true))
require.NoError(t, integration.UpdateDockerConfig(homeDir, pluginPath, true))

bytes, err := os.ReadFile(configPath)
require.NoError(t, err, "error reading docker CLI config")
Expand All @@ -97,7 +99,7 @@ func TestSetupPluginDirConfig(t *testing.T) {
require.NoError(t, os.WriteFile(configPath, existingContents, 0o644))
config = make(map[string]any)

require.NoError(t, integration.SetupPluginDirConfig(homeDir, pluginPath, false))
require.NoError(t, integration.UpdateDockerConfig(homeDir, pluginPath, false))

bytes, err := os.ReadFile(configPath)
require.NoError(t, err, "error reading docker CLI config")
Expand All @@ -115,7 +117,7 @@ func TestSetupPluginDirConfig(t *testing.T) {
existingContents := []byte(`this is not JSON`)
require.NoError(t, os.WriteFile(configPath, existingContents, 0o644))

assert.Error(t, integration.SetupPluginDirConfig(homeDir, pluginPath, true))
assert.Error(t, integration.UpdateDockerConfig(homeDir, pluginPath, true))

bytes, err := os.ReadFile(configPath)
require.NoError(t, err, "error reading docker CLI config")
Expand All @@ -132,7 +134,7 @@ func TestSetupPluginDirConfig(t *testing.T) {
require.NoError(t, err)
require.NoError(t, os.WriteFile(configPath, existingContents, 0o644))

require.Error(t, integration.SetupPluginDirConfig(homeDir, pluginPath, false))
require.Error(t, integration.UpdateDockerConfig(homeDir, pluginPath, false))

bytes, err := os.ReadFile(configPath)
require.NoError(t, err, "error reading docker CLI config")
Expand All @@ -152,7 +154,7 @@ func TestSetupPluginDirConfig(t *testing.T) {
require.NoError(t, err)
require.NoError(t, os.WriteFile(configPath, existingContents, 0o644))

require.Error(t, integration.SetupPluginDirConfig(homeDir, pluginPath, false))
require.Error(t, integration.UpdateDockerConfig(homeDir, pluginPath, false))

bytes, err := os.ReadFile(configPath)
require.NoError(t, err, "error reading docker CLI config")
Expand Down

0 comments on commit 9cf491f

Please sign in to comment.