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 ( import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"net/http"
"os" "os"
"path" "path"
"strings" "strings"
"github.com/docker/distribution/registry/auth/token"
"github.com/docker/notary" "github.com/docker/notary"
"github.com/docker/notary/client" "github.com/docker/notary/client"
"github.com/docker/notary/trustpinning" "github.com/docker/notary/trustpinning"
"github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/data"
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/common/utils/registry" "github.com/vmware/harbor/src/common/utils/registry"
"github.com/vmware/harbor/src/common/utils/registry/auth"
"github.com/vmware/harbor/src/ui/config" "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" "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. // 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 // 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) { func GetTargets(notaryEndpoint string, username string, fqRepo string) ([]Target, error) {
res := []Target{} 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) tr := registry.NewTransport(registry.GetHTTPTransport(true), authorizer)
gun := data.GUN(fqRepo) gun := data.GUN(fqRepo)
notaryRepo, err := client.NewFileCachedNotaryRepository(notaryCachePath, gun, notaryEndpoint, tr, mockRetriever, trustPin) notaryRepo, err := client.NewFileCachedNotaryRepository(notaryCachePath, gun, notaryEndpoint, tr, mockRetriever, trustPin)
@ -109,3 +122,13 @@ func DigestFromTarget(t Target) (string, error) {
} }
return digest.NewDigestFromHex("sha256", hex.EncodeToString(sha)).String(), nil 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" "sync"
"time" "time"
"github.com/docker/distribution/registry/auth/token"
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/common/utils/registry" "github.com/vmware/harbor/src/common/utils/registry"
@ -33,19 +34,8 @@ const (
scheme = "bearer" 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 { 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 // tokenAuthorizer implements registry.Modifier interface. It parses scopses
@ -84,7 +74,7 @@ func (t *tokenAuthorizer) Modify(req *http.Request) error {
if len(scopes) <= 1 { if len(scopes) <= 1 {
key := "" key := ""
if len(scopes) == 1 { if len(scopes) == 1 {
key = scopes[0].string() key = scopeString(scopes[0])
} }
token = t.getCachedToken(key) token = t.getCachedToken(key)
} }
@ -104,7 +94,7 @@ func (t *tokenAuthorizer) Modify(req *http.Request) error {
if len(scopes) <= 1 { if len(scopes) <= 1 {
key := "" key := ""
if len(scopes) == 1 { if len(scopes) == 1 {
key = scopes[0].string() key = scopeString(scopes[0])
} }
t.updateCachedToken(key, token) t.updateCachedToken(key, token)
} }
@ -115,6 +105,13 @@ func (t *tokenAuthorizer) Modify(req *http.Request) error {
return nil 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 // some requests are sent to backend storage, such as s3, this method filters
// the requests only sent to registry // the requests only sent to registry
func (t *tokenAuthorizer) filterReq(req *http.Request) (bool, error) { 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 // parse scopes from the request according to its method, path and query string
func parseScopes(req *http.Request) ([]*Scope, error) { func parseScopes(req *http.Request) ([]*token.ResourceActions, error) {
scopes := []*Scope{} scopes := []*token.ResourceActions{}
from := req.URL.Query().Get("from") from := req.URL.Query().Get("from")
if len(from) != 0 { if len(from) != 0 {
scopes = append(scopes, &Scope{ scopes = append(scopes, &token.ResourceActions{
Type: "repository", Type: "repository",
Name: from, Name: from,
Actions: []string{"pull"}, Actions: []string{"pull"},
}) })
} }
var scope *Scope var scope *token.ResourceActions
path := strings.TrimRight(req.URL.Path, "/") path := strings.TrimRight(req.URL.Path, "/")
repository := parseRepository(path) repository := parseRepository(path)
if len(repository) > 0 { if len(repository) > 0 {
// pull, push, delete blob/manifest // pull, push, delete blob/manifest
scope = &Scope{ scope = &token.ResourceActions{
Type: "repository", Type: "repository",
Name: repository, Name: repository,
} }
@ -176,7 +173,7 @@ func parseScopes(req *http.Request) ([]*Scope, error) {
} }
} else if catalog.MatchString(path) { } else if catalog.MatchString(path) {
// catalog // catalog
scope = &Scope{ scope = &token.ResourceActions{
Type: "registry", Type: "registry",
Name: "catalog", Name: "catalog",
Actions: []string{"*"}, Actions: []string{"*"},
@ -195,7 +192,7 @@ func parseScopes(req *http.Request) ([]*Scope, error) {
strs := []string{} strs := []string{}
for _, s := range scopes { for _, s := range scopes {
strs = append(strs, s.string()) strs = append(strs, scopeString(s))
} }
log.Debugf("scopses parsed from request: %s", strings.Join(strs, " ")) log.Debugf("scopses parsed from request: %s", strings.Join(strs, " "))
@ -295,7 +292,7 @@ type standardTokenGenerator struct {
} }
// get token from token service // 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 // ping first if the realm or service is null
if len(s.realm) == 0 || len(s.service) == 0 { if len(s.realm) == 0 || len(s.service) == 0 {
realm, service, err := ping(s.client, endpoint) realm, service, err := ping(s.client, endpoint)
@ -336,21 +333,8 @@ type rawTokenGenerator struct {
} }
// generate token directly // generate token directly
func (r *rawTokenGenerator) generate(scopes []*Scope, endpoint string) (*models.Token, error) { func (r *rawTokenGenerator) generate(scopes []*token.ResourceActions, endpoint string) (*models.Token, error) {
strs := []string{} return token_util.MakeToken(r.username, r.service, scopes)
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 buildPingURL(endpoint string) string { func buildPingURL(endpoint string) string {

View File

@ -22,6 +22,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/docker/distribution/registry/auth/token"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
@ -81,7 +82,7 @@ func TestParseScopes(t *testing.T) {
scopses, err := parseScopes(req) scopses, err := parseScopes(req)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(scopses)) assert.Equal(t, 1, len(scopses))
assert.EqualValues(t, &Scope{ assert.EqualValues(t, &token.ResourceActions{
Type: "repository", Type: "repository",
Name: "library", Name: "library",
Actions: []string{ Actions: []string{
@ -101,7 +102,7 @@ func TestParseScopes(t *testing.T) {
scopses, err = parseScopes(req) scopses, err = parseScopes(req)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(scopses)) assert.Equal(t, 1, len(scopses))
assert.EqualValues(t, &Scope{ assert.EqualValues(t, &token.ResourceActions{
Type: "registry", Type: "registry",
Name: "catalog", Name: "catalog",
Actions: []string{ Actions: []string{
@ -114,7 +115,7 @@ func TestParseScopes(t *testing.T) {
scopses, err = parseScopes(req) scopses, err = parseScopes(req)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(scopses)) assert.Equal(t, 1, len(scopses))
assert.EqualValues(t, &Scope{ assert.EqualValues(t, &token.ResourceActions{
Type: "repository", Type: "repository",
Name: "library/mysql/5.6", Name: "library/mysql/5.6",
Actions: []string{ Actions: []string{

View File

@ -20,8 +20,8 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"github.com/docker/distribution/registry/auth/token"
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
registry_error "github.com/vmware/harbor/src/common/utils/error" registry_error "github.com/vmware/harbor/src/common/utils/error"
"github.com/vmware/harbor/src/common/utils/registry" "github.com/vmware/harbor/src/common/utils/registry"
) )
@ -32,7 +32,7 @@ const (
// GetToken requests a token against the endpoint using credetial provided // GetToken requests a token against the endpoint using credetial provided
func GetToken(endpoint string, insecure bool, credential Credential, func GetToken(endpoint string, insecure bool, credential Credential,
scopes []*Scope) (*models.Token, error) { scopes []*token.ResourceActions) (*models.Token, error) {
client := &http.Client{ client := &http.Client{
Transport: registry.GetHTTPTransport(insecure), 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, 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) u, err := url.Parse(realm)
if err != nil { if err != nil {
return nil, err return nil, err
@ -49,7 +49,7 @@ func getToken(client *http.Client, credential Credential, realm, service string,
query := u.Query() query := u.Query()
query.Add("service", service) query.Add("service", service)
for _, scope := range scopes { for _, scope := range scopes {
query.Add("scope", scope.string()) query.Add("scope", scopeString(scope))
} }
u.RawQuery = query.Encode() u.RawQuery = query.Encode()

View File

@ -93,51 +93,26 @@ func filterAccess(access []*token.ResourceActions, ctx security.Context,
return nil return nil
} }
//RegistryTokenForUI calls genTokenForUI to get raw token for registry // MakeToken makes a valid jwt token based on parms.
func RegistryTokenForUI(username string, service string, scopes []string) (string, int, *time.Time, error) { func MakeToken(username, service string, access []*token.ResourceActions) (*models.Token, 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) {
pk, err := libtrust.LoadKeyFile(privateKey) pk, err := libtrust.LoadKeyFile(privateKey)
if err != nil { if err != nil {
return "", 0, nil, err return nil, err
} }
expiration, err := config.TokenExpiration() expiration, err := config.TokenExpiration()
if err != nil { if err != nil {
return "", 0, nil, err return nil, err
} }
tk, expiresIn, issuedAt, err := makeTokenCore(issuer, username, service, expiration, access, pk) 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 { if err != nil {
return nil, err return nil, err
} }
rs := fmt.Sprintf("%s.%s", tk.Raw, base64UrlEncode(tk.Signature))
return &models.Token{ return &models.Token{
Token: raw, Token: rs,
ExpiresIn: expires, ExpiresIn: expiresIn,
IssuedAt: issued.Format(time.RFC3339), IssuedAt: issuedAt.Format(time.RFC3339),
}, nil }, nil
} }

View File

@ -202,7 +202,7 @@ func (g generalCreator) Create(r *http.Request) (*models.Token, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return makeToken(ctx.GetUsername(), g.service, access) return MakeToken(ctx.GetUsername(), g.service, access)
} }
func parseScopes(u *url.URL) []string { func parseScopes(u *url.URL) []string {

View File

@ -111,7 +111,7 @@ func TestMakeToken(t *testing.T) {
}} }}
svc := "harbor-registry" svc := "harbor-registry"
u := "tester" u := "tester"
tokenJSON, err := makeToken(u, svc, ra) tokenJSON, err := MakeToken(u, svc, ra)
if err != nil { if err != nil {
t.Errorf("Error while making token: %v", err) 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)) 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) { func NewRepositoryClientForUI(username, repository string) (*registry.Repository, error) {
endpoint, err := config.RegistryURL() endpoint, err := config.RegistryURL()
if err != nil { if err != nil {