add docker image search api
Signed-off-by: soulseen <sunzhu@yunify.com>
This commit is contained in:
162
pkg/models/registries/token.go
Normal file
162
pkg/models/registries/token.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package registries
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
log "github.com/golang/glog"
|
||||
)
|
||||
|
||||
func (t authToken) String() (string, error) {
|
||||
if t.Token != "" {
|
||||
return t.Token, nil
|
||||
}
|
||||
if t.AccessToken != "" {
|
||||
return t.AccessToken, nil
|
||||
}
|
||||
return "", errors.New("auth token cannot be empty")
|
||||
}
|
||||
|
||||
func (a *authService) Request(username, password string) (*http.Request, error) {
|
||||
q := a.Realm.Query()
|
||||
q.Set("service", a.Service)
|
||||
for _, s := range a.Scope {
|
||||
q.Set("scope", s)
|
||||
}
|
||||
// q.Set("scope", "repository:r.j3ss.co/htop:push,pull")
|
||||
a.Realm.RawQuery = q.Encode()
|
||||
|
||||
req, err := http.NewRequest("GET", a.Realm.String(), nil)
|
||||
|
||||
if username != "" || password != "" {
|
||||
req.SetBasicAuth(username, password)
|
||||
}
|
||||
|
||||
return req, err
|
||||
}
|
||||
|
||||
func isTokenDemand(resp *http.Response) (*authService, error) {
|
||||
if resp == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if resp.StatusCode != http.StatusUnauthorized {
|
||||
return nil, nil
|
||||
}
|
||||
return parseAuthHeader(resp.Header)
|
||||
}
|
||||
|
||||
// Token returns the required token for the specific resource url. If the registry requires basic authentication, this
|
||||
// function returns ErrBasicAuth.
|
||||
func (r *Registry) Token(url string) (str string, err error) {
|
||||
log.Info("Get token from ", url)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resp, err := r.Client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusForbidden && gcrMatcher.MatchString(url) {
|
||||
// GCR is not sending HTTP 401 on missing credentials but a HTTP 403 without
|
||||
// any further information about why the request failed. Sending the credentials
|
||||
// from the Docker config fixes this.
|
||||
return "", ErrBasicAuth
|
||||
}
|
||||
|
||||
a, err := isTokenDemand(resp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if a == nil {
|
||||
log.Info("registry.token authService=nil")
|
||||
return "", nil
|
||||
}
|
||||
|
||||
authReq, err := a.Request(r.Username, r.Password)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resp, err = r.Client.Do(authReq)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("getting image failed with secret")
|
||||
}
|
||||
|
||||
var authToken authToken
|
||||
if err := json.NewDecoder(resp.Body).Decode(&authToken); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
token, err := authToken.String()
|
||||
return token, err
|
||||
}
|
||||
|
||||
func parseAuthHeader(header http.Header) (*authService, error) {
|
||||
ch, err := parseChallenge(header.Get("www-authenticate"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
func parseChallenge(challengeHeader string) (*authService, error) {
|
||||
if basicRegex.MatchString(challengeHeader) {
|
||||
return nil, ErrBasicAuth
|
||||
}
|
||||
|
||||
match := bearerRegex.FindAllStringSubmatch(challengeHeader, -1)
|
||||
if d := len(match); d != 1 {
|
||||
return nil, fmt.Errorf("malformed auth challenge header: '%s', %d", challengeHeader, d)
|
||||
}
|
||||
parts := strings.SplitN(strings.TrimSpace(match[0][1]), ",", 3)
|
||||
|
||||
var realm, service string
|
||||
var scope []string
|
||||
for _, s := range parts {
|
||||
p := strings.SplitN(s, "=", 2)
|
||||
if len(p) != 2 {
|
||||
return nil, fmt.Errorf("malformed auth challenge header: '%s'", challengeHeader)
|
||||
}
|
||||
key := p[0]
|
||||
value := strings.TrimSuffix(strings.TrimPrefix(p[1], `"`), `"`)
|
||||
switch key {
|
||||
case "realm":
|
||||
realm = value
|
||||
case "service":
|
||||
service = value
|
||||
case "scope":
|
||||
scope = strings.Fields(value)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown field in challege header %s: %v", key, challengeHeader)
|
||||
}
|
||||
}
|
||||
parsedRealm, err := url.Parse(realm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a := &authService{
|
||||
Realm: parsedRealm,
|
||||
Service: service,
|
||||
Scope: scope,
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
Reference in New Issue
Block a user