diff --git a/src/common/utils/notary/helper.go b/src/common/utils/notary/helper.go index 60b7dbb89..b6e1abb1c 100644 --- a/src/common/utils/notary/helper.go +++ b/src/common/utils/notary/helper.go @@ -29,6 +29,7 @@ import ( "github.com/vmware/harbor/src/common/utils/registry" "github.com/vmware/harbor/src/common/utils/registry/auth" "github.com/vmware/harbor/src/ui/config" + "github.com/vmware/harbor/src/ui/service/token" "github.com/opencontainers/go-digest" ) @@ -74,12 +75,8 @@ func GetInternalTargets(notaryEndpoint string, username string, repo string) ([] // like "10.117.4.117/library/ubuntu", instead of "library/ubuntu" (fqRepo for fully-qualified repo) func GetTargets(notaryEndpoint string, username string, fqRepo string) ([]Target, error) { res := []Target{} - authorizer := auth.NewNotaryUsernameTokenAuthorizer(username, "repository", fqRepo, "pull") - store, err := auth.NewAuthorizerStore(strings.Split(notaryEndpoint, "//")[1], true, authorizer) - if err != nil { - return res, err - } - tr := registry.NewTransport(registry.GetHTTPTransport(true), store) + authorizer := auth.NewRawTokenAuthorizer(username, token.Notary) + tr := registry.NewTransport(registry.GetHTTPTransport(true), authorizer) gun := data.GUN(fqRepo) notaryRepo, err := client.NewFileCachedNotaryRepository(notaryCachePath, gun, notaryEndpoint, tr, mockRetriever, trustPin) if err != nil { diff --git a/src/common/utils/notary/helper_test.go b/src/common/utils/notary/helper_test.go index 83988d8a6..ee8065e01 100644 --- a/src/common/utils/notary/helper_test.go +++ b/src/common/utils/notary/helper_test.go @@ -16,6 +16,7 @@ package notary import ( "encoding/json" "fmt" + "github.com/stretchr/testify/assert" "github.com/vmware/harbor/src/common" notarytest "github.com/vmware/harbor/src/common/utils/notary/test" @@ -36,9 +37,10 @@ func TestMain(m *testing.M) { notaryServer = notarytest.NewNotaryServer(endpoint) defer notaryServer.Close() var defaultConfig = map[string]interface{}{ - common.ExtEndpoint: "https://" + endpoint, - common.WithNotary: true, - common.CfgExpiration: 5, + common.ExtEndpoint: "https://" + endpoint, + common.WithNotary: true, + common.CfgExpiration: 5, + common.TokenExpiration: 30, } adminServer, err := utilstest.NewAdminserver(defaultConfig) if err != nil { diff --git a/src/common/utils/registry/auth/authorizer.go b/src/common/utils/registry/auth/authorizer.go deleted file mode 100644 index 63476bdfa..000000000 --- a/src/common/utils/registry/auth/authorizer.go +++ /dev/null @@ -1,107 +0,0 @@ -// 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 ( - "fmt" - "net/http" - "net/url" - "strings" - "time" - - "github.com/docker/distribution/registry/client/auth/challenge" - "github.com/vmware/harbor/src/common/utils" - "github.com/vmware/harbor/src/common/utils/registry" -) - -// Authorizer authorizes requests according to the schema -type Authorizer interface { - // Scheme : basic, bearer - Scheme() string - //Authorize adds basic auth or token auth to the header of request - Authorize(req *http.Request, params map[string]string) error -} - -// AuthorizerStore holds a authorizer list, which will authorize request. -// And it implements interface Modifier -type AuthorizerStore struct { - authorizers []Authorizer - ping *url.URL - challenges []challenge.Challenge -} - -// NewAuthorizerStore ... -func NewAuthorizerStore(endpoint string, insecure bool, authorizers ...Authorizer) (*AuthorizerStore, error) { - endpoint = utils.FormatEndpoint(endpoint) - - client := &http.Client{ - Transport: registry.GetHTTPTransport(insecure), - Timeout: 30 * time.Second, - } - - pingURL := buildPingURL(endpoint) - resp, err := client.Get(pingURL) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - challenges := ParseChallengeFromResponse(resp) - ping, err := url.Parse(pingURL) - if err != nil { - return nil, err - } - return &AuthorizerStore{ - authorizers: authorizers, - ping: ping, - challenges: challenges, - }, nil -} - -func buildPingURL(endpoint string) string { - return fmt.Sprintf("%s/v2/", endpoint) -} - -// Modify adds authorization to the request -func (a *AuthorizerStore) Modify(req *http.Request) error { - //only handle the requests sent to registry - v2Index := strings.Index(req.URL.Path, "/v2/") - if v2Index == -1 { - return nil - } - - ping := url.URL{ - Host: req.URL.Host, - Scheme: req.URL.Scheme, - Path: req.URL.Path[:v2Index+4], - } - - if ping.Host != a.ping.Host || ping.Scheme != a.ping.Scheme || - ping.Path != a.ping.Path { - return nil - } - - for _, challenge := range a.challenges { - for _, authorizer := range a.authorizers { - if authorizer.Scheme() == challenge.Scheme { - if err := authorizer.Authorize(req, challenge.Parameters); err != nil { - return err - } - } - } - } - - return nil -} diff --git a/src/common/utils/registry/auth/authorizer_test.go b/src/common/utils/registry/auth/authorizer_test.go deleted file mode 100644 index 3b368e143..000000000 --- a/src/common/utils/registry/auth/authorizer_test.go +++ /dev/null @@ -1,108 +0,0 @@ -// 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 ( - "net/http" - "net/url" - "strings" - "testing" - - ch "github.com/docker/distribution/registry/client/auth/challenge" - "github.com/vmware/harbor/src/common/utils/test" -) - -func TestNewAuthorizerStore(t *testing.T) { - handler := test.Handler(&test.Response{ - StatusCode: http.StatusUnauthorized, - Headers: map[string]string{ - "Www-Authenticate": "Bearer realm=\"https://auth.docker.io/token\",service=\"registry.docker.io\"", - }, - }) - - server := test.NewServer(&test.RequestHandlerMapping{ - Method: "GET", - Pattern: "/v2/", - Handler: handler, - }) - defer server.Close() - - _, err := NewAuthorizerStore(server.URL, false, nil) - if err != nil { - t.Fatalf("failed to create authorizer store: %v", err) - } -} - -type simpleAuthorizer struct { -} - -func (s *simpleAuthorizer) Scheme() string { - return "bearer" -} - -func (s *simpleAuthorizer) Authorize(req *http.Request, - params map[string]string) error { - req.Header.Set("Authorization", "Bearer token") - return nil -} - -func TestModify(t *testing.T) { - authorizer := &simpleAuthorizer{} - challenge := ch.Challenge{ - Scheme: "bearer", - } - - ping, err := url.Parse("http://example.com/v2/") - if err != nil { - t.Fatalf("failed to parse URL: %v", err) - } - as := &AuthorizerStore{ - authorizers: []Authorizer{authorizer}, - ping: ping, - challenges: []ch.Challenge{challenge}, - } - - req, err := http.NewRequest("GET", "http://example.com/v2/ubuntu/manifests/14.04", nil) - if err != nil { - t.Fatalf("failed to create request: %v", err) - } - - if err = as.Modify(req); err != nil { - t.Fatalf("failed to modify request: %v", err) - } - - header := req.Header.Get("Authorization") - if len(header) == 0 { - t.Fatal("\"Authorization\" header not found") - } - - if !strings.HasPrefix(header, "Bearer") { - t.Fatal("\"Authorization\" header does not start with \"Bearer\"") - } - - req, err = http.NewRequest("GET", "http://example.com", nil) - if err != nil { - t.Fatalf("failed to create request: %v", err) - } - - if err = as.Modify(req); err != nil { - t.Fatalf("failed to modify request: %v", err) - } - - header = req.Header.Get("Authorization") - if len(header) != 0 { - t.Fatal("\"Authorization\" header should not be added") - } -} diff --git a/src/common/utils/registry/auth/path.go b/src/common/utils/registry/auth/path.go new file mode 100644 index 000000000..954847dac --- /dev/null +++ b/src/common/utils/registry/auth/path.go @@ -0,0 +1,56 @@ +// 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 ( + "regexp" + + "github.com/docker/distribution/digest" + "github.com/docker/distribution/reference" + "github.com/vmware/harbor/src/common/utils/log" +) + +var ( + base = regexp.MustCompile("/v2") + catalog = regexp.MustCompile("/v2/_catalog") + tag = regexp.MustCompile("/v2/(" + reference.NameRegexp.String() + ")/tags/list") + manifest = regexp.MustCompile("/v2/(" + reference.NameRegexp.String() + ")/manifests/(" + reference.TagRegexp.String() + "|" + digest.DigestRegexp.String() + ")") + blob = regexp.MustCompile("/v2/(" + reference.NameRegexp.String() + ")/blobs/" + digest.DigestRegexp.String()) + blobUpload = regexp.MustCompile("/v2/(" + reference.NameRegexp.String() + ")/blobs/uploads") + blobUploadChunk = regexp.MustCompile("/v2/(" + reference.NameRegexp.String() + ")/blobs/uploads/[a-zA-Z0-9-_.=]+") + + repoRegExps = []*regexp.Regexp{tag, manifest, blob, blobUploadChunk, blobUpload} +) + +// parse the repository name from path, if the path doesn't match any +// regular expressions in repoRegExps, nil string will be returned +func parseRepository(path string) string { + for _, regExp := range repoRegExps { + subs := regExp.FindStringSubmatch(path) + // no match + if subs == nil { + continue + } + + // match + if len(subs) < 2 { + log.Warningf("unexpected length of sub matches: %d, should >= 2 ", len(subs)) + continue + } + return subs[1] + } + + return "" +} diff --git a/src/common/utils/registry/auth/path_test.go b/src/common/utils/registry/auth/path_test.go new file mode 100644 index 000000000..995af6f7d --- /dev/null +++ b/src/common/utils/registry/auth/path_test.go @@ -0,0 +1,43 @@ +// 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 ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseRepository(t *testing.T) { + cases := []struct { + input string + output string + }{ + {"/v2", ""}, + {"/v2/_catalog", ""}, + {"/v2/library/tags/list", "library"}, + {"/v2/tags/list", ""}, + {"/v2/tags/list/tags/list", "tags/list"}, + {"/v2/library/manifests/latest", "library"}, + {"/v2/library/manifests/sha256:1234567890", "library"}, + {"/v2/library/blobs/sha256:1234567890", "library"}, + {"/v2/library/blobs/uploads", "library"}, + {"/v2/library/blobs/uploads/1234567890", "library"}, + } + + for _, c := range cases { + assert.Equal(t, c.output, parseRepository(c.input)) + } +} diff --git a/src/common/utils/registry/auth/tokenauthorizer.go b/src/common/utils/registry/auth/tokenauthorizer.go index 3bbc74f14..905bc1487 100644 --- a/src/common/utils/registry/auth/tokenauthorizer.go +++ b/src/common/utils/registry/auth/tokenauthorizer.go @@ -17,16 +17,20 @@ package auth import ( "fmt" "net/http" + "net/url" "strings" "sync" "time" + "github.com/vmware/harbor/src/common/models" + "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/registry" token_util "github.com/vmware/harbor/src/ui/service/token" ) const ( latency int = 10 //second, the network latency when token is received + scheme = "bearer" ) // Scope ... @@ -40,144 +44,255 @@ func (s *Scope) string() string { return fmt.Sprintf("%s:%s:%s", s.Type, s.Name, strings.Join(s.Actions, ",")) } -type tokenGenerator func(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) +type tokenGenerator interface { + generate(realm, service string, scopes []*Scope) (*models.Token, error) +} -// Implements interface Authorizer +// tokenAuthorizer implements registry.Modifier interface. It parses scopses +// from the request, generates authentication token and modifies the requset +// by adding the token type tokenAuthorizer struct { - scope *Scope - tg tokenGenerator - cache string // cached token - expiresAt *time.Time // The UTC standard time at when the token will expire - sync.Mutex + realm string + service string + registryURL *url.URL // used to filter request + generator tokenGenerator + client *http.Client + cachedTokens map[string]*models.Token + sync.RWMutex } -// Scheme returns the scheme that the handler can handle -func (t *tokenAuthorizer) Scheme() string { - return "bearer" -} +// add token to the request +func (t *tokenAuthorizer) Modify(req *http.Request) error { + //only handle requests sent to registry + goon, err := t.filterReq(req) + if err != nil { + return err + } -// 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 token string + if !goon { + log.Debugf("the request %s is not sent to registry, skip", req.URL.String()) + return nil + } - hasFrom := false - from := req.URL.Query().Get("from") - if len(from) != 0 { - s := &Scope{ - Type: "repository", - Name: from, - Actions: []string{"pull"}, + // parse scopes from request + scopes := parseScopes(req) + + var token *models.Token + // try to get token from cache if the request is for empty scope(login) + // or single scope + if len(scopes) <= 1 { + key := "" + if len(scopes) == 1 { + key = scopes[0].string() } - scopes = append(scopes, s) - // do not cache the token if "from" appears - hasFrom = true + token = t.getCachedToken(key) } - if t.scope != nil { - scopes = append(scopes, t.scope) - } - - expired := true - - cachedToken, cachedExpiredAt := t.getCachedToken() - - if len(cachedToken) != 0 && cachedExpiredAt != nil { - expired = cachedExpiredAt.Before(time.Now().UTC()) - } - - if expired || hasFrom { - scopeStrs := []string{} - for _, scope := range scopes { - scopeStrs = append(scopeStrs, scope.string()) + // request a new token if the token is null + if token == nil { + // ping first if the realm and service are both null + if len(t.realm) == 0 && len(t.service) == 0 { + realm, service, err := ping(t.client, t.registryURL.String()) + if err != nil { + return err + } + if len(realm) == 0 { + log.Warning("empty realm, skip") + return nil + } + t.realm = realm + t.service = service } - to, expiresIn, _, err := t.tg(params["realm"], params["service"], scopeStrs) + token, err = t.generator.generate(t.realm, t.service, scopes) if err != nil { return err } - token = to - - if !hasFrom { - t.updateCachedToken(to, expiresIn) + // only cache the token for empty scope(login) or single scope request + if len(scopes) <= 1 { + key := "" + if len(scopes) == 1 { + key = scopes[0].string() + } + t.updateCachedToken(key, token) } - } else { - token = cachedToken } - req.Header.Add(http.CanonicalHeaderKey("Authorization"), fmt.Sprintf("Bearer %s", token)) + req.Header.Add(http.CanonicalHeaderKey("Authorization"), fmt.Sprintf("Bearer %s", token.Token)) return nil } -func (t *tokenAuthorizer) getCachedToken() (string, *time.Time) { - t.Lock() - defer t.Unlock() - return t.cache, t.expiresAt +// some requests are sent to backend storage, such as s3, this method filters +// the requests only sent to registry +func (t *tokenAuthorizer) filterReq(req *http.Request) (bool, error) { + // the registryURL is nil when the first request comes, init it with + // the scheme and host of the request which must be sent to the registry + if t.registryURL == nil { + u, err := url.Parse(buildPingURL(req.URL.Scheme + "://" + req.URL.Host)) + if err != nil { + return false, err + } + t.registryURL = u + } + + v2Index := strings.Index(req.URL.Path, "/v2/") + if v2Index == -1 { + return false, nil + } + + if req.URL.Host != t.registryURL.Host || req.URL.Scheme != t.registryURL.Scheme || + req.URL.Path[:v2Index+4] != t.registryURL.Path { + return false, nil + } + + return true, nil } -func (t *tokenAuthorizer) updateCachedToken(token string, expiresIn int) { - t.Lock() - defer t.Unlock() - t.cache = token - n := (time.Duration)(expiresIn - latency) - e := time.Now().Add(n * time.Second).UTC() - t.expiresAt = &e +// parse scopes from the request according to its method, path and query string +func parseScopes(req *http.Request) []*Scope { + scopes := []*Scope{} + + from := req.URL.Query().Get("from") + if len(from) != 0 { + scopes = append(scopes, &Scope{ + Type: "repository", + Name: from, + Actions: []string{"pull"}, + }) + } + + var scope *Scope + path := strings.TrimRight(req.URL.Path, "/") + repository := parseRepository(path) + if len(repository) > 0 { + // pull, push, delete blob/manifest + scope = &Scope{ + Type: "repository", + Name: repository, + } + switch req.Method { + case http.MethodGet: + scope.Actions = []string{"pull"} + case http.MethodPost, http.MethodPut, http.MethodPatch: + scope.Actions = []string{"push"} + case http.MethodDelete: + scope.Actions = []string{"*"} + default: + scope = nil + log.Warningf("unsupported method: %s", req.Method) + } + } else if catalog.MatchString(path) { + // catalog + scope = &Scope{ + Type: "registry", + Name: "catalog", + Actions: []string{"*"}, + } + } else if base.MatchString(path) { + // base + scope = nil + } else { + // unknow + log.Warningf("can not parse scope from the request: %s %s", req.Method, req.URL.Path) + } + + if scope != nil { + scopes = append(scopes, scope) + } + + strs := []string{} + for _, s := range scopes { + strs = append(strs, s.string()) + } + log.Debugf("scopses parsed from request: %s", strings.Join(strs, " ")) + + return scopes } -// Implements interface Authorizer -type standardTokenAuthorizer struct { - tokenAuthorizer - client *http.Client - credential Credential - tokenServiceEndpoint string +func (t *tokenAuthorizer) getCachedToken(scope string) *models.Token { + t.RLock() + defer t.RUnlock() + token := t.cachedTokens[scope] + if token == nil { + return nil + } + + issueAt, err := time.Parse(time.RFC3339, token.IssuedAt) + if err != nil { + log.Errorf("failed parse %s: %v", token.IssuedAt, err) + return nil + } + + if issueAt.Add(time.Duration(token.ExpiresIn-latency) * time.Second).Before(time.Now().UTC()) { + return nil + } + + log.Debug("get token from cache") + return token +} + +func (t *tokenAuthorizer) updateCachedToken(scope string, token *models.Token) { + t.Lock() + defer t.Unlock() + t.cachedTokens[scope] = token +} + +// ping returns the realm, service and error +func ping(client *http.Client, endpoint string) (string, string, error) { + resp, err := client.Get(endpoint) + if err != nil { + return "", "", err + } + defer resp.Body.Close() + + challenges := ParseChallengeFromResponse(resp) + for _, challenge := range challenges { + if scheme == challenge.Scheme { + realm := challenge.Parameters["realm"] + service := challenge.Parameters["service"] + return realm, service, nil + } + } + return "", "", fmt.Errorf("schemes %v are unsupportted", challenges) } // NewStandardTokenAuthorizer returns a standard token authorizer. The authorizer will request a token // from token server and add it to the origin request -// If tokenServiceEndpoint is set, the token request will be sent to it instead of the server get from authorizer +// If customizedTokenService is set, the token request will be sent to it instead of the server get from authorizer // The usage please refer to the function tokenURL func NewStandardTokenAuthorizer(credential Credential, insecure bool, - tokenServiceEndpoint string, scopeType, scopeName string, scopeActions ...string) Authorizer { - authorizer := &standardTokenAuthorizer{ - client: &http.Client{ - Transport: registry.GetHTTPTransport(insecure), - Timeout: 30 * time.Second, - }, - credential: credential, - tokenServiceEndpoint: tokenServiceEndpoint, + customizedTokenService ...string) registry.Modifier { + client := &http.Client{ + Transport: registry.GetHTTPTransport(insecure), + Timeout: 30 * time.Second, } - if len(scopeType) != 0 || len(scopeName) != 0 { - authorizer.scope = &Scope{ - Type: scopeType, - Name: scopeName, - Actions: scopeActions, - } + generator := &standardTokenGenerator{ + credential: credential, + client: client, + } + if len(customizedTokenService) > 0 { + generator.customizedTokenService = customizedTokenService[0] } - authorizer.tg = authorizer.generateToken - - return authorizer + return &tokenAuthorizer{ + cachedTokens: make(map[string]*models.Token), + generator: generator, + client: client, + } } -func (s *standardTokenAuthorizer) generateToken(realm, service string, scopes []string) (string, int, *time.Time, error) { +// standardTokenGenerator implements interface tokenGenerator +type standardTokenGenerator struct { + credential Credential + customizedTokenService string + client *http.Client +} + +// get token from token service +func (s *standardTokenGenerator) generate(realm, service string, scopes []*Scope) (*models.Token, error) { realm = s.tokenURL(realm) - tk, err := getToken(s.client, s.credential, realm, - service, scopes) - if err != nil { - return "", 0, nil, err - } - - if len(tk.IssuedAt) == 0 { - return tk.Token, tk.ExpiresIn, nil, nil - } - - issuedAt, err := time.Parse(time.RFC3339, tk.IssuedAt) - if err != nil { - return "", 0, nil, err - } - - return tk.Token, tk.ExpiresIn, &issuedAt, nil + return getToken(s.client, s.credential, realm, service, scopes) } // when the registry client is used inside Harbor, the token request @@ -187,62 +302,50 @@ func (s *standardTokenAuthorizer) generateToken(realm, service string, scopes [] // 1. performance issue // 2. the realm field returned by registry is an IP which can not reachable // inside Harbor -func (s *standardTokenAuthorizer) tokenURL(realm string) string { - if len(s.tokenServiceEndpoint) != 0 { - return s.tokenServiceEndpoint +func (s *standardTokenGenerator) tokenURL(realm string) string { + if len(s.customizedTokenService) != 0 { + return s.customizedTokenService } return realm } -// Implements interface Handler -type usernameTokenAuthorizer struct { - tokenAuthorizer - username string -} - -// NewRegistryUsernameTokenAuthorizer returns an authorizer to generate token for registry according to -// the user's privileges -func NewRegistryUsernameTokenAuthorizer(username, scopeType, scopeName string, scopeActions ...string) Authorizer { - return newUsernameTokenAuthorizer(false, username, scopeType, scopeName, scopeActions...) -} - -// NewNotaryUsernameTokenAuthorizer returns an authorizer to generate token for notary according to -// the user's privileges -func NewNotaryUsernameTokenAuthorizer(username, scopeType, scopeName string, scopeActions ...string) Authorizer { - return newUsernameTokenAuthorizer(true, username, scopeType, scopeName, scopeActions...) -} - -// newUsernameTokenAuthorizer returns a authorizer which will generate a token according to -// the user's privileges -func newUsernameTokenAuthorizer(notary bool, username, scopeType, scopeName string, scopeActions ...string) Authorizer { - authorizer := &usernameTokenAuthorizer{ +// NewRawTokenAuthorizer returns a token authorizer which calls method to create +// token directly +func NewRawTokenAuthorizer(username, service string) registry.Modifier { + generator := &rawTokenGenerator{ username: username, } - authorizer.scope = &Scope{ - Type: scopeType, - Name: scopeName, - Actions: scopeActions, + return &tokenAuthorizer{ + service: service, + cachedTokens: make(map[string]*models.Token), + generator: generator, } - if notary { - authorizer.tg = authorizer.genNotaryToken - } else { - authorizer.tg = authorizer.genRegistryToken +} + +// rawTokenGenerator implements interface tokenGenerator +type rawTokenGenerator struct { + username string +} + +// generate token directly +func (r *rawTokenGenerator) generate(realm, service string, scopes []*Scope) (*models.Token, error) { + strs := []string{} + for _, scope := range scopes { + strs = append(strs, scope.string()) } - return authorizer + token, expiresIn, issuedAt, err := token_util.RegistryTokenForUI(r.username, service, strs) + if err != nil { + return nil, err + } + + return &models.Token{ + Token: token, + ExpiresIn: expiresIn, + IssuedAt: issuedAt.Format(time.RFC3339), + }, nil } -func (u *usernameTokenAuthorizer) generateToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) { - token, expiresIn, issuedAt, err = token_util.RegistryTokenForUI(u.username, service, scopes) - return -} - -func (u *usernameTokenAuthorizer) genRegistryToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) { - token, expiresIn, issuedAt, err = token_util.RegistryTokenForUI(u.username, service, scopes) - return -} - -func (u *usernameTokenAuthorizer) genNotaryToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) { - token, expiresIn, issuedAt, err = token_util.NotaryTokenForUI(u.username, service, scopes) - return +func buildPingURL(endpoint string) string { + return fmt.Sprintf("%s/v2/", endpoint) } diff --git a/src/common/utils/registry/auth/tokenauthorizer_test.go b/src/common/utils/registry/auth/tokenauthorizer_test.go index c93a35802..751fa1893 100644 --- a/src/common/utils/registry/auth/tokenauthorizer_test.go +++ b/src/common/utils/registry/auth/tokenauthorizer_test.go @@ -15,54 +15,184 @@ package auth import ( + "encoding/json" + "fmt" "net/http" + "strings" "testing" + "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/utils/test" ) -func TestAuthorizeOfStandardTokenAuthorizer(t *testing.T) { - handler := test.Handler(&test.Response{ - Body: []byte(` - { - "token":"token", - "expires_in":300, - "issued_at":"2016-08-17T23:17:58+08:00" - } - `), +func TestFilterReq(t *testing.T) { + authorizer := tokenAuthorizer{} + + // v2 + req, err := http.NewRequest(http.MethodGet, "http://registry/v2/", nil) + require.Nil(t, err) + goon, err := authorizer.filterReq(req) + assert.Nil(t, err) + assert.True(t, goon) + + // catalog + req, err = http.NewRequest(http.MethodGet, "http://registry/v2/_catalog?n=1000", nil) + require.Nil(t, err) + goon, err = authorizer.filterReq(req) + assert.Nil(t, err) + assert.True(t, goon) + + // contains two v2 in path + req, err = http.NewRequest(http.MethodGet, "http://registry/v2/library/v2/tags/list", nil) + require.Nil(t, err) + goon, err = authorizer.filterReq(req) + assert.Nil(t, err) + assert.True(t, goon) + + // different scheme + req, err = http.NewRequest(http.MethodGet, "https://registry/v2/library/golang/tags/list", nil) + require.Nil(t, err) + goon, err = authorizer.filterReq(req) + assert.Nil(t, err) + assert.False(t, goon) + + // different host + req, err = http.NewRequest(http.MethodGet, "http://vmware.com/v2/library/golang/tags/list", nil) + require.Nil(t, err) + goon, err = authorizer.filterReq(req) + assert.Nil(t, err) + assert.False(t, goon) + + // different path + req, err = http.NewRequest(http.MethodGet, "https://registry/s3/ssss", nil) + require.Nil(t, err) + goon, err = authorizer.filterReq(req) + assert.Nil(t, err) + assert.False(t, goon) +} + +func TestParseScopes(t *testing.T) { + // contains from in query string + req, err := http.NewRequest(http.MethodGet, "http://registry/v2?from=library", nil) + require.Nil(t, err) + scopses := parseScopes(req) + assert.Equal(t, 1, len(scopses)) + assert.EqualValues(t, &Scope{ + Type: "repository", + Name: "library", + Actions: []string{ + "pull"}, + }, scopses[0]) + + // v2 + req, err = http.NewRequest(http.MethodGet, "http://registry/v2", nil) + require.Nil(t, err) + scopses = parseScopes(req) + assert.Equal(t, 0, len(scopses)) + + // catalog + req, err = http.NewRequest(http.MethodGet, "http://registry/v2/_catalog", nil) + require.Nil(t, err) + scopses = parseScopes(req) + assert.Equal(t, 1, len(scopses)) + assert.EqualValues(t, &Scope{ + Type: "registry", + Name: "catalog", + Actions: []string{ + "*"}, + }, scopses[0]) + + // manifest + req, err = http.NewRequest(http.MethodPut, "http://registry/v2/library/mysql/5.6/manifests/1", nil) + require.Nil(t, err) + scopses = parseScopes(req) + assert.Equal(t, 1, len(scopses)) + assert.EqualValues(t, &Scope{ + Type: "repository", + Name: "library/mysql/5.6", + Actions: []string{ + "push"}, + }, scopses[0]) +} + +func TestGetAndUpdateCachedToken(t *testing.T) { + authorizer := &tokenAuthorizer{ + cachedTokens: make(map[string]*models.Token), + } + + // empty cache + token := authorizer.getCachedToken("") + assert.Nil(t, token) + + // put a valid token into cache + token = &models.Token{ + Token: "token", + ExpiresIn: 60, + IssuedAt: time.Now().Format(time.RFC3339), + } + authorizer.updateCachedToken("", token) + token2 := authorizer.getCachedToken("") + assert.EqualValues(t, token, token2) + + // put a expired token into cache + token = &models.Token{ + Token: "token", + ExpiresIn: 60, + IssuedAt: time.Now().Add(-time.Second * 120).Format("2006-01-02 15:04:05.999999999 -0700 MST"), + } + authorizer.updateCachedToken("", token) + token2 = authorizer.getCachedToken("") + assert.Nil(t, token2) +} + +func TestModifyOfStandardTokenAuthorizer(t *testing.T) { + token := &models.Token{ + Token: "token", + ExpiresIn: 3600, + IssuedAt: time.Now().String(), + } + data, err := json.Marshal(token) + require.Nil(t, err) + + tokenHandler := test.Handler(&test.Response{ + Body: data, }) - server := test.NewServer(&test.RequestHandlerMapping{ - Method: "GET", - Pattern: "/token", - Handler: handler, + tokenServer := test.NewServer( + &test.RequestHandlerMapping{ + Method: "GET", + Pattern: "/service/token", + Handler: tokenHandler, + }) + defer tokenServer.Close() + + header := fmt.Sprintf("Bearer realm=\"%s/service/token\",service=\"registry\"", + tokenServer.URL) + pingHandler := test.Handler(&test.Response{ + StatusCode: http.StatusUnauthorized, + Headers: map[string]string{ + "WWW-Authenticate": header, + }, }) - defer server.Close() + registryServer := test.NewServer( + &test.RequestHandlerMapping{ + Method: "GET", + Pattern: "/v2", + Handler: pingHandler, + }) + defer registryServer.Close() - authorizer := NewStandardTokenAuthorizer(nil, false, "", "repository", "library/ubuntu", "pull") - req, err := http.NewRequest("GET", "http://registry", nil) - if err != nil { - t.Fatalf("failed to create request: %v", err) - } + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v2/", registryServer.URL), nil) + require.Nil(t, err) - params := map[string]string{ - "realm": server.URL + "/token", - } + authorizer := NewStandardTokenAuthorizer(nil, false) - if err := authorizer.Authorize(req, params); err != nil { - t.Fatalf("failed to authorize request: %v", err) - } + err = authorizer.Modify(req) + require.Nil(t, err) tk := req.Header.Get("Authorization") - if tk != "Bearer token" { - t.Errorf("unexpected token: %s != %s", tk, "Bearer token") - } -} - -func TestSchemeOfStandardTokenAuthorizer(t *testing.T) { - authorizer := &standardTokenAuthorizer{} - if authorizer.Scheme() != "bearer" { - t.Errorf("unexpected scheme: %s != %s", authorizer.Scheme(), "bearer") - } - + assert.Equal(t, strings.ToLower("Bearer "+token.Token), strings.ToLower(tk)) } diff --git a/src/common/utils/registry/auth/util.go b/src/common/utils/registry/auth/util.go index 373c95890..47d5ac963 100644 --- a/src/common/utils/registry/auth/util.go +++ b/src/common/utils/registry/auth/util.go @@ -37,16 +37,11 @@ func GetToken(endpoint string, insecure bool, credential Credential, Transport: registry.GetHTTPTransport(insecure), } - scopesStr := []string{} - for _, scope := range scopes { - scopesStr = append(scopesStr, scope.string()) - } - - return getToken(client, credential, endpoint, service, scopesStr) + return getToken(client, credential, endpoint, service, scopes) } func getToken(client *http.Client, credential Credential, realm, service string, - scopes []string) (*models.Token, error) { + scopes []*Scope) (*models.Token, error) { u, err := url.Parse(realm) if err != nil { return nil, err @@ -54,7 +49,7 @@ func getToken(client *http.Client, credential Credential, realm, service string, query := u.Query() query.Add("service", service) for _, scope := range scopes { - query.Add("scope", scope) + query.Add("scope", scope.string()) } u.RawQuery = query.Encode() diff --git a/src/jobservice/api/scan.go b/src/jobservice/api/scan.go index 214590881..2a7b986eb 100644 --- a/src/jobservice/api/scan.go +++ b/src/jobservice/api/scan.go @@ -50,7 +50,7 @@ func (isj *ImageScanJob) Post() { } c := &http.Cookie{Name: models.UISecretCookie, Value: config.JobserviceSecret()} repoClient, err := utils.NewRepositoryClient(regURL, false, auth.NewCookieCredential(c), - config.InternalTokenServiceEndpoint(), data.Repo, "pull", "push", "*") + config.InternalTokenServiceEndpoint(), data.Repo) if err != nil { log.Errorf("An error occurred while creating repository client: %v", err) isj.RenderError(http.StatusInternalServerError, "Failed to repository client") diff --git a/src/jobservice/replication/transfer.go b/src/jobservice/replication/transfer.go index dadf6a11d..5fae137aa 100644 --- a/src/jobservice/replication/transfer.go +++ b/src/jobservice/replication/transfer.go @@ -132,7 +132,7 @@ func (i *Initializer) enter() (string, error) { c := &http.Cookie{Name: models.UISecretCookie, Value: i.srcSecret} srcCred := auth.NewCookieCredential(c) srcClient, err := utils.NewRepositoryClient(i.srcURL, i.insecure, srcCred, - config.InternalTokenServiceEndpoint(), i.repository, "pull", "push", "*") + config.InternalTokenServiceEndpoint(), i.repository) if err != nil { i.logger.Errorf("an error occurred while creating source repository client: %v", err) return "", err @@ -141,7 +141,7 @@ func (i *Initializer) enter() (string, error) { dstCred := auth.NewBasicAuthCredential(i.dstUsr, i.dstPwd) dstClient, err := utils.NewRepositoryClient(i.dstURL, i.insecure, dstCred, - "", i.repository, "pull", "push", "*") + "", i.repository) if err != nil { i.logger.Errorf("an error occurred while creating destination repository client: %v", err) return "", err diff --git a/src/jobservice/scan/handlers.go b/src/jobservice/scan/handlers.go index 66f29b2f4..cee15a2b6 100644 --- a/src/jobservice/scan/handlers.go +++ b/src/jobservice/scan/handlers.go @@ -43,7 +43,7 @@ func (iz *Initializer) Enter() (string, error) { } c := &http.Cookie{Name: models.UISecretCookie, Value: config.JobserviceSecret()} repoClient, err := utils.NewRepositoryClient(regURL, false, auth.NewCookieCredential(c), - config.InternalTokenServiceEndpoint(), iz.Context.Repository, "pull") + config.InternalTokenServiceEndpoint(), iz.Context.Repository) if err != nil { logger.Errorf("An error occurred while creating repository client: %v", err) return "", err diff --git a/src/jobservice/utils/utils.go b/src/jobservice/utils/utils.go index ce6b775f5..1c4f50af6 100644 --- a/src/jobservice/utils/utils.go +++ b/src/jobservice/utils/utils.go @@ -26,24 +26,15 @@ import ( //NewRepositoryClient create a repository client with scope type "reopsitory" and scope as the repository it would access. func NewRepositoryClient(endpoint string, insecure bool, credential auth.Credential, - tokenServiceEndpoint, repository string, actions ...string) (*registry.Repository, error) { + tokenServiceEndpoint, repository string) (*registry.Repository, error) { authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, - tokenServiceEndpoint, "repository", repository, actions...) - - store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer) - if err != nil { - return nil, err - } + tokenServiceEndpoint) uam := &userAgentModifier{ userAgent: "harbor-registry-client", } - client, err := registry.NewRepositoryWithModifiers(repository, endpoint, insecure, store, uam) - if err != nil { - return nil, err - } - return client, nil + return registry.NewRepositoryWithModifiers(repository, endpoint, insecure, authorizer, uam) } type userAgentModifier struct { diff --git a/src/ui/api/repository.go b/src/ui/api/repository.go index 038824d6f..3abfa20b8 100644 --- a/src/ui/api/repository.go +++ b/src/ui/api/repository.go @@ -189,7 +189,7 @@ func (ra *RepositoryAPI) Delete() { return } - rc, err := ra.initRepositoryClient(repoName) + rc, err := uiutils.NewRepositoryClientForUI(ra.SecurityCtx.GetUsername(), repoName) if err != nil { log.Errorf("error occurred while initializing repository client for %s: %v", repoName, err) ra.CustomAbort(http.StatusInternalServerError, "internal error") @@ -305,7 +305,7 @@ func (ra *RepositoryAPI) GetTag() { return } - client, err := ra.initRepositoryClient(repository) + client, err := uiutils.NewRepositoryClientForUI(ra.SecurityCtx.GetUsername(), repository) if err != nil { ra.HandleInternalServerError(fmt.Sprintf("failed to initialize the client for %s: %v", repository, err)) @@ -354,7 +354,7 @@ func (ra *RepositoryAPI) GetTags() { return } - client, err := ra.initRepositoryClient(repoName) + client, err := uiutils.NewRepositoryClientForUI(ra.SecurityCtx.GetUsername(), repoName) if err != nil { log.Errorf("error occurred while initializing repository client for %s: %v", repoName, err) ra.CustomAbort(http.StatusInternalServerError, "internal error") @@ -495,7 +495,7 @@ func (ra *RepositoryAPI) GetManifests() { return } - rc, err := ra.initRepositoryClient(repoName) + rc, err := uiutils.NewRepositoryClientForUI(ra.SecurityCtx.GetUsername(), repoName) if err != nil { log.Errorf("error occurred while initializing repository client for %s: %v", repoName, err) ra.CustomAbort(http.StatusInternalServerError, "internal error") @@ -557,16 +557,6 @@ func getManifest(client *registry.Repository, return result, nil } -func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repository, err error) { - endpoint, err := config.RegistryURL() - if err != nil { - return nil, err - } - - return uiutils.NewRepositoryClientForUI(endpoint, true, ra.SecurityCtx.GetUsername(), - repoName, "pull", "push", "*") -} - //GetTopRepos returns the most populor repositories func (ra *RepositoryAPI) GetTopRepos() { count, err := ra.GetInt("count", 10) @@ -790,7 +780,7 @@ func (ra *RepositoryAPI) checkExistence(repository, tag string) (bool, string, e log.Errorf("project %s not found", project) return false, "", nil } - client, err := ra.initRepositoryClient(repository) + client, err := uiutils.NewRepositoryClientForUI(ra.SecurityCtx.GetUsername(), repository) if err != nil { return false, "", fmt.Errorf("failed to initialize the client for %s: %v", repository, err) } diff --git a/src/ui/api/search.go b/src/ui/api/search.go index a17be9a31..bf260edac 100644 --- a/src/ui/api/search.go +++ b/src/ui/api/search.go @@ -25,7 +25,6 @@ import ( "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/utils" "github.com/vmware/harbor/src/common/utils/log" - "github.com/vmware/harbor/src/ui/config" uiutils "github.com/vmware/harbor/src/ui/utils" ) @@ -165,13 +164,7 @@ func filterRepositories(projects []*models.Project, keyword string) ( } func getTags(repository string) ([]string, error) { - url, err := config.RegistryURL() - if err != nil { - return nil, err - } - - client, err := uiutils.NewRepositoryClientForUI(url, true, - "admin", repository, "pull") + client, err := uiutils.NewRepositoryClientForUI("harbor-ui", repository) if err != nil { return nil, err } diff --git a/src/ui/api/target.go b/src/ui/api/target.go index eaa0c3f98..6d27f5b23 100644 --- a/src/ui/api/target.go +++ b/src/ui/api/target.go @@ -24,10 +24,10 @@ import ( "github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/utils" + registry_error "github.com/vmware/harbor/src/common/utils/error" "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/registry" "github.com/vmware/harbor/src/common/utils/registry/auth" - registry_error "github.com/vmware/harbor/src/common/utils/error" "github.com/vmware/harbor/src/ui/config" ) @@ -64,8 +64,7 @@ func (t *TargetAPI) ping(endpoint, username, password string) { log.Errorf("failed to check whether insecure or not: %v", err) t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) } - registry, err := newRegistryClient(endpoint, !verify, username, password, - "", "", "") + registry, err := newRegistryClient(endpoint, !verify, username, password) if err != nil { // timeout, dns resolve error, connection refused, etc. if urlErr, ok := err.(*url.Error); ok { @@ -345,23 +344,10 @@ func (t *TargetAPI) Delete() { } } -func newRegistryClient(endpoint string, insecure bool, username, password, scopeType, scopeName string, - scopeActions ...string) (*registry.Registry, error) { +func newRegistryClient(endpoint string, insecure bool, username, password string) (*registry.Registry, error) { credential := auth.NewBasicAuthCredential(username, password) - - authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, - "", scopeType, scopeName, scopeActions...) - - store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer) - if err != nil { - return nil, err - } - - client, err := registry.NewRegistryWithModifiers(endpoint, insecure, store) - if err != nil { - return nil, err - } - return client, nil + authorizer := auth.NewStandardTokenAuthorizer(credential, insecure) + return registry.NewRegistryWithModifiers(endpoint, insecure, authorizer) } // ListPolicies ... diff --git a/src/ui/api/utils.go b/src/ui/api/utils.go index a639057f9..35a7b8aa1 100644 --- a/src/ui/api/utils.go +++ b/src/ui/api/utils.go @@ -34,6 +34,7 @@ import ( "github.com/vmware/harbor/src/common/utils/registry/auth" "github.com/vmware/harbor/src/ui/config" "github.com/vmware/harbor/src/ui/projectmanager" + "github.com/vmware/harbor/src/ui/service/token" uiutils "github.com/vmware/harbor/src/ui/utils" ) @@ -279,12 +280,7 @@ func diffRepos(reposInRegistry []string, reposInDB []string, } // TODO remove the workaround when the bug of registry is fixed - endpoint, err := config.RegistryURL() - if err != nil { - return needsAdd, needsDel, err - } - client, err := uiutils.NewRepositoryClientForUI(endpoint, true, - "admin", repoInR, "pull") + client, err := uiutils.NewRepositoryClientForUI("harbor-ui", repoInR) if err != nil { return needsAdd, needsDel, err } @@ -304,11 +300,7 @@ func diffRepos(reposInRegistry []string, reposInDB []string, j++ } else { // TODO remove the workaround when the bug of registry is fixed - endpoint, err := config.RegistryURL() - if err != nil { - return needsAdd, needsDel, err - } - client, err := uiutils.NewRepositoryClientForUI(endpoint, true, "admin", repoInR, "pull") + client, err := uiutils.NewRepositoryClientForUI("harbor-ui", repoInR) if err != nil { return needsAdd, needsDel, err } @@ -340,12 +332,7 @@ func diffRepos(reposInRegistry []string, reposInDB []string, continue } - endpoint, err := config.RegistryURL() - if err != nil { - log.Errorf("failed to get registry URL: %v", err) - continue - } - client, err := uiutils.NewRepositoryClientForUI(endpoint, true, "admin", repoInR, "pull") + client, err := uiutils.NewRepositoryClientForUI("harbor-ui", repoInR) if err != nil { log.Errorf("failed to create repository client: %v", err) continue @@ -377,7 +364,6 @@ func projectExists(pm projectmanager.ProjectManager, repository string) (bool, e return pm.Exist(project) } -// TODO need a registry client which accept a raw token as param func initRegistryClient() (r *registry.Registry, err error) { endpoint, err := config.RegistryURL() if err != nil { @@ -393,12 +379,8 @@ func initRegistryClient() (r *registry.Registry, err error) { return nil, err } - registryClient, err := NewRegistryClient(endpoint, true, "admin", - "registry", "catalog", "*") - if err != nil { - return nil, err - } - return registryClient, nil + authorizer := auth.NewRawTokenAuthorizer("harbor-ui", token.Registry) + return registry.NewRegistryWithModifiers(endpoint, true, authorizer) } func buildReplicationURL() string { @@ -449,24 +431,6 @@ func repositoryExist(name string, client *registry.Repository) (bool, error) { return len(tags) != 0, nil } -// NewRegistryClient ... -// TODO need a registry client which accept a raw token as param -func NewRegistryClient(endpoint string, insecure bool, username, scopeType, scopeName string, - scopeActions ...string) (*registry.Registry, error) { - authorizer := auth.NewRegistryUsernameTokenAuthorizer(username, scopeType, scopeName, scopeActions...) - - store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer) - if err != nil { - return nil, err - } - - client, err := registry.NewRegistryWithModifiers(endpoint, insecure, store) - if err != nil { - return nil, err - } - return client, nil -} - // transformVulnerabilities transforms the returned value of Clair API to a list of VulnerabilityItem func transformVulnerabilities(layerWithVuln *models.ClairLayerEnvelope) []*models.VulnerabilityItem { res := []*models.VulnerabilityItem{} diff --git a/src/ui/proxy/interceptors.go b/src/ui/proxy/interceptors.go index 61518f053..3b0020244 100644 --- a/src/ui/proxy/interceptors.go +++ b/src/ui/proxy/interceptors.go @@ -27,7 +27,7 @@ const ( manifestURLPattern = `^/v2/((?:[a-z0-9]+(?:[._-][a-z0-9]+)*/)+)manifests/([\w][\w.:-]{0,127})` imageInfoCtxKey = contextKey("ImageInfo") //TODO: temp solution, remove after vmware/harbor#2242 is resolved. - tokenUsername = "admin" + tokenUsername = "harbor-ui" ) // Record the docker deamon raw response. diff --git a/src/ui/service/token/authutils.go b/src/ui/service/token/authutils.go index 4544818f6..dbdeb6943 100644 --- a/src/ui/service/token/authutils.go +++ b/src/ui/service/token/authutils.go @@ -93,9 +93,6 @@ func filterAccess(access []*token.ResourceActions, ctx security.Context, return nil } -// TODO merge RegistryTokenForUI NotaryTokenForUI genTokenForUI -// to one function - //RegistryTokenForUI calls genTokenForUI to get raw token for registry func RegistryTokenForUI(username string, service string, scopes []string) (string, int, *time.Time, error) { return genTokenForUI(username, service, scopes) diff --git a/src/ui/service/token/creator.go b/src/ui/service/token/creator.go index 359398bb9..0db22bc4d 100644 --- a/src/ui/service/token/creator.go +++ b/src/ui/service/token/creator.go @@ -33,8 +33,10 @@ var registryFilterMap map[string]accessFilter var notaryFilterMap map[string]accessFilter const ( - notary = "harbor-notary" - registry = "harbor-registry" + // Notary service + Notary = "harbor-notary" + // Registry service + Registry = "harbor-registry" ) //InitCreators initialize the token creators for different services @@ -57,14 +59,14 @@ func InitCreators() { }, }, } - creatorMap[notary] = &generalCreator{ - service: notary, + creatorMap[Notary] = &generalCreator{ + service: Notary, filterMap: notaryFilterMap, } } - creatorMap[registry] = &generalCreator{ - service: registry, + creatorMap[Registry] = &generalCreator{ + service: Registry, filterMap: registryFilterMap, } } diff --git a/src/ui/utils/utils.go b/src/ui/utils/utils.go index 025c452d5..3ad5f4f3f 100644 --- a/src/ui/utils/utils.go +++ b/src/ui/utils/utils.go @@ -22,6 +22,7 @@ import ( "github.com/vmware/harbor/src/common/utils/registry" "github.com/vmware/harbor/src/common/utils/registry/auth" "github.com/vmware/harbor/src/ui/config" + "github.com/vmware/harbor/src/ui/service/token" "bytes" "encoding/json" @@ -32,11 +33,6 @@ import ( // ScanAllImages scans all images of Harbor by submiting jobs to jobservice, the whole process will move one if failed to subit any job of a single image. func ScanAllImages() error { - regURL, err := config.RegistryURL() - if err != nil { - log.Errorf("Failed to load registry url") - return err - } repos, err := dao.GetAllRepositories() if err != nil { log.Errorf("Failed to list all repositories, error: %v", err) @@ -49,7 +45,7 @@ func ScanAllImages() error { var err error var tags []string for _, r := range repos { - repoClient, err = NewRepositoryClientForUI(regURL, true, "harbor-ui", r.Name, "pull") + repoClient, err = NewRepositoryClientForUI("harbor-ui", r.Name) if err != nil { log.Errorf("Failed to initialize client for repository: %s, error: %v, skip scanning", r.Name, err) continue @@ -113,19 +109,13 @@ func TriggerImageScan(repository string, tag string) error { } // NewRepositoryClientForUI ... -// TODO need a registry client which accept a raw token as param -func NewRepositoryClientForUI(endpoint string, insecure bool, username, repository string, - scopeActions ...string) (*registry.Repository, error) { - - authorizer := auth.NewRegistryUsernameTokenAuthorizer(username, "repository", repository, scopeActions...) - store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer) +func NewRepositoryClientForUI(username, repository string) (*registry.Repository, error) { + endpoint, err := config.RegistryURL() if err != nil { return nil, err } - client, err := registry.NewRepositoryWithModifiers(repository, endpoint, insecure, store) - if err != nil { - return nil, err - } - return client, nil + insecure := true + authorizer := auth.NewRawTokenAuthorizer(username, token.Registry) + return registry.NewRepositoryWithModifiers(repository, endpoint, insecure, authorizer) }