commit
						e267ebfc6b
					
				|  | @ -1,3 +1,4 @@ | ||||||
| Sam Alba <sam@dotcloud.com> (@samalba) | Sam Alba <sam@dotcloud.com> (@samalba) | ||||||
| Joffrey Fuhrer <joffrey@dotcloud.com> (@shin-) | Joffrey Fuhrer <joffrey@dotcloud.com> (@shin-) | ||||||
| Ken Cochrane <ken@dotcloud.com> (@kencochrane) | 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" | 	"encoding/json" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/dotcloud/docker/utils" |  | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path" | 	"path" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"github.com/dotcloud/docker/utils" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Where we store the config file
 | // Where we store the config file
 | ||||||
|  | @ -152,10 +153,16 @@ func SaveConfig(configFile *ConfigFile) error { | ||||||
| // try to register/login to the registry server
 | // try to register/login to the registry server
 | ||||||
| func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, error) { | func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, error) { | ||||||
| 	var ( | 	var ( | ||||||
| 		status        string | 		status  string | ||||||
| 		reqBody       []byte | 		reqBody []byte | ||||||
| 		err           error | 		err     error | ||||||
| 		client        = &http.Client{} | 		client  = &http.Client{ | ||||||
|  | 			Transport: &http.Transport{ | ||||||
|  | 				DisableKeepAlives: true, | ||||||
|  | 				Proxy:             http.ProxyFromEnvironment, | ||||||
|  | 			}, | ||||||
|  | 			CheckRedirect: AddRequiredHeadersToRedirectedRequests, | ||||||
|  | 		} | ||||||
| 		reqStatusCode = 0 | 		reqStatusCode = 0 | ||||||
| 		serverAddress = authConfig.ServerAddress | 		serverAddress = authConfig.ServerAddress | ||||||
| 	) | 	) | ||||||
|  |  | ||||||
|  | @ -256,12 +256,43 @@ func (r *Registry) GetRemoteImageJSON(imgID, registry string, token []string) ([ | ||||||
| 	return jsonString, imageSize, nil | 	return jsonString, imageSize, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string) (io.ReadCloser, error) { | func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string, imgSize int64) (io.ReadCloser, error) { | ||||||
| 	req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/layer", nil) | 	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 { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("Error while getting from the server: %s\n", err) | 		return nil, fmt.Errorf("Error while getting from the server: %s\n", err) | ||||||
| 	} | 	} | ||||||
| 	setTokenAuth(req, token) | 	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) | 	res, err := r.client.Do(req) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
|  | @ -725,8 +756,52 @@ type Registry struct { | ||||||
| 	indexEndpoint string | 	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) { | 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{ | 	httpTransport := &http.Transport{ | ||||||
|  | 		Dial:              httpDial, | ||||||
| 		DisableKeepAlives: true, | 		DisableKeepAlives: true, | ||||||
| 		Proxy:             http.ProxyFromEnvironment, | 		Proxy:             http.ProxyFromEnvironment, | ||||||
| 	} | 	} | ||||||
|  | @ -734,10 +809,12 @@ func NewRegistry(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, inde | ||||||
| 	r = &Registry{ | 	r = &Registry{ | ||||||
| 		authConfig: authConfig, | 		authConfig: authConfig, | ||||||
| 		client: &http.Client{ | 		client: &http.Client{ | ||||||
| 			Transport: httpTransport, | 			Transport:     httpTransport, | ||||||
|  | 			CheckRedirect: AddRequiredHeadersToRedirectedRequests, | ||||||
| 		}, | 		}, | ||||||
| 		indexEndpoint: indexEndpoint, | 		indexEndpoint: indexEndpoint, | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	r.client.Jar, err = cookiejar.New(nil) | 	r.client.Jar, err = cookiejar.New(nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
|  |  | ||||||
|  | @ -2,10 +2,12 @@ package registry | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/dotcloud/docker/utils" | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"testing" | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/dotcloud/docker/utils" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
|  | @ -70,7 +72,7 @@ func TestGetRemoteImageJSON(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func TestGetRemoteImageLayer(t *testing.T) { | func TestGetRemoteImageLayer(t *testing.T) { | ||||||
| 	r := spawnTestRegistry(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 { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|  | @ -78,7 +80,7 @@ func TestGetRemoteImageLayer(t *testing.T) { | ||||||
| 		t.Fatal("Expected non-nil data result") | 		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 { | 	if err == nil { | ||||||
| 		t.Fatal("Expected image not found error") | 		t.Fatal("Expected image not found error") | ||||||
| 	} | 	} | ||||||
|  | @ -231,3 +233,70 @@ func TestValidRepositoryName(t *testing.T) { | ||||||
| 		t.Fail() | 		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