From e09891e2cfeac92c324067b6b5209e6ed98b784c Mon Sep 17 00:00:00 2001 From: Richard Scothern Date: Fri, 26 Feb 2016 14:18:09 -0800 Subject: [PATCH] URL parse auth endpoints to normalize hostname to lowercase. It is possible for a middlebox to lowercase the URL at somepoint causing a lookup in the auth challenges table to fail. Lowercase hostname before using as keys to challenge map. Signed-off-by: Richard Scothern --- docs/client/auth/authchallenge.go | 11 ++++--- docs/client/auth/authchallenge_test.go | 43 ++++++++++++++++++++++++++ docs/client/auth/session.go | 4 +-- docs/handlers/blobupload.go | 1 + docs/proxy/proxyregistry.go | 19 ++++++------ 5 files changed, 61 insertions(+), 17 deletions(-) diff --git a/docs/client/auth/authchallenge.go b/docs/client/auth/authchallenge.go index a6ad45d8..c8cd83bb 100644 --- a/docs/client/auth/authchallenge.go +++ b/docs/client/auth/authchallenge.go @@ -25,7 +25,7 @@ type Challenge struct { type ChallengeManager interface { // GetChallenges returns the challenges for the given // endpoint URL. - GetChallenges(endpoint string) ([]Challenge, error) + GetChallenges(endpoint url.URL) ([]Challenge, error) // AddResponse adds the response to the challenge // manager. The challenges will be parsed out of @@ -48,8 +48,10 @@ func NewSimpleChallengeManager() ChallengeManager { type simpleChallengeManager map[string][]Challenge -func (m simpleChallengeManager) GetChallenges(endpoint string) ([]Challenge, error) { - challenges := m[endpoint] +func (m simpleChallengeManager) GetChallenges(endpoint url.URL) ([]Challenge, error) { + endpoint.Host = strings.ToLower(endpoint.Host) + + challenges := m[endpoint.String()] return challenges, nil } @@ -60,11 +62,10 @@ func (m simpleChallengeManager) AddResponse(resp *http.Response) error { } urlCopy := url.URL{ Path: resp.Request.URL.Path, - Host: resp.Request.URL.Host, + Host: strings.ToLower(resp.Request.URL.Host), Scheme: resp.Request.URL.Scheme, } m[urlCopy.String()] = challenges - return nil } diff --git a/docs/client/auth/authchallenge_test.go b/docs/client/auth/authchallenge_test.go index 9b6a5adc..953ed5b4 100644 --- a/docs/client/auth/authchallenge_test.go +++ b/docs/client/auth/authchallenge_test.go @@ -1,7 +1,10 @@ package auth import ( + "fmt" "net/http" + "net/url" + "strings" "testing" ) @@ -36,3 +39,43 @@ func TestAuthChallengeParse(t *testing.T) { } } + +func TestAuthChallengeNormalization(t *testing.T) { + testAuthChallengeNormalization(t, "reg.EXAMPLE.com") + testAuthChallengeNormalization(t, "bɿɒʜɔiɿ-ɿɘƚƨim-ƚol-ɒ-ƨʞnɒʜƚ.com") +} + +func testAuthChallengeNormalization(t *testing.T, host string) { + + scm := NewSimpleChallengeManager() + + url, err := url.Parse(fmt.Sprintf("http://%s/v2/", host)) + if err != nil { + t.Fatal(err) + } + + resp := &http.Response{ + Request: &http.Request{ + URL: url, + }, + Header: make(http.Header), + StatusCode: http.StatusUnauthorized, + } + resp.Header.Add("WWW-Authenticate", fmt.Sprintf("Bearer realm=\"https://%s/token\",service=\"registry.example.com\"", host)) + + err = scm.AddResponse(resp) + if err != nil { + t.Fatal(err) + } + + lowered := *url + lowered.Host = strings.ToLower(lowered.Host) + c, err := scm.GetChallenges(lowered) + if err != nil { + t.Fatal(err) + } + + if len(c) == 0 { + t.Fatal("Expected challenge for lower-cased-host URL") + } +} diff --git a/docs/client/auth/session.go b/docs/client/auth/session.go index a9b228c5..d8ea1f75 100644 --- a/docs/client/auth/session.go +++ b/docs/client/auth/session.go @@ -67,9 +67,7 @@ func (ea *endpointAuthorizer) ModifyRequest(req *http.Request) error { Path: req.URL.Path[:v2Root+4], } - pingEndpoint := ping.String() - - challenges, err := ea.challenges.GetChallenges(pingEndpoint) + challenges, err := ea.challenges.GetChallenges(ping) if err != nil { return err } diff --git a/docs/handlers/blobupload.go b/docs/handlers/blobupload.go index e2c34d83..f631e4d4 100644 --- a/docs/handlers/blobupload.go +++ b/docs/handlers/blobupload.go @@ -340,6 +340,7 @@ func (buh *blobUploadHandler) blobUploadResponse(w http.ResponseWriter, r *http. w.Header().Set("Docker-Upload-UUID", buh.UUID) w.Header().Set("Location", uploadURL) + w.Header().Set("Content-Length", "0") w.Header().Set("Range", fmt.Sprintf("0-%d", endRange)) diff --git a/docs/proxy/proxyregistry.go b/docs/proxy/proxyregistry.go index e25fe783..f0685788 100644 --- a/docs/proxy/proxyregistry.go +++ b/docs/proxy/proxyregistry.go @@ -22,13 +22,13 @@ import ( type proxyingRegistry struct { embedded distribution.Namespace // provides local registry functionality scheduler *scheduler.TTLExpirationScheduler - remoteURL string + remoteURL url.URL authChallenger authChallenger } // NewRegistryPullThroughCache creates a registry acting as a pull through cache func NewRegistryPullThroughCache(ctx context.Context, registry distribution.Namespace, driver driver.StorageDriver, config configuration.Proxy) (distribution.Namespace, error) { - _, err := url.Parse(config.RemoteURL) + remoteURL, err := url.Parse(config.RemoteURL) if err != nil { return nil, err } @@ -99,9 +99,9 @@ func NewRegistryPullThroughCache(ctx context.Context, registry distribution.Name return &proxyingRegistry{ embedded: registry, scheduler: s, - remoteURL: config.RemoteURL, + remoteURL: *remoteURL, authChallenger: &remoteAuthChallenger{ - remoteURL: config.RemoteURL, + remoteURL: *remoteURL, cm: auth.NewSimpleChallengeManager(), cs: cs, }, @@ -131,7 +131,7 @@ func (pr *proxyingRegistry) Repository(ctx context.Context, name reference.Named return nil, err } - remoteRepo, err := client.NewRepository(ctx, name, pr.remoteURL, tr) + remoteRepo, err := client.NewRepository(ctx, name, pr.remoteURL.String(), tr) if err != nil { return nil, err } @@ -174,7 +174,7 @@ type authChallenger interface { } type remoteAuthChallenger struct { - remoteURL string + remoteURL url.URL sync.Mutex cm auth.ChallengeManager cs auth.CredentialStore @@ -193,8 +193,9 @@ func (r *remoteAuthChallenger) tryEstablishChallenges(ctx context.Context) error r.Lock() defer r.Unlock() - remoteURL := r.remoteURL + "/v2/" - challenges, err := r.cm.GetChallenges(remoteURL) + remoteURL := r.remoteURL + remoteURL.Path = "/v2/" + challenges, err := r.cm.GetChallenges(r.remoteURL) if err != nil { return err } @@ -204,7 +205,7 @@ func (r *remoteAuthChallenger) tryEstablishChallenges(ctx context.Context) error } // establish challenge type with upstream - if err := ping(r.cm, remoteURL, challengeHeader); err != nil { + if err := ping(r.cm, remoteURL.String(), challengeHeader); err != nil { return err }