diff --git a/auth/auth.go b/auth/auth.go index 62d8e7f522..85844a5472 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -223,6 +223,28 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e } else { return "", fmt.Errorf("Registration: %s", reqBody) } + } else if reqStatusCode == 401 { + // This case would happen with private registries where /v1/users is + // protected, so people can use `docker login` as an auth check. + req, err := factory.NewRequest("GET", serverAddress+"users/", nil) + req.SetBasicAuth(authConfig.Username, authConfig.Password) + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + if resp.StatusCode == 200 { + status = "Login Succeeded" + } else if resp.StatusCode == 401 { + return "", fmt.Errorf("Wrong login/password, please try again") + } else { + return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, + resp.StatusCode, resp.Header) + } } else { return "", fmt.Errorf("Unexpected status code [%d] : %s", reqStatusCode, reqBody) } diff --git a/registry/registry.go b/registry/registry.go index 6aea458e99..6c9255aa46 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -153,6 +153,12 @@ func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) { return res, err } +func setTokenAuth(req *http.Request, token []string) { + if req.Header.Get("Authorization") == "" { // Don't override + req.Header.Set("Authorization", "Token "+strings.Join(token, ",")) + } +} + // Retrieve the history of a given image from the Registry. // Return a list of the parent's json (requested image included) func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]string, error) { @@ -160,7 +166,7 @@ func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]s if err != nil { return nil, err } - req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) + setTokenAuth(req, token) res, err := doWithCookies(r.client, req) if err != nil { return nil, err @@ -193,7 +199,7 @@ func (r *Registry) LookupRemoteImage(imgID, registry string, token []string) boo if err != nil { return false } - req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) + setTokenAuth(req, token) res, err := doWithCookies(r.client, req) if err != nil { return false @@ -209,7 +215,7 @@ func (r *Registry) GetRemoteImageJSON(imgID, registry string, token []string) ([ if err != nil { return nil, -1, fmt.Errorf("Failed to download json: %s", err) } - req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) + setTokenAuth(req, token) res, err := doWithCookies(r.client, req) if err != nil { return nil, -1, fmt.Errorf("Failed to download json: %s", err) @@ -236,7 +242,7 @@ func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string) ( if err != nil { return nil, fmt.Errorf("Error while getting from the server: %s\n", err) } - req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) + setTokenAuth(req, token) res, err := doWithCookies(r.client, req) if err != nil { return nil, err @@ -262,7 +268,7 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [ if err != nil { return nil, err } - req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) + setTokenAuth(req, token) res, err := doWithCookies(r.client, req) if err != nil { return nil, err @@ -290,7 +296,8 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [ return nil, fmt.Errorf("Could not reach any registry endpoint") } -func (r *Registry) GetRepositoryData(indexEp, remote string) (*RepositoryData, error) { +func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) { + indexEp := r.indexEndpoint repositoryTarget := fmt.Sprintf("%srepositories/%s/images", indexEp, remote) utils.Debugf("[registry] Calling GET %s", repositoryTarget) @@ -364,7 +371,7 @@ func (r *Registry) PushImageChecksumRegistry(imgData *ImgData, registry string, if err != nil { return err } - req.Header.Set("Authorization", "Token "+strings.Join(token, ",")) + setTokenAuth(req, token) req.Header.Set("X-Docker-Checksum", imgData.Checksum) res, err := doWithCookies(r.client, req) @@ -401,7 +408,7 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis return err } req.Header.Add("Content-type", "application/json") - req.Header.Set("Authorization", "Token "+strings.Join(token, ",")) + setTokenAuth(req, token) res, err := doWithCookies(r.client, req) if err != nil { @@ -436,7 +443,7 @@ func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registr } req.ContentLength = -1 req.TransferEncoding = []string{"chunked"} - req.Header.Set("Authorization", "Token "+strings.Join(token, ",")) + setTokenAuth(req, token) res, err := doWithCookies(r.client, req) if err != nil { return "", fmt.Errorf("Failed to upload layer: %s", err) @@ -465,7 +472,7 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token return err } req.Header.Add("Content-type", "application/json") - req.Header.Set("Authorization", "Token "+strings.Join(token, ",")) + setTokenAuth(req, token) req.ContentLength = int64(len(revision)) res, err := doWithCookies(r.client, req) if err != nil { @@ -478,8 +485,9 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token return nil } -func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) { +func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) { cleanImgList := []*ImgData{} + indexEp := r.indexEndpoint if validate { for _, elem := range imgList { @@ -583,6 +591,7 @@ func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData } func (r *Registry) SearchRepositories(term string) (*SearchResults, error) { + utils.Debugf("Index server: %s", r.indexEndpoint) u := auth.IndexServerAddress() + "search?q=" + url.QueryEscape(term) req, err := r.reqFactory.NewRequest("GET", u, nil) if err != nil { @@ -644,12 +653,13 @@ type ImgData struct { } type Registry struct { - client *http.Client - authConfig *auth.AuthConfig - reqFactory *utils.HTTPRequestFactory + client *http.Client + authConfig *auth.AuthConfig + reqFactory *utils.HTTPRequestFactory + indexEndpoint string } -func NewRegistry(root string, authConfig *auth.AuthConfig, factory *utils.HTTPRequestFactory) (r *Registry, err error) { +func NewRegistry(authConfig *auth.AuthConfig, factory *utils.HTTPRequestFactory, indexEndpoint string) (r *Registry, err error) { httpTransport := &http.Transport{ DisableKeepAlives: true, Proxy: http.ProxyFromEnvironment, @@ -660,12 +670,21 @@ func NewRegistry(root string, authConfig *auth.AuthConfig, factory *utils.HTTPRe client: &http.Client{ Transport: httpTransport, }, + indexEndpoint: indexEndpoint, } r.client.Jar, err = cookiejar.New(nil) if err != nil { return nil, err } + // If we're working with a private registry over HTTPS, send Basic Auth headers + // alongside our requests. + if indexEndpoint != auth.IndexServerAddress() && strings.HasPrefix(indexEndpoint, "https://") { + utils.Debugf("Endpoint %s is eligible for private registry auth. Enabling decorator.", indexEndpoint) + dec := utils.NewHTTPAuthDecorator(authConfig.Username, authConfig.Password) + factory.AddDecorator(dec) + } + r.reqFactory = factory return r, nil } diff --git a/registry/registry_test.go b/registry/registry_test.go index fb43da66aa..69eb25b247 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -15,7 +15,7 @@ var ( func spawnTestRegistry(t *testing.T) *Registry { authConfig := &auth.AuthConfig{} - r, err := NewRegistry("", authConfig, utils.NewHTTPRequestFactory()) + r, err := NewRegistry(authConfig, utils.NewHTTPRequestFactory(), makeURL("/v1/")) if err != nil { t.Fatal(err) } @@ -99,7 +99,7 @@ func TestGetRemoteTags(t *testing.T) { func TestGetRepositoryData(t *testing.T) { r := spawnTestRegistry(t) - data, err := r.GetRepositoryData(makeURL("/v1/"), "foo42/bar") + data, err := r.GetRepositoryData("foo42/bar") if err != nil { t.Fatal(err) } @@ -168,15 +168,14 @@ func TestPushImageJSONIndex(t *testing.T) { Checksum: "sha256:bea7bf2e4bacd479344b737328db47b18880d09096e6674165533aa994f5e9f2", }, } - ep := makeURL("/v1/") - repoData, err := r.PushImageJSONIndex(ep, "foo42/bar", imgData, false, nil) + repoData, err := r.PushImageJSONIndex("foo42/bar", imgData, false, nil) if err != nil { t.Fatal(err) } if repoData == nil { t.Fatal("Expected RepositoryData object") } - repoData, err = r.PushImageJSONIndex(ep, "foo42/bar", imgData, true, []string{ep}) + repoData, err = r.PushImageJSONIndex("foo42/bar", imgData, true, []string{r.indexEndpoint}) if err != nil { t.Fatal(err) } diff --git a/server.go b/server.go index a5ab4f2646..40ee7e3c42 100644 --- a/server.go +++ b/server.go @@ -425,7 +425,7 @@ func (srv *Server) recursiveLoad(address, tmpImageDir string) error { } func (srv *Server) ImagesSearch(term string) ([]registry.SearchResult, error) { - r, err := registry.NewRegistry(srv.runtime.config.Root, nil, srv.HTTPRequestFactory(nil)) + r, err := registry.NewRegistry(nil, srv.HTTPRequestFactory(nil), auth.IndexServerAddress()) if err != nil { return nil, err } @@ -816,10 +816,10 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin return nil } -func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName, remoteName, askedTag, indexEp string, sf *utils.StreamFormatter, parallel bool) error { +func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName, remoteName, askedTag string, sf *utils.StreamFormatter, parallel bool) error { out.Write(sf.FormatStatus("", "Pulling repository %s", localName)) - repoData, err := r.GetRepositoryData(indexEp, remoteName) + repoData, err := r.GetRepositoryData(remoteName) if err != nil { return err } @@ -989,11 +989,6 @@ func (srv *Server) poolRemove(kind, key string) error { } func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig, metaHeaders map[string][]string, parallel bool) error { - r, err := registry.NewRegistry(srv.runtime.config.Root, authConfig, srv.HTTPRequestFactory(metaHeaders)) - if err != nil { - return err - } - out = utils.NewWriteFlusher(out) c, err := srv.poolAdd("pull", localName+":"+tag) @@ -1014,12 +1009,17 @@ func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *ut return err } + r, err := registry.NewRegistry(authConfig, srv.HTTPRequestFactory(metaHeaders), endpoint) + if err != nil { + return err + } + if endpoint == auth.IndexServerAddress() { // If pull "index.docker.io/foo/bar", it's stored locally under "foo/bar" localName = remoteName } - if err = srv.pullRepository(r, out, localName, remoteName, tag, endpoint, sf, parallel); err != nil { + if err = srv.pullRepository(r, out, localName, remoteName, tag, sf, parallel); err != nil { return err } @@ -1081,7 +1081,7 @@ func flatten(slc [][]*registry.ImgData) []*registry.ImgData { return result } -func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName, remoteName string, localRepo map[string]string, indexEp string, sf *utils.StreamFormatter) error { +func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName, remoteName string, localRepo map[string]string, sf *utils.StreamFormatter) error { out = utils.NewWriteFlusher(out) imgList, err := srv.getImageList(localRepo) if err != nil { @@ -1091,7 +1091,7 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName out.Write(sf.FormatStatus("", "Sending image list")) var repoData *registry.RepositoryData - repoData, err = r.PushImageJSONIndex(indexEp, remoteName, flattenedImgList, false, nil) + repoData, err = r.PushImageJSONIndex(remoteName, flattenedImgList, false, nil) if err != nil { return err } @@ -1137,7 +1137,7 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName } } - if _, err := r.PushImageJSONIndex(indexEp, remoteName, flattenedImgList, true, repoData.Endpoints); err != nil { + if _, err := r.PushImageJSONIndex(remoteName, flattenedImgList, true, repoData.Endpoints); err != nil { return err } @@ -1203,7 +1203,7 @@ func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFo out = utils.NewWriteFlusher(out) img, err := srv.runtime.graph.Get(localName) - r, err2 := registry.NewRegistry(srv.runtime.config.Root, authConfig, srv.HTTPRequestFactory(metaHeaders)) + r, err2 := registry.NewRegistry(authConfig, srv.HTTPRequestFactory(metaHeaders), endpoint) if err2 != nil { return err2 } @@ -1213,7 +1213,7 @@ func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFo out.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", localName, reposLen)) // If it fails, try to get the repository if localRepo, exists := srv.runtime.repositories.Repositories[localName]; exists { - if err := srv.pushRepository(r, out, localName, remoteName, localRepo, endpoint, sf); err != nil { + if err := srv.pushRepository(r, out, localName, remoteName, localRepo, sf); err != nil { return err } return nil @@ -1852,7 +1852,6 @@ func NewServer(eng *engine.Engine, config *DaemonConfig) (*Server, error) { pushingPool: make(map[string]chan struct{}), events: make([]utils.JSONMessage, 0, 64), //only keeps the 64 last events listeners: make(map[string]chan utils.JSONMessage), - reqFactory: nil, } runtime.srv = srv return srv, nil @@ -1861,15 +1860,12 @@ func NewServer(eng *engine.Engine, config *DaemonConfig) (*Server, error) { func (srv *Server) HTTPRequestFactory(metaHeaders map[string][]string) *utils.HTTPRequestFactory { srv.Lock() defer srv.Unlock() - if srv.reqFactory == nil { - ud := utils.NewHTTPUserAgentDecorator(srv.versionInfos()...) - md := &utils.HTTPMetaHeadersDecorator{ - Headers: metaHeaders, - } - factory := utils.NewHTTPRequestFactory(ud, md) - srv.reqFactory = factory + ud := utils.NewHTTPUserAgentDecorator(srv.versionInfos()...) + md := &utils.HTTPMetaHeadersDecorator{ + Headers: metaHeaders, } - return srv.reqFactory + factory := utils.NewHTTPRequestFactory(ud, md) + return factory } func (srv *Server) LogEvent(action, id, from string) *utils.JSONMessage { @@ -1904,6 +1900,5 @@ type Server struct { pushingPool map[string]chan struct{} events []utils.JSONMessage listeners map[string]chan utils.JSONMessage - reqFactory *utils.HTTPRequestFactory Eng *engine.Engine } diff --git a/utils/http.go b/utils/http.go index 5eb77d1949..463d3b4fdd 100644 --- a/utils/http.go +++ b/utils/http.go @@ -107,6 +107,23 @@ func (h *HTTPMetaHeadersDecorator) ChangeRequest(req *http.Request) (newReq *htt return req, nil } +type HTTPAuthDecorator struct { + login string + password string +} + +func NewHTTPAuthDecorator(login, password string) HTTPRequestDecorator { + ret := new(HTTPAuthDecorator) + ret.login = login + ret.password = password + return ret +} + +func (self *HTTPAuthDecorator) ChangeRequest(req *http.Request) (*http.Request, error) { + req.SetBasicAuth(self.login, self.password) + return req, nil +} + // HTTPRequestFactory creates an HTTP request // and applies a list of decorators on the request. type HTTPRequestFactory struct { @@ -119,6 +136,10 @@ func NewHTTPRequestFactory(d ...HTTPRequestDecorator) *HTTPRequestFactory { } } +func (self *HTTPRequestFactory) AddDecorator(d... HTTPRequestDecorator) { + self.decorators = append(self.decorators, d...) +} + // NewRequest() creates a new *http.Request, // applies all decorators in the HTTPRequestFactory on the request, // then applies decorators provided by d on the request. @@ -144,5 +165,6 @@ func (h *HTTPRequestFactory) NewRequest(method, urlStr string, body io.Reader, d return nil, err } } + Debugf("%v -- HEADERS: %v", req.URL, req.Header) return req, err }