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 <richard.scothern@gmail.com>
This commit is contained in:
Richard Scothern 2016-02-26 14:18:09 -08:00
parent e430d77342
commit 8bd613aa11
5 changed files with 61 additions and 17 deletions

View file

@ -25,7 +25,7 @@ type Challenge struct {
type ChallengeManager interface { type ChallengeManager interface {
// GetChallenges returns the challenges for the given // GetChallenges returns the challenges for the given
// endpoint URL. // endpoint URL.
GetChallenges(endpoint string) ([]Challenge, error) GetChallenges(endpoint url.URL) ([]Challenge, error)
// AddResponse adds the response to the challenge // AddResponse adds the response to the challenge
// manager. The challenges will be parsed out of // manager. The challenges will be parsed out of
@ -48,8 +48,10 @@ func NewSimpleChallengeManager() ChallengeManager {
type simpleChallengeManager map[string][]Challenge type simpleChallengeManager map[string][]Challenge
func (m simpleChallengeManager) GetChallenges(endpoint string) ([]Challenge, error) { func (m simpleChallengeManager) GetChallenges(endpoint url.URL) ([]Challenge, error) {
challenges := m[endpoint] endpoint.Host = strings.ToLower(endpoint.Host)
challenges := m[endpoint.String()]
return challenges, nil return challenges, nil
} }
@ -60,11 +62,10 @@ func (m simpleChallengeManager) AddResponse(resp *http.Response) error {
} }
urlCopy := url.URL{ urlCopy := url.URL{
Path: resp.Request.URL.Path, Path: resp.Request.URL.Path,
Host: resp.Request.URL.Host, Host: strings.ToLower(resp.Request.URL.Host),
Scheme: resp.Request.URL.Scheme, Scheme: resp.Request.URL.Scheme,
} }
m[urlCopy.String()] = challenges m[urlCopy.String()] = challenges
return nil return nil
} }

View file

@ -1,7 +1,10 @@
package auth package auth
import ( import (
"fmt"
"net/http" "net/http"
"net/url"
"strings"
"testing" "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")
}
}

View file

@ -67,9 +67,7 @@ func (ea *endpointAuthorizer) ModifyRequest(req *http.Request) error {
Path: req.URL.Path[:v2Root+4], Path: req.URL.Path[:v2Root+4],
} }
pingEndpoint := ping.String() challenges, err := ea.challenges.GetChallenges(ping)
challenges, err := ea.challenges.GetChallenges(pingEndpoint)
if err != nil { if err != nil {
return err return err
} }

View file

@ -340,6 +340,7 @@ func (buh *blobUploadHandler) blobUploadResponse(w http.ResponseWriter, r *http.
w.Header().Set("Docker-Upload-UUID", buh.UUID) w.Header().Set("Docker-Upload-UUID", buh.UUID)
w.Header().Set("Location", uploadURL) w.Header().Set("Location", uploadURL)
w.Header().Set("Content-Length", "0") w.Header().Set("Content-Length", "0")
w.Header().Set("Range", fmt.Sprintf("0-%d", endRange)) w.Header().Set("Range", fmt.Sprintf("0-%d", endRange))

View file

@ -22,13 +22,13 @@ import (
type proxyingRegistry struct { type proxyingRegistry struct {
embedded distribution.Namespace // provides local registry functionality embedded distribution.Namespace // provides local registry functionality
scheduler *scheduler.TTLExpirationScheduler scheduler *scheduler.TTLExpirationScheduler
remoteURL string remoteURL url.URL
authChallenger authChallenger authChallenger authChallenger
} }
// NewRegistryPullThroughCache creates a registry acting as a pull through cache // 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) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -99,9 +99,9 @@ func NewRegistryPullThroughCache(ctx context.Context, registry distribution.Name
return &proxyingRegistry{ return &proxyingRegistry{
embedded: registry, embedded: registry,
scheduler: s, scheduler: s,
remoteURL: config.RemoteURL, remoteURL: *remoteURL,
authChallenger: &remoteAuthChallenger{ authChallenger: &remoteAuthChallenger{
remoteURL: config.RemoteURL, remoteURL: *remoteURL,
cm: auth.NewSimpleChallengeManager(), cm: auth.NewSimpleChallengeManager(),
cs: cs, cs: cs,
}, },
@ -131,7 +131,7 @@ func (pr *proxyingRegistry) Repository(ctx context.Context, name reference.Named
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }
@ -174,7 +174,7 @@ type authChallenger interface {
} }
type remoteAuthChallenger struct { type remoteAuthChallenger struct {
remoteURL string remoteURL url.URL
sync.Mutex sync.Mutex
cm auth.ChallengeManager cm auth.ChallengeManager
cs auth.CredentialStore cs auth.CredentialStore
@ -193,8 +193,9 @@ func (r *remoteAuthChallenger) tryEstablishChallenges(ctx context.Context) error
r.Lock() r.Lock()
defer r.Unlock() defer r.Unlock()
remoteURL := r.remoteURL + "/v2/" remoteURL := r.remoteURL
challenges, err := r.cm.GetChallenges(remoteURL) remoteURL.Path = "/v2/"
challenges, err := r.cm.GetChallenges(r.remoteURL)
if err != nil { if err != nil {
return err return err
} }
@ -204,7 +205,7 @@ func (r *remoteAuthChallenger) tryEstablishChallenges(ctx context.Context) error
} }
// establish challenge type with upstream // 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 return err
} }