From a9a754dad19368fa02f49f822b31358cc898f2f1 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 6 May 2014 14:31:47 -0400 Subject: [PATCH 1/5] registry: adding vbatts to the MAINTAINERS Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- docs/MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/MAINTAINERS b/docs/MAINTAINERS index bf3984f5..af791fb4 100644 --- a/docs/MAINTAINERS +++ b/docs/MAINTAINERS @@ -1,3 +1,4 @@ Sam Alba (@samalba) Joffrey Fuhrer (@shin-) Ken Cochrane (@kencochrane) +Vincent Batts (@vbatts) From 3a21f339f1637aded4121715555c0e5fc7269f0e Mon Sep 17 00:00:00 2001 From: Derek Date: Thu, 22 May 2014 23:58:56 -0700 Subject: [PATCH 2/5] Use Timeout Conn wrapper to set read deadline for downloading layer Docker-DCO-1.1-Signed-off-by: Derek (github: crquan) --- docs/registry.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/registry.go b/docs/registry.go index 2e3e7e03..3d0a3ed2 100644 --- a/docs/registry.go +++ b/docs/registry.go @@ -726,7 +726,17 @@ type Registry struct { } func NewRegistry(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, indexEndpoint string) (r *Registry, err error) { + httpDial := func(proto string, addr string) (net.Conn, error) { + conn, err := net.Dial(proto, addr) + if err != nil { + return nil, err + } + conn = utils.NewTimeoutConn(conn, time.Duration(1)*time.Minute) + return conn, nil + } + httpTransport := &http.Transport{ + Dial: httpDial, DisableKeepAlives: true, Proxy: http.ProxyFromEnvironment, } @@ -738,6 +748,7 @@ func NewRegistry(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, inde }, indexEndpoint: indexEndpoint, } + r.client.Jar, err = cookiejar.New(nil) if err != nil { return nil, err From 96412d40fd7bfbd47041f6b5a7805cd66bb4982c Mon Sep 17 00:00:00 2001 From: unclejack Date: Wed, 26 Mar 2014 02:33:17 +0200 Subject: [PATCH 3/5] resume pulling the layer on disconnect Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- docs/registry.go | 45 ++++++++++++++++++++++++++++++++++++++++--- docs/registry_test.go | 4 ++-- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/docs/registry.go b/docs/registry.go index 3d0a3ed2..7bcf0660 100644 --- a/docs/registry.go +++ b/docs/registry.go @@ -256,12 +256,43 @@ func (r *Registry) GetRemoteImageJSON(imgID, registry string, token []string) ([ return jsonString, imageSize, nil } -func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string) (io.ReadCloser, error) { - req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/layer", nil) +func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string, imgSize int64) (io.ReadCloser, error) { + var ( + retries = 5 + headRes *http.Response + hasResume bool = false + imageURL = fmt.Sprintf("%simages/%s/layer", registry, imgID) + ) + headReq, err := r.reqFactory.NewRequest("HEAD", imageURL, nil) + if err != nil { + return nil, fmt.Errorf("Error while getting from the server: %s\n", err) + } + setTokenAuth(headReq, token) + for i := 1; i <= retries; i++ { + headRes, err = r.client.Do(headReq) + if err != nil && i == retries { + return nil, fmt.Errorf("Eror while making head request: %s\n", err) + } else if err != nil { + time.Sleep(time.Duration(i) * 5 * time.Second) + continue + } + break + } + + if headRes.Header.Get("Accept-Ranges") == "bytes" && imgSize > 0 { + hasResume = true + } + + req, err := r.reqFactory.NewRequest("GET", imageURL, nil) if err != nil { return nil, fmt.Errorf("Error while getting from the server: %s\n", err) } setTokenAuth(req, token) + if hasResume { + utils.Debugf("server supports resume") + return utils.ResumableRequestReader(r.client, req, 5, imgSize), nil + } + utils.Debugf("server doesn't support resume") res, err := r.client.Do(req) if err != nil { return nil, err @@ -725,6 +756,13 @@ type Registry struct { indexEndpoint string } +func AddRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error { + if via != nil && via[0] != nil { + req.Header = via[0].Header + } + return nil +} + func NewRegistry(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, indexEndpoint string) (r *Registry, err error) { httpDial := func(proto string, addr string) (net.Conn, error) { conn, err := net.Dial(proto, addr) @@ -744,7 +782,8 @@ func NewRegistry(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, inde r = &Registry{ authConfig: authConfig, client: &http.Client{ - Transport: httpTransport, + Transport: httpTransport, + CheckRedirect: AddRequiredHeadersToRedirectedRequests, }, indexEndpoint: indexEndpoint, } diff --git a/docs/registry_test.go b/docs/registry_test.go index 0a5be5e5..e207359e 100644 --- a/docs/registry_test.go +++ b/docs/registry_test.go @@ -70,7 +70,7 @@ func TestGetRemoteImageJSON(t *testing.T) { func TestGetRemoteImageLayer(t *testing.T) { r := spawnTestRegistry(t) - data, err := r.GetRemoteImageLayer(IMAGE_ID, makeURL("/v1/"), TOKEN) + data, err := r.GetRemoteImageLayer(IMAGE_ID, makeURL("/v1/"), TOKEN, 0) if err != nil { t.Fatal(err) } @@ -78,7 +78,7 @@ func TestGetRemoteImageLayer(t *testing.T) { t.Fatal("Expected non-nil data result") } - _, err = r.GetRemoteImageLayer("abcdef", makeURL("/v1/"), TOKEN) + _, err = r.GetRemoteImageLayer("abcdef", makeURL("/v1/"), TOKEN, 0) if err == nil { t.Fatal("Expected image not found error") } From 0ac3b3981fc85fcad9ce6c44531bc61ec746990f Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 3 Jun 2014 00:46:06 +0000 Subject: [PATCH 4/5] Add redirect and env proxy support to docker login Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- docs/auth.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/auth.go b/docs/auth.go index 4fdd51fd..7384efba 100644 --- a/docs/auth.go +++ b/docs/auth.go @@ -5,12 +5,13 @@ import ( "encoding/json" "errors" "fmt" - "github.com/dotcloud/docker/utils" "io/ioutil" "net/http" "os" "path" "strings" + + "github.com/dotcloud/docker/utils" ) // Where we store the config file @@ -152,10 +153,16 @@ func SaveConfig(configFile *ConfigFile) error { // try to register/login to the registry server func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, error) { var ( - status string - reqBody []byte - err error - client = &http.Client{} + status string + reqBody []byte + err error + client = &http.Client{ + Transport: &http.Transport{ + DisableKeepAlives: true, + Proxy: http.ProxyFromEnvironment, + }, + CheckRedirect: AddRequiredHeadersToRedirectedRequests, + } reqStatusCode = 0 serverAddress = authConfig.ServerAddress ) From 8e8ffacf49a1c9128b56eeb2bfd3e5d20e8d67d8 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 5 Jun 2014 18:37:37 +0000 Subject: [PATCH 5/5] only forward auth to trusted locations Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- docs/registry.go | 29 +++++++++++++++++- docs/registry_test.go | 71 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/docs/registry.go b/docs/registry.go index 7bcf0660..8d1a9f22 100644 --- a/docs/registry.go +++ b/docs/registry.go @@ -756,9 +756,36 @@ type Registry struct { indexEndpoint string } +func trustedLocation(req *http.Request) bool { + var ( + trusteds = []string{"docker.com", "docker.io"} + hostname = strings.SplitN(req.Host, ":", 2)[0] + ) + if req.URL.Scheme != "https" { + return false + } + + for _, trusted := range trusteds { + if strings.HasSuffix(hostname, trusted) { + return true + } + } + return false +} + func AddRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error { if via != nil && via[0] != nil { - req.Header = via[0].Header + if trustedLocation(req) && trustedLocation(via[0]) { + req.Header = via[0].Header + } else { + for k, v := range via[0].Header { + if k != "Authorization" { + for _, vv := range v { + req.Header.Add(k, vv) + } + } + } + } } return nil } diff --git a/docs/registry_test.go b/docs/registry_test.go index e207359e..2857ab4a 100644 --- a/docs/registry_test.go +++ b/docs/registry_test.go @@ -2,10 +2,12 @@ package registry import ( "fmt" - "github.com/dotcloud/docker/utils" + "net/http" "net/url" "strings" "testing" + + "github.com/dotcloud/docker/utils" ) var ( @@ -231,3 +233,70 @@ func TestValidRepositoryName(t *testing.T) { t.Fail() } } + +func TestTrustedLocation(t *testing.T) { + for _, url := range []string{"http://example.com", "https://example.com:7777", "http://docker.io", "http://test.docker.io"} { + req, _ := http.NewRequest("GET", url, nil) + if trustedLocation(req) == true { + t.Fatalf("'%s' shouldn't be detected as a trusted location", url) + } + } + + for _, url := range []string{"https://docker.io", "https://test.docker.io:80"} { + req, _ := http.NewRequest("GET", url, nil) + if trustedLocation(req) == false { + t.Fatalf("'%s' should be detected as a trusted location", url) + } + } +} + +func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) { + for _, urls := range [][]string{ + {"http://docker.io", "https://docker.com"}, + {"https://foo.docker.io:7777", "http://bar.docker.com"}, + {"https://foo.docker.io", "https://example.com"}, + } { + reqFrom, _ := http.NewRequest("GET", urls[0], nil) + reqFrom.Header.Add("Content-Type", "application/json") + reqFrom.Header.Add("Authorization", "super_secret") + reqTo, _ := http.NewRequest("GET", urls[1], nil) + + AddRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom}) + + if len(reqTo.Header) != 1 { + t.Fatal("Expected 1 headers, got %d", len(reqTo.Header)) + } + + if reqTo.Header.Get("Content-Type") != "application/json" { + t.Fatal("'Content-Type' should be 'application/json'") + } + + if reqTo.Header.Get("Authorization") != "" { + t.Fatal("'Authorization' should be empty") + } + } + + for _, urls := range [][]string{ + {"https://docker.io", "https://docker.com"}, + {"https://foo.docker.io:7777", "https://bar.docker.com"}, + } { + reqFrom, _ := http.NewRequest("GET", urls[0], nil) + reqFrom.Header.Add("Content-Type", "application/json") + reqFrom.Header.Add("Authorization", "super_secret") + reqTo, _ := http.NewRequest("GET", urls[1], nil) + + AddRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom}) + + if len(reqTo.Header) != 2 { + t.Fatal("Expected 2 headers, got %d", len(reqTo.Header)) + } + + if reqTo.Header.Get("Content-Type") != "application/json" { + t.Fatal("'Content-Type' should be 'application/json'") + } + + if reqTo.Header.Get("Authorization") != "super_secret" { + t.Fatal("'Authorization' should be 'super_secret'") + } + } +}