harbor/utils/registry/auth/tokenauthorizer.go

275 lines
6.7 KiB
Go
Raw Normal View History

2016-04-27 11:59:43 +02:00
/*
Copyright (c) 2016 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 (
"crypto/tls"
2016-04-27 11:59:43 +02:00
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
2016-04-27 11:59:43 +02:00
"strings"
"sync"
2016-04-27 11:59:43 +02:00
"time"
token_util "github.com/vmware/harbor/service/token"
"github.com/vmware/harbor/utils/log"
2016-05-24 08:59:36 +02:00
registry_error "github.com/vmware/harbor/utils/registry/error"
2016-04-27 11:59:43 +02:00
)
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, ","))
}
2016-04-28 12:49:59 +02:00
type tokenGenerator func(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error)
2016-04-27 11:59:43 +02:00
// Implements interface Authorizer
type tokenAuthorizer struct {
2016-04-28 12:49:59 +02:00
scope *scope
tg tokenGenerator
cache string // cached token
expiresIn int // The duration in seconds since the token was issued that it will remain valid
issuedAt *time.Time // The RFC3339-serialized UTC standard time at which a given token was issued
sync.Mutex
2016-04-27 11:59:43 +02:00
}
2016-04-28 12:49:59 +02:00
// Scheme returns the scheme that the handler can handle
func (t *tokenAuthorizer) Scheme() string {
2016-04-27 11:59:43 +02:00
return "bearer"
}
// 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 {
2016-04-27 11:59:43 +02:00
var scopes []*scope
2016-04-29 10:59:54 +02:00
var token string
hasFrom := false
from := req.URL.Query().Get("from")
if len(from) != 0 {
s := &scope{
Type: "repository",
Name: from,
Actions: []string{"pull"},
}
scopes = append(scopes, s)
// do not cache the token if "from" appears
hasFrom = true
}
2016-04-27 11:59:43 +02:00
2016-05-24 08:59:36 +02:00
if t.scope != nil {
scopes = append(scopes, t.scope)
}
2016-04-27 11:59:43 +02:00
2016-04-28 12:49:59 +02:00
expired := true
2016-04-27 11:59:43 +02:00
cachedToken, cachedExpiredIn, cachedIssuedAt := t.getCachedToken()
if len(cachedToken) != 0 && cachedExpiredIn != 0 && cachedIssuedAt != nil {
expired = cachedIssuedAt.Add(time.Duration(cachedExpiredIn) * time.Second).Before(time.Now().UTC())
2016-04-27 11:59:43 +02:00
}
2016-04-29 10:59:54 +02:00
if expired || hasFrom {
2016-04-27 11:59:43 +02:00
scopeStrs := []string{}
for _, scope := range scopes {
scopeStrs = append(scopeStrs, scope.string())
}
2016-04-29 11:46:00 +02:00
to, expiresIn, issuedAt, err := t.tg(params["realm"], params["service"], scopeStrs)
2016-04-27 11:59:43 +02:00
if err != nil {
return err
}
2016-04-29 11:46:00 +02:00
token = to
2016-04-28 12:49:59 +02:00
2016-04-29 10:59:54 +02:00
if !hasFrom {
t.updateCachedToken(to, expiresIn, issuedAt)
2016-04-29 10:59:54 +02:00
}
} else {
token = cachedToken
2016-04-27 11:59:43 +02:00
}
2016-04-29 10:59:54 +02:00
req.Header.Add(http.CanonicalHeaderKey("Authorization"), fmt.Sprintf("Bearer %s", token))
2016-04-27 11:59:43 +02:00
return nil
}
func (t *tokenAuthorizer) getCachedToken() (string, int, *time.Time) {
t.Lock()
defer t.Unlock()
return t.cache, t.expiresIn, t.issuedAt
}
func (t *tokenAuthorizer) updateCachedToken(token string, expiresIn int, issuedAt *time.Time) {
t.Lock()
defer t.Unlock()
t.cache = token
t.expiresIn = expiresIn
t.issuedAt = issuedAt
}
// Implements interface Authorizer
type standardTokenAuthorizer struct {
tokenAuthorizer
2016-04-27 11:59:43 +02:00
client *http.Client
credential Credential
}
// NewStandardTokenAuthorizer returns a standard token authorizer. The authorizer will request a token
2016-04-27 11:59:43 +02:00
// from token server and add it to the origin request
func NewStandardTokenAuthorizer(credential Credential, insecure bool, scopeType, scopeName string, scopeActions ...string) Authorizer {
t := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: insecure,
},
}
authorizer := &standardTokenAuthorizer{
2016-04-27 11:59:43 +02:00
client: &http.Client{
Transport: t,
2016-04-27 11:59:43 +02:00
},
credential: credential,
}
2016-05-24 08:59:36 +02:00
if len(scopeType) != 0 || len(scopeName) != 0 {
authorizer.scope = &scope{
2016-05-24 08:59:36 +02:00
Type: scopeType,
Name: scopeName,
Actions: scopeActions,
}
2016-04-27 11:59:43 +02:00
}
2016-05-24 08:59:36 +02:00
authorizer.tg = authorizer.generateToken
2016-04-27 11:59:43 +02:00
return authorizer
2016-04-27 11:59:43 +02:00
}
func (s *standardTokenAuthorizer) generateToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) {
realm = tokenURL(realm)
2016-04-27 11:59:43 +02:00
u, err := url.Parse(realm)
if err != nil {
2016-04-29 08:59:00 +02:00
return
2016-04-27 11:59:43 +02:00
}
q := u.Query()
q.Add("service", service)
for _, scope := range scopes {
q.Add("scope", scope)
}
u.RawQuery = q.Encode()
r, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
2016-04-29 08:59:00 +02:00
return
2016-04-27 11:59:43 +02:00
}
2016-08-10 04:48:58 +02:00
if s.credential != nil {
s.credential.AddAuthorization(r)
}
2016-04-27 11:59:43 +02:00
resp, err := s.client.Do(r)
if err != nil {
2016-04-29 08:59:00 +02:00
return
2016-04-27 11:59:43 +02:00
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
2016-04-29 08:59:00 +02:00
return
2016-04-27 11:59:43 +02:00
}
if resp.StatusCode != http.StatusOK {
2016-05-24 08:59:36 +02:00
err = &registry_error.Error{
2016-04-27 11:59:43 +02:00
StatusCode: resp.StatusCode,
2016-05-24 08:59:36 +02:00
Detail: string(b),
2016-04-27 11:59:43 +02:00
}
2016-04-29 08:59:00 +02:00
return
2016-04-27 11:59:43 +02:00
}
tk := struct {
2016-04-29 08:59:00 +02:00
Token string `json:"token"`
2016-05-10 04:59:59 +02:00
ExpiresIn int `json:"expires_in"`
2016-04-29 08:59:00 +02:00
IssuedAt string `json:"issued_at"`
2016-04-27 11:59:43 +02:00
}{}
if err = json.Unmarshal(b, &tk); err != nil {
2016-04-29 08:59:00 +02:00
return
}
token = tk.Token
2016-05-10 04:59:59 +02:00
expiresIn = tk.ExpiresIn
2016-05-12 07:41:48 +02:00
if len(tk.IssuedAt) != 0 {
t, err := time.Parse(time.RFC3339, tk.IssuedAt)
if err != nil {
log.Errorf("error occurred while parsing issued_at: %v", err)
err = nil
} else {
issuedAt = &t
}
2016-04-27 11:59:43 +02:00
}
2016-04-29 08:59:00 +02:00
return
2016-04-27 11:59:43 +02:00
}
// when the registry client is used inside Harbor, the token request
// can be posted to token service directly rather than going through nginx.
// this solution can resolve two problems:
// 1. performance issue
// 2. the realm field returned by registry is an IP which can not reachable
// inside Harbor
func tokenURL(realm string) string {
extEndpoint := os.Getenv("EXT_ENDPOINT")
tokenURL := os.Getenv("TOKEN_URL")
if len(extEndpoint) != 0 && len(tokenURL) != 0 &&
strings.Contains(realm, extEndpoint) {
realm = strings.TrimRight(tokenURL, "/") + "/service/token"
}
return realm
}
2016-04-29 08:59:00 +02:00
// Implements interface Handler
type usernameTokenAuthorizer struct {
tokenAuthorizer
2016-04-27 11:59:43 +02:00
username string
}
// NewUsernameTokenAuthorizer returns a authorizer which will generate a token according to
2016-04-27 11:59:43 +02:00
// the user's privileges
func NewUsernameTokenAuthorizer(username string, scopeType, scopeName string, scopeActions ...string) Authorizer {
authorizer := &usernameTokenAuthorizer{
2016-04-27 11:59:43 +02:00
username: username,
}
authorizer.scope = &scope{
2016-04-27 11:59:43 +02:00
Type: scopeType,
Name: scopeName,
Actions: scopeActions,
}
authorizer.tg = authorizer.generateToken
2016-04-27 11:59:43 +02:00
return authorizer
2016-04-27 11:59:43 +02:00
}
func (u *usernameTokenAuthorizer) generateToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) {
2016-04-29 08:59:00 +02:00
token, expiresIn, issuedAt, err = token_util.GenTokenForUI(u.username, service, scopes)
2016-04-28 12:49:59 +02:00
return
2016-04-27 11:59:43 +02:00
}