Merge pull request #10605 from reasonerjt/basic-authorizer-registry

Switch to basic authentication for registry
This commit is contained in:
Daniel Jiang 2020-02-02 00:34:47 +09:00 committed by GitHub
commit e75220ab38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 223 additions and 107 deletions

View File

@ -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.

View File

@ -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:

View File

@ -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

View File

@ -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'])

View File

@ -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,

View 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
}

View 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)
}

View File

@ -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),
})

View File

@ -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,

View File

@ -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.

View File

@ -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)

View File

@ -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)

View File

@ -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,

View File

@ -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)
}
}
}

View 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)
}

View File

@ -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()).

View File

@ -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))

View File

@ -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)

View File

@ -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