github.com/ncdc/docker@v0.10.1-0.20160129113957-6c6729ef5b74/registry/auth.go (about)

     1  package registry
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"strings"
     9  
    10  	"github.com/Sirupsen/logrus"
    11  	"github.com/docker/engine-api/types"
    12  	registrytypes "github.com/docker/engine-api/types/registry"
    13  )
    14  
    15  // Login tries to register/login to the registry server.
    16  func Login(authConfig *types.AuthConfig, registryEndpoint *Endpoint) (string, error) {
    17  	// Separates the v2 registry login logic from the v1 logic.
    18  	if registryEndpoint.Version == APIVersion2 {
    19  		return loginV2(authConfig, registryEndpoint, "" /* scope */)
    20  	}
    21  	return loginV1(authConfig, registryEndpoint)
    22  }
    23  
    24  // loginV1 tries to register/login to the v1 registry server.
    25  func loginV1(authConfig *types.AuthConfig, registryEndpoint *Endpoint) (string, error) {
    26  	var (
    27  		status         string
    28  		respBody       []byte
    29  		err            error
    30  		respStatusCode = 0
    31  		serverAddress  = authConfig.ServerAddress
    32  	)
    33  
    34  	logrus.Debugf("attempting v1 login to registry endpoint %s", registryEndpoint)
    35  
    36  	if serverAddress == "" {
    37  		return "", fmt.Errorf("Server Error: Server Address not set.")
    38  	}
    39  
    40  	loginAgainstOfficialIndex := serverAddress == IndexServer
    41  
    42  	// to avoid sending the server address to the server it should be removed before being marshaled
    43  	authCopy := *authConfig
    44  	authCopy.ServerAddress = ""
    45  
    46  	jsonBody, err := json.Marshal(authCopy)
    47  	if err != nil {
    48  		return "", fmt.Errorf("Config Error: %s", err)
    49  	}
    50  
    51  	// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
    52  	b := strings.NewReader(string(jsonBody))
    53  	resp1, err := registryEndpoint.client.Post(serverAddress+"users/", "application/json; charset=utf-8", b)
    54  	if err != nil {
    55  		return "", fmt.Errorf("Server Error: %s", err)
    56  	}
    57  	defer resp1.Body.Close()
    58  	respStatusCode = resp1.StatusCode
    59  	respBody, err = ioutil.ReadAll(resp1.Body)
    60  	if err != nil {
    61  		return "", fmt.Errorf("Server Error: [%#v] %s", respStatusCode, err)
    62  	}
    63  
    64  	if respStatusCode == 201 {
    65  		if loginAgainstOfficialIndex {
    66  			status = "Account created. Please use the confirmation link we sent" +
    67  				" to your e-mail to activate it."
    68  		} else {
    69  			// *TODO: Use registry configuration to determine what this says, if anything?
    70  			status = "Account created. Please see the documentation of the registry " + serverAddress + " for instructions how to activate it."
    71  		}
    72  	} else if respStatusCode == 400 {
    73  		if string(respBody) == "\"Username or email already exists\"" {
    74  			req, err := http.NewRequest("GET", serverAddress+"users/", nil)
    75  			req.SetBasicAuth(authConfig.Username, authConfig.Password)
    76  			resp, err := registryEndpoint.client.Do(req)
    77  			if err != nil {
    78  				return "", err
    79  			}
    80  			defer resp.Body.Close()
    81  			body, err := ioutil.ReadAll(resp.Body)
    82  			if err != nil {
    83  				return "", err
    84  			}
    85  			if resp.StatusCode == 200 {
    86  				return "Login Succeeded", nil
    87  			} else if resp.StatusCode == 401 {
    88  				return "", fmt.Errorf("Wrong login/password, please try again")
    89  			} else if resp.StatusCode == 403 {
    90  				if loginAgainstOfficialIndex {
    91  					return "", fmt.Errorf("Login: Account is not Active. Please check your e-mail for a confirmation link.")
    92  				}
    93  				// *TODO: Use registry configuration to determine what this says, if anything?
    94  				return "", fmt.Errorf("Login: Account is not Active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress)
    95  			} else if resp.StatusCode == 500 { // Issue #14326
    96  				logrus.Errorf("%s returned status code %d. Response Body :\n%s", req.URL.String(), resp.StatusCode, body)
    97  				return "", fmt.Errorf("Internal Server Error")
    98  			}
    99  			return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, resp.StatusCode, resp.Header)
   100  		}
   101  		return "", fmt.Errorf("Registration: %s", respBody)
   102  
   103  	} else if respStatusCode == 401 {
   104  		// This case would happen with private registries where /v1/users is
   105  		// protected, so people can use `docker login` as an auth check.
   106  		req, err := http.NewRequest("GET", serverAddress+"users/", nil)
   107  		req.SetBasicAuth(authConfig.Username, authConfig.Password)
   108  		resp, err := registryEndpoint.client.Do(req)
   109  		if err != nil {
   110  			return "", err
   111  		}
   112  		defer resp.Body.Close()
   113  		body, err := ioutil.ReadAll(resp.Body)
   114  		if err != nil {
   115  			return "", err
   116  		}
   117  		if resp.StatusCode == 200 {
   118  			return "Login Succeeded", nil
   119  		} else if resp.StatusCode == 401 {
   120  			return "", fmt.Errorf("Wrong login/password, please try again")
   121  		} else {
   122  			return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
   123  				resp.StatusCode, resp.Header)
   124  		}
   125  	} else {
   126  		return "", fmt.Errorf("Unexpected status code [%d] : %s", respStatusCode, respBody)
   127  	}
   128  	return status, nil
   129  }
   130  
   131  // loginV2 tries to login to the v2 registry server. The given registry endpoint has been
   132  // pinged or setup with a list of authorization challenges. Each of these challenges are
   133  // tried until one of them succeeds. Currently supported challenge schemes are:
   134  // 		HTTP Basic Authorization
   135  // 		Token Authorization with a separate token issuing server
   136  // NOTE: the v2 logic does not attempt to create a user account if one doesn't exist. For
   137  // now, users should create their account through other means like directly from a web page
   138  // served by the v2 registry service provider. Whether this will be supported in the future
   139  // is to be determined.
   140  func loginV2(authConfig *types.AuthConfig, registryEndpoint *Endpoint, scope string) (string, error) {
   141  	logrus.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint)
   142  	var (
   143  		err       error
   144  		allErrors []error
   145  	)
   146  
   147  	for _, challenge := range registryEndpoint.AuthChallenges {
   148  		params := make(map[string]string, len(challenge.Parameters)+1)
   149  		for k, v := range challenge.Parameters {
   150  			params[k] = v
   151  		}
   152  		params["scope"] = scope
   153  		logrus.Debugf("trying %q auth challenge with params %v", challenge.Scheme, params)
   154  
   155  		switch strings.ToLower(challenge.Scheme) {
   156  		case "basic":
   157  			err = tryV2BasicAuthLogin(authConfig, params, registryEndpoint)
   158  		case "bearer":
   159  			err = tryV2TokenAuthLogin(authConfig, params, registryEndpoint)
   160  		default:
   161  			// Unsupported challenge types are explicitly skipped.
   162  			err = fmt.Errorf("unsupported auth scheme: %q", challenge.Scheme)
   163  		}
   164  
   165  		if err == nil {
   166  			return "Login Succeeded", nil
   167  		}
   168  
   169  		logrus.Debugf("error trying auth challenge %q: %s", challenge.Scheme, err)
   170  
   171  		allErrors = append(allErrors, err)
   172  	}
   173  
   174  	return "", fmt.Errorf("no successful auth challenge for %s - errors: %s", registryEndpoint, allErrors)
   175  }
   176  
   177  func tryV2BasicAuthLogin(authConfig *types.AuthConfig, params map[string]string, registryEndpoint *Endpoint) error {
   178  	req, err := http.NewRequest("GET", registryEndpoint.Path(""), nil)
   179  	if err != nil {
   180  		return err
   181  	}
   182  
   183  	req.SetBasicAuth(authConfig.Username, authConfig.Password)
   184  
   185  	resp, err := registryEndpoint.client.Do(req)
   186  	if err != nil {
   187  		return err
   188  	}
   189  	defer resp.Body.Close()
   190  
   191  	if resp.StatusCode != http.StatusOK {
   192  		return fmt.Errorf("basic auth attempt to %s realm %q failed with status: %d %s", registryEndpoint, params["realm"], resp.StatusCode, http.StatusText(resp.StatusCode))
   193  	}
   194  
   195  	return nil
   196  }
   197  
   198  func tryV2TokenAuthLogin(authConfig *types.AuthConfig, params map[string]string, registryEndpoint *Endpoint) error {
   199  	token, err := getToken(authConfig.Username, authConfig.Password, params, registryEndpoint)
   200  	if err != nil {
   201  		return err
   202  	}
   203  
   204  	req, err := http.NewRequest("GET", registryEndpoint.Path(""), nil)
   205  	if err != nil {
   206  		return err
   207  	}
   208  
   209  	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
   210  
   211  	resp, err := registryEndpoint.client.Do(req)
   212  	if err != nil {
   213  		return err
   214  	}
   215  	defer resp.Body.Close()
   216  
   217  	if resp.StatusCode != http.StatusOK {
   218  		return fmt.Errorf("token auth attempt to %s realm %q failed with status: %d %s", registryEndpoint, params["realm"], resp.StatusCode, http.StatusText(resp.StatusCode))
   219  	}
   220  
   221  	return nil
   222  }
   223  
   224  // ResolveAuthConfig matches an auth configuration to a server address or a URL
   225  func ResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registrytypes.IndexInfo) types.AuthConfig {
   226  	configKey := GetAuthConfigKey(index)
   227  	// First try the happy case
   228  	if c, found := authConfigs[configKey]; found || index.Official {
   229  		return c
   230  	}
   231  
   232  	convertToHostname := func(url string) string {
   233  		stripped := url
   234  		if strings.HasPrefix(url, "http://") {
   235  			stripped = strings.Replace(url, "http://", "", 1)
   236  		} else if strings.HasPrefix(url, "https://") {
   237  			stripped = strings.Replace(url, "https://", "", 1)
   238  		}
   239  
   240  		nameParts := strings.SplitN(stripped, "/", 2)
   241  
   242  		return nameParts[0]
   243  	}
   244  
   245  	// Maybe they have a legacy config file, we will iterate the keys converting
   246  	// them to the new format and testing
   247  	for registry, ac := range authConfigs {
   248  		if configKey == convertToHostname(registry) {
   249  			return ac
   250  		}
   251  	}
   252  
   253  	// When all else fails, return an empty auth config
   254  	return types.AuthConfig{}
   255  }