From 27a14f1a9d27b624066ec37bebc9e9e184e5b59d Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Wed, 4 Apr 2018 16:14:24 -0700 Subject: [PATCH] remotes/docker: protect secret with a mutex Signed-off-by: Stephen J Day --- remotes/docker/resolver.go | 46 ++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/remotes/docker/resolver.go b/remotes/docker/resolver.go index 371027489f7b..eca02d532cf3 100644 --- a/remotes/docker/resolver.go +++ b/remotes/docker/resolver.go @@ -27,6 +27,7 @@ import ( "path" "strconv" "strings" + "sync" "time" "github.com/containerd/containerd/images" @@ -253,12 +254,12 @@ func (r *dockerResolver) Pusher(ctx context.Context, ref string) (remotes.Pusher type dockerBase struct { refspec reference.Spec base url.URL - token string - client *http.Client - useBasic bool - username string - secret string + client *http.Client + useBasic bool + username, secret string + token string + mu sync.Mutex } func (r *dockerResolver) base(refspec reference.Spec) (*dockerBase, error) { @@ -300,6 +301,23 @@ func (r *dockerResolver) base(refspec reference.Spec) (*dockerBase, error) { }, nil } +func (r *dockerBase) getToken() string { + r.mu.Lock() + defer r.mu.Unlock() + + return r.token +} + +func (r *dockerBase) setToken(token string) bool { + r.mu.Lock() + defer r.mu.Unlock() + + changed := r.token != token + r.token = token + + return changed +} + func (r *dockerBase) url(ps ...string) string { url := r.base url.Path = path.Join(url.Path, path.Join(ps...)) @@ -307,10 +325,11 @@ func (r *dockerBase) url(ps ...string) string { } func (r *dockerBase) authorize(req *http.Request) { + token := r.getToken() if r.useBasic { req.SetBasicAuth(r.username, r.secret) - } else if r.token != "" { - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", r.token)) + } else if token != "" { + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) } } @@ -358,7 +377,7 @@ func (r *dockerBase) retryRequest(ctx context.Context, req *http.Request, respon for _, c := range parseAuthHeader(last.Header) { if c.scheme == bearerAuth { if err := invalidAuthorization(c, responses); err != nil { - r.token = "" + r.setToken("") return nil, err } if err := r.setTokenAuth(ctx, c.parameters); err != nil { @@ -443,19 +462,22 @@ func (r *dockerBase) setTokenAuth(ctx context.Context, params map[string]string) if len(to.scopes) == 0 { return errors.Errorf("no scope specified for token auth challenge") } + + var token string if r.secret != "" { // Credential information is provided, use oauth POST endpoint - r.token, err = r.fetchTokenWithOAuth(ctx, to) + token, err = r.fetchTokenWithOAuth(ctx, to) if err != nil { return errors.Wrap(err, "failed to fetch oauth token") } } else { // Do request anonymously - r.token, err = r.getToken(ctx, to) + token, err = r.fetchToken(ctx, to) if err != nil { return errors.Wrap(err, "failed to fetch anonymous token") } } + r.setToken(token) return nil } @@ -500,7 +522,7 @@ func (r *dockerBase) fetchTokenWithOAuth(ctx context.Context, to tokenOptions) ( // As of September 2017, GCR is known to return 404. // As of February 2018, JFrog Artifactory is known to return 401. if (resp.StatusCode == 405 && r.username != "") || resp.StatusCode == 404 || resp.StatusCode == 401 { - return r.getToken(ctx, to) + return r.fetchToken(ctx, to) } else if resp.StatusCode < 200 || resp.StatusCode >= 400 { b, _ := ioutil.ReadAll(io.LimitReader(resp.Body, 64000)) // 64KB log.G(ctx).WithFields(logrus.Fields{ @@ -530,7 +552,7 @@ type getTokenResponse struct { } // getToken fetches a token using a GET request -func (r *dockerBase) getToken(ctx context.Context, to tokenOptions) (string, error) { +func (r *dockerBase) fetchToken(ctx context.Context, to tokenOptions) (string, error) { req, err := http.NewRequest("GET", to.realm, nil) if err != nil { return "", err