Switch to basic authentication for registry

1. Add basic authorizer for registry which modify the request
to add basic authorization header to request based on configuration.
2. Set basic auth header for proxy when accessing registry
3. Switche the registry to use basic auth by default and use the basic
authorizer to access Harbor.
4. Make necessary change to test cases, particularly
"test_robot_account.py" and "docker_api.py", because the error is
changed after siwtched to basic auth from token auth.  #10604 is opened
to track the follow up work.

Signed-off-by: Daniel Jiang <jiangd@vmware.com>
This commit is contained in:
Daniel Jiang 2020-01-28 03:31:18 +08:00
parent a1b25e1fec
commit 2064a1cd6d
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