do not ping if using raw token authorizer

This commit is contained in:
Wenkai Yin 2017-07-24 17:41:06 +08:00
parent 274f764622
commit cc264f85e7
21 changed files with 567 additions and 543 deletions

View File

@ -29,6 +29,7 @@ import (
"github.com/vmware/harbor/src/common/utils/registry" "github.com/vmware/harbor/src/common/utils/registry"
"github.com/vmware/harbor/src/common/utils/registry/auth" "github.com/vmware/harbor/src/common/utils/registry/auth"
"github.com/vmware/harbor/src/ui/config" "github.com/vmware/harbor/src/ui/config"
"github.com/vmware/harbor/src/ui/service/token"
"github.com/opencontainers/go-digest" "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) // 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) { func GetTargets(notaryEndpoint string, username string, fqRepo string) ([]Target, error) {
res := []Target{} res := []Target{}
authorizer := auth.NewNotaryUsernameTokenAuthorizer(username, "repository", fqRepo, "pull") authorizer := auth.NewRawTokenAuthorizer(username, token.Notary)
store, err := auth.NewAuthorizerStore(strings.Split(notaryEndpoint, "//")[1], true, authorizer) tr := registry.NewTransport(registry.GetHTTPTransport(true), authorizer)
if err != nil {
return res, err
}
tr := registry.NewTransport(registry.GetHTTPTransport(true), store)
gun := data.GUN(fqRepo) gun := data.GUN(fqRepo)
notaryRepo, err := client.NewFileCachedNotaryRepository(notaryCachePath, gun, notaryEndpoint, tr, mockRetriever, trustPin) notaryRepo, err := client.NewFileCachedNotaryRepository(notaryCachePath, gun, notaryEndpoint, tr, mockRetriever, trustPin)
if err != nil { if err != nil {

View File

@ -16,6 +16,7 @@ package notary
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common"
notarytest "github.com/vmware/harbor/src/common/utils/notary/test" notarytest "github.com/vmware/harbor/src/common/utils/notary/test"
@ -36,9 +37,10 @@ func TestMain(m *testing.M) {
notaryServer = notarytest.NewNotaryServer(endpoint) notaryServer = notarytest.NewNotaryServer(endpoint)
defer notaryServer.Close() defer notaryServer.Close()
var defaultConfig = map[string]interface{}{ var defaultConfig = map[string]interface{}{
common.ExtEndpoint: "https://" + endpoint, common.ExtEndpoint: "https://" + endpoint,
common.WithNotary: true, common.WithNotary: true,
common.CfgExpiration: 5, common.CfgExpiration: 5,
common.TokenExpiration: 30,
} }
adminServer, err := utilstest.NewAdminserver(defaultConfig) adminServer, err := utilstest.NewAdminserver(defaultConfig)
if err != nil { if err != nil {

View File

@ -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
}

View File

@ -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")
}
}

View File

@ -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 ""
}

View File

@ -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))
}
}

View File

@ -17,16 +17,20 @@ package auth
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/common/utils/registry" "github.com/vmware/harbor/src/common/utils/registry"
token_util "github.com/vmware/harbor/src/ui/service/token" token_util "github.com/vmware/harbor/src/ui/service/token"
) )
const ( const (
latency int = 10 //second, the network latency when token is received latency int = 10 //second, the network latency when token is received
scheme = "bearer"
) )
// Scope ... // Scope ...
@ -40,144 +44,255 @@ func (s *Scope) string() string {
return fmt.Sprintf("%s:%s:%s", s.Type, s.Name, strings.Join(s.Actions, ",")) 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 { type tokenAuthorizer struct {
scope *Scope realm string
tg tokenGenerator service string
cache string // cached token registryURL *url.URL // used to filter request
expiresAt *time.Time // The UTC standard time at when the token will expire generator tokenGenerator
sync.Mutex client *http.Client
cachedTokens map[string]*models.Token
sync.RWMutex
} }
// Scheme returns the scheme that the handler can handle // add token to the request
func (t *tokenAuthorizer) Scheme() string { func (t *tokenAuthorizer) Modify(req *http.Request) error {
return "bearer" //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 if !goon {
func (t *tokenAuthorizer) Authorize(req *http.Request, params map[string]string) error { log.Debugf("the request %s is not sent to registry, skip", req.URL.String())
var scopes []*Scope return nil
var token string }
hasFrom := false // parse scopes from request
from := req.URL.Query().Get("from") scopes := parseScopes(req)
if len(from) != 0 {
s := &Scope{ var token *models.Token
Type: "repository", // try to get token from cache if the request is for empty scope(login)
Name: from, // or single scope
Actions: []string{"pull"}, if len(scopes) <= 1 {
key := ""
if len(scopes) == 1 {
key = scopes[0].string()
} }
scopes = append(scopes, s) token = t.getCachedToken(key)
// do not cache the token if "from" appears
hasFrom = true
} }
if t.scope != nil { // request a new token if the token is null
scopes = append(scopes, t.scope) if token == nil {
} // ping first if the realm and service are both null
if len(t.realm) == 0 && len(t.service) == 0 {
expired := true realm, service, err := ping(t.client, t.registryURL.String())
if err != nil {
cachedToken, cachedExpiredAt := t.getCachedToken() return err
}
if len(cachedToken) != 0 && cachedExpiredAt != nil { if len(realm) == 0 {
expired = cachedExpiredAt.Before(time.Now().UTC()) log.Warning("empty realm, skip")
} return nil
}
if expired || hasFrom { t.realm = realm
scopeStrs := []string{} t.service = service
for _, scope := range scopes {
scopeStrs = append(scopeStrs, scope.string())
} }
to, expiresIn, _, err := t.tg(params["realm"], params["service"], scopeStrs) token, err = t.generator.generate(t.realm, t.service, scopes)
if err != nil { if err != nil {
return err return err
} }
token = to // only cache the token for empty scope(login) or single scope request
if len(scopes) <= 1 {
if !hasFrom { key := ""
t.updateCachedToken(to, expiresIn) 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 return nil
} }
func (t *tokenAuthorizer) getCachedToken() (string, *time.Time) { // some requests are sent to backend storage, such as s3, this method filters
t.Lock() // the requests only sent to registry
defer t.Unlock() func (t *tokenAuthorizer) filterReq(req *http.Request) (bool, error) {
return t.cache, t.expiresAt // 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) { // parse scopes from the request according to its method, path and query string
t.Lock() func parseScopes(req *http.Request) []*Scope {
defer t.Unlock() scopes := []*Scope{}
t.cache = token
n := (time.Duration)(expiresIn - latency) from := req.URL.Query().Get("from")
e := time.Now().Add(n * time.Second).UTC() if len(from) != 0 {
t.expiresAt = &e 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 func (t *tokenAuthorizer) getCachedToken(scope string) *models.Token {
type standardTokenAuthorizer struct { t.RLock()
tokenAuthorizer defer t.RUnlock()
client *http.Client token := t.cachedTokens[scope]
credential Credential if token == nil {
tokenServiceEndpoint string 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 // NewStandardTokenAuthorizer returns a standard token authorizer. The authorizer will request a token
// from token server and add it to the origin request // 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 // The usage please refer to the function tokenURL
func NewStandardTokenAuthorizer(credential Credential, insecure bool, func NewStandardTokenAuthorizer(credential Credential, insecure bool,
tokenServiceEndpoint string, scopeType, scopeName string, scopeActions ...string) Authorizer { customizedTokenService ...string) registry.Modifier {
authorizer := &standardTokenAuthorizer{ client := &http.Client{
client: &http.Client{ Transport: registry.GetHTTPTransport(insecure),
Transport: registry.GetHTTPTransport(insecure), Timeout: 30 * time.Second,
Timeout: 30 * time.Second,
},
credential: credential,
tokenServiceEndpoint: tokenServiceEndpoint,
} }
if len(scopeType) != 0 || len(scopeName) != 0 { generator := &standardTokenGenerator{
authorizer.scope = &Scope{ credential: credential,
Type: scopeType, client: client,
Name: scopeName, }
Actions: scopeActions, if len(customizedTokenService) > 0 {
} generator.customizedTokenService = customizedTokenService[0]
} }
authorizer.tg = authorizer.generateToken return &tokenAuthorizer{
cachedTokens: make(map[string]*models.Token),
return authorizer 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) realm = s.tokenURL(realm)
tk, err := getToken(s.client, s.credential, realm, return getToken(s.client, s.credential, realm, service, scopes)
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
} }
// when the registry client is used inside Harbor, the token request // 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 // 1. performance issue
// 2. the realm field returned by registry is an IP which can not reachable // 2. the realm field returned by registry is an IP which can not reachable
// inside Harbor // inside Harbor
func (s *standardTokenAuthorizer) tokenURL(realm string) string { func (s *standardTokenGenerator) tokenURL(realm string) string {
if len(s.tokenServiceEndpoint) != 0 { if len(s.customizedTokenService) != 0 {
return s.tokenServiceEndpoint return s.customizedTokenService
} }
return realm return realm
} }
// Implements interface Handler // NewRawTokenAuthorizer returns a token authorizer which calls method to create
type usernameTokenAuthorizer struct { // token directly
tokenAuthorizer func NewRawTokenAuthorizer(username, service string) registry.Modifier {
username string generator := &rawTokenGenerator{
}
// 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{
username: username, username: username,
} }
authorizer.scope = &Scope{ return &tokenAuthorizer{
Type: scopeType, service: service,
Name: scopeName, cachedTokens: make(map[string]*models.Token),
Actions: scopeActions, generator: generator,
} }
if notary { }
authorizer.tg = authorizer.genNotaryToken
} else { // rawTokenGenerator implements interface tokenGenerator
authorizer.tg = authorizer.genRegistryToken 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) { func buildPingURL(endpoint string) string {
token, expiresIn, issuedAt, err = token_util.RegistryTokenForUI(u.username, service, scopes) return fmt.Sprintf("%s/v2/", endpoint)
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
} }

View File

@ -15,54 +15,184 @@
package auth package auth
import ( import (
"encoding/json"
"fmt"
"net/http" "net/http"
"strings"
"testing" "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" "github.com/vmware/harbor/src/common/utils/test"
) )
func TestAuthorizeOfStandardTokenAuthorizer(t *testing.T) { func TestFilterReq(t *testing.T) {
handler := test.Handler(&test.Response{ authorizer := tokenAuthorizer{}
Body: []byte(`
{ // v2
"token":"token", req, err := http.NewRequest(http.MethodGet, "http://registry/v2/", nil)
"expires_in":300, require.Nil(t, err)
"issued_at":"2016-08-17T23:17:58+08:00" 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{ tokenServer := test.NewServer(
Method: "GET", &test.RequestHandlerMapping{
Pattern: "/token", Method: "GET",
Handler: handler, 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(http.MethodGet, fmt.Sprintf("%s/v2/", registryServer.URL), nil)
req, err := http.NewRequest("GET", "http://registry", nil) require.Nil(t, err)
if err != nil {
t.Fatalf("failed to create request: %v", err)
}
params := map[string]string{ authorizer := NewStandardTokenAuthorizer(nil, false)
"realm": server.URL + "/token",
}
if err := authorizer.Authorize(req, params); err != nil { err = authorizer.Modify(req)
t.Fatalf("failed to authorize request: %v", err) require.Nil(t, err)
}
tk := req.Header.Get("Authorization") tk := req.Header.Get("Authorization")
if tk != "Bearer token" { assert.Equal(t, strings.ToLower("Bearer "+token.Token), strings.ToLower(tk))
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")
}
} }

View File

@ -37,16 +37,11 @@ func GetToken(endpoint string, insecure bool, credential Credential,
Transport: registry.GetHTTPTransport(insecure), Transport: registry.GetHTTPTransport(insecure),
} }
scopesStr := []string{} return getToken(client, credential, endpoint, service, scopes)
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, 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) u, err := url.Parse(realm)
if err != nil { if err != nil {
return nil, err return nil, err
@ -54,7 +49,7 @@ func getToken(client *http.Client, credential Credential, realm, service string,
query := u.Query() query := u.Query()
query.Add("service", service) query.Add("service", service)
for _, scope := range scopes { for _, scope := range scopes {
query.Add("scope", scope) query.Add("scope", scope.string())
} }
u.RawQuery = query.Encode() u.RawQuery = query.Encode()

View File

@ -50,7 +50,7 @@ func (isj *ImageScanJob) Post() {
} }
c := &http.Cookie{Name: models.UISecretCookie, Value: config.JobserviceSecret()} c := &http.Cookie{Name: models.UISecretCookie, Value: config.JobserviceSecret()}
repoClient, err := utils.NewRepositoryClient(regURL, false, auth.NewCookieCredential(c), repoClient, err := utils.NewRepositoryClient(regURL, false, auth.NewCookieCredential(c),
config.InternalTokenServiceEndpoint(), data.Repo, "pull", "push", "*") config.InternalTokenServiceEndpoint(), data.Repo)
if err != nil { if err != nil {
log.Errorf("An error occurred while creating repository client: %v", err) log.Errorf("An error occurred while creating repository client: %v", err)
isj.RenderError(http.StatusInternalServerError, "Failed to repository client") isj.RenderError(http.StatusInternalServerError, "Failed to repository client")

View File

@ -132,7 +132,7 @@ func (i *Initializer) enter() (string, error) {
c := &http.Cookie{Name: models.UISecretCookie, Value: i.srcSecret} c := &http.Cookie{Name: models.UISecretCookie, Value: i.srcSecret}
srcCred := auth.NewCookieCredential(c) srcCred := auth.NewCookieCredential(c)
srcClient, err := utils.NewRepositoryClient(i.srcURL, i.insecure, srcCred, srcClient, err := utils.NewRepositoryClient(i.srcURL, i.insecure, srcCred,
config.InternalTokenServiceEndpoint(), i.repository, "pull", "push", "*") config.InternalTokenServiceEndpoint(), i.repository)
if err != nil { if err != nil {
i.logger.Errorf("an error occurred while creating source repository client: %v", err) i.logger.Errorf("an error occurred while creating source repository client: %v", err)
return "", err return "", err
@ -141,7 +141,7 @@ func (i *Initializer) enter() (string, error) {
dstCred := auth.NewBasicAuthCredential(i.dstUsr, i.dstPwd) dstCred := auth.NewBasicAuthCredential(i.dstUsr, i.dstPwd)
dstClient, err := utils.NewRepositoryClient(i.dstURL, i.insecure, dstCred, dstClient, err := utils.NewRepositoryClient(i.dstURL, i.insecure, dstCred,
"", i.repository, "pull", "push", "*") "", i.repository)
if err != nil { if err != nil {
i.logger.Errorf("an error occurred while creating destination repository client: %v", err) i.logger.Errorf("an error occurred while creating destination repository client: %v", err)
return "", err return "", err

View File

@ -43,7 +43,7 @@ func (iz *Initializer) Enter() (string, error) {
} }
c := &http.Cookie{Name: models.UISecretCookie, Value: config.JobserviceSecret()} c := &http.Cookie{Name: models.UISecretCookie, Value: config.JobserviceSecret()}
repoClient, err := utils.NewRepositoryClient(regURL, false, auth.NewCookieCredential(c), repoClient, err := utils.NewRepositoryClient(regURL, false, auth.NewCookieCredential(c),
config.InternalTokenServiceEndpoint(), iz.Context.Repository, "pull") config.InternalTokenServiceEndpoint(), iz.Context.Repository)
if err != nil { if err != nil {
logger.Errorf("An error occurred while creating repository client: %v", err) logger.Errorf("An error occurred while creating repository client: %v", err)
return "", err return "", err

View File

@ -26,24 +26,15 @@ import (
//NewRepositoryClient create a repository client with scope type "reopsitory" and scope as the repository it would access. //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, 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, authorizer := auth.NewStandardTokenAuthorizer(credential, insecure,
tokenServiceEndpoint, "repository", repository, actions...) tokenServiceEndpoint)
store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer)
if err != nil {
return nil, err
}
uam := &userAgentModifier{ uam := &userAgentModifier{
userAgent: "harbor-registry-client", userAgent: "harbor-registry-client",
} }
client, err := registry.NewRepositoryWithModifiers(repository, endpoint, insecure, store, uam) return registry.NewRepositoryWithModifiers(repository, endpoint, insecure, authorizer, uam)
if err != nil {
return nil, err
}
return client, nil
} }
type userAgentModifier struct { type userAgentModifier struct {

View File

@ -189,7 +189,7 @@ func (ra *RepositoryAPI) Delete() {
return return
} }
rc, err := ra.initRepositoryClient(repoName) rc, err := uiutils.NewRepositoryClientForUI(ra.SecurityCtx.GetUsername(), repoName)
if err != nil { if err != nil {
log.Errorf("error occurred while initializing repository client for %s: %v", repoName, err) log.Errorf("error occurred while initializing repository client for %s: %v", repoName, err)
ra.CustomAbort(http.StatusInternalServerError, "internal error") ra.CustomAbort(http.StatusInternalServerError, "internal error")
@ -305,7 +305,7 @@ func (ra *RepositoryAPI) GetTag() {
return return
} }
client, err := ra.initRepositoryClient(repository) client, err := uiutils.NewRepositoryClientForUI(ra.SecurityCtx.GetUsername(), repository)
if err != nil { if err != nil {
ra.HandleInternalServerError(fmt.Sprintf("failed to initialize the client for %s: %v", ra.HandleInternalServerError(fmt.Sprintf("failed to initialize the client for %s: %v",
repository, err)) repository, err))
@ -354,7 +354,7 @@ func (ra *RepositoryAPI) GetTags() {
return return
} }
client, err := ra.initRepositoryClient(repoName) client, err := uiutils.NewRepositoryClientForUI(ra.SecurityCtx.GetUsername(), repoName)
if err != nil { if err != nil {
log.Errorf("error occurred while initializing repository client for %s: %v", repoName, err) log.Errorf("error occurred while initializing repository client for %s: %v", repoName, err)
ra.CustomAbort(http.StatusInternalServerError, "internal error") ra.CustomAbort(http.StatusInternalServerError, "internal error")
@ -495,7 +495,7 @@ func (ra *RepositoryAPI) GetManifests() {
return return
} }
rc, err := ra.initRepositoryClient(repoName) rc, err := uiutils.NewRepositoryClientForUI(ra.SecurityCtx.GetUsername(), repoName)
if err != nil { if err != nil {
log.Errorf("error occurred while initializing repository client for %s: %v", repoName, err) log.Errorf("error occurred while initializing repository client for %s: %v", repoName, err)
ra.CustomAbort(http.StatusInternalServerError, "internal error") ra.CustomAbort(http.StatusInternalServerError, "internal error")
@ -557,16 +557,6 @@ func getManifest(client *registry.Repository,
return result, nil 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 //GetTopRepos returns the most populor repositories
func (ra *RepositoryAPI) GetTopRepos() { func (ra *RepositoryAPI) GetTopRepos() {
count, err := ra.GetInt("count", 10) 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) log.Errorf("project %s not found", project)
return false, "", nil return false, "", nil
} }
client, err := ra.initRepositoryClient(repository) client, err := uiutils.NewRepositoryClientForUI(ra.SecurityCtx.GetUsername(), repository)
if err != nil { if err != nil {
return false, "", fmt.Errorf("failed to initialize the client for %s: %v", repository, err) return false, "", fmt.Errorf("failed to initialize the client for %s: %v", repository, err)
} }

View File

@ -25,7 +25,6 @@ import (
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils" "github.com/vmware/harbor/src/common/utils"
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/config"
uiutils "github.com/vmware/harbor/src/ui/utils" 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) { func getTags(repository string) ([]string, error) {
url, err := config.RegistryURL() client, err := uiutils.NewRepositoryClientForUI("harbor-ui", repository)
if err != nil {
return nil, err
}
client, err := uiutils.NewRepositoryClientForUI(url, true,
"admin", repository, "pull")
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -24,10 +24,10 @@ import (
"github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils" "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/log"
"github.com/vmware/harbor/src/common/utils/registry" "github.com/vmware/harbor/src/common/utils/registry"
"github.com/vmware/harbor/src/common/utils/registry/auth" "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" "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) log.Errorf("failed to check whether insecure or not: %v", err)
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) 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 { if err != nil {
// timeout, dns resolve error, connection refused, etc. // timeout, dns resolve error, connection refused, etc.
if urlErr, ok := err.(*url.Error); ok { 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, func newRegistryClient(endpoint string, insecure bool, username, password string) (*registry.Registry, error) {
scopeActions ...string) (*registry.Registry, error) {
credential := auth.NewBasicAuthCredential(username, password) credential := auth.NewBasicAuthCredential(username, password)
authorizer := auth.NewStandardTokenAuthorizer(credential, insecure)
authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, return registry.NewRegistryWithModifiers(endpoint, insecure, authorizer)
"", 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
} }
// ListPolicies ... // ListPolicies ...

View File

@ -34,6 +34,7 @@ import (
"github.com/vmware/harbor/src/common/utils/registry/auth" "github.com/vmware/harbor/src/common/utils/registry/auth"
"github.com/vmware/harbor/src/ui/config" "github.com/vmware/harbor/src/ui/config"
"github.com/vmware/harbor/src/ui/projectmanager" "github.com/vmware/harbor/src/ui/projectmanager"
"github.com/vmware/harbor/src/ui/service/token"
uiutils "github.com/vmware/harbor/src/ui/utils" 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 // TODO remove the workaround when the bug of registry is fixed
endpoint, err := config.RegistryURL() client, err := uiutils.NewRepositoryClientForUI("harbor-ui", repoInR)
if err != nil {
return needsAdd, needsDel, err
}
client, err := uiutils.NewRepositoryClientForUI(endpoint, true,
"admin", repoInR, "pull")
if err != nil { if err != nil {
return needsAdd, needsDel, err return needsAdd, needsDel, err
} }
@ -304,11 +300,7 @@ func diffRepos(reposInRegistry []string, reposInDB []string,
j++ j++
} else { } else {
// TODO remove the workaround when the bug of registry is fixed // TODO remove the workaround when the bug of registry is fixed
endpoint, err := config.RegistryURL() client, err := uiutils.NewRepositoryClientForUI("harbor-ui", repoInR)
if err != nil {
return needsAdd, needsDel, err
}
client, err := uiutils.NewRepositoryClientForUI(endpoint, true, "admin", repoInR, "pull")
if err != nil { if err != nil {
return needsAdd, needsDel, err return needsAdd, needsDel, err
} }
@ -340,12 +332,7 @@ func diffRepos(reposInRegistry []string, reposInDB []string,
continue continue
} }
endpoint, err := config.RegistryURL() client, err := uiutils.NewRepositoryClientForUI("harbor-ui", repoInR)
if err != nil {
log.Errorf("failed to get registry URL: %v", err)
continue
}
client, err := uiutils.NewRepositoryClientForUI(endpoint, true, "admin", repoInR, "pull")
if err != nil { if err != nil {
log.Errorf("failed to create repository client: %v", err) log.Errorf("failed to create repository client: %v", err)
continue continue
@ -377,7 +364,6 @@ func projectExists(pm projectmanager.ProjectManager, repository string) (bool, e
return pm.Exist(project) return pm.Exist(project)
} }
// TODO need a registry client which accept a raw token as param
func initRegistryClient() (r *registry.Registry, err error) { func initRegistryClient() (r *registry.Registry, err error) {
endpoint, err := config.RegistryURL() endpoint, err := config.RegistryURL()
if err != nil { if err != nil {
@ -393,12 +379,8 @@ func initRegistryClient() (r *registry.Registry, err error) {
return nil, err return nil, err
} }
registryClient, err := NewRegistryClient(endpoint, true, "admin", authorizer := auth.NewRawTokenAuthorizer("harbor-ui", token.Registry)
"registry", "catalog", "*") return registry.NewRegistryWithModifiers(endpoint, true, authorizer)
if err != nil {
return nil, err
}
return registryClient, nil
} }
func buildReplicationURL() string { func buildReplicationURL() string {
@ -449,24 +431,6 @@ func repositoryExist(name string, client *registry.Repository) (bool, error) {
return len(tags) != 0, nil 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 // transformVulnerabilities transforms the returned value of Clair API to a list of VulnerabilityItem
func transformVulnerabilities(layerWithVuln *models.ClairLayerEnvelope) []*models.VulnerabilityItem { func transformVulnerabilities(layerWithVuln *models.ClairLayerEnvelope) []*models.VulnerabilityItem {
res := []*models.VulnerabilityItem{} res := []*models.VulnerabilityItem{}

View File

@ -27,7 +27,7 @@ const (
manifestURLPattern = `^/v2/((?:[a-z0-9]+(?:[._-][a-z0-9]+)*/)+)manifests/([\w][\w.:-]{0,127})` manifestURLPattern = `^/v2/((?:[a-z0-9]+(?:[._-][a-z0-9]+)*/)+)manifests/([\w][\w.:-]{0,127})`
imageInfoCtxKey = contextKey("ImageInfo") imageInfoCtxKey = contextKey("ImageInfo")
//TODO: temp solution, remove after vmware/harbor#2242 is resolved. //TODO: temp solution, remove after vmware/harbor#2242 is resolved.
tokenUsername = "admin" tokenUsername = "harbor-ui"
) )
// Record the docker deamon raw response. // Record the docker deamon raw response.

View File

@ -93,9 +93,6 @@ func filterAccess(access []*token.ResourceActions, ctx security.Context,
return nil return nil
} }
// TODO merge RegistryTokenForUI NotaryTokenForUI genTokenForUI
// to one function
//RegistryTokenForUI calls genTokenForUI to get raw token for registry //RegistryTokenForUI calls genTokenForUI to get raw token for registry
func RegistryTokenForUI(username string, service string, scopes []string) (string, int, *time.Time, error) { func RegistryTokenForUI(username string, service string, scopes []string) (string, int, *time.Time, error) {
return genTokenForUI(username, service, scopes) return genTokenForUI(username, service, scopes)

View File

@ -33,8 +33,10 @@ var registryFilterMap map[string]accessFilter
var notaryFilterMap map[string]accessFilter var notaryFilterMap map[string]accessFilter
const ( const (
notary = "harbor-notary" // Notary service
registry = "harbor-registry" Notary = "harbor-notary"
// Registry service
Registry = "harbor-registry"
) )
//InitCreators initialize the token creators for different services //InitCreators initialize the token creators for different services
@ -57,14 +59,14 @@ func InitCreators() {
}, },
}, },
} }
creatorMap[notary] = &generalCreator{ creatorMap[Notary] = &generalCreator{
service: notary, service: Notary,
filterMap: notaryFilterMap, filterMap: notaryFilterMap,
} }
} }
creatorMap[registry] = &generalCreator{ creatorMap[Registry] = &generalCreator{
service: registry, service: Registry,
filterMap: registryFilterMap, filterMap: registryFilterMap,
} }
} }

View File

@ -22,6 +22,7 @@ import (
"github.com/vmware/harbor/src/common/utils/registry" "github.com/vmware/harbor/src/common/utils/registry"
"github.com/vmware/harbor/src/common/utils/registry/auth" "github.com/vmware/harbor/src/common/utils/registry/auth"
"github.com/vmware/harbor/src/ui/config" "github.com/vmware/harbor/src/ui/config"
"github.com/vmware/harbor/src/ui/service/token"
"bytes" "bytes"
"encoding/json" "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. // 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 { func ScanAllImages() error {
regURL, err := config.RegistryURL()
if err != nil {
log.Errorf("Failed to load registry url")
return err
}
repos, err := dao.GetAllRepositories() repos, err := dao.GetAllRepositories()
if err != nil { if err != nil {
log.Errorf("Failed to list all repositories, error: %v", err) log.Errorf("Failed to list all repositories, error: %v", err)
@ -49,7 +45,7 @@ func ScanAllImages() error {
var err error var err error
var tags []string var tags []string
for _, r := range repos { for _, r := range repos {
repoClient, err = NewRepositoryClientForUI(regURL, true, "harbor-ui", r.Name, "pull") repoClient, err = NewRepositoryClientForUI("harbor-ui", r.Name)
if err != nil { if err != nil {
log.Errorf("Failed to initialize client for repository: %s, error: %v, skip scanning", r.Name, err) log.Errorf("Failed to initialize client for repository: %s, error: %v, skip scanning", r.Name, err)
continue continue
@ -113,19 +109,13 @@ func TriggerImageScan(repository string, tag string) error {
} }
// NewRepositoryClientForUI ... // NewRepositoryClientForUI ...
// TODO need a registry client which accept a raw token as param func NewRepositoryClientForUI(username, repository string) (*registry.Repository, error) {
func NewRepositoryClientForUI(endpoint string, insecure bool, username, repository string, endpoint, err := config.RegistryURL()
scopeActions ...string) (*registry.Repository, error) {
authorizer := auth.NewRegistryUsernameTokenAuthorizer(username, "repository", repository, scopeActions...)
store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
client, err := registry.NewRepositoryWithModifiers(repository, endpoint, insecure, store) insecure := true
if err != nil { authorizer := auth.NewRawTokenAuthorizer(username, token.Registry)
return nil, err return registry.NewRepositoryWithModifiers(repository, endpoint, insecure, authorizer)
}
return client, nil
} }