mirror of
https://github.com/goharbor/harbor.git
synced 2025-01-03 06:28:06 +01:00
Merge pull request #2878 from ywk253100/170724_registry
Refactor registry client
This commit is contained in:
commit
ce169e74dc
@ -17,18 +17,20 @@ package notary
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/distribution/registry/auth/token"
|
||||
"github.com/docker/notary"
|
||||
"github.com/docker/notary/client"
|
||||
"github.com/docker/notary/trustpinning"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"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"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
tokenutil "github.com/vmware/harbor/src/ui/service/token"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
@ -71,15 +73,23 @@ func GetInternalTargets(notaryEndpoint string, username string, repo string) ([]
|
||||
|
||||
// GetTargets is a help function called by API to fetch signature information of a given repository.
|
||||
// Per docker's convention the repository should contain the information of endpoint, i.e. it should look
|
||||
// like "10.117.4.117/library/ubuntu", instead of "library/ubuntu" (fqRepo for fully-qualified repo)
|
||||
// like "192.168.0.1/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)
|
||||
t, err := tokenutil.MakeToken(username, tokenutil.Notary,
|
||||
[]*token.ResourceActions{
|
||||
&token.ResourceActions{
|
||||
Type: "repository",
|
||||
Name: fqRepo,
|
||||
Actions: []string{"pull"},
|
||||
}})
|
||||
if err != nil {
|
||||
return res, err
|
||||
return nil, err
|
||||
}
|
||||
tr := registry.NewTransport(registry.GetHTTPTransport(true), store)
|
||||
authorizer := ¬aryAuthorizer{
|
||||
token: t.Token,
|
||||
}
|
||||
tr := registry.NewTransport(registry.GetHTTPTransport(true), authorizer)
|
||||
gun := data.GUN(fqRepo)
|
||||
notaryRepo, err := client.NewFileCachedNotaryRepository(notaryCachePath, gun, notaryEndpoint, tr, mockRetriever, trustPin)
|
||||
if err != nil {
|
||||
@ -112,3 +122,13 @@ func DigestFromTarget(t Target) (string, error) {
|
||||
}
|
||||
return digest.NewDigestFromHex("sha256", hex.EncodeToString(sha)).String(), nil
|
||||
}
|
||||
|
||||
type notaryAuthorizer struct {
|
||||
token string
|
||||
}
|
||||
|
||||
func (n *notaryAuthorizer) Modify(req *http.Request) error {
|
||||
req.Header.Add(http.CanonicalHeaderKey("Authorization"),
|
||||
fmt.Sprintf("Bearer %s", n.token))
|
||||
return 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"
|
||||
@ -36,9 +37,10 @@ func TestMain(m *testing.M) {
|
||||
notaryServer = notarytest.NewNotaryServer(endpoint)
|
||||
defer notaryServer.Close()
|
||||
var defaultConfig = map[string]interface{}{
|
||||
common.ExtEndpoint: "https://" + endpoint,
|
||||
common.WithNotary: true,
|
||||
common.CfgExpiration: 5,
|
||||
common.ExtEndpoint: "https://" + endpoint,
|
||||
common.WithNotary: true,
|
||||
common.CfgExpiration: 5,
|
||||
common.TokenExpiration: 30,
|
||||
}
|
||||
adminServer, err := utilstest.NewAdminserver(defaultConfig)
|
||||
if err != nil {
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
59
src/common/utils/registry/auth/path.go
Normal file
59
src/common/utils/registry/auth/path.go
Normal file
@ -0,0 +1,59 @@
|
||||
// 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
|
||||
// the subs should contain at least 2 matching texts, the first one matches
|
||||
// the whole regular expression, and the second one matches the repository
|
||||
// part
|
||||
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,232 +17,326 @@ package auth
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution/registry/auth/token"
|
||||
"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 ...
|
||||
type Scope struct {
|
||||
Type string
|
||||
Name string
|
||||
Actions []string
|
||||
type tokenGenerator interface {
|
||||
generate(scopes []*token.ResourceActions, endpoint string) (*models.Token, error)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// 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
|
||||
registryURL *url.URL // used to filter request
|
||||
generator tokenGenerator
|
||||
client *http.Client
|
||||
cachedTokens map[string]*models.Token
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// Scheme returns the scheme that the handler can handle
|
||||
func (t *tokenAuthorizer) Scheme() string {
|
||||
return "bearer"
|
||||
}
|
||||
// add token to the request
|
||||
func (t *tokenAuthorizer) Modify(req *http.Request) error {
|
||||
//only handle requests sent to registry
|
||||
goon, err := t.filterReq(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// AuthorizeRequest will add authorization header which contains a token before the request is sent
|
||||
func (t *tokenAuthorizer) Authorize(req *http.Request, params map[string]string) error {
|
||||
var scopes []*Scope
|
||||
var token string
|
||||
if !goon {
|
||||
log.Debugf("the request %s is not sent to registry, skip", req.URL.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
hasFrom := false
|
||||
from := req.URL.Query().Get("from")
|
||||
if len(from) != 0 {
|
||||
s := &Scope{
|
||||
Type: "repository",
|
||||
Name: from,
|
||||
Actions: []string{"pull"},
|
||||
// parse scopes from request
|
||||
scopes, err := parseScopes(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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 = scopeString(scopes[0])
|
||||
}
|
||||
scopes = append(scopes, s)
|
||||
// do not cache the token if "from" appears
|
||||
hasFrom = true
|
||||
token = t.getCachedToken(key)
|
||||
}
|
||||
|
||||
if t.scope != nil {
|
||||
scopes = append(scopes, t.scope)
|
||||
}
|
||||
|
||||
expired := true
|
||||
|
||||
cachedToken, cachedExpiredAt := t.getCachedToken()
|
||||
|
||||
if len(cachedToken) != 0 && cachedExpiredAt != nil {
|
||||
expired = cachedExpiredAt.Before(time.Now().UTC())
|
||||
}
|
||||
|
||||
if expired || hasFrom {
|
||||
scopeStrs := []string{}
|
||||
for _, scope := range scopes {
|
||||
scopeStrs = append(scopeStrs, scope.string())
|
||||
}
|
||||
to, expiresIn, _, err := t.tg(params["realm"], params["service"], scopeStrs)
|
||||
// request a new token if the token is null
|
||||
if token == nil {
|
||||
token, err = t.generator.generate(scopes, t.registryURL.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
token = to
|
||||
|
||||
if !hasFrom {
|
||||
t.updateCachedToken(to, expiresIn)
|
||||
// if the token is null(this happens if the registry needs no authentication), return
|
||||
// directly. Or the token will be cached
|
||||
if token == nil {
|
||||
return nil
|
||||
}
|
||||
// only cache the token for empty scope(login) or single scope request
|
||||
if len(scopes) <= 1 {
|
||||
key := ""
|
||||
if len(scopes) == 1 {
|
||||
key = scopeString(scopes[0])
|
||||
}
|
||||
t.updateCachedToken(key, token)
|
||||
}
|
||||
} else {
|
||||
token = cachedToken
|
||||
}
|
||||
|
||||
req.Header.Add(http.CanonicalHeaderKey("Authorization"), fmt.Sprintf("Bearer %s", token))
|
||||
req.Header.Add(http.CanonicalHeaderKey("Authorization"), fmt.Sprintf("Bearer %s", token.Token))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tokenAuthorizer) getCachedToken() (string, *time.Time) {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
return t.cache, t.expiresAt
|
||||
func scopeString(scope *token.ResourceActions) string {
|
||||
if scope == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s:%s:%s", scope.Type, scope.Name, strings.Join(scope.Actions, ","))
|
||||
}
|
||||
|
||||
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
|
||||
// some requests are sent to backend storage, such as s3, this method filters
|
||||
// the requests only sent to registry
|
||||
func (t *tokenAuthorizer) filterReq(req *http.Request) (bool, error) {
|
||||
// the registryURL is nil when the first request comes, init it with
|
||||
// the scheme and host of the request which must be sent to the registry
|
||||
if t.registryURL == nil {
|
||||
u, err := url.Parse(buildPingURL(req.URL.Scheme + "://" + req.URL.Host))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
t.registryURL = u
|
||||
}
|
||||
|
||||
v2Index := strings.Index(req.URL.Path, "/v2/")
|
||||
if v2Index == -1 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if req.URL.Host != t.registryURL.Host || req.URL.Scheme != t.registryURL.Scheme ||
|
||||
req.URL.Path[:v2Index+4] != t.registryURL.Path {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Implements interface Authorizer
|
||||
type standardTokenAuthorizer struct {
|
||||
tokenAuthorizer
|
||||
client *http.Client
|
||||
credential Credential
|
||||
tokenServiceEndpoint string
|
||||
// parse scopes from the request according to its method, path and query string
|
||||
func parseScopes(req *http.Request) ([]*token.ResourceActions, error) {
|
||||
scopes := []*token.ResourceActions{}
|
||||
|
||||
from := req.URL.Query().Get("from")
|
||||
if len(from) != 0 {
|
||||
scopes = append(scopes, &token.ResourceActions{
|
||||
Type: "repository",
|
||||
Name: from,
|
||||
Actions: []string{"pull"},
|
||||
})
|
||||
}
|
||||
|
||||
var scope *token.ResourceActions
|
||||
path := strings.TrimRight(req.URL.Path, "/")
|
||||
repository := parseRepository(path)
|
||||
if len(repository) > 0 {
|
||||
// pull, push, delete blob/manifest
|
||||
scope = &token.ResourceActions{
|
||||
Type: "repository",
|
||||
Name: repository,
|
||||
}
|
||||
switch req.Method {
|
||||
case http.MethodGet, http.MethodHead:
|
||||
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 = &token.ResourceActions{
|
||||
Type: "registry",
|
||||
Name: "catalog",
|
||||
Actions: []string{"*"},
|
||||
}
|
||||
} else if base.MatchString(path) {
|
||||
// base
|
||||
scope = nil
|
||||
} else {
|
||||
// unknow
|
||||
return scopes, fmt.Errorf("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, scopeString(s))
|
||||
}
|
||||
log.Debugf("scopses parsed from request: %s", strings.Join(strs, " "))
|
||||
|
||||
return scopes, nil
|
||||
}
|
||||
|
||||
func (t *tokenAuthorizer) getCachedToken(scope string) *models.Token {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
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)
|
||||
delete(t.cachedTokens, scope)
|
||||
return nil
|
||||
}
|
||||
|
||||
if issueAt.Add(time.Duration(token.ExpiresIn-latency) * time.Second).Before(time.Now().UTC()) {
|
||||
delete(t.cachedTokens, scope)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debugf("get token for scope %s from cache", scope)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
log.Warningf("schemes %v are unsupportted", challenges)
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
// 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
|
||||
// The usage please refer to the function tokenURL
|
||||
// If customizedTokenService is set, the token request will be sent to it instead of the server get from authorizer
|
||||
func NewStandardTokenAuthorizer(credential Credential, insecure bool,
|
||||
tokenServiceEndpoint string, scopeType, scopeName string, scopeActions ...string) Authorizer {
|
||||
authorizer := &standardTokenAuthorizer{
|
||||
client: &http.Client{
|
||||
Transport: registry.GetHTTPTransport(insecure),
|
||||
Timeout: 30 * time.Second,
|
||||
},
|
||||
credential: credential,
|
||||
tokenServiceEndpoint: tokenServiceEndpoint,
|
||||
customizedTokenService ...string) registry.Modifier {
|
||||
client := &http.Client{
|
||||
Transport: registry.GetHTTPTransport(insecure),
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
if len(scopeType) != 0 || len(scopeName) != 0 {
|
||||
authorizer.scope = &Scope{
|
||||
Type: scopeType,
|
||||
Name: scopeName,
|
||||
Actions: scopeActions,
|
||||
generator := &standardTokenGenerator{
|
||||
credential: credential,
|
||||
client: client,
|
||||
}
|
||||
|
||||
// when the registry client is used inside Harbor, the token request
|
||||
// can be posted to token service directly rather than going through nginx.
|
||||
// If realm is set as the internal url of token service, this can resolve
|
||||
// two problems:
|
||||
// 1. performance issue
|
||||
// 2. the realm field returned by registry is an IP which can not reachable
|
||||
// inside Harbor
|
||||
if len(customizedTokenService) > 0 {
|
||||
generator.realm = customizedTokenService[0]
|
||||
}
|
||||
|
||||
return &tokenAuthorizer{
|
||||
cachedTokens: make(map[string]*models.Token),
|
||||
generator: generator,
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// standardTokenGenerator implements interface tokenGenerator
|
||||
type standardTokenGenerator struct {
|
||||
realm string
|
||||
service string
|
||||
credential Credential
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// get token from token service
|
||||
func (s *standardTokenGenerator) generate(scopes []*token.ResourceActions, endpoint string) (*models.Token, error) {
|
||||
// ping first if the realm or service is null
|
||||
if len(s.realm) == 0 || len(s.service) == 0 {
|
||||
realm, service, err := ping(s.client, endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(realm) == 0 {
|
||||
log.Warning("empty realm, skip")
|
||||
return nil, nil
|
||||
}
|
||||
if len(s.realm) == 0 {
|
||||
s.realm = realm
|
||||
}
|
||||
s.service = service
|
||||
}
|
||||
|
||||
authorizer.tg = authorizer.generateToken
|
||||
|
||||
return authorizer
|
||||
return getToken(s.client, s.credential, s.realm, s.service, scopes)
|
||||
}
|
||||
|
||||
func (s *standardTokenAuthorizer) generateToken(realm, service string, scopes []string) (string, int, *time.Time, error) {
|
||||
realm = s.tokenURL(realm)
|
||||
tk, err := getToken(s.client, s.credential, realm,
|
||||
service, scopes)
|
||||
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
|
||||
// can be posted to token service directly rather than going through nginx.
|
||||
// If realm is set as the internal url of token service, this can resolve
|
||||
// two problems:
|
||||
// 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
|
||||
}
|
||||
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{
|
||||
service: service,
|
||||
username: username,
|
||||
}
|
||||
|
||||
authorizer.scope = &Scope{
|
||||
Type: scopeType,
|
||||
Name: scopeName,
|
||||
Actions: scopeActions,
|
||||
return &tokenAuthorizer{
|
||||
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 {
|
||||
service string
|
||||
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(scopes []*token.ResourceActions, endpoint string) (*models.Token, error) {
|
||||
return token_util.MakeToken(r.username, r.service, scopes)
|
||||
}
|
||||
|
||||
func (u *usernameTokenAuthorizer) genNotaryToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) {
|
||||
token, expiresIn, issuedAt, err = token_util.NotaryTokenForUI(u.username, service, scopes)
|
||||
return
|
||||
func buildPingURL(endpoint string) string {
|
||||
return fmt.Sprintf("%s/v2/", endpoint)
|
||||
}
|
||||
|
@ -15,54 +15,195 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution/registry/auth/token"
|
||||
"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, err := parseScopes(req)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(scopses))
|
||||
assert.EqualValues(t, &token.ResourceActions{
|
||||
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, err = parseScopes(req)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(scopses))
|
||||
|
||||
// catalog
|
||||
req, err = http.NewRequest(http.MethodGet, "http://registry/v2/_catalog", nil)
|
||||
require.Nil(t, err)
|
||||
scopses, err = parseScopes(req)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(scopses))
|
||||
assert.EqualValues(t, &token.ResourceActions{
|
||||
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, err = parseScopes(req)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(scopses))
|
||||
assert.EqualValues(t, &token.ResourceActions{
|
||||
Type: "repository",
|
||||
Name: "library/mysql/5.6",
|
||||
Actions: []string{
|
||||
"push"},
|
||||
}, scopses[0])
|
||||
|
||||
// invalid
|
||||
req, err = http.NewRequest(http.MethodPut, "http://registry/other", nil)
|
||||
require.Nil(t, err)
|
||||
scopses, err = parseScopes(req)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestGetAndUpdateCachedToken(t *testing.T) {
|
||||
authorizer := &tokenAuthorizer{
|
||||
cachedTokens: make(map[string]*models.Token),
|
||||
}
|
||||
|
||||
// empty cache
|
||||
token := authorizer.getCachedToken("")
|
||||
assert.Nil(t, token)
|
||||
|
||||
// put a valid token into cache
|
||||
token = &models.Token{
|
||||
Token: "token",
|
||||
ExpiresIn: 60,
|
||||
IssuedAt: time.Now().Format(time.RFC3339),
|
||||
}
|
||||
authorizer.updateCachedToken("", token)
|
||||
token2 := authorizer.getCachedToken("")
|
||||
assert.EqualValues(t, token, token2)
|
||||
|
||||
// put a expired token into cache
|
||||
token = &models.Token{
|
||||
Token: "token",
|
||||
ExpiresIn: 60,
|
||||
IssuedAt: time.Now().Add(-time.Second * 120).Format("2006-01-02 15:04:05.999999999 -0700 MST"),
|
||||
}
|
||||
authorizer.updateCachedToken("", token)
|
||||
token2 = authorizer.getCachedToken("")
|
||||
assert.Nil(t, token2)
|
||||
}
|
||||
|
||||
func TestModifyOfStandardTokenAuthorizer(t *testing.T) {
|
||||
token := &models.Token{
|
||||
Token: "token",
|
||||
ExpiresIn: 3600,
|
||||
IssuedAt: time.Now().String(),
|
||||
}
|
||||
data, err := json.Marshal(token)
|
||||
require.Nil(t, err)
|
||||
|
||||
tokenHandler := test.Handler(&test.Response{
|
||||
Body: data,
|
||||
})
|
||||
|
||||
server := test.NewServer(&test.RequestHandlerMapping{
|
||||
Method: "GET",
|
||||
Pattern: "/token",
|
||||
Handler: handler,
|
||||
tokenServer := test.NewServer(
|
||||
&test.RequestHandlerMapping{
|
||||
Method: "GET",
|
||||
Pattern: "/service/token",
|
||||
Handler: tokenHandler,
|
||||
})
|
||||
defer tokenServer.Close()
|
||||
|
||||
header := fmt.Sprintf("Bearer realm=\"%s/service/token\",service=\"registry\"",
|
||||
tokenServer.URL)
|
||||
pingHandler := test.Handler(&test.Response{
|
||||
StatusCode: http.StatusUnauthorized,
|
||||
Headers: map[string]string{
|
||||
"WWW-Authenticate": header,
|
||||
},
|
||||
})
|
||||
defer server.Close()
|
||||
registryServer := test.NewServer(
|
||||
&test.RequestHandlerMapping{
|
||||
Method: "GET",
|
||||
Pattern: "/v2",
|
||||
Handler: pingHandler,
|
||||
})
|
||||
defer registryServer.Close()
|
||||
|
||||
authorizer := NewStandardTokenAuthorizer(nil, false, "", "repository", "library/ubuntu", "pull")
|
||||
req, err := http.NewRequest("GET", "http://registry", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", err)
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v2/", registryServer.URL), nil)
|
||||
require.Nil(t, err)
|
||||
|
||||
params := map[string]string{
|
||||
"realm": server.URL + "/token",
|
||||
}
|
||||
authorizer := NewStandardTokenAuthorizer(nil, false)
|
||||
|
||||
if err := authorizer.Authorize(req, params); err != nil {
|
||||
t.Fatalf("failed to authorize request: %v", err)
|
||||
}
|
||||
err = authorizer.Modify(req)
|
||||
require.Nil(t, err)
|
||||
|
||||
tk := req.Header.Get("Authorization")
|
||||
if tk != "Bearer token" {
|
||||
t.Errorf("unexpected token: %s != %s", tk, "Bearer token")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSchemeOfStandardTokenAuthorizer(t *testing.T) {
|
||||
authorizer := &standardTokenAuthorizer{}
|
||||
if authorizer.Scheme() != "bearer" {
|
||||
t.Errorf("unexpected scheme: %s != %s", authorizer.Scheme(), "bearer")
|
||||
}
|
||||
|
||||
assert.Equal(t, strings.ToLower("Bearer "+token.Token), strings.ToLower(tk))
|
||||
}
|
||||
|
@ -20,8 +20,8 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/docker/distribution/registry/auth/token"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
|
||||
registry_error "github.com/vmware/harbor/src/common/utils/error"
|
||||
"github.com/vmware/harbor/src/common/utils/registry"
|
||||
)
|
||||
@ -32,21 +32,16 @@ const (
|
||||
|
||||
// GetToken requests a token against the endpoint using credetial provided
|
||||
func GetToken(endpoint string, insecure bool, credential Credential,
|
||||
scopes []*Scope) (*models.Token, error) {
|
||||
scopes []*token.ResourceActions) (*models.Token, error) {
|
||||
client := &http.Client{
|
||||
Transport: registry.GetHTTPTransport(insecure),
|
||||
}
|
||||
|
||||
scopesStr := []string{}
|
||||
for _, scope := range scopes {
|
||||
scopesStr = append(scopesStr, scope.string())
|
||||
}
|
||||
|
||||
return getToken(client, credential, endpoint, service, scopesStr)
|
||||
return getToken(client, credential, endpoint, service, scopes)
|
||||
}
|
||||
|
||||
func getToken(client *http.Client, credential Credential, realm, service string,
|
||||
scopes []string) (*models.Token, error) {
|
||||
scopes []*token.ResourceActions) (*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", scopeString(scope))
|
||||
}
|
||||
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
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/distribution/registry/auth/token"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/registry"
|
||||
"github.com/vmware/harbor/src/common/utils/registry/auth"
|
||||
@ -26,24 +27,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 {
|
||||
@ -65,14 +57,15 @@ func BuildBlobURL(endpoint, repository, digest string) string {
|
||||
func GetTokenForRepo(repository string) (string, error) {
|
||||
c := &http.Cookie{Name: models.UISecretCookie, Value: config.JobserviceSecret()}
|
||||
credentail := auth.NewCookieCredential(c)
|
||||
token, err := auth.GetToken(config.InternalTokenServiceEndpoint(), true, credentail, []*auth.Scope{&auth.Scope{
|
||||
Type: "repository",
|
||||
Name: repository,
|
||||
Actions: []string{"pull"},
|
||||
}})
|
||||
t, err := auth.GetToken(config.InternalTokenServiceEndpoint(), true, credentail,
|
||||
[]*token.ResourceActions{&token.ResourceActions{
|
||||
Type: "repository",
|
||||
Name: repository,
|
||||
Actions: []string{"pull"},
|
||||
}})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return token.Token, nil
|
||||
return t.Token, nil
|
||||
}
|
||||
|
@ -190,7 +190,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")
|
||||
@ -306,7 +306,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))
|
||||
@ -355,7 +355,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")
|
||||
@ -496,7 +496,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")
|
||||
@ -558,16 +558,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)
|
||||
@ -808,7 +798,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 {
|
||||
@ -427,24 +409,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{}
|
||||
|
@ -191,6 +191,7 @@ func TokenExpiration() (int, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return int(cfg[common.TokenExpiration].(float64)), nil
|
||||
}
|
||||
|
||||
|
@ -30,9 +30,10 @@ func TestMain(m *testing.M) {
|
||||
defer notaryServer.Close()
|
||||
NotaryEndpoint = notaryServer.URL
|
||||
var defaultConfig = map[string]interface{}{
|
||||
common.ExtEndpoint: "https://" + endpoint,
|
||||
common.WithNotary: true,
|
||||
common.CfgExpiration: 5,
|
||||
common.ExtEndpoint: "https://" + endpoint,
|
||||
common.WithNotary: true,
|
||||
common.CfgExpiration: 5,
|
||||
common.TokenExpiration: 30,
|
||||
}
|
||||
adminServer, err := utilstest.NewAdminserver(defaultConfig)
|
||||
if err != nil {
|
||||
@ -117,6 +118,7 @@ func TestPMSPolicyChecker(t *testing.T) {
|
||||
common.WithNotary: true,
|
||||
common.CfgExpiration: 5,
|
||||
common.AdmiralEndpoint: admiralEndpoint,
|
||||
common.TokenExpiration: 30,
|
||||
}
|
||||
adminServer, err := utilstest.NewAdminserver(defaultConfigAdmiral)
|
||||
if err != nil {
|
||||
|
@ -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,54 +93,26 @@ 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)
|
||||
}
|
||||
|
||||
//NotaryTokenForUI calls genTokenForUI to get raw token for notary
|
||||
func NotaryTokenForUI(username string, service string, scopes []string) (string, int, *time.Time, error) {
|
||||
return genTokenForUI(username, service, scopes)
|
||||
}
|
||||
|
||||
// genTokenForUI is for the UI process to call, so it won't establish a https connection from UI to proxy.
|
||||
func genTokenForUI(username string, service string,
|
||||
scopes []string) (string, int, *time.Time, error) {
|
||||
access := GetResourceActions(scopes)
|
||||
return MakeRawToken(username, service, access)
|
||||
}
|
||||
|
||||
// MakeRawToken makes a valid jwt token based on parms.
|
||||
func MakeRawToken(username, service string, access []*token.ResourceActions) (token string, expiresIn int, issuedAt *time.Time, err error) {
|
||||
// MakeToken makes a valid jwt token based on parms.
|
||||
func MakeToken(username, service string, access []*token.ResourceActions) (*models.Token, error) {
|
||||
pk, err := libtrust.LoadKeyFile(privateKey)
|
||||
if err != nil {
|
||||
return "", 0, nil, err
|
||||
return nil, err
|
||||
}
|
||||
expiration, err := config.TokenExpiration()
|
||||
if err != nil {
|
||||
return "", 0, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tk, expiresIn, issuedAt, err := makeTokenCore(issuer, username, service, expiration, access, pk)
|
||||
if err != nil {
|
||||
return "", 0, nil, err
|
||||
}
|
||||
rs := fmt.Sprintf("%s.%s", tk.Raw, base64UrlEncode(tk.Signature))
|
||||
return rs, expiresIn, issuedAt, nil
|
||||
}
|
||||
|
||||
func makeToken(username, service string, access []*token.ResourceActions) (*models.Token, error) {
|
||||
raw, expires, issued, err := MakeRawToken(username, service, access)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rs := fmt.Sprintf("%s.%s", tk.Raw, base64UrlEncode(tk.Signature))
|
||||
return &models.Token{
|
||||
Token: raw,
|
||||
ExpiresIn: expires,
|
||||
IssuedAt: issued.Format(time.RFC3339),
|
||||
Token: rs,
|
||||
ExpiresIn: expiresIn,
|
||||
IssuedAt: issuedAt.Format(time.RFC3339),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
@ -200,7 +202,7 @@ func (g generalCreator) Create(r *http.Request) (*models.Token, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return makeToken(ctx.GetUsername(), g.service, access)
|
||||
return MakeToken(ctx.GetUsername(), g.service, access)
|
||||
}
|
||||
|
||||
func parseScopes(u *url.URL) []string {
|
||||
|
@ -111,7 +111,7 @@ func TestMakeToken(t *testing.T) {
|
||||
}}
|
||||
svc := "harbor-registry"
|
||||
u := "tester"
|
||||
tokenJSON, err := makeToken(u, svc, ra)
|
||||
tokenJSON, err := MakeToken(u, svc, ra)
|
||||
if err != nil {
|
||||
t.Errorf("Error while making token: %v", err)
|
||||
}
|
||||
|
@ -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 on if failed to submit 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)
|
||||
@ -44,33 +40,28 @@ func ScanAllImages() error {
|
||||
}
|
||||
log.Infof("Scanning all images on Harbor.")
|
||||
|
||||
go scanRepos(repos, regURL)
|
||||
go scanRepos(repos)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ScanImagesByProjectID scans all images under a projet, the whole process will move on if failed to submit any job of a single image.
|
||||
func ScanImagesByProjectID(id int64) error {
|
||||
regURL, err := config.RegistryURL()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to load registry url")
|
||||
return err
|
||||
}
|
||||
repos, err := dao.GetRepositoriesByProject(id, "", 0, 0)
|
||||
if err != nil {
|
||||
log.Errorf("Failed list repositories in project %d, error: %v", id, err)
|
||||
return err
|
||||
}
|
||||
log.Infof("Scanning all images in project: %d ", id)
|
||||
go scanRepos(repos, regURL)
|
||||
go scanRepos(repos)
|
||||
return nil
|
||||
}
|
||||
|
||||
func scanRepos(repos []*models.RepoRecord, regURL string) {
|
||||
func scanRepos(repos []*models.RepoRecord) {
|
||||
var repoClient *registry.Repository
|
||||
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
|
||||
@ -131,20 +122,15 @@ func TriggerImageScan(repository string, tag string) error {
|
||||
return RequestAsUI("POST", url, bytes.NewBuffer(b), NewStatusRespHandler(http.StatusOK))
|
||||
}
|
||||
|
||||
// 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)
|
||||
// NewRepositoryClientForUI creates a repository client that can only be used to
|
||||
// access the internal registry
|
||||
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