This commit is contained in:
Wenkai Yin 2017-07-28 12:59:23 +08:00
parent 1da9b8653b
commit a8dc75dd15
8 changed files with 68 additions and 84 deletions

View File

@ -17,19 +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"
"github.com/vmware/harbor/src/ui/service/token"
tokenutil "github.com/vmware/harbor/src/ui/service/token"
"github.com/opencontainers/go-digest"
)
@ -72,10 +73,22 @@ 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.NewRawTokenAuthorizer(username, token.Notary)
t, err := tokenutil.MakeToken(username, tokenutil.Notary,
[]*token.ResourceActions{
&token.ResourceActions{
Type: "repository",
Name: fqRepo,
Actions: []string{"pull"},
}})
if err != nil {
return nil, err
}
authorizer := &notaryAuthorizer{
token: t.Token,
}
tr := registry.NewTransport(registry.GetHTTPTransport(true), authorizer)
gun := data.GUN(fqRepo)
notaryRepo, err := client.NewFileCachedNotaryRepository(notaryCachePath, gun, notaryEndpoint, tr, mockRetriever, trustPin)
@ -109,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
}

View File

@ -22,6 +22,7 @@ import (
"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"
@ -33,19 +34,8 @@ const (
scheme = "bearer"
)
// Scope ...
type Scope struct {
Type string
Name string
Actions []string
}
func (s *Scope) string() string {
return fmt.Sprintf("%s:%s:%s", s.Type, s.Name, strings.Join(s.Actions, ","))
}
type tokenGenerator interface {
generate(scopes []*Scope, endpoint string) (*models.Token, error)
generate(scopes []*token.ResourceActions, endpoint string) (*models.Token, error)
}
// tokenAuthorizer implements registry.Modifier interface. It parses scopses
@ -84,7 +74,7 @@ func (t *tokenAuthorizer) Modify(req *http.Request) error {
if len(scopes) <= 1 {
key := ""
if len(scopes) == 1 {
key = scopes[0].string()
key = scopeString(scopes[0])
}
token = t.getCachedToken(key)
}
@ -104,7 +94,7 @@ func (t *tokenAuthorizer) Modify(req *http.Request) error {
if len(scopes) <= 1 {
key := ""
if len(scopes) == 1 {
key = scopes[0].string()
key = scopeString(scopes[0])
}
t.updateCachedToken(key, token)
}
@ -115,6 +105,13 @@ func (t *tokenAuthorizer) Modify(req *http.Request) error {
return nil
}
func scopeString(scope *token.ResourceActions) string {
if scope == nil {
return ""
}
return fmt.Sprintf("%s:%s:%s", scope.Type, scope.Name, strings.Join(scope.Actions, ","))
}
// 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) {
@ -142,24 +139,24 @@ func (t *tokenAuthorizer) filterReq(req *http.Request) (bool, error) {
}
// parse scopes from the request according to its method, path and query string
func parseScopes(req *http.Request) ([]*Scope, error) {
scopes := []*Scope{}
func parseScopes(req *http.Request) ([]*token.ResourceActions, error) {
scopes := []*token.ResourceActions{}
from := req.URL.Query().Get("from")
if len(from) != 0 {
scopes = append(scopes, &Scope{
scopes = append(scopes, &token.ResourceActions{
Type: "repository",
Name: from,
Actions: []string{"pull"},
})
}
var scope *Scope
var scope *token.ResourceActions
path := strings.TrimRight(req.URL.Path, "/")
repository := parseRepository(path)
if len(repository) > 0 {
// pull, push, delete blob/manifest
scope = &Scope{
scope = &token.ResourceActions{
Type: "repository",
Name: repository,
}
@ -176,7 +173,7 @@ func parseScopes(req *http.Request) ([]*Scope, error) {
}
} else if catalog.MatchString(path) {
// catalog
scope = &Scope{
scope = &token.ResourceActions{
Type: "registry",
Name: "catalog",
Actions: []string{"*"},
@ -195,7 +192,7 @@ func parseScopes(req *http.Request) ([]*Scope, error) {
strs := []string{}
for _, s := range scopes {
strs = append(strs, s.string())
strs = append(strs, scopeString(s))
}
log.Debugf("scopses parsed from request: %s", strings.Join(strs, " "))
@ -295,7 +292,7 @@ type standardTokenGenerator struct {
}
// get token from token service
func (s *standardTokenGenerator) generate(scopes []*Scope, endpoint string) (*models.Token, error) {
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)
@ -336,21 +333,8 @@ type rawTokenGenerator struct {
}
// generate token directly
func (r *rawTokenGenerator) generate(scopes []*Scope, endpoint string) (*models.Token, error) {
strs := []string{}
for _, scope := range scopes {
strs = append(strs, scope.string())
}
token, expiresIn, issuedAt, err := token_util.RegistryTokenForUI(r.username, r.service, strs)
if err != nil {
return nil, err
}
return &models.Token{
Token: token,
ExpiresIn: expiresIn,
IssuedAt: issuedAt.Format(time.RFC3339),
}, nil
func (r *rawTokenGenerator) generate(scopes []*token.ResourceActions, endpoint string) (*models.Token, error) {
return token_util.MakeToken(r.username, r.service, scopes)
}
func buildPingURL(endpoint string) string {

View File

@ -22,6 +22,7 @@ import (
"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"
@ -81,7 +82,7 @@ func TestParseScopes(t *testing.T) {
scopses, err := parseScopes(req)
assert.Nil(t, err)
assert.Equal(t, 1, len(scopses))
assert.EqualValues(t, &Scope{
assert.EqualValues(t, &token.ResourceActions{
Type: "repository",
Name: "library",
Actions: []string{
@ -101,7 +102,7 @@ func TestParseScopes(t *testing.T) {
scopses, err = parseScopes(req)
assert.Nil(t, err)
assert.Equal(t, 1, len(scopses))
assert.EqualValues(t, &Scope{
assert.EqualValues(t, &token.ResourceActions{
Type: "registry",
Name: "catalog",
Actions: []string{
@ -114,7 +115,7 @@ func TestParseScopes(t *testing.T) {
scopses, err = parseScopes(req)
assert.Nil(t, err)
assert.Equal(t, 1, len(scopses))
assert.EqualValues(t, &Scope{
assert.EqualValues(t, &token.ResourceActions{
Type: "repository",
Name: "library/mysql/5.6",
Actions: []string{

View File

@ -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,7 +32,7 @@ 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),
}
@ -41,7 +41,7 @@ func GetToken(endpoint string, insecure bool, credential Credential,
}
func getToken(client *http.Client, credential Credential, realm, service string,
scopes []*Scope) (*models.Token, error) {
scopes []*token.ResourceActions) (*models.Token, error) {
u, err := url.Parse(realm)
if err != nil {
return nil, err
@ -49,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.string())
query.Add("scope", scopeString(scope))
}
u.RawQuery = query.Encode()

View File

@ -93,51 +93,26 @@ func filterAccess(access []*token.ResourceActions, ctx security.Context,
return nil
}
//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
}

View File

@ -202,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 {

View File

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

View File

@ -122,7 +122,8 @@ func TriggerImageScan(repository string, tag string) error {
return RequestAsUI("POST", url, bytes.NewBuffer(b), NewStatusRespHandler(http.StatusOK))
}
// NewRepositoryClientForUI ...
// 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 {