mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-27 04:35:16 +01:00
update
This commit is contained in:
parent
1da9b8653b
commit
a8dc75dd15
@ -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 := ¬aryAuthorizer{
|
||||||
|
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
|
||||||
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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{
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user