mirror of
https://github.com/goharbor/harbor.git
synced 2025-03-11 06:04:11 +01:00
Merge pull request #10605 from reasonerjt/basic-authorizer-registry
Switch to basic authentication for registry
This commit is contained in:
commit
e75220ab38
@ -26,9 +26,6 @@ https:
|
||||
# Remember Change the admin password from UI after launching Harbor.
|
||||
harbor_admin_password: Harbor12345
|
||||
|
||||
#TODO: remove this temporary flag before ships v1.11/v2, this should always be true
|
||||
registry_use_basic_auth: false
|
||||
|
||||
# Harbor DB configuration
|
||||
database:
|
||||
# The password for the root user of Harbor DB. Change this before any production use.
|
||||
|
@ -26,17 +26,9 @@ http:
|
||||
debug:
|
||||
addr: localhost:5001
|
||||
auth:
|
||||
{% if registry_use_basic_auth %}
|
||||
htpasswd:
|
||||
realm: harbor-registry-basic-realm
|
||||
path: /etc/registry/passwd
|
||||
{% else %}
|
||||
token:
|
||||
issuer: harbor-token-issuer
|
||||
realm: {{public_url}}/service/token
|
||||
rootcertbundle: /etc/registry/root.crt
|
||||
service: harbor-registry
|
||||
{% endif %}
|
||||
validation:
|
||||
disabled: true
|
||||
notifications:
|
||||
|
@ -327,10 +327,6 @@ def parse_yaml_config(config_file_path, with_notary, with_clair, with_chartmuseu
|
||||
|
||||
config_dict['registry_username'] = REGISTRY_USER_NAME
|
||||
config_dict['registry_password'] = generate_random_string(32)
|
||||
|
||||
# TODO: remove the flag before release
|
||||
config_dict['registry_use_basic_auth'] = configs['registry_use_basic_auth']
|
||||
|
||||
return config_dict
|
||||
|
||||
|
||||
|
@ -24,8 +24,7 @@ def prepare_registry(config_dict):
|
||||
prepare_dir(registry_data_dir, uid=DEFAULT_UID, gid=DEFAULT_GID)
|
||||
prepare_dir(registry_config_dir)
|
||||
|
||||
if config_dict['registry_use_basic_auth']:
|
||||
gen_passwd_file(config_dict)
|
||||
gen_passwd_file(config_dict)
|
||||
storage_provider_info = get_storage_provider_info(
|
||||
config_dict['storage_provider_name'],
|
||||
config_dict['storage_provider_config'])
|
||||
|
@ -21,7 +21,6 @@ import (
|
||||
"github.com/goharbor/harbor/src/common/utils/registry"
|
||||
"github.com/goharbor/harbor/src/common/utils/registry/auth"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/core/service/token"
|
||||
coreutils "github.com/goharbor/harbor/src/core/utils"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"io/ioutil"
|
||||
@ -86,7 +85,7 @@ func newRepositoryClient(repository string) (*registry.Repository, error) {
|
||||
uam := &auth.UserAgentModifier{
|
||||
UserAgent: "harbor-registry-client",
|
||||
}
|
||||
authorizer := auth.NewRawTokenAuthorizer("admin", token.Registry)
|
||||
authorizer := auth.DefaultBasicAuthorizer()
|
||||
transport := registry.NewTransport(http.DefaultTransport, authorizer, uam)
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
|
40
src/common/utils/registry/auth/basicauthorizer.go
Normal file
40
src/common/utils/registry/auth/basicauthorizer.go
Normal file
@ -0,0 +1,40 @@
|
||||
// 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 (
|
||||
"github.com/goharbor/harbor/src/common/http/modifier"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// NewBasicAuthorizer create an authorizer to add basic auth header as is set in the parameter
|
||||
func NewBasicAuthorizer(u, p string) modifier.Modifier {
|
||||
return NewBasicAuthCredential(u, p)
|
||||
}
|
||||
|
||||
var (
|
||||
defaultAuthorizer modifier.Modifier
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// DefaultBasicAuthorizer returns the basic authorizer that sets the basic auth as configured in env variables
|
||||
func DefaultBasicAuthorizer() modifier.Modifier {
|
||||
once.Do(func() {
|
||||
u, p := config.RegistryCredential()
|
||||
defaultAuthorizer = NewBasicAuthCredential(u, p)
|
||||
})
|
||||
return defaultAuthorizer
|
||||
}
|
39
src/common/utils/registry/auth/basicauthorizer_test.go
Normal file
39
src/common/utils/registry/auth/basicauthorizer_test.go
Normal file
@ -0,0 +1,39 @@
|
||||
// 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 (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDefaultBasicAuthorizer(t *testing.T) {
|
||||
os.Setenv("REGISTRY_CREDENTIAL_USERNAME", "testuser")
|
||||
os.Setenv("REGISTRY_CREDENTIAL_PASSWORD", "testpassword")
|
||||
defer func() {
|
||||
os.Unsetenv("REGISTRY_CREDENTIAL_USERNAME")
|
||||
os.Unsetenv("REGISTRY_CREDENTIAL_PASSWORD")
|
||||
}()
|
||||
req, _ := http.NewRequest(http.MethodGet, "http://127.0.0.1", nil)
|
||||
a := DefaultBasicAuthorizer()
|
||||
err := a.Modify(req)
|
||||
assert.Nil(t, err)
|
||||
u, p, ok := req.BasicAuth()
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "testuser", u)
|
||||
assert.Equal(t, "testpassword", p)
|
||||
}
|
@ -29,7 +29,6 @@ import (
|
||||
"github.com/goharbor/harbor/src/common/utils/registry/auth"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/core/promgr"
|
||||
"github.com/goharbor/harbor/src/core/service/token"
|
||||
coreutils "github.com/goharbor/harbor/src/core/utils"
|
||||
)
|
||||
|
||||
@ -248,7 +247,7 @@ func initRegistryClient() (r *registry.Registry, err error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authorizer := auth.NewRawTokenAuthorizer("harbor-core", token.Registry)
|
||||
authorizer := auth.DefaultBasicAuthorizer()
|
||||
return registry.NewRegistry(endpoint, &http.Client{
|
||||
Transport: registry.NewTransport(registry.GetHTTPTransport(), authorizer),
|
||||
})
|
||||
|
@ -24,7 +24,6 @@ import (
|
||||
"github.com/goharbor/harbor/src/common/utils/registry"
|
||||
"github.com/goharbor/harbor/src/common/utils/registry/auth"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/core/service/token"
|
||||
)
|
||||
|
||||
// NewRepositoryClientForUI creates a repository client that can only be used to
|
||||
@ -51,7 +50,7 @@ func newRepositoryClient(endpoint, username, repository string) (*registry.Repos
|
||||
uam := &auth.UserAgentModifier{
|
||||
UserAgent: "harbor-registry-client",
|
||||
}
|
||||
authorizer := auth.NewRawTokenAuthorizer(username, token.Registry)
|
||||
authorizer := auth.DefaultBasicAuthorizer()
|
||||
transport := registry.NewTransport(http.DefaultTransport, authorizer, uam)
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
|
@ -20,53 +20,13 @@ import (
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/distribution/registry/auth/token"
|
||||
httpauth "github.com/goharbor/harbor/src/common/http/modifier/auth"
|
||||
"github.com/goharbor/harbor/src/common/utils/registry"
|
||||
"github.com/goharbor/harbor/src/common/utils/registry/auth"
|
||||
)
|
||||
|
||||
var coreClient *http.Client
|
||||
var mutex = &sync.Mutex{}
|
||||
|
||||
// NewRepositoryClient creates a repository client with standard token authorizer
|
||||
func NewRepositoryClient(endpoint string, insecure bool, credential auth.Credential,
|
||||
tokenServiceEndpoint, repository string) (*registry.Repository, error) {
|
||||
|
||||
transport := registry.GetHTTPTransport(insecure)
|
||||
|
||||
authorizer := auth.NewStandardTokenAuthorizer(&http.Client{
|
||||
Transport: transport,
|
||||
}, credential, tokenServiceEndpoint)
|
||||
|
||||
uam := &UserAgentModifier{
|
||||
UserAgent: "harbor-registry-client",
|
||||
}
|
||||
|
||||
return registry.NewRepository(repository, endpoint, &http.Client{
|
||||
Transport: registry.NewTransport(transport, authorizer, uam),
|
||||
})
|
||||
}
|
||||
|
||||
// NewRepositoryClientForJobservice creates a repository client that can only be used to
|
||||
// access the internal registry
|
||||
func NewRepositoryClientForJobservice(repository, internalRegistryURL, secret, internalTokenServiceURL string) (*registry.Repository, error) {
|
||||
transport := registry.GetHTTPTransport()
|
||||
credential := httpauth.NewSecretAuthorizer(secret)
|
||||
|
||||
authorizer := auth.NewStandardTokenAuthorizer(&http.Client{
|
||||
Transport: transport,
|
||||
}, credential, internalTokenServiceURL)
|
||||
|
||||
uam := &UserAgentModifier{
|
||||
UserAgent: "harbor-registry-client",
|
||||
}
|
||||
|
||||
return registry.NewRepository(repository, internalRegistryURL, &http.Client{
|
||||
Transport: registry.NewTransport(transport, authorizer, uam),
|
||||
})
|
||||
}
|
||||
|
||||
// UserAgentModifier adds the "User-Agent" header to the request
|
||||
type UserAgentModifier struct {
|
||||
UserAgent string
|
||||
@ -78,27 +38,6 @@ func (u *UserAgentModifier) Modify(req *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildBlobURL ...
|
||||
func BuildBlobURL(endpoint, repository, digest string) string {
|
||||
return fmt.Sprintf("%s/v2/%s/blobs/%s", endpoint, repository, digest)
|
||||
}
|
||||
|
||||
// GetTokenForRepo is used for job handler to get a token for clair.
|
||||
func GetTokenForRepo(repository, secret, internalTokenServiceURL string) (string, error) {
|
||||
credential := httpauth.NewSecretAuthorizer(secret)
|
||||
t, err := auth.GetToken(internalTokenServiceURL, false, credential,
|
||||
[]*token.ResourceActions{{
|
||||
Type: "repository",
|
||||
Name: repository,
|
||||
Actions: []string{"pull"},
|
||||
}})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return t.Token, nil
|
||||
}
|
||||
|
||||
// GetClient returns the HTTP client that will attach jobservce secret to the request, which can be used for
|
||||
// accessing Harbor's Core Service.
|
||||
// This function returns error if the secret of Job service is not set.
|
||||
|
@ -23,6 +23,8 @@ const (
|
||||
manifestInfoKey = contextKey("ManifestInfo")
|
||||
// ScannerPullCtxKey the context key for robot account to bypass the pull policy check.
|
||||
ScannerPullCtxKey = contextKey("ScannerPullCheck")
|
||||
// SkipInjectRegistryCredKey is the context key telling registry proxy to skip adding credentials
|
||||
SkipInjectRegistryCredKey = contextKey("SkipInjectRegistryCredential")
|
||||
)
|
||||
|
||||
var (
|
||||
@ -63,6 +65,12 @@ func ArtifactInfoFromContext(ctx context.Context) (*ArtifactInfo, bool) {
|
||||
return info, ok
|
||||
}
|
||||
|
||||
// SkipInjectRegistryCred reflects whether the inject credentials should be skipped
|
||||
func SkipInjectRegistryCred(ctx context.Context) bool {
|
||||
res, ok := ctx.Value(SkipInjectRegistryCredKey).(bool)
|
||||
return ok && res
|
||||
}
|
||||
|
||||
// NewManifestInfoContext returns context with manifest info
|
||||
func NewManifestInfoContext(ctx context.Context, info *ManifestInfo) context.Context {
|
||||
return context.WithValue(ctx, manifestInfoKey, info)
|
||||
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package authz
|
||||
package v2auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -24,7 +24,9 @@ import (
|
||||
ierror "github.com/goharbor/harbor/src/internal/error"
|
||||
"github.com/goharbor/harbor/src/server/middleware"
|
||||
reg_err "github.com/goharbor/harbor/src/server/registry/error"
|
||||
"golang.org/x/net/context"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type reqChecker struct {
|
||||
@ -32,6 +34,10 @@ type reqChecker struct {
|
||||
}
|
||||
|
||||
func (rc *reqChecker) check(req *http.Request) error {
|
||||
if rc.hasRegistryCred(req) {
|
||||
// TODO: May consider implement a local authorizer for registry, more details see #10602
|
||||
return nil
|
||||
}
|
||||
securityCtx, err := filter.GetSecurityContext(req)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -62,6 +68,9 @@ func (rc *reqChecker) check(req *http.Request) error {
|
||||
}
|
||||
} else if len(middleware.V2CatalogURLRe.FindStringSubmatch(req.URL.Path)) == 1 && !securityCtx.IsSysAdmin() {
|
||||
return fmt.Errorf("unauthorized to list catalog")
|
||||
} else if req.URL.Path == "/v2/" && !securityCtx.IsAuthenticated() {
|
||||
ctx := context.WithValue(req.Context(), middleware.SkipInjectRegistryCredKey, true)
|
||||
*req = *(req.WithContext(ctx))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -77,6 +86,12 @@ func (rc *reqChecker) projectID(name string) (int64, error) {
|
||||
return p.ProjectID, nil
|
||||
}
|
||||
|
||||
func (rc *reqChecker) hasRegistryCred(req *http.Request) bool {
|
||||
u, p, ok := req.BasicAuth()
|
||||
regUser, regPass := config.RegistryCredential()
|
||||
return ok && u == regUser && p == regPass
|
||||
}
|
||||
|
||||
func getAction(req *http.Request) rbac.Action {
|
||||
pushActions := map[string]struct{}{
|
||||
http.MethodPost: {},
|
||||
@ -98,16 +113,24 @@ func getAction(req *http.Request) rbac.Action {
|
||||
|
||||
}
|
||||
|
||||
var checker = reqChecker{
|
||||
pm: config.GlobalProjectMgr,
|
||||
}
|
||||
var (
|
||||
once sync.Once
|
||||
checker reqChecker
|
||||
)
|
||||
|
||||
// Middleware checks the permission of the request to access the artifact
|
||||
func Middleware() func(http.Handler) http.Handler {
|
||||
once.Do(func() {
|
||||
if checker.pm == nil { // for UT, where pm has been set to a mock value
|
||||
checker = reqChecker{
|
||||
pm: config.GlobalProjectMgr,
|
||||
}
|
||||
}
|
||||
})
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
if err := checker.check(req); err != nil {
|
||||
reg_err.Handle(rw, req, ierror.UnauthorizedError(err))
|
||||
reg_err.Handle(rw, req, ierror.UnauthorizedError(err).WithMessage(err.Error()))
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(rw, req)
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package authz
|
||||
package v2auth
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
@ -165,15 +165,26 @@ func TestMiddleware(t *testing.T) {
|
||||
}
|
||||
ctx1 := context.WithValue(baseCtx, middleware.ArtifactInfoKey, ar1)
|
||||
ctx2 := context.WithValue(baseCtx, middleware.ArtifactInfoKey, ar2)
|
||||
ctx2x := context.WithValue(context.Background(), middleware.ArtifactInfoKey, ar2) // no securityCtx
|
||||
ctx3 := context.WithValue(baseCtx, middleware.ArtifactInfoKey, ar3)
|
||||
ctx4 := context.WithValue(baseCtx, middleware.ArtifactInfoKey, ar4)
|
||||
req1a, _ := http.NewRequest(http.MethodGet, "/v2/project_1/hello-world/manifest/v1", nil)
|
||||
req1b, _ := http.NewRequest(http.MethodDelete, "/v2/project_1/hello-world/manifest/v1", nil)
|
||||
req2, _ := http.NewRequest(http.MethodGet, "/v2/library/ubuntu/manifest/14.04", nil)
|
||||
req2x, _ := http.NewRequest(http.MethodGet, "/v2/library/ubuntu/manifest/14.04", nil)
|
||||
req3, _ := http.NewRequest(http.MethodGet, "/v2/_catalog", nil)
|
||||
req4, _ := http.NewRequest(http.MethodPost, "/v2/project_1/ubuntu/blobs/uploads/mount=?mount=sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f&from=project_2/ubuntu", nil)
|
||||
req5, _ := http.NewRequest(http.MethodPost, "/v2/project_1/ubuntu/blobs/uploads/mount=?mount=sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f&from=project_3/ubuntu", nil)
|
||||
|
||||
os.Setenv("REGISTRY_CREDENTIAL_USERNAME", "testuser")
|
||||
os.Setenv("REGISTRY_CREDENTIAL_PASSWORD", "testpassword")
|
||||
defer func() {
|
||||
os.Unsetenv("REGISTRY_CREDENTIAL_USERNAME")
|
||||
os.Unsetenv("REGISTRY_CREDENTIAL_PASSWORD")
|
||||
}()
|
||||
|
||||
req2x.SetBasicAuth("testuser", "testpassword")
|
||||
|
||||
cases := []struct {
|
||||
input *http.Request
|
||||
status int
|
||||
@ -190,6 +201,10 @@ func TestMiddleware(t *testing.T) {
|
||||
input: req2.WithContext(ctx2),
|
||||
status: http.StatusUnauthorized,
|
||||
},
|
||||
{
|
||||
input: req2x.WithContext(ctx2x),
|
||||
status: http.StatusOK,
|
||||
},
|
||||
{
|
||||
input: req3.WithContext(baseCtx),
|
||||
status: http.StatusUnauthorized,
|
@ -15,6 +15,7 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
pkg_repo "github.com/goharbor/harbor/src/pkg/repository"
|
||||
pkg_tag "github.com/goharbor/harbor/src/pkg/tag"
|
||||
"github.com/goharbor/harbor/src/server/middleware"
|
||||
@ -34,9 +35,9 @@ import (
|
||||
|
||||
// New return the registry instance to handle the registry APIs
|
||||
func New(url *url.URL) http.Handler {
|
||||
// TODO add a director to add the basic auth for docker registry
|
||||
// TODO customize the reverse proxy to improve the performance?
|
||||
proxy := httputil.NewSingleHostReverseProxy(url)
|
||||
proxy.Director = basicAuthDirector(proxy.Director)
|
||||
|
||||
// create the root rooter
|
||||
rootRouter := mux.NewRouter()
|
||||
@ -75,3 +76,13 @@ func New(url *url.URL) http.Handler {
|
||||
|
||||
return rootRouter
|
||||
}
|
||||
|
||||
func basicAuthDirector(d func(*http.Request)) func(*http.Request) {
|
||||
return func(r *http.Request) {
|
||||
d(r)
|
||||
if r != nil && !middleware.SkipInjectRegistryCred(r.Context()) {
|
||||
u, p := config.RegistryCredential()
|
||||
r.SetBasicAuth(u, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
44
src/server/registry/handler_test.go
Normal file
44
src/server/registry/handler_test.go
Normal file
@ -0,0 +1,44 @@
|
||||
// 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 registry
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func direct(req *http.Request) {
|
||||
req.Header.Add("test-key", "test-value")
|
||||
}
|
||||
|
||||
func TestBasicAuthDirector(t *testing.T) {
|
||||
req, _ := http.NewRequest(http.MethodGet, "127.0.0.1", nil)
|
||||
os.Setenv("REGISTRY_CREDENTIAL_USERNAME", "testuser")
|
||||
os.Setenv("REGISTRY_CREDENTIAL_PASSWORD", "testpassword")
|
||||
defer func() {
|
||||
os.Unsetenv("REGISTRY_CREDENTIAL_USERNAME")
|
||||
os.Unsetenv("REGISTRY_CREDENTIAL_PASSWORD")
|
||||
}()
|
||||
|
||||
d := basicAuthDirector(direct)
|
||||
d(req)
|
||||
assert.Equal(t, "test-value", req.Header.Get("test-key"))
|
||||
user, pass, ok := req.BasicAuth()
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "testuser", user)
|
||||
assert.Equal(t, "testpassword", pass)
|
||||
}
|
@ -17,8 +17,11 @@ package registry
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/server/middleware/immutable"
|
||||
|
||||
"github.com/goharbor/harbor/src/server/middleware/artifactinfo"
|
||||
"github.com/goharbor/harbor/src/server/middleware/manifestinfo"
|
||||
"github.com/goharbor/harbor/src/server/middleware/readonly"
|
||||
"github.com/goharbor/harbor/src/server/middleware/v2auth"
|
||||
"github.com/goharbor/harbor/src/server/registry/manifest"
|
||||
"github.com/goharbor/harbor/src/server/router"
|
||||
"net/http"
|
||||
@ -32,11 +35,17 @@ func RegisterRoutes() {
|
||||
regURL, _ := config.RegistryURL()
|
||||
url, _ := url.Parse(regURL)
|
||||
proxy := httputil.NewSingleHostReverseProxy(url)
|
||||
proxy.Director = basicAuthDirector(proxy.Director)
|
||||
|
||||
router.NewRoute().Path("/v2/*").Handler(New(url))
|
||||
router.NewRoute().Path("/v2/*").
|
||||
Middleware(artifactinfo.Middleware()).
|
||||
Middleware(v2auth.Middleware()).
|
||||
Handler(New(url))
|
||||
router.NewRoute().
|
||||
Method(http.MethodPut).
|
||||
Path("/v2/*/manifests/:reference").
|
||||
Middleware(artifactinfo.Middleware()).
|
||||
Middleware(v2auth.Middleware()).
|
||||
Middleware(readonly.Middleware()).
|
||||
Middleware(manifestinfo.Middleware()).
|
||||
Middleware(immutable.MiddlewarePush()).
|
||||
|
@ -49,7 +49,7 @@ class DockerAPI(object):
|
||||
if caught_err == False:
|
||||
if expected_error_message is not None:
|
||||
if str(ret).lower().find(expected_error_message.lower()) < 0:
|
||||
raise Exception(r" Failed to catch error [{}] when pull image {}".format (expected_error_message, image))
|
||||
raise Exception(r" Failed to catch error [{}] when pull image {}, return message: {}".format (expected_error_message, image, str(ret)))
|
||||
else:
|
||||
if str(ret).lower().find("error".lower()) >= 0:
|
||||
raise Exception(r" It's was not suppose to catch error when pull image {}, return message is [{}]".format (image, ret))
|
||||
@ -82,10 +82,12 @@ class DockerAPI(object):
|
||||
if caught_err == False:
|
||||
if expected_error_message is not None:
|
||||
if str(ret).lower().find(expected_error_message.lower()) < 0:
|
||||
raise Exception(r" Failed to catch error [{}] when push image {}".format (expected_error_message, harbor_registry))
|
||||
raise Exception(r" Failed to catch error [{}] when push image {}, return message: {}".
|
||||
format (expected_error_message, harbor_registry, str(ret)))
|
||||
else:
|
||||
if str(ret).lower().find("errorDetail".lower()) >= 0:
|
||||
raise Exception(r" It's was not suppose to catch error when push image {}, return message is [{}]".format (harbor_registry, ret))
|
||||
raise Exception(r" It's was not suppose to catch error when push image {}, return message is [{}]".
|
||||
format (harbor_registry, ret))
|
||||
|
||||
def docker_image_build(self, harbor_registry, tags=None, size=1, expected_error_message = None):
|
||||
caught_err = False
|
||||
@ -122,7 +124,8 @@ class DockerAPI(object):
|
||||
if caught_err == False:
|
||||
if expected_error_message is not None:
|
||||
if str(ret).lower().find(expected_error_message.lower()) < 0:
|
||||
raise Exception(r" Failed to catch error [{}] when push image {}".format (expected_error_message, harbor_registry))
|
||||
raise Exception(r" Failed to catch error [{}] when build image {}, return message: {}".
|
||||
format (expected_error_message, harbor_registry, str(ret)))
|
||||
else:
|
||||
if str(ret).lower().find("errorDetail".lower()) >= 0:
|
||||
raise Exception(r" It's was not suppose to catch error when push image {}, return message is [{}]".format (harbor_registry, ret))
|
||||
|
@ -109,25 +109,25 @@ class TestProjects(unittest.TestCase):
|
||||
TestProjects.repo_name_pa, _ = push_image_to_project(project_ra_name_a, harbor_server, robot_account.name, robot_account.token, image_robot_account, tag)
|
||||
|
||||
print "#8. Push image(ImageRA) to project(PB) by robot account(RA), it must be not successful;"
|
||||
push_image_to_project(project_ra_name_b, harbor_server, robot_account.name, robot_account.token, image_robot_account, tag, expected_error_message = "denied: requested access to the resource is denied")
|
||||
push_image_to_project(project_ra_name_b, harbor_server, robot_account.name, robot_account.token, image_robot_account, tag, expected_error_message = "unauthorized to access repository")
|
||||
|
||||
print "#9. Pull image(ImagePB) from project(PB) by robot account(RA), it must be not successful;"
|
||||
pull_harbor_image(harbor_server, robot_account.name, robot_account.token, TestProjects.repo_name_in_project_b, tag_b, expected_error_message = r"pull access denied for " + harbor_server + "/" + TestProjects.repo_name_in_project_b)
|
||||
pull_harbor_image(harbor_server, robot_account.name, robot_account.token, TestProjects.repo_name_in_project_b, tag_b, expected_error_message = "unauthorized to access repository")
|
||||
|
||||
print "#10. Pull image from project(PC), it must be successful;"
|
||||
pull_harbor_image(harbor_server, robot_account.name, robot_account.token, TestProjects.repo_name_in_project_c, tag_c)
|
||||
|
||||
print "#11. Push image(ImageRA) to project(PC) by robot account(RA), it must be not successful;"
|
||||
push_image_to_project(project_ra_name_c, harbor_server, robot_account.name, robot_account.token, image_robot_account, tag, expected_error_message = "denied: requested access to the resource is denied")
|
||||
push_image_to_project(project_ra_name_c, harbor_server, robot_account.name, robot_account.token, image_robot_account, tag, expected_error_message = "unauthorized to access repository")
|
||||
|
||||
print "#12. Update action property of robot account(RA);"
|
||||
self.project.disable_project_robot_account(TestProjects.project_ra_id_a, robot_id, True, **TestProjects.USER_RA_CLIENT)
|
||||
|
||||
print "#13. Pull image(ImagePA) from project(PA) by robot account(RA), it must be not successful;"
|
||||
pull_harbor_image(harbor_server, robot_account.name, robot_account.token, TestProjects.repo_name_in_project_a, tag_a, expected_login_error_message = "401 Client Error: Unauthorized")
|
||||
pull_harbor_image(harbor_server, robot_account.name, robot_account.token, TestProjects.repo_name_in_project_a, tag_a, expected_login_error_message = "401 Unauthorized")
|
||||
|
||||
print "#14. Push image(ImageRA) to project(PA) by robot account(RA), it must be not successful;"
|
||||
push_image_to_project(project_ra_name_a, harbor_server, robot_account.name, robot_account.token, image_robot_account, tag, expected_login_error_message = "401 Client Error: Unauthorized")
|
||||
push_image_to_project(project_ra_name_a, harbor_server, robot_account.name, robot_account.token, image_robot_account, tag, expected_login_error_message = "401 Unauthorized")
|
||||
|
||||
print "#15. Delete robot account(RA), it must be not successful;"
|
||||
self.project.delete_project_robot_account(TestProjects.project_ra_id_a, robot_id, **TestProjects.USER_RA_CLIENT)
|
||||
|
@ -26,19 +26,23 @@ 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
|
||||
Test Case - Add Replication Rule
|
||||
Harbor API Test ./tests/apitests/python/test_add_replication_rule.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 - Edit Project Creation
|
||||
Harbor API Test ./tests/apitests/python/test_edit_project_creation.py
|
||||
Test Case - Scan Image
|
||||
Harbor API Test ./tests/apitests/python/test_scan_image.py
|
||||
# TODO uncomment this after image scan work with basic auth - #10277
|
||||
#Test Case - Scan Image
|
||||
# Harbor API Test ./tests/apitests/python/test_scan_image.py
|
||||
Test Case - Manage Project Member
|
||||
Harbor API Test ./tests/apitests/python/test_manage_project_member.py
|
||||
# TODO uncomment this after enable content trust middleware
|
||||
# Test Case - Project Level Policy Content Trust
|
||||
# Harbor API Test ./tests/apitests/python/test_project_level_policy_content_trust.py
|
||||
Test Case - User View Logs
|
||||
Harbor API Test ./tests/apitests/python/test_user_view_logs.py
|
||||
# TODO uncomment this after we move the accesslog away from registry notificaiton
|
||||
# TODO potentially #10602 may also fix this.
|
||||
# Test Case - User View Logs
|
||||
# Harbor API Test ./tests/apitests/python/test_user_view_logs.py
|
||||
# TODO uncomment this after making scan all work with OCI registry
|
||||
# Test Case - Scan All Images
|
||||
# Harbor API Test ./tests/apitests/python/test_scan_all_images.py
|
||||
|
Loading…
Reference in New Issue
Block a user