forked from TrueCloudLab/distribution
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:
parent
bb4d128523
commit
e09891e2cf
5 changed files with 61 additions and 17 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue