diff --git a/src/common/utils/registry/auth/tokenauthorizer.go b/src/common/utils/registry/auth/tokenauthorizer.go index b1766e5e0..118aa3444 100644 --- a/src/common/utils/registry/auth/tokenauthorizer.go +++ b/src/common/utils/registry/auth/tokenauthorizer.go @@ -15,20 +15,13 @@ package auth import ( - "encoding/json" "fmt" - "io/ioutil" "net/http" - "net/url" "strings" "sync" "time" - //"github.com/vmware/harbor/src/common/config" - "github.com/vmware/harbor/src/common/models" - "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/registry" - registry_error "github.com/vmware/harbor/src/common/utils/error" token_util "github.com/vmware/harbor/src/ui/service/token" ) @@ -36,13 +29,14 @@ const ( latency int = 10 //second, the network latency when token is received ) -type scope struct { +// Scope ... +type Scope struct { Type string Name string Actions []string } -func (s *scope) string() string { +func (s *Scope) string() string { return fmt.Sprintf("%s:%s:%s", s.Type, s.Name, strings.Join(s.Actions, ",")) } @@ -50,7 +44,7 @@ type tokenGenerator func(realm, service string, scopes []string) (token string, // Implements interface Authorizer type tokenAuthorizer struct { - scope *scope + scope *Scope tg tokenGenerator cache string // cached token expiresAt *time.Time // The UTC standard time at when the token will expire @@ -64,13 +58,13 @@ func (t *tokenAuthorizer) Scheme() string { // AuthorizeRequest will add authorization header which contains a token before the request is sent func (t *tokenAuthorizer) Authorize(req *http.Request, params map[string]string) error { - var scopes []*scope + var scopes []*Scope var token string hasFrom := false from := req.URL.Query().Get("from") if len(from) != 0 { - s := &scope{ + s := &Scope{ Type: "repository", Name: from, Actions: []string{"pull"}, @@ -154,7 +148,7 @@ func NewStandardTokenAuthorizer(credential Credential, insecure bool, } if len(scopeType) != 0 || len(scopeName) != 0 { - authorizer.scope = &scope{ + authorizer.scope = &Scope{ Type: scopeType, Name: scopeName, Actions: scopeActions, @@ -166,66 +160,21 @@ func NewStandardTokenAuthorizer(credential Credential, insecure bool, return authorizer } -func (s *standardTokenAuthorizer) generateToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) { +func (s *standardTokenAuthorizer) generateToken(realm, service string, scopes []string) (string, int, *time.Time, error) { realm = s.tokenURL(realm) + tk, err := getToken(s.client, s.credential, realm, + service, scopes) - u, err := url.Parse(realm) + if len(tk.IssuedAt) == 0 { + return tk.Token, tk.ExpiresIn, nil, nil + } + + issuedAt, err := time.Parse(time.RFC3339, tk.IssuedAt) if err != nil { - return - } - q := u.Query() - q.Add("service", service) - for _, scope := range scopes { - q.Add("scope", scope) - } - u.RawQuery = q.Encode() - r, err := http.NewRequest("GET", u.String(), nil) - if err != nil { - return + return "", 0, nil, err } - if s.credential != nil { - s.credential.AddAuthorization(r) - } - - resp, err := s.client.Do(r) - if err != nil { - return - } - - defer resp.Body.Close() - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - return - } - if resp.StatusCode != http.StatusOK { - err = ®istry_error.Error{ - StatusCode: resp.StatusCode, - Detail: string(b), - } - return - } - - tk := models.Token{} - if err = json.Unmarshal(b, &tk); err != nil { - return - } - - token = tk.Token - - expiresIn = tk.ExpiresIn - - if len(tk.IssuedAt) != 0 { - t, err := time.Parse(time.RFC3339, tk.IssuedAt) - if err != nil { - log.Errorf("error occurred while parsing issued_at: %v", err) - err = nil - } else { - issuedAt = &t - } - } - - return + return tk.Token, tk.ExpiresIn, &issuedAt, nil } // when the registry client is used inside Harbor, the token request @@ -267,7 +216,7 @@ func newUsernameTokenAuthorizer(notary bool, username, scopeType, scopeName stri username: username, } - authorizer.scope = &scope{ + authorizer.scope = &Scope{ Type: scopeType, Name: scopeName, Actions: scopeActions, diff --git a/src/common/utils/registry/auth/util.go b/src/common/utils/registry/auth/util.go new file mode 100644 index 000000000..719418294 --- /dev/null +++ b/src/common/utils/registry/auth/util.go @@ -0,0 +1,93 @@ +// Copyright (c) 2017 VMware, Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package auth + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "net/url" + + "github.com/vmware/harbor/src/common/models" + + registry_error "github.com/vmware/harbor/src/common/utils/error" + "github.com/vmware/harbor/src/common/utils/registry" +) + +const ( + service = "harbor-registry" +) + +// GetToken requests a token against the endpoint using credetial provided +func GetToken(endpoint string, insecure bool, credential Credential, + scopes []*Scope) (*models.Token, error) { + client := &http.Client{ + Transport: registry.GetHTTPTransport(insecure), + } + + scopesStr := []string{} + for _, scope := range scopes { + scopesStr = append(scopesStr, scope.string()) + } + + return getToken(client, credential, endpoint, service, scopesStr) +} + +func getToken(client *http.Client, credential Credential, realm, service string, + scopes []string) (*models.Token, error) { + u, err := url.Parse(realm) + if err != nil { + return nil, err + } + query := u.Query() + query.Add("service", service) + for _, scope := range scopes { + query.Add("scope", scope) + } + u.RawQuery = query.Encode() + + req, err := http.NewRequest(http.MethodGet, u.String(), nil) + if err != nil { + return nil, err + } + + if credential != nil { + credential.AddAuthorization(req) + } + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + return nil, ®istry_error.Error{ + StatusCode: resp.StatusCode, + Detail: string(data), + } + } + + token := &models.Token{} + if err = json.Unmarshal(data, token); err != nil { + return nil, err + } + + return token, nil +} diff --git a/src/jobservice/utils/utils.go b/src/jobservice/utils/utils.go index 30546e777..ce6b775f5 100644 --- a/src/jobservice/utils/utils.go +++ b/src/jobservice/utils/utils.go @@ -15,11 +15,8 @@ package utils import ( - "encoding/json" "fmt" - "io/ioutil" "net/http" - "net/url" "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/utils/registry" @@ -64,39 +61,18 @@ func BuildBlobURL(endpoint, repository, digest string) string { return fmt.Sprintf("%s/v2/%s/blobs/%s", endpoint, repository, digest) } -//GetTokenForRepo is a temp solution for job handler to get a token for clair. -//TODO: Get rid of it when it can get a token from repository client. +//GetTokenForRepo is used for job handler to get a token for clair. func GetTokenForRepo(repository string) (string, error) { - u, err := url.Parse(config.InternalTokenServiceEndpoint()) - if err != nil { - return "", err - } - q := u.Query() - q.Add("service", "harbor-registry") - q.Add("scope", fmt.Sprintf("repository:%s:pull", repository)) - u.RawQuery = q.Encode() - r, err := http.NewRequest("GET", u.String(), nil) - if err != nil { - return "", err - } c := &http.Cookie{Name: models.UISecretCookie, Value: config.JobserviceSecret()} - r.AddCookie(c) - client := &http.Client{} - resp, err := client.Do(r) + credentail := auth.NewCookieCredential(c) + token, err := auth.GetToken(config.InternalTokenServiceEndpoint(), true, credentail, []*auth.Scope{&auth.Scope{ + Type: "repository", + Name: repository, + Actions: []string{"pull"}, + }}) if err != nil { return "", err } - defer resp.Body.Close() - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - return "", err - } - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("Unexpected response from token service, code: %d, %s", resp.StatusCode, string(b)) - } - tk := models.Token{} - if err := json.Unmarshal(b, &tk); err != nil { - return "", err - } - return tk.Token, nil + + return token.Token, nil }