Merge pull request #2658 from ywk253100/170627_registry

Provide a method to get token from token service
This commit is contained in:
Daniel Jiang 2017-06-29 17:56:38 +08:00 committed by GitHub
commit ea827ffd6e
3 changed files with 120 additions and 102 deletions

View File

@ -15,20 +15,13 @@
package auth package auth
import ( import (
"encoding/json"
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"net/url"
"strings" "strings"
"sync" "sync"
"time" "time"
//"github.com/vmware/harbor/src/common/config"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/common/utils/registry" "github.com/vmware/harbor/src/common/utils/registry"
registry_error "github.com/vmware/harbor/src/common/utils/error"
token_util "github.com/vmware/harbor/src/ui/service/token" token_util "github.com/vmware/harbor/src/ui/service/token"
) )
@ -36,13 +29,14 @@ const (
latency int = 10 //second, the network latency when token is received latency int = 10 //second, the network latency when token is received
) )
type scope struct { // Scope ...
type Scope struct {
Type string Type string
Name string Name string
Actions []string Actions []string
} }
func (s *scope) string() string { func (s *Scope) string() string {
return fmt.Sprintf("%s:%s:%s", s.Type, s.Name, strings.Join(s.Actions, ",")) return fmt.Sprintf("%s:%s:%s", s.Type, s.Name, strings.Join(s.Actions, ","))
} }
@ -50,7 +44,7 @@ type tokenGenerator func(realm, service string, scopes []string) (token string,
// Implements interface Authorizer // Implements interface Authorizer
type tokenAuthorizer struct { type tokenAuthorizer struct {
scope *scope scope *Scope
tg tokenGenerator tg tokenGenerator
cache string // cached token cache string // cached token
expiresAt *time.Time // The UTC standard time at when the token will expire expiresAt *time.Time // The UTC standard time at when the token will expire
@ -64,13 +58,13 @@ func (t *tokenAuthorizer) Scheme() string {
// AuthorizeRequest will add authorization header which contains a token before the request is sent // 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 { func (t *tokenAuthorizer) Authorize(req *http.Request, params map[string]string) error {
var scopes []*scope var scopes []*Scope
var token string var token string
hasFrom := false hasFrom := false
from := req.URL.Query().Get("from") from := req.URL.Query().Get("from")
if len(from) != 0 { if len(from) != 0 {
s := &scope{ s := &Scope{
Type: "repository", Type: "repository",
Name: from, Name: from,
Actions: []string{"pull"}, Actions: []string{"pull"},
@ -154,7 +148,7 @@ func NewStandardTokenAuthorizer(credential Credential, insecure bool,
} }
if len(scopeType) != 0 || len(scopeName) != 0 { if len(scopeType) != 0 || len(scopeName) != 0 {
authorizer.scope = &scope{ authorizer.scope = &Scope{
Type: scopeType, Type: scopeType,
Name: scopeName, Name: scopeName,
Actions: scopeActions, Actions: scopeActions,
@ -166,66 +160,21 @@ func NewStandardTokenAuthorizer(credential Credential, insecure bool,
return authorizer return authorizer
} }
func (s *standardTokenAuthorizer) generateToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) { func (s *standardTokenAuthorizer) generateToken(realm, service string, scopes []string) (string, int, *time.Time, error) {
realm = s.tokenURL(realm) realm = s.tokenURL(realm)
tk, err := getToken(s.client, s.credential, realm,
service, scopes)
u, err := url.Parse(realm) if len(tk.IssuedAt) == 0 {
return tk.Token, tk.ExpiresIn, nil, nil
}
issuedAt, err := time.Parse(time.RFC3339, tk.IssuedAt)
if err != nil { if err != nil {
return return "", 0, nil, err
}
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 {
return
} }
if s.credential != nil { return tk.Token, tk.ExpiresIn, &issuedAt, nil
s.credential.AddAuthorization(r)
}
resp, err := s.client.Do(r)
if err != nil {
return
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
if resp.StatusCode != http.StatusOK {
err = &registry_error.Error{
StatusCode: resp.StatusCode,
Detail: string(b),
}
return
}
tk := models.Token{}
if err = json.Unmarshal(b, &tk); err != nil {
return
}
token = tk.Token
expiresIn = tk.ExpiresIn
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
}
}
return
} }
// when the registry client is used inside Harbor, the token request // when the registry client is used inside Harbor, the token request
@ -267,7 +216,7 @@ func newUsernameTokenAuthorizer(notary bool, username, scopeType, scopeName stri
username: username, username: username,
} }
authorizer.scope = &scope{ authorizer.scope = &Scope{
Type: scopeType, Type: scopeType,
Name: scopeName, Name: scopeName,
Actions: scopeActions, Actions: scopeActions,

View File

@ -0,0 +1,93 @@
// Copyright (c) 2017 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 (
"encoding/json"
"io/ioutil"
"net/http"
"net/url"
"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"
)
const (
service = "harbor-registry"
)
// GetToken requests a token against the endpoint using credetial provided
func GetToken(endpoint string, insecure bool, credential Credential,
scopes []*Scope) (*models.Token, error) {
client := &http.Client{
Transport: registry.GetHTTPTransport(insecure),
}
scopesStr := []string{}
for _, scope := range scopes {
scopesStr = append(scopesStr, scope.string())
}
return getToken(client, credential, endpoint, service, scopesStr)
}
func getToken(client *http.Client, credential Credential, realm, service string,
scopes []string) (*models.Token, error) {
u, err := url.Parse(realm)
if err != nil {
return nil, err
}
query := u.Query()
query.Add("service", service)
for _, scope := range scopes {
query.Add("scope", scope)
}
u.RawQuery = query.Encode()
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil {
return nil, err
}
if credential != nil {
credential.AddAuthorization(req)
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, &registry_error.Error{
StatusCode: resp.StatusCode,
Detail: string(data),
}
}
token := &models.Token{}
if err = json.Unmarshal(data, token); err != nil {
return nil, err
}
return token, nil
}

View File

@ -15,11 +15,8 @@
package utils package utils
import ( import (
"encoding/json"
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"net/url"
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/registry" "github.com/vmware/harbor/src/common/utils/registry"
@ -64,39 +61,18 @@ func BuildBlobURL(endpoint, repository, digest string) string {
return fmt.Sprintf("%s/v2/%s/blobs/%s", endpoint, repository, digest) return fmt.Sprintf("%s/v2/%s/blobs/%s", endpoint, repository, digest)
} }
//GetTokenForRepo is a temp solution for job handler to get a token for clair. //GetTokenForRepo is used for job handler to get a token for clair.
//TODO: Get rid of it when it can get a token from repository client.
func GetTokenForRepo(repository string) (string, error) { func GetTokenForRepo(repository string) (string, error) {
u, err := url.Parse(config.InternalTokenServiceEndpoint())
if err != nil {
return "", err
}
q := u.Query()
q.Add("service", "harbor-registry")
q.Add("scope", fmt.Sprintf("repository:%s:pull", repository))
u.RawQuery = q.Encode()
r, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
return "", err
}
c := &http.Cookie{Name: models.UISecretCookie, Value: config.JobserviceSecret()} c := &http.Cookie{Name: models.UISecretCookie, Value: config.JobserviceSecret()}
r.AddCookie(c) credentail := auth.NewCookieCredential(c)
client := &http.Client{} token, err := auth.GetToken(config.InternalTokenServiceEndpoint(), true, credentail, []*auth.Scope{&auth.Scope{
resp, err := client.Do(r) Type: "repository",
Name: repository,
Actions: []string{"pull"},
}})
if err != nil { if err != nil {
return "", err return "", err
} }
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body) return token.Token, nil
if err != nil {
return "", err
}
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("Unexpected response from token service, code: %d, %s", resp.StatusCode, string(b))
}
tk := models.Token{}
if err := json.Unmarshal(b, &tk); err != nil {
return "", err
}
return tk.Token, nil
} }