Signed-off-by: Antonio Murdaca <runcom@redhat.com>
| ... | ... |
@@ -49,7 +49,8 @@ func (cli *DockerCli) RegistryAuthenticationPrivilegedFunc(index *registrytypes. |
| 49 | 49 |
return func() (string, error) {
|
| 50 | 50 |
fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName) |
| 51 | 51 |
indexServer := registry.GetAuthConfigKey(index) |
| 52 |
- authConfig, err := cli.ConfigureAuth("", "", indexServer, false)
|
|
| 52 |
+ isDefaultRegistry := indexServer == cli.ElectAuthServer(context.Background()) |
|
| 53 |
+ authConfig, err := cli.ConfigureAuth("", "", indexServer, isDefaultRegistry)
|
|
| 53 | 54 |
if err != nil {
|
| 54 | 55 |
return "", err |
| 55 | 56 |
} |
| ... | ... |
@@ -91,6 +92,10 @@ func (cli *DockerCli) ConfigureAuth(flUser, flPassword, serverAddress string, is |
| 91 | 91 |
cli.in = os.Stdin |
| 92 | 92 |
} |
| 93 | 93 |
|
| 94 |
+ if !isDefaultRegistry {
|
|
| 95 |
+ serverAddress = registry.ConvertToHostname(serverAddress) |
|
| 96 |
+ } |
|
| 97 |
+ |
|
| 94 | 98 |
authconfig, err := GetCredentials(cli.configFile, serverAddress) |
| 95 | 99 |
if err != nil {
|
| 96 | 100 |
return authconfig, err |
| ... | ... |
@@ -50,14 +50,18 @@ func runLogin(dockerCli *client.DockerCli, opts loginOptions) error {
|
| 50 | 50 |
ctx := context.Background() |
| 51 | 51 |
clnt := dockerCli.Client() |
| 52 | 52 |
|
| 53 |
- var serverAddress string |
|
| 54 |
- var isDefaultRegistry bool |
|
| 53 |
+ var ( |
|
| 54 |
+ serverAddress string |
|
| 55 |
+ authServer = dockerCli.ElectAuthServer(ctx) |
|
| 56 |
+ ) |
|
| 55 | 57 |
if opts.serverAddress != "" {
|
| 56 | 58 |
serverAddress = opts.serverAddress |
| 57 | 59 |
} else {
|
| 58 |
- serverAddress = dockerCli.ElectAuthServer(ctx) |
|
| 59 |
- isDefaultRegistry = true |
|
| 60 |
+ serverAddress = authServer |
|
| 60 | 61 |
} |
| 62 |
+ |
|
| 63 |
+ isDefaultRegistry := serverAddress == authServer |
|
| 64 |
+ |
|
| 61 | 65 |
authConfig, err := dockerCli.ConfigureAuth(opts.user, opts.password, serverAddress, isDefaultRegistry) |
| 62 | 66 |
if err != nil {
|
| 63 | 67 |
return err |
| ... | ... |
@@ -7,6 +7,7 @@ import ( |
| 7 | 7 |
|
| 8 | 8 |
"github.com/docker/docker/api/client" |
| 9 | 9 |
"github.com/docker/docker/cli" |
| 10 |
+ "github.com/docker/docker/registry" |
|
| 10 | 11 |
"github.com/spf13/cobra" |
| 11 | 12 |
) |
| 12 | 13 |
|
| ... | ... |
@@ -31,21 +32,45 @@ func NewLogoutCommand(dockerCli *client.DockerCli) *cobra.Command {
|
| 31 | 31 |
|
| 32 | 32 |
func runLogout(dockerCli *client.DockerCli, serverAddress string) error {
|
| 33 | 33 |
ctx := context.Background() |
| 34 |
+ var isDefaultRegistry bool |
|
| 34 | 35 |
|
| 35 | 36 |
if serverAddress == "" {
|
| 36 | 37 |
serverAddress = dockerCli.ElectAuthServer(ctx) |
| 38 |
+ isDefaultRegistry = true |
|
| 39 |
+ } |
|
| 40 |
+ |
|
| 41 |
+ var ( |
|
| 42 |
+ loggedIn bool |
|
| 43 |
+ regsToLogout []string |
|
| 44 |
+ hostnameAddress = serverAddress |
|
| 45 |
+ regsToTry = []string{serverAddress}
|
|
| 46 |
+ ) |
|
| 47 |
+ if !isDefaultRegistry {
|
|
| 48 |
+ hostnameAddress = registry.ConvertToHostname(serverAddress) |
|
| 49 |
+ // the tries below are kept for backward compatibily where a user could have |
|
| 50 |
+ // saved the registry in one of the following format. |
|
| 51 |
+ regsToTry = append(regsToTry, hostnameAddress, "http://"+hostnameAddress, "https://"+hostnameAddress) |
|
| 37 | 52 |
} |
| 38 | 53 |
|
| 39 | 54 |
// check if we're logged in based on the records in the config file |
| 40 | 55 |
// which means it couldn't have user/pass cause they may be in the creds store |
| 41 |
- if _, ok := dockerCli.ConfigFile().AuthConfigs[serverAddress]; !ok {
|
|
| 42 |
- fmt.Fprintf(dockerCli.Out(), "Not logged in to %s\n", serverAddress) |
|
| 56 |
+ for _, s := range regsToTry {
|
|
| 57 |
+ if _, ok := dockerCli.ConfigFile().AuthConfigs[s]; ok {
|
|
| 58 |
+ loggedIn = true |
|
| 59 |
+ regsToLogout = append(regsToLogout, s) |
|
| 60 |
+ } |
|
| 61 |
+ } |
|
| 62 |
+ |
|
| 63 |
+ if !loggedIn {
|
|
| 64 |
+ fmt.Fprintf(dockerCli.Out(), "Not logged in to %s\n", hostnameAddress) |
|
| 43 | 65 |
return nil |
| 44 | 66 |
} |
| 45 | 67 |
|
| 46 |
- fmt.Fprintf(dockerCli.Out(), "Removing login credentials for %s\n", serverAddress) |
|
| 47 |
- if err := client.EraseCredentials(dockerCli.ConfigFile(), serverAddress); err != nil {
|
|
| 48 |
- fmt.Fprintf(dockerCli.Err(), "WARNING: could not erase credentials: %v\n", err) |
|
| 68 |
+ fmt.Fprintf(dockerCli.Out(), "Removing login credentials for %s\n", hostnameAddress) |
|
| 69 |
+ for _, r := range regsToLogout {
|
|
| 70 |
+ if err := client.EraseCredentials(dockerCli.ConfigFile(), r); err != nil {
|
|
| 71 |
+ fmt.Fprintf(dockerCli.Err(), "WARNING: could not erase credentials: %v\n", err) |
|
| 72 |
+ } |
|
| 49 | 73 |
} |
| 50 | 74 |
|
| 51 | 75 |
return nil |
| ... | ... |
@@ -1,9 +1,8 @@ |
| 1 | 1 |
package credentials |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "strings" |
|
| 5 |
- |
|
| 6 | 4 |
"github.com/docker/docker/cliconfig/configfile" |
| 5 |
+ "github.com/docker/docker/registry" |
|
| 7 | 6 |
"github.com/docker/engine-api/types" |
| 8 | 7 |
) |
| 9 | 8 |
|
| ... | ... |
@@ -32,8 +31,8 @@ func (c *fileStore) Get(serverAddress string) (types.AuthConfig, error) {
|
| 32 | 32 |
if !ok {
|
| 33 | 33 |
// Maybe they have a legacy config file, we will iterate the keys converting |
| 34 | 34 |
// them to the new format and testing |
| 35 |
- for registry, ac := range c.file.AuthConfigs {
|
|
| 36 |
- if serverAddress == convertToHostname(registry) {
|
|
| 35 |
+ for r, ac := range c.file.AuthConfigs {
|
|
| 36 |
+ if serverAddress == registry.ConvertToHostname(r) {
|
|
| 37 | 37 |
return ac, nil |
| 38 | 38 |
} |
| 39 | 39 |
} |
| ... | ... |
@@ -52,16 +51,3 @@ func (c *fileStore) Store(authConfig types.AuthConfig) error {
|
| 52 | 52 |
c.file.AuthConfigs[authConfig.ServerAddress] = authConfig |
| 53 | 53 |
return c.file.Save() |
| 54 | 54 |
} |
| 55 |
- |
|
| 56 |
-func convertToHostname(url string) string {
|
|
| 57 |
- stripped := url |
|
| 58 |
- if strings.HasPrefix(url, "http://") {
|
|
| 59 |
- stripped = strings.Replace(url, "http://", "", 1) |
|
| 60 |
- } else if strings.HasPrefix(url, "https://") {
|
|
| 61 |
- stripped = strings.Replace(url, "https://", "", 1) |
|
| 62 |
- } |
|
| 63 |
- |
|
| 64 |
- nameParts := strings.SplitN(stripped, "/", 2) |
|
| 65 |
- |
|
| 66 |
- return nameParts[0] |
|
| 67 |
-} |
| ... | ... |
@@ -1,9 +1,11 @@ |
| 1 | 1 |
package main |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "bytes" |
|
| 4 | 5 |
"fmt" |
| 5 | 6 |
"io/ioutil" |
| 6 | 7 |
"os" |
| 8 |
+ "os/exec" |
|
| 7 | 9 |
"path/filepath" |
| 8 | 10 |
|
| 9 | 11 |
"github.com/docker/docker/pkg/integration/checker" |
| ... | ... |
@@ -54,3 +56,45 @@ func (s *DockerRegistryAuthHtpasswdSuite) TestLogoutWithExternalAuth(c *check.C) |
| 54 | 54 |
c.Assert(err, check.NotNil, check.Commentf(out)) |
| 55 | 55 |
c.Assert(out, checker.Contains, "Error: image dockercli/busybox:authtest not found") |
| 56 | 56 |
} |
| 57 |
+ |
|
| 58 |
+// #23100 |
|
| 59 |
+func (s *DockerRegistryAuthHtpasswdSuite) TestLogoutWithWrongHostnamesStored(c *check.C) {
|
|
| 60 |
+ osPath := os.Getenv("PATH")
|
|
| 61 |
+ defer os.Setenv("PATH", osPath)
|
|
| 62 |
+ |
|
| 63 |
+ workingDir, err := os.Getwd() |
|
| 64 |
+ c.Assert(err, checker.IsNil) |
|
| 65 |
+ absolute, err := filepath.Abs(filepath.Join(workingDir, "fixtures", "auth")) |
|
| 66 |
+ c.Assert(err, checker.IsNil) |
|
| 67 |
+ testPath := fmt.Sprintf("%s%c%s", osPath, filepath.ListSeparator, absolute)
|
|
| 68 |
+ |
|
| 69 |
+ os.Setenv("PATH", testPath)
|
|
| 70 |
+ |
|
| 71 |
+ cmd := exec.Command("docker-credential-shell-test", "store")
|
|
| 72 |
+ stdin := bytes.NewReader([]byte(fmt.Sprintf(`{"ServerURL": "https://%s", "Username": "%s", "Secret": "%s"}`, privateRegistryURL, s.reg.username, s.reg.password)))
|
|
| 73 |
+ cmd.Stdin = stdin |
|
| 74 |
+ c.Assert(cmd.Run(), checker.IsNil) |
|
| 75 |
+ |
|
| 76 |
+ tmp, err := ioutil.TempDir("", "integration-cli-")
|
|
| 77 |
+ c.Assert(err, checker.IsNil) |
|
| 78 |
+ |
|
| 79 |
+ externalAuthConfig := fmt.Sprintf(`{ "auths": {"https://%s": {}}, "credsStore": "shell-test" }`, privateRegistryURL)
|
|
| 80 |
+ |
|
| 81 |
+ configPath := filepath.Join(tmp, "config.json") |
|
| 82 |
+ err = ioutil.WriteFile(configPath, []byte(externalAuthConfig), 0644) |
|
| 83 |
+ c.Assert(err, checker.IsNil) |
|
| 84 |
+ |
|
| 85 |
+ dockerCmd(c, "--config", tmp, "login", "-u", s.reg.username, "-p", s.reg.password, privateRegistryURL) |
|
| 86 |
+ |
|
| 87 |
+ b, err := ioutil.ReadFile(configPath) |
|
| 88 |
+ c.Assert(err, checker.IsNil) |
|
| 89 |
+ c.Assert(string(b), checker.Contains, fmt.Sprintf("\"https://%s\": {}", privateRegistryURL))
|
|
| 90 |
+ c.Assert(string(b), checker.Contains, fmt.Sprintf("\"%s\": {}", privateRegistryURL))
|
|
| 91 |
+ |
|
| 92 |
+ dockerCmd(c, "--config", tmp, "logout", privateRegistryURL) |
|
| 93 |
+ |
|
| 94 |
+ b, err = ioutil.ReadFile(configPath) |
|
| 95 |
+ c.Assert(err, checker.IsNil) |
|
| 96 |
+ c.Assert(string(b), checker.Not(checker.Contains), fmt.Sprintf("\"https://%s\": {}", privateRegistryURL))
|
|
| 97 |
+ c.Assert(string(b), checker.Not(checker.Contains), fmt.Sprintf("\"%s\": {}", privateRegistryURL))
|
|
| 98 |
+} |
| ... | ... |
@@ -387,6 +387,52 @@ func (s *DockerRegistrySuite) TestPullManifestList(c *check.C) {
|
| 387 | 387 |
dockerCmd(c, "rmi", repoName) |
| 388 | 388 |
} |
| 389 | 389 |
|
| 390 |
+// #23100 |
|
| 391 |
+func (s *DockerRegistryAuthHtpasswdSuite) TestPullWithExternalAuthLoginWithScheme(c *check.C) {
|
|
| 392 |
+ osPath := os.Getenv("PATH")
|
|
| 393 |
+ defer os.Setenv("PATH", osPath)
|
|
| 394 |
+ |
|
| 395 |
+ workingDir, err := os.Getwd() |
|
| 396 |
+ c.Assert(err, checker.IsNil) |
|
| 397 |
+ absolute, err := filepath.Abs(filepath.Join(workingDir, "fixtures", "auth")) |
|
| 398 |
+ c.Assert(err, checker.IsNil) |
|
| 399 |
+ testPath := fmt.Sprintf("%s%c%s", osPath, filepath.ListSeparator, absolute)
|
|
| 400 |
+ |
|
| 401 |
+ os.Setenv("PATH", testPath)
|
|
| 402 |
+ |
|
| 403 |
+ repoName := fmt.Sprintf("%v/dockercli/busybox:authtest", privateRegistryURL)
|
|
| 404 |
+ |
|
| 405 |
+ tmp, err := ioutil.TempDir("", "integration-cli-")
|
|
| 406 |
+ c.Assert(err, checker.IsNil) |
|
| 407 |
+ |
|
| 408 |
+ externalAuthConfig := `{ "credsStore": "shell-test" }`
|
|
| 409 |
+ |
|
| 410 |
+ configPath := filepath.Join(tmp, "config.json") |
|
| 411 |
+ err = ioutil.WriteFile(configPath, []byte(externalAuthConfig), 0644) |
|
| 412 |
+ c.Assert(err, checker.IsNil) |
|
| 413 |
+ |
|
| 414 |
+ dockerCmd(c, "--config", tmp, "login", "-u", s.reg.username, "-p", s.reg.password, privateRegistryURL) |
|
| 415 |
+ |
|
| 416 |
+ b, err := ioutil.ReadFile(configPath) |
|
| 417 |
+ c.Assert(err, checker.IsNil) |
|
| 418 |
+ c.Assert(string(b), checker.Not(checker.Contains), "\"auth\":") |
|
| 419 |
+ |
|
| 420 |
+ dockerCmd(c, "--config", tmp, "tag", "busybox", repoName) |
|
| 421 |
+ dockerCmd(c, "--config", tmp, "push", repoName) |
|
| 422 |
+ |
|
| 423 |
+ dockerCmd(c, "--config", tmp, "logout", privateRegistryURL) |
|
| 424 |
+ dockerCmd(c, "--config", tmp, "login", "-u", s.reg.username, "-p", s.reg.password, "https://"+privateRegistryURL) |
|
| 425 |
+ dockerCmd(c, "--config", tmp, "pull", repoName) |
|
| 426 |
+ |
|
| 427 |
+ // likewise push should work |
|
| 428 |
+ repoName2 := fmt.Sprintf("%v/dockercli/busybox:nocreds", privateRegistryURL)
|
|
| 429 |
+ dockerCmd(c, "tag", repoName, repoName2) |
|
| 430 |
+ dockerCmd(c, "--config", tmp, "push", repoName2) |
|
| 431 |
+ |
|
| 432 |
+ // logout should work w scheme also because it will be stripped |
|
| 433 |
+ dockerCmd(c, "--config", tmp, "logout", "https://"+privateRegistryURL) |
|
| 434 |
+} |
|
| 435 |
+ |
|
| 390 | 436 |
func (s *DockerRegistryAuthHtpasswdSuite) TestPullWithExternalAuth(c *check.C) {
|
| 391 | 437 |
osPath := os.Getenv("PATH")
|
| 392 | 438 |
defer os.Setenv("PATH", osPath)
|
| ... | ... |
@@ -206,6 +206,21 @@ func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifi |
| 206 | 206 |
|
| 207 | 207 |
} |
| 208 | 208 |
|
| 209 |
+// ConvertToHostname converts a registry url which has http|https prepended |
|
| 210 |
+// to just an hostname. |
|
| 211 |
+func ConvertToHostname(url string) string {
|
|
| 212 |
+ stripped := url |
|
| 213 |
+ if strings.HasPrefix(url, "http://") {
|
|
| 214 |
+ stripped = strings.TrimPrefix(url, "http://") |
|
| 215 |
+ } else if strings.HasPrefix(url, "https://") {
|
|
| 216 |
+ stripped = strings.TrimPrefix(url, "https://") |
|
| 217 |
+ } |
|
| 218 |
+ |
|
| 219 |
+ nameParts := strings.SplitN(stripped, "/", 2) |
|
| 220 |
+ |
|
| 221 |
+ return nameParts[0] |
|
| 222 |
+} |
|
| 223 |
+ |
|
| 209 | 224 |
// ResolveAuthConfig matches an auth configuration to a server address or a URL |
| 210 | 225 |
func ResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registrytypes.IndexInfo) types.AuthConfig {
|
| 211 | 226 |
configKey := GetAuthConfigKey(index) |
| ... | ... |
@@ -214,23 +229,10 @@ func ResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registryt |
| 214 | 214 |
return c |
| 215 | 215 |
} |
| 216 | 216 |
|
| 217 |
- convertToHostname := func(url string) string {
|
|
| 218 |
- stripped := url |
|
| 219 |
- if strings.HasPrefix(url, "http://") {
|
|
| 220 |
- stripped = strings.Replace(url, "http://", "", 1) |
|
| 221 |
- } else if strings.HasPrefix(url, "https://") {
|
|
| 222 |
- stripped = strings.Replace(url, "https://", "", 1) |
|
| 223 |
- } |
|
| 224 |
- |
|
| 225 |
- nameParts := strings.SplitN(stripped, "/", 2) |
|
| 226 |
- |
|
| 227 |
- return nameParts[0] |
|
| 228 |
- } |
|
| 229 |
- |
|
| 230 | 217 |
// Maybe they have a legacy config file, we will iterate the keys converting |
| 231 | 218 |
// them to the new format and testing |
| 232 | 219 |
for registry, ac := range authConfigs {
|
| 233 |
- if configKey == convertToHostname(registry) {
|
|
| 220 |
+ if configKey == ConvertToHostname(registry) {
|
|
| 234 | 221 |
return ac |
| 235 | 222 |
} |
| 236 | 223 |
} |