commit
						e267ebfc6b
					
				|  | @ -1,3 +1,4 @@ | |||
| Sam Alba <sam@dotcloud.com> (@samalba) | ||||
| Joffrey Fuhrer <joffrey@dotcloud.com> (@shin-) | ||||
| Ken Cochrane <ken@dotcloud.com> (@kencochrane) | ||||
| Vincent Batts <vbatts@redhat.com> (@vbatts) | ||||
|  |  | |||
							
								
								
									
										17
									
								
								docs/auth.go
								
								
								
								
							
							
						
						
									
										17
									
								
								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 | ||||
| 	) | ||||
|  |  | |||
|  | @ -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,8 +756,52 @@ 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 { | ||||
| 		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 | ||||
| } | ||||
| 
 | ||||
| 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, | ||||
| 	} | ||||
|  | @ -734,10 +809,12 @@ func NewRegistry(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, inde | |||
| 	r = &Registry{ | ||||
| 		authConfig: authConfig, | ||||
| 		client: &http.Client{ | ||||
| 			Transport: httpTransport, | ||||
| 			Transport:     httpTransport, | ||||
| 			CheckRedirect: AddRequiredHeadersToRedirectedRequests, | ||||
| 		}, | ||||
| 		indexEndpoint: indexEndpoint, | ||||
| 	} | ||||
| 
 | ||||
| 	r.client.Jar, err = cookiejar.New(nil) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
|  |  | |||
|  | @ -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 ( | ||||
|  | @ -70,7 +72,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 +80,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") | ||||
| 	} | ||||
|  | @ -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'") | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue