harbor authentication process

| 分类 技术  | 标签 Docker-registry  Harbor 

本文从源码层面描述harbor authentication流程

一、harbor auth流程

harbor v1.8.1为例进行分析,目录结构日下:

src/
|-- Gopkg.lock
|-- Gopkg.toml
|-- chartserver
|-- cmd
|-- common
|-- core
	|-- api
	|-- auth
	|-- config
	|-- controllers
	|-- filter
	|-- label
	|-- main.go
	|-- notifier
	|-- promgr
	|-- proxy
	|-- router.go
	|-- service
	|-- systeminfo
	|-- utils
	`-- views
|-- favicon.ico
|-- jobservice
|-- portal
|-- registryctl
|-- replication
|-- testing
`-- vendor

auth入口在core/filter/security.go文件:

// Init ReqCtxMofiers list
func Init() {
	// integration with admiral
	if config.WithAdmiral() {
		reqCtxModifiers = []ReqCtxModifier{
			&secretReqCtxModifier{config.SecretStore},
			&tokenReqCtxModifier{},
			&basicAuthReqCtxModifier{},
			&unauthorizedReqCtxModifier{}}
		return
	}

	// standalone
	reqCtxModifiers = []ReqCtxModifier{
		&configCtxModifier{},
		&secretReqCtxModifier{config.SecretStore},
		&oidcCliReqCtxModifier{},
		&idTokenReqCtxModifier{},
		&authProxyReqCtxModifier{},
		&robotAuthReqCtxModifier{},
		&basicAuthReqCtxModifier{},
		&sessionReqCtxModifier{},
		&unauthorizedReqCtxModifier{}}
}

逐层认证,直到一个成功为止:

// SecurityFilter authenticates the request and passes a security context
// and a project manager with it which can be used to do some authN & authZ
func SecurityFilter(ctx *beegoctx.Context) {
	if ctx == nil {
		return
	}

	req := ctx.Request
	if req == nil {
		return
	}

	// add security context and project manager to request context
	for _, modifier := range reqCtxModifiers {
		if modifier.Modify(ctx) {
			break
		}
	}
}

这里以basicAuthReqCtxModifier为例看认证逻辑,如下:

type basicAuthReqCtxModifier struct{}

func (b *basicAuthReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
	username, password, ok := ctx.Request.BasicAuth()
	if !ok {
		return false
	}
	log.Debug("got user information via basic auth")

	// integration with admiral
	if config.WithAdmiral() {
		// Can't get a token from Admiral's login API, we can only
		// create a project manager with the token of the solution user.
		// That way may cause some wrong permission promotion in some API
		// calls, so we just handle the requests which are necessary
		match := false
		var err error
		path := ctx.Request.URL.Path
		for _, pattern := range basicAuthReqPatterns {
			match, err = regexp.MatchString(pattern.path, path)
			if err != nil {
				log.Errorf("failed to match %s with pattern %s", path, pattern)
				continue
			}
			if match {
				break
			}
		}
		if !match {
			log.Debugf("basic auth is not supported for request %s %s, skip",
				ctx.Request.Method, ctx.Request.URL.Path)
			return false
		}

		token, err := config.TokenReader.ReadToken()
		if err != nil {
			log.Errorf("failed to read solution user token: %v", err)
			return false
		}
		authCtx, err := authcontext.Login(config.AdmiralClient,
			config.AdmiralEndpoint(), username, password, token)
		if err != nil {
			log.Errorf("failed to authenticate %s: %v", username, err)
			return false
		}

		log.Debug("using global project manager...")
		pm := config.GlobalProjectMgr
		log.Debug("creating admiral security context...")
		securCtx := admr.NewSecurityContext(authCtx, pm)

		setSecurCtxAndPM(ctx.Request, securCtx, pm)
		return true
	}

	// standalone
	user, err := auth.Login(models.AuthModel{
		Principal: username,
		Password:  password,
	})
	if err != nil {
		log.Errorf("failed to authenticate %s: %v", username, err)
		return false
	}
	if user == nil {
		log.Debug("basic auth user is nil")
		return false
	}
	log.Debug("using local database project manager")
	pm := config.GlobalProjectMgr
	log.Debug("creating local database security context...")
	securCtx := local.NewSecurityContext(user, pm)
	setSecurCtxAndPM(ctx.Request, securCtx, pm)
	return true
}

首先是auth.Login,如下:

// standalone
user, err := auth.Login(models.AuthModel{
	Principal: username,
	Password:  password,
})
if err != nil {
	log.Errorf("failed to authenticate %s: %v", username, err)
	return false
}
if user == nil {
	log.Debug("basic auth user is nil")
	return false
}

这里会用到core/auth/authenticator.go,逻辑如下:

// Login authenticates user credentials based on setting.
func Login(m models.AuthModel) (*models.User, error) {

	authMode, err := config.AuthMode()
	if err != nil {
		return nil, err
	}
	if authMode == "" || dao.IsSuperUser(m.Principal) {
		authMode = common.DBAuth
	}
	log.Debug("Current AUTH_MODE is ", authMode)

	authenticator, ok := registry[authMode]
	if !ok {
		return nil, fmt.Errorf("Unrecognized auth_mode: %s", authMode)
	}
	if lock.IsLocked(m.Principal) {
		log.Debugf("%s is locked due to login failure, login failed", m.Principal)
		return nil, nil
	}
	user, err := authenticator.Authenticate(m)
	if err != nil {
		if _, ok = err.(ErrAuth); ok {
			log.Debugf("Login failed, locking %s, and sleep for %v", m.Principal, frozenTime)
			lock.Lock(m.Principal)
			time.Sleep(frozenTime)
		}
		return nil, err
	}
	err = authenticator.PostAuthenticate(user)
	return user, err
}

获取认证模式,然后调用对应的Authenticate函数。这里认证模式总共有:db_authldapuaa(oauth2)oidc这几种,这里以最简单的db_auth为例分析:

src/core/auth/
|-- auth_test.go
|-- authenticator.go
|-- authproxy
|-- db
|-- ldap
|-- lock.go
`-- uaa
// Authenticate calls dao to authenticate user.
func (d *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
	u, err := dao.LoginByDb(m)
	if err != nil {
		return nil, err
	}
	if u == nil {
		return nil, auth.NewErrAuth("Invalid credentials")
	}
	return u, nil
}

func init() {
	auth.Register("db_auth", &Auth{})
}

// LoginByDb is used for user to login with database auth mode.
func LoginByDb(auth models.AuthModel) (*models.User, error) {
	o := GetOrmer()

	var users []models.User
	n, err := o.Raw(`select * from harbor_user where (username = ? or email = ?) and deleted = false`,
		auth.Principal, auth.Principal).QueryRows(&users)
	if err != nil {
		return nil, err
	}
	if n == 0 {
		return nil, nil
	}

	user := users[0]

	if user.Password != utils.Encrypt(auth.Password, user.Salt) {
		return nil, nil
	}

	user.Password = "" // do not return the password

	return &user, nil
}

// User holds the details of a user.
type User struct {
	UserID   int    `orm:"pk;auto;column(user_id)" json:"user_id"`
	Username string `orm:"column(username)" json:"username"`
	Email    string `orm:"column(email)" json:"email"`
	Password string `orm:"column(password)" json:"password"`
	Realname string `orm:"column(realname)" json:"realname"`
	Comment  string `orm:"column(comment)" json:"comment"`
	Deleted  bool   `orm:"column(deleted)" json:"deleted"`
	Rolename string `orm:"-" json:"role_name"`
	// if this field is named as "RoleID", beego orm can not map role_id
	// to it.
	Role int `orm:"-" json:"role_id"`
	//	RoleList     []Role `json:"role_list"`
	HasAdminRole bool         `orm:"column(sysadmin_flag)" json:"has_admin_role"`
	ResetUUID    string       `orm:"column(reset_uuid)" json:"reset_uuid"`
	Salt         string       `orm:"column(salt)" json:"-"`
	CreationTime time.Time    `orm:"column(creation_time);auto_now_add" json:"creation_time"`
	UpdateTime   time.Time    `orm:"column(update_time);auto_now" json:"update_time"`
	GroupList    []*UserGroup `orm:"-" json:"-"`
	OIDCUserMeta *OIDCUser    `orm:"-" json:"oidc_user_meta,omitempty"`
}

查询PostgreSQL数据库,若存在,则返回用户信息。之后构建project manager(project操作管理)以及security context(rbac鉴权):

log.Debug("using global project manager...")
pm := config.GlobalProjectMgr
log.Debug("creating admiral security context...")
securCtx := admr.NewSecurityContext(authCtx, pm)

setSecurCtxAndPM(ctx.Request, securCtx, pm)
return true

对于project manager这里简单默认为:local driver(PostgreSQL),如下:

src/core/promgr/pmsdriver/
|-- admiral
|-- driver.go
`-- local
var (
	// SecretStore manages secrets
	SecretStore *secret.Store
	// GlobalProjectMgr is initialized based on the deploy mode
	GlobalProjectMgr promgr.ProjectManager
	keyProvider      comcfg.KeyProvider
	// AdmiralClient is initialized only under integration deploy mode
	// and can be passed to project manager as a parameter
	AdmiralClient *http.Client
	// TokenReader is used in integration mode to read token
	TokenReader admiral.TokenReader
	// defined as a var for testing.
	defaultCACertPath = "/etc/core/ca/ca.crt"
	cfgMgr            *comcfg.CfgManager
)

func initProjectManager() error {
	var driver pmsdriver.PMSDriver
	if WithAdmiral() {
		log.Debugf("Initialising Admiral client with certificate: %s", defaultCACertPath)
		content, err := ioutil.ReadFile(defaultCACertPath)
		if err != nil {
			return err
		}
		pool := x509.NewCertPool()
		if ok := pool.AppendCertsFromPEM(content); !ok {
			return fmt.Errorf("failed to append cert content into cert worker")
		}
		AdmiralClient = &http.Client{
			Transport: &http.Transport{
				Proxy: http.ProxyFromEnvironment,
				TLSClientConfig: &tls.Config{
					RootCAs: pool,
				},
			},
		}

		// integration with admiral
		log.Info("initializing the project manager based on PMS...")
		path := os.Getenv("SERVICE_TOKEN_FILE_PATH")
		if len(path) == 0 {
			path = defaultTokenFilePath
		}
		log.Infof("service token file path: %s", path)
		TokenReader = &admiral.FileTokenReader{
			Path: path,
		}
		driver = admiral.NewDriver(AdmiralClient, AdmiralEndpoint(), TokenReader)
	} else {
		// standalone
		log.Info("initializing the project manager based on local database...")
		driver = local.NewDriver()
	}
	GlobalProjectMgr = promgr.NewDefaultProjectManager(driver, true)
	return nil

}

对于security context这里创建为:local SecurityContext(其它认证对应不同的authz SecurityContext),如下:

securCtx := local.NewSecurityContext(user, pm)

func setSecurCtxAndPM(req *http.Request, ctx security.Context, pm promgr.ProjectManager) {
	addToReqContext(req, SecurCtxKey, ctx)
	addToReqContext(req, PmKey, pm)
}
src/common/security/
|-- admiral
|-- context.go
|-- local
|-- robot
`-- secret

这里看local SecurityContext,如下:

// Can returns whether the user can do action on resource
func (s *SecurityContext) Can(action rbac.Action, resource rbac.Resource) bool {
	ns, err := resource.GetNamespace()
	if err == nil {
		switch ns.Kind() {
		case "project":
			projectIDOrName := ns.Identity()
			isPublicProject, _ := s.pm.IsPublic(projectIDOrName)
			projectNamespace := rbac.NewProjectNamespace(projectIDOrName, isPublicProject)
			user := project.NewUser(s, projectNamespace, s.GetProjectRoles(projectIDOrName)...)
			return rbac.HasPermission(user, resource, action)
		}
	}

	return false
}

总结:

  • 目录结构:
src/common/security/——鉴权
src/core/auth/——认证
src/core/filter/security.go——auth入口
  • 这里若要添加一种认证和鉴权,则只需要分别在auth目录和security目录分别创建一个目录,对应认证和鉴权逻辑

二、docker distribution token协议流程

docker login x.x.x.x

login日志:

May  6 15:21:24 x.x.x.x proxy[xxxx]: x.x.x.x - "GET /v2/ HTTP/1.1" 401 87 "-" "docker/18.09.5 go/go1.10.8 git-commit/e8ff056 kernel/3.10.0-957.el7.x86_64 os/linux arch/amd64 UpstreamClient(Docker-Client/18.09.5 \x5C(linux\x5C))" 0.008 0.008 .
May  6 15:21:24 x.x.x.x proxy[xxxx]: x.x.x.x - "GET /service/token?account=xxx&client_id=docker&offline_token=true&service=harbor-registry HTTP/1.1" 200 893 "-" "docker/18.09.5 go/go1.10.8 git-commit/e8ff056 kernel/3.10.0-957.el7.x86_64 os/linux arch/amd64 UpstreamClient(Docker-Client/18.09.5 \x5C(linux\x5C))" 0.025 0.025 .
May  6 15:21:24 x.x.x.x proxy[xxxx]: x.x.x.x - "GET /v2/ HTTP/1.1" 200 2 "-" "docker/18.09.5 go/go1.10.8 git-commit/e8ff056 kernel/3.10.0-957.el7.x86_64 os/linux arch/amd64 UpstreamClient(Docker-Client/18.09.5 \x5C(linux\x5C))" 0.004 0.004 .

harbor core proxy入口:

beego.Router("/v2/*", &controllers.RegistryProxy{}, "*:Handle")
...
// Handle is the only entrypoint for incoming requests, all requests must go through this func.
func (p *RegistryProxy) Handle() {
	req := p.Ctx.Request
	rw := p.Ctx.ResponseWriter
	proxy.Handle(rw, req)
}
...
// Init initialize the Proxy instance and handler chain.
func Init(urls ...string) error {
	var err error
	var registryURL string
	if len(urls) > 1 {
		return fmt.Errorf("the parm, urls should have only 0 or 1 elements")
	}
	if len(urls) == 0 {
		registryURL, err = config.RegistryURL()
		if err != nil {
			return err
		}
	} else {
		registryURL = urls[0]
	}
	targetURL, err := url.Parse(registryURL)
	if err != nil {
		return err
	}
	Proxy = httputil.NewSingleHostReverseProxy(targetURL)
	handlers = handlerChain{head: readonlyHandler{next: urlHandler{next: listReposHandler{next: contentTrustHandler{next: vulnerableHandler{next: Proxy}}}}}}
	return nil
}

// Handle handles the request.
func Handle(rw http.ResponseWriter, req *http.Request) {
	handlers.head.ServeHTTP(rw, req)
}

这里会经过5层handler,最后到达proxy,也即:docker distribution

  • 1、访问docker distribution,获取auth server地址
May  6 15:21:24 x.x.x.x proxy[xxxx]: x.x.x.x - "GET /v2/ HTTP/1.1" 401 87 "-" "docker/18.09.5 go/go1.10.8 git-commit/e8ff056 kernel/3.10.0-957.el7.x86_64 os/linux arch/amd64 UpstreamClient(Docker-Client/18.09.5 \x5C(linux\x5C))" 0.008 0.008 .

/v2/请求会直接到docker distribution,根据v2 token协议返回401,并在报头Www-Authenticate中返回token服务器地址进行后续认证,如下:

HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
Docker-Distribution-Api-Version: registry/2.0
Www-Authenticate: Bearer realm="http://x.x.x.x/service/token",service="harbor-registry"
Date: Mon, 06 May 2019 07:57:46 GMT
Content-Length: 87

{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":null}]}
  • 2、访问token server获取token
May  6 15:21:24 x.x.x.x proxy[xxxx]: x.x.x.x - "GET /service/token?account=xxx&client_id=docker&offline_token=true&service=harbor-registry HTTP/1.1" 200 893 "-" "docker/18.09.5 go/go1.10.8 git-commit/e8ff056 kernel/3.10.0-957.el7.x86_64 os/linux arch/amd64 UpstreamClient(Docker-Client/18.09.5 \x5C(linux\x5C))" 0.025 0.025 .
GET /service/token?account=xxx&client_id=docker&offline_token=true&service=harbor-registry HTTP/1.1
Host: x.x.x.x
User-Agent: docker/18.09.5 go/go1.10.8 git-commit/e8ff056 kernel/3.10.0-957.el7.x86_64 os/linux arch/amd64 UpstreamClient(Docker-Client/18.09.5 \(linux\))
Authorization: Basic xxxxxxxxxxxxxx
Accept-Encoding: gzip
Connection: close

根据路由:

beego.Router("/service/token", &token.Handler{})
...
// Handler handles request on /service/token, which is the auth provider for registry.
type Handler struct {
	beego.Controller
}

// Get handles GET request, it checks the http header for user credentials
// and parse service and scope based on docker registry v2 standard,
// checkes the permission against local DB and generates jwt token.
func (h *Handler) Get() {
	request := h.Ctx.Request
	log.Debugf("URL for token request: %s", request.URL.String())
	service := h.GetString("service")
	tokenCreator, ok := creatorMap[service]
	if !ok {
		errMsg := fmt.Sprintf("Unable to handle service: %s", service)
		log.Errorf(errMsg)
		h.CustomAbort(http.StatusBadRequest, errMsg)
	}
	token, err := tokenCreator.Create(request)
	if err != nil {
		if _, ok := err.(*unauthorizedError); ok {
			h.CustomAbort(http.StatusUnauthorized, "")
		}
		log.Errorf("Unexpected error when creating the token, error: %v", err)
		h.CustomAbort(http.StatusInternalServerError, "")
	}
	h.Data["json"] = token
	h.ServeJSON()

}
...
const (
	// Notary service
	Notary = "harbor-notary"
	// Registry service
	Registry = "harbor-registry"
)

// InitCreators initialize the token creators for different services
func InitCreators() {
	creatorMap = make(map[string]Creator)
	registryFilterMap = map[string]accessFilter{
		"repository": &repositoryFilter{
			parser: &basicParser{},
		},
		"registry": &registryFilter{},
	}
	ext, err := config.ExtURL()
	if err != nil {
		log.Warningf("Failed to get ext url, err: %v, the token service will not be functional with notary requests", err)
	} else {
		notaryFilterMap = map[string]accessFilter{
			"repository": &repositoryFilter{
				parser: &endpointParser{
					endpoint: ext,
				},
			},
		}
		creatorMap[Notary] = &generalCreator{
			service:   Notary,
			filterMap: notaryFilterMap,
		}
	}

	creatorMap[Registry] = &generalCreator{
		service:   Registry,
		filterMap: registryFilterMap,
	}
}

根据service值:harbor-registry,会跳转到generalCreatorCreate,如下:

func (g generalCreator) Create(r *http.Request) (*models.Token, error) {
	var err error
	scopes := parseScopes(r.URL)
	log.Debugf("scopes: %v", scopes)

	ctx, err := filter.GetSecurityContext(r)
	if err != nil {
		return nil, fmt.Errorf("failed to  get security context from request")
	}

	pm, err := filter.GetProjectManager(r)
	if err != nil {
		return nil, fmt.Errorf("failed to  get project manager from request")
	}

	// for docker login
	if !ctx.IsAuthenticated() {
		if len(scopes) == 0 {
			return nil, &unauthorizedError{}
		}
	}
	access := GetResourceActions(scopes)
	err = filterAccess(access, ctx, pm, g.filterMap)
	if err != nil {
		return nil, err
	}
	return MakeToken(ctx.GetUsername(), g.service, access)
}

这里会用到harbor的认证和鉴权如下:

func setSecurCtxAndPM(req *http.Request, ctx security.Context, pm promgr.ProjectManager) {
	addToReqContext(req, SecurCtxKey, ctx)
	addToReqContext(req, PmKey, pm)
}

func addToReqContext(req *http.Request, key, value interface{}) {
	*req = *(req.WithContext(context.WithValue(req.Context(), key, value)))
}

// GetSecurityContext tries to get security context from request and returns it
func GetSecurityContext(req *http.Request) (security.Context, error) {
	if req == nil {
		return nil, fmt.Errorf("request is nil")
	}

	ctx := req.Context().Value(SecurCtxKey)
	if ctx == nil {
		return nil, fmt.Errorf("the security context got from request is nil")
	}

	c, ok := ctx.(security.Context)
	if !ok {
		return nil, fmt.Errorf("the variable got from request is not security context type")
	}

	return c, nil
}

// GetProjectManager tries to get project manager from request and returns it
func GetProjectManager(req *http.Request) (promgr.ProjectManager, error) {
	if req == nil {
		return nil, fmt.Errorf("request is nil")
	}

	pm := req.Context().Value(PmKey)
	if pm == nil {
		return nil, fmt.Errorf("the project manager got from request is nil")
	}

	p, ok := pm.(promgr.ProjectManager)
	if !ok {
		return nil, fmt.Errorf("the variable got from request is not project manager type")
	}

	return p, nil
}

对于docker login,若没有认证成功,则返回认证失败;对于docker pulldocker push等操作,则会进行鉴权,如下:

// filterAccess iterate a list of resource actions and try to use the filter that matches the resource type to filter the actions.
func filterAccess(access []*token.ResourceActions, ctx security.Context,
	pm promgr.ProjectManager, filters map[string]accessFilter) error {
	var err error
	for _, a := range access {
		f, ok := filters[a.Type]
		if !ok {
			a.Actions = []string{}
			log.Warningf("No filter found for access type: %s, skip filter, the access of resource '%s' will be set empty.", a.Type, a.Name)
			continue
		}
		err = f.filter(ctx, pm, a)
		log.Debugf("user: %s, access: %v", ctx.GetUsername(), a)
		if err != nil {
			return err
		}
	}
	return nil
}

const (
	// Notary service
	Notary = "harbor-notary"
	// Registry service
	Registry = "harbor-registry"
)

// InitCreators initialize the token creators for different services
func InitCreators() {
	creatorMap = make(map[string]Creator)
	registryFilterMap = map[string]accessFilter{
		"repository": &repositoryFilter{
			parser: &basicParser{},
		},
		"registry": &registryFilter{},
	}
	ext, err := config.ExtURL()
	if err != nil {
		log.Warningf("Failed to get ext url, err: %v, the token service will not be functional with notary requests", err)
	} else {
		notaryFilterMap = map[string]accessFilter{
			"repository": &repositoryFilter{
				parser: &endpointParser{
					endpoint: ext,
				},
			},
		}
		creatorMap[Notary] = &generalCreator{
			service:   Notary,
			filterMap: notaryFilterMap,
		}
	}

	creatorMap[Registry] = &generalCreator{
		service:   Registry,
		filterMap: registryFilterMap,
	}
}

// An accessFilter will filter access based on userinfo
type accessFilter interface {
	filter(ctx security.Context, pm promgr.ProjectManager, a *token.ResourceActions) error
}

type registryFilter struct {
}

func (reg registryFilter) filter(ctx security.Context, pm promgr.ProjectManager,
	a *token.ResourceActions) error {
	// Do not filter if the request is to access registry catalog
	if a.Name != "catalog" {
		return fmt.Errorf("Unable to handle, type: %s, name: %s", a.Type, a.Name)
	}
	if !ctx.IsSysAdmin() {
		// Set the actions to empty is the user is not admin
		a.Actions = []string{}
	}
	return nil
}

// repositoryFilter filters the access based on Harbor's permission model
type repositoryFilter struct {
	parser imageParser
}

func (rep repositoryFilter) filter(ctx security.Context, pm promgr.ProjectManager,
	a *token.ResourceActions) error {
	// clear action list to assign to new acess element after perm check.
	img, err := rep.parser.parse(a.Name)
	if err != nil {
		return err
	}
	projectName := img.namespace
	permission := ""

	exist, err := pm.Exists(projectName)
	if err != nil {
		return err
	}
	if !exist {
		log.Debugf("project %s does not exist, set empty permission", projectName)
		a.Actions = []string{}
		return nil
	}

	resource := rbac.NewProjectNamespace(projectName).Resource(rbac.ResourceRepository)
	if ctx.Can(rbac.ActionPush, resource) && ctx.Can(rbac.ActionPull, resource) {
		permission = "RWM"
	} else if ctx.Can(rbac.ActionPush, resource) {
		permission = "RW"
	} else if ctx.Can(rbac.ActionPull, resource) {
		permission = "R"
	}

	a.Actions = permToActions(permission)
	return nil
}

func permToActions(p string) []string {
	res := []string{}
	if strings.Contains(p, "W") {
		res = append(res, "push")
	}
	if strings.Contains(p, "M") {
		res = append(res, "*")
	}
	if strings.Contains(p, "R") {
		res = append(res, "pull")
	}
	return res
}

按照逻辑是会返回一个token,如下:

// 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 nil, err
	}
	expiration, err := config.TokenExpiration()
	if err != nil {
		return nil, err
	}

	tk, expiresIn, issuedAt, err := makeTokenCore(issuer, username, service, expiration, access, pk)
	if err != nil {
		return nil, err
	}
	rs := fmt.Sprintf("%s.%s", tk.Raw, base64UrlEncode(tk.Signature))
	return &models.Token{
		Token:     rs,
		ExpiresIn: expiresIn,
		IssuedAt:  issuedAt.Format(time.RFC3339),
	}, nil
}

数据如下:

{"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IlBZWU86VEVXVTpWN0pIOjI2SlY6QVFUWjpMSkMzOlNYVko6WEdIQTozNEYyOjJMQVE6WlJNSzpaN1E2In0.eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJqbGhhd24iLCJhdWQiOiJyZWdpc3RyeS5kb2NrZXIuY29tIiwiZXhwIjoxNDE1Mzg3MzE1LCJuYmYiOjE0MTUzODcwMTUsImlhdCI6MTQxNTM4NzAxNSwianRpIjoidFlKQ08xYzZjbnl5N2tBbjBjN3JLUGdiVjFIMWJGd3MiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6InNhbWFsYmEvbXktYXBwIiwiYWN0aW9ucyI6WyJwdXNoIl19XX0.QhflHPfbd6eVF4lM9bwYpFZIV0PfikbyXuLx959ykRTBpe3CYnzs6YBK8FToVb5R47920PVLrh8zuLzdCr9t3w", "expires_in": 3600,"issued_at": "2019-8-2T23:00:00Z"}
  • 3、携带token访问docker distribution
May  6 15:21:24 x.x.x.x proxy[xxxx]: x.x.x.x - "GET /v2/ HTTP/1.1" 200 2 "-" "docker/18.09.5 go/go1.10.8 git-commit/e8ff056 kernel/3.10.0-957.el7.x86_64 os/linux arch/amd64 UpstreamClient(Docker-Client/18.09.5 \x5C(linux\x5C))" 0.004 0.004 .
GET /v2/ HTTP/1.1
Host: x.x.x.x
User-Agent: docker/18.09.5 go/go1.10.8 git-commit/e8ff056 kernel/3.10.0-957.el7.x86_64 os/linux arch/amd64 UpstreamClient(Docker-Client/18.09.5 \(linux\))
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IlBZWU86VEVXVTpWN0pIOjI2SlY6QVFUWjpMSkMzOlNYVko6WEdIQTozNEYyOjJMQVE6WlJNSzpaN1E2In0.eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJqbGhhd24iLCJhdWQiOiJyZWdpc3RyeS5kb2NrZXIuY29tIiwiZXhwIjoxNDE1Mzg3MzE1LCJuYmYiOjE0MTUzODcwMTUsImlhdCI6MTQxNTM4NzAxNSwianRpIjoidFlKQ08xYzZjbnl5N2tBbjBjN3JLUGdiVjFIMWJGd3MiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6InNhbWFsYmEvbXktYXBwIiwiYWN0aW9ucyI6WyJwdXNoIl19XX0.QhflHPfbd6eVF4lM9bwYpFZIV0PfikbyXuLx959ykRTBpe3CYnzs6YBK8FToVb5R47920PVLrh8zuLzdCr9t3w
Accept-Encoding: gzip
Connection: close

Refs


上一篇     下一篇