Merge pull request #10612 from ywk253100/200202_replication_basic_auth

Do enhancement for the registry authorizer
This commit is contained in:
Wenkai Yin(尹文开) 2020-02-11 22:09:40 +08:00 committed by GitHub
commit d66c1a4a21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 156 additions and 5 deletions

View File

@ -0,0 +1,123 @@
// Copyright Project Harbor Authors
//
// 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 (
"fmt"
"github.com/docker/distribution/registry/client/auth/challenge"
"github.com/goharbor/harbor/src/common/http/modifier"
"net/http"
"net/url"
"strings"
"sync"
)
// NewAuthorizer creates an authorizer that can handle different auth schemes
func NewAuthorizer(credential Credential, client ...*http.Client) modifier.Modifier {
authorizer := &authorizer{
credential: credential,
}
if len(client) > 0 {
authorizer.client = client[0]
}
if authorizer.client == nil {
authorizer.client = http.DefaultClient
}
return authorizer
}
// authorizer authorizes the request with the provided credential.
// It determines the auth scheme of registry automatically and calls
// different underlying authorizers to do the auth work
type authorizer struct {
sync.Mutex
client *http.Client
url *url.URL // registry URL
authorizer modifier.Modifier // the underlying authorizer
credential Credential
}
func (a *authorizer) Modify(req *http.Request) error {
// Nil URL means this is the first time the authorizer is called
// Try to connect to the registry and determine the auth method
if a.url == nil {
// to avoid concurrent issue
a.Lock()
defer a.Unlock()
if err := a.initialize(req.URL); err != nil {
return err
}
}
// check whether the request targets the registry
if !a.isTarget(req) {
return nil
}
return a.authorizer.Modify(req)
}
func (a *authorizer) initialize(u *url.URL) error {
if a.url != nil {
return nil
}
url, err := url.Parse(u.Scheme + "://" + u.Host + "/v2/")
if err != nil {
return err
}
a.url = url
resp, err := a.client.Get(a.url.String())
if err != nil {
return err
}
challenges := ParseChallengeFromResponse(resp)
// no challenge, mean no auth
if len(challenges) == 0 {
a.authorizer = &nullAuthorizer{}
return nil
}
cm := map[string]challenge.Challenge{}
for _, challenge := range challenges {
cm[challenge.Scheme] = challenge
}
if _, exist := cm["basic"]; exist {
a.authorizer = a.credential
return nil
}
if _, exist := cm["bearer"]; exist {
// TODO clean up the code of "StandardTokenAuthorizer"
// TODO Currently, the checking of auth scheme is done twice, this can be avoided
a.authorizer = NewStandardTokenAuthorizer(a.client, a.credential)
return nil
}
return fmt.Errorf("unspported auth scheme: %v", challenges)
}
// Check whether the request targets to the registry.
// If doesn't, the request shouldn't be handled by the authorizer.
// e.g. the requests sent to backend storage(s3, etc.)
func (a *authorizer) isTarget(req *http.Request) bool {
index := strings.Index(req.URL.Path, "/v2/")
if index == -1 {
return false
}
if req.URL.Host != a.url.Host || req.URL.Scheme != a.url.Scheme ||
req.URL.Path[:index+4] != a.url.Path {
return false
}
return true
}

View File

@ -0,0 +1,24 @@
// Copyright Project Harbor Authors
//
// 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 "net/http"
type nullAuthorizer struct{}
func (n *nullAuthorizer) Modify(req *http.Request) error {
// do nothing
return nil
}

View File

@ -79,9 +79,14 @@ func NewAdapter(registry *model.Registry) (*Adapter, error) {
registry.Credential.AccessSecret)
}
}
authorizer := auth.NewStandardTokenAuthorizer(&http.Client{
authorizer := auth.NewAuthorizer(cred, &http.Client{
Transport: util.GetHTTPTransport(registry.Insecure),
}, cred, registry.TokenServiceURL)
})
/*
authorizer := auth.NewStandardTokenAuthorizer(&http.Client{
Transport: util.GetHTTPTransport(registry.Insecure),
}, cred, registry.TokenServiceURL)
*/
return NewAdapterWithCustomizedAuthorizer(registry, authorizer)
}

View File

@ -26,9 +26,8 @@ Test Case - Delete a Repository of a Certain Project Created by Normal User
Harbor API Test ./tests/apitests/python/test_del_repo.py
Test Case - Add a System Global Label to a Certain Tag
Harbor API Test ./tests/apitests/python/test_add_sys_label_to_tag.py
# TODO uncomment this after replication works with basic auth - #10509
# Test Case - Add Replication Rule
# Harbor API Test ./tests/apitests/python/test_add_replication_rule.py
Test Case - Add Replication Rule
Harbor API Test ./tests/apitests/python/test_add_replication_rule.py
Test Case - Edit Project Creation
Harbor API Test ./tests/apitests/python/test_edit_project_creation.py
# TODO uncomment this after image scan work with basic auth - #10277