mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-23 18:55:18 +01:00
do not ping if using raw token authorizer
This commit is contained in:
parent
274f764622
commit
cc264f85e7
@ -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 {
|
||||
|
@ -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"
|
||||
@ -39,6 +40,7 @@ func TestMain(m *testing.M) {
|
||||
common.ExtEndpoint: "https://" + endpoint,
|
||||
common.WithNotary: true,
|
||||
common.CfgExpiration: 5,
|
||||
common.TokenExpiration: 30,
|
||||
}
|
||||
adminServer, err := utilstest.NewAdminserver(defaultConfig)
|
||||
if err != nil {
|
||||
|
@ -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
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
56
src/common/utils/registry/auth/path.go
Normal file
56
src/common/utils/registry/auth/path.go
Normal 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 ""
|
||||
}
|
43
src/common/utils/registry/auth/path_test.go
Normal file
43
src/common/utils/registry/auth/path_test.go
Normal 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))
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
hasFrom := false
|
||||
from := req.URL.Query().Get("from")
|
||||
if len(from) != 0 {
|
||||
s := &Scope{
|
||||
Type: "repository",
|
||||
Name: from,
|
||||
Actions: []string{"pull"},
|
||||
}
|
||||
scopes = append(scopes, s)
|
||||
// do not cache the token if "from" appears
|
||||
hasFrom = true
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
to, expiresIn, _, err := t.tg(params["realm"], params["service"], scopeStrs)
|
||||
// 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
|
||||
}
|
||||
token = to
|
||||
|
||||
if !hasFrom {
|
||||
t.updateCachedToken(to, expiresIn)
|
||||
}
|
||||
} else {
|
||||
token = cachedToken
|
||||
if !goon {
|
||||
log.Debugf("the request %s is not sent to registry, skip", req.URL.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
req.Header.Add(http.CanonicalHeaderKey("Authorization"), fmt.Sprintf("Bearer %s", token))
|
||||
// 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()
|
||||
}
|
||||
token = t.getCachedToken(key)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
token, err = t.generator.generate(t.realm, t.service, scopes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
v2Index := strings.Index(req.URL.Path, "/v2/")
|
||||
if v2Index == -1 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Implements interface Authorizer
|
||||
type standardTokenAuthorizer struct {
|
||||
tokenAuthorizer
|
||||
client *http.Client
|
||||
credential Credential
|
||||
tokenServiceEndpoint string
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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{
|
||||
customizedTokenService ...string) registry.Modifier {
|
||||
client := &http.Client{
|
||||
Transport: registry.GetHTTPTransport(insecure),
|
||||
Timeout: 30 * time.Second,
|
||||
},
|
||||
}
|
||||
|
||||
generator := &standardTokenGenerator{
|
||||
credential: credential,
|
||||
tokenServiceEndpoint: tokenServiceEndpoint,
|
||||
client: client,
|
||||
}
|
||||
if len(customizedTokenService) > 0 {
|
||||
generator.customizedTokenService = customizedTokenService[0]
|
||||
}
|
||||
|
||||
if len(scopeType) != 0 || len(scopeName) != 0 {
|
||||
authorizer.scope = &Scope{
|
||||
Type: scopeType,
|
||||
Name: scopeName,
|
||||
Actions: scopeActions,
|
||||
return &tokenAuthorizer{
|
||||
cachedTokens: make(map[string]*models.Token),
|
||||
generator: generator,
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
authorizer.tg = authorizer.generateToken
|
||||
|
||||
return authorizer
|
||||
// standardTokenGenerator implements interface tokenGenerator
|
||||
type standardTokenGenerator struct {
|
||||
credential Credential
|
||||
customizedTokenService string
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func (s *standardTokenAuthorizer) generateToken(realm, service string, scopes []string) (string, int, *time.Time, error) {
|
||||
// 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
|
||||
}
|
||||
return authorizer
|
||||
}
|
||||
|
||||
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
|
||||
// rawTokenGenerator implements interface tokenGenerator
|
||||
type rawTokenGenerator struct {
|
||||
username string
|
||||
}
|
||||
|
||||
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
|
||||
// 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())
|
||||
}
|
||||
token, expiresIn, issuedAt, err := token_util.RegistryTokenForUI(r.username, service, strs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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
|
||||
return &models.Token{
|
||||
Token: token,
|
||||
ExpiresIn: expiresIn,
|
||||
IssuedAt: issuedAt.Format(time.RFC3339),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildPingURL(endpoint string) string {
|
||||
return fmt.Sprintf("%s/v2/", endpoint)
|
||||
}
|
||||
|
@ -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{
|
||||
tokenServer := test.NewServer(
|
||||
&test.RequestHandlerMapping{
|
||||
Method: "GET",
|
||||
Pattern: "/token",
|
||||
Handler: handler,
|
||||
Pattern: "/service/token",
|
||||
Handler: tokenHandler,
|
||||
})
|
||||
defer server.Close()
|
||||
defer tokenServer.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)
|
||||
}
|
||||
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,
|
||||
},
|
||||
})
|
||||
registryServer := test.NewServer(
|
||||
&test.RequestHandlerMapping{
|
||||
Method: "GET",
|
||||
Pattern: "/v2",
|
||||
Handler: pingHandler,
|
||||
})
|
||||
defer registryServer.Close()
|
||||
|
||||
params := map[string]string{
|
||||
"realm": server.URL + "/token",
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v2/", registryServer.URL), nil)
|
||||
require.Nil(t, err)
|
||||
|
||||
if err := authorizer.Authorize(req, params); err != nil {
|
||||
t.Fatalf("failed to authorize request: %v", err)
|
||||
}
|
||||
authorizer := NewStandardTokenAuthorizer(nil, false)
|
||||
|
||||
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))
|
||||
}
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 ...
|
||||
|
@ -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{}
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user