From ecd0bbf2dd095d5bce43118ad39baa500e46e980 Mon Sep 17 00:00:00 2001 From: yixingj Date: Thu, 21 Dec 2017 16:45:20 +0800 Subject: [PATCH 1/8] Enable Clair in HA Run clair with Core Harbor services Add check logic for Clair DB --- Makefile | 2 ++ make/ha/docker-compose.clair.tpl | 32 ++++++++++++++++++++++++++++++++ make/install.sh | 6 ++++-- make/prepare | 9 +++++++-- 4 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 make/ha/docker-compose.clair.tpl diff --git a/Makefile b/Makefile index d42a8c97e..75ea0af33 100644 --- a/Makefile +++ b/Makefile @@ -306,6 +306,8 @@ modify_composefile_clair: @cp $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSECLAIRTPLFILENAME) $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSECLAIRFILENAME) @$(SEDCMD) -i 's/__postgresql_version__/$(CLAIRDBVERSION)/g' $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSECLAIRFILENAME) @$(SEDCMD) -i 's/__clair_version__/$(CLAIRVERSION)-$(VERSIONTAG)/g' $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSECLAIRFILENAME) + @cp $(DOCKERCOMPOSEFILEPATH)/ha/$(DOCKERCOMPOSECLAIRTPLFILENAME) $(DOCKERCOMPOSEFILEPATH)/ha/$(DOCKERCOMPOSECLAIRFILENAME) + @$(SEDCMD) -i 's/__clair_version__/$(CLAIRVERSION)-$(VERSIONTAG)/g' $(DOCKERCOMPOSEFILEPATH)/ha/$(DOCKERCOMPOSECLAIRFILENAME) modify_sourcefiles: @echo "change mode of source files." diff --git a/make/ha/docker-compose.clair.tpl b/make/ha/docker-compose.clair.tpl new file mode 100644 index 000000000..3a5590e0c --- /dev/null +++ b/make/ha/docker-compose.clair.tpl @@ -0,0 +1,32 @@ +version: '2' +services: + ui: + networks: + harbor-clair: + aliases: + - harbor-ui + jobservice: + networks: + - harbor-clair + registry: + networks: + - harbor-clair + clair: + networks: + - harbor-clair + container_name: clair + image: vmware/clair-photon:__clair_version__ + restart: always + cpu_quota: 150000 + depends_on: + - log + volumes: + - ./common/config/clair:/config + logging: + driver: "syslog" + options: + syslog-address: "tcp://127.0.0.1:1514" + tag: "clair" +networks: + harbor-clair: + external: false diff --git a/make/install.sh b/make/install.sh index f80805618..069c4b722 100755 --- a/make/install.sh +++ b/make/install.sh @@ -165,7 +165,7 @@ if [ $with_notary ] && [ ! $harbor_ha ] then prepare_para="${prepare_para} --with-notary" fi -if [ $with_clair ] && [ ! $harbor_ha ] +if [ $with_clair ] then prepare_para="${prepare_para} --with-clair" fi @@ -182,7 +182,7 @@ if [ $with_notary ] && [ ! $harbor_ha ] then docker_compose_list="${docker_compose_list} -f docker-compose.notary.yml" fi -if [ $with_clair ] && [ ! $harbor_ha ] +if [ $with_clair ] then docker_compose_list="${docker_compose_list} -f docker-compose.clair.yml" fi @@ -199,6 +199,8 @@ if [ $harbor_ha ] then mv docker-compose.yml docker-compose.yml.bak cp ha/docker-compose.yml docker-compose.yml + mv docker-compose.clair.yml docker-compose.clair.yml.bak + cp ha/docker-compose.clair.yml docker-compose.clair.yml fi docker-compose $docker_compose_list up -d diff --git a/make/prepare b/make/prepare index 1546f3113..0c397deb4 100755 --- a/make/prepare +++ b/make/prepare @@ -30,8 +30,13 @@ def validate(conf, args): redis_url = rcp.get("configuration", "redis_url") if redis_url is None or len(redis_url) < 1: raise Exception("Error: In HA mode redis is required redis_url need to point to an redis cluster") - if args.notary_mode or args.clair_mode: - raise Exception("Error: HA mode doesn't support clair and notary currently") + if args.notary_mode: + raise Exception("Error: HA mode doesn't support Notary currently") + if args.clair_mode: + clair_db_host = rcp.get("configuration", "clair_db_host") + if "postgres" == clair_db_host: + raise Exception("Error: In HA mode, clair_db_host in harbor.cfg needs to point to an external Postgres DB address.") + cert_path = rcp.get("configuration", "ssl_cert") cert_key_path = rcp.get("configuration", "ssl_cert_key") shared_cert_key = os.path.join(base_dir, "ha", os.path.basename(cert_key_path)) From 35716dedd376a99b1d6b4be9ccd33782985e3b9d Mon Sep 17 00:00:00 2001 From: stonezdj Date: Fri, 8 Dec 2017 15:07:36 +0800 Subject: [PATCH 2/8] Sync user email in ldap #3663 --- src/common/dao/config_test.go | 4 +- src/common/dao/user.go | 16 ++++++- src/common/dao/user_test.go | 12 +----- src/ui/auth/authenticator.go | 40 ++++++++++++++++++ src/ui/auth/db/db.go | 10 ++--- src/ui/auth/ldap/ldap.go | 80 ++++++++++++++++++++++------------- src/ui/auth/ldap/ldap_test.go | 57 +++++++++++++++++++++++++ src/ui/auth/uaa/uaa.go | 1 + 8 files changed, 170 insertions(+), 50 deletions(-) diff --git a/src/common/dao/config_test.go b/src/common/dao/config_test.go index d48628ddb..447dff871 100644 --- a/src/common/dao/config_test.go +++ b/src/common/dao/config_test.go @@ -46,7 +46,7 @@ func TestAuthModeCanBeModified(t *testing.T) { t.Fatalf("failed to register user: %v", err) } defer func(id int64) { - if err := deleteUser(id); err != nil { + if err := CleanUser(id); err != nil { t.Fatalf("failed to delete user %d: %v", id, err) } }(id) @@ -68,4 +68,4 @@ func TestAuthModeCanBeModified(t *testing.T) { t.Errorf("unexpected result: %t != %t", flag, false) } } -} \ No newline at end of file +} diff --git a/src/common/dao/user.go b/src/common/dao/user.go index 17ed5e37f..2ce881ad5 100644 --- a/src/common/dao/user.go +++ b/src/common/dao/user.go @@ -258,9 +258,12 @@ func DeleteUser(userID int) error { } // ChangeUserProfile ... -func ChangeUserProfile(user models.User) error { +func ChangeUserProfile(user models.User, cols ...string) error { o := GetOrmer() - if _, err := o.Update(&user, "Email", "Realname", "Comment"); err != nil { + if len(cols) == 0 { + cols = []string{"Email", "Realname", "Comment"} + } + if _, err := o.Update(&user, cols...); err != nil { log.Errorf("update user failed, error: %v", err) return err } @@ -290,3 +293,12 @@ func OnBoardUser(u *models.User) error { } return nil } + +//CleanUser - Clean this user information from DB +func CleanUser(id int64) error { + if _, err := GetOrmer().QueryTable(&models.User{}). + Filter("UserID", id).Delete(); err != nil { + return err + } + return nil +} diff --git a/src/common/dao/user_test.go b/src/common/dao/user_test.go index f8bf76e92..edcbce6a6 100644 --- a/src/common/dao/user_test.go +++ b/src/common/dao/user_test.go @@ -39,7 +39,7 @@ func TestDeleteUser(t *testing.T) { t.Fatalf("failed to register user: %v", err) } defer func(id int64) { - if err := deleteUser(id); err != nil { + if err := CleanUser(id); err != nil { t.Fatalf("failed to delete user %d: %v", id, err) } }(id) @@ -88,13 +88,5 @@ func TestOnBoardUser(t *testing.T) { err = OnBoardUser(u) assert.Nil(err) assert.True(u.UserID == id) - deleteUser(int64(id)) -} - -func deleteUser(id int64) error { - if _, err := GetOrmer().QueryTable(&models.User{}). - Filter("UserID", id).Delete(); err != nil { - return err - } - return nil + CleanUser(int64(id)) } diff --git a/src/ui/auth/authenticator.go b/src/ui/auth/authenticator.go index 111f001aa..eb9da0d06 100644 --- a/src/ui/auth/authenticator.go +++ b/src/ui/auth/authenticator.go @@ -39,6 +39,34 @@ type AuthenticateHelper interface { OnBoardUser(u *models.User) error // Get user information from account repository SearchUser(username string) (*models.User, error) + // Update user information after authenticate, such as Onboard or sync info etc + PostAuthenticate(u *models.User) error +} + +// DefaultAuthenticateHelper - default AuthenticateHelper implementation +type DefaultAuthenticateHelper struct { +} + +// Authenticate ... +func (d *DefaultAuthenticateHelper) Authenticate(m models.AuthModel) (*models.User, error) { + return nil, nil +} + +// OnBoardUser will check if a user exists in user table, if not insert the user and +// put the id in the pointer of user model, if it does exist, fill in the user model based +// on the data record of the user +func (d *DefaultAuthenticateHelper) OnBoardUser(u *models.User) error { + return nil +} + +//SearchUser - Get user information from account repository +func (d *DefaultAuthenticateHelper) SearchUser(username string) (*models.User, error) { + return nil, nil +} + +//PostAuthenticate - Update user information after authenticate, such as Onboard or sync info etc +func (d *DefaultAuthenticateHelper) PostAuthenticate(u *models.User) error { + return nil } var registry = make(map[string]AuthenticateHelper) @@ -79,6 +107,9 @@ func Login(m models.AuthModel) (*models.User, error) { lock.Lock(m.Principal) time.Sleep(frozenTime) } + + authenticator.PostAuthenticate(user) + return user, err } @@ -112,3 +143,12 @@ func SearchUser(username string) (*models.User, error) { } return helper.SearchUser(username) } + +// PostAuthenticate - +func PostAuthenticate(u *models.User) error { + helper, err := getHelper() + if err != nil { + return err + } + return helper.PostAuthenticate(u) +} diff --git a/src/ui/auth/db/db.go b/src/ui/auth/db/db.go index c75f396bf..8cfee4c94 100644 --- a/src/ui/auth/db/db.go +++ b/src/ui/auth/db/db.go @@ -21,7 +21,9 @@ import ( ) // Auth implements Authenticator interface to authenticate user against DB. -type Auth struct{} +type Auth struct { + auth.DefaultAuthenticateHelper +} // Authenticate calls dao to authenticate user. func (d *Auth) Authenticate(m models.AuthModel) (*models.User, error) { @@ -32,12 +34,6 @@ func (d *Auth) Authenticate(m models.AuthModel) (*models.User, error) { return u, nil } -// OnBoardUser - Dummy implementation when auth_mod is db_auth -func (d *Auth) OnBoardUser(user *models.User) error { - //No need to create user in local database - return nil -} - // SearchUser - Check if user exist in local db func (d *Auth) SearchUser(username string) (*models.User, error) { var queryCondition = models.User{ diff --git a/src/ui/auth/ldap/ldap.go b/src/ui/auth/ldap/ldap.go index 885ef8824..8ca332a34 100644 --- a/src/ui/auth/ldap/ldap.go +++ b/src/ui/auth/ldap/ldap.go @@ -16,6 +16,7 @@ package ldap import ( "fmt" + "regexp" "strings" "github.com/vmware/harbor/src/common/dao" @@ -26,7 +27,9 @@ import ( ) // Auth implements AuthenticateHelper interface to authenticate against LDAP -type Auth struct{} +type Auth struct { + auth.DefaultAuthenticateHelper +} // Authenticate checks user's credential against LDAP based on basedn template and LDAP URL, // if the check is successful a dummy record will be inserted into DB, such that this user can @@ -68,7 +71,7 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) { u := models.User{} u.Username = ldapUsers[0].Username - u.Email = ldapUsers[0].Email + u.Email = strings.TrimSpace(ldapUsers[0].Email) u.Realname = ldapUsers[0].Realname dn := ldapUsers[0].DN @@ -78,34 +81,7 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) { log.Warningf("Failed to bind user, username: %s, dn: %s, error: %v", u.Username, dn, err) return nil, nil } - exist, err := dao.UserExists(u, "username") - if err != nil { - return nil, err - } - - if exist { - currentUser, err := dao.GetUser(u) - if err != nil { - return nil, err - } - u.UserID = currentUser.UserID - u.HasAdminRole = currentUser.HasAdminRole - } else { - var user models.User - user.Username = ldapUsers[0].Username - user.Email = ldapUsers[0].Email - user.Realname = ldapUsers[0].Realname - - err = auth.OnBoardUser(&user) - if err != nil || user.UserID <= 0 { - log.Errorf("Can't import user %s, error: %v", ldapUsers[0].Username, err) - return nil, fmt.Errorf("can't import user %s, error: %v", ldapUsers[0].Username, err) - } - u.UserID = user.UserID - } - return &u, nil - } // OnBoardUser will check if a user exists in user table, if not insert the user and @@ -153,6 +129,52 @@ func (l *Auth) SearchUser(username string) (*models.User, error) { return &user, nil } +//PostAuthenticate -- If user exist in harbor DB, sync email address, if not exist, call OnBoardUser +func (l *Auth) PostAuthenticate(u *models.User) error { + + exist, err := dao.UserExists(*u, "username") + if err != nil { + return err + } + + if exist { + queryCondition := models.User{ + Username: u.Username, + } + dbUser, err := dao.GetUser(queryCondition) + if err != nil { + return err + } + if dbUser == nil { + fmt.Printf("User not found in DB %+v", u) + return nil + } + u.UserID = dbUser.UserID + u.HasAdminRole = dbUser.HasAdminRole + + if dbUser.Email != u.Email { + Re := regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`) + if !Re.MatchString(u.Email) { + log.Debugf("Not a valid email address: %v, skip to sync", u.Email) + } else { + dao.ChangeUserProfile(*u, "Email") + } + u.Email = dbUser.Email + } + + return nil + } + + err = auth.OnBoardUser(u) + if err != nil { + return err + } + if u.UserID <= 0 { + return fmt.Errorf("Can not OnBoardUser %v", u) + } + return nil +} + func init() { auth.Register("ldap_auth", &Auth{}) } diff --git a/src/ui/auth/ldap/ldap_test.go b/src/ui/auth/ldap/ldap_test.go index 6163b6fe7..909e13d2b 100644 --- a/src/ui/auth/ldap/ldap_test.go +++ b/src/ui/auth/ldap/ldap_test.go @@ -19,6 +19,7 @@ import ( "os" "testing" + "github.com/stretchr/testify/assert" "github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/models" @@ -204,3 +205,59 @@ func TestAuthenticateHelperSearchUser(t *testing.T) { t.Error("Failed to search user test") } } + +func TestPostAuthentication(t *testing.T) { + + assert := assert.New(t) + user1 := &models.User{ + Username: "test003", + Email: "test003@vmware.com", + Realname: "test003", + } + + queryCondition := models.User{ + Username: "test003", + Realname: "test003", + } + + err := auth.OnBoardUser(user1) + assert.Nil(err) + + user2 := &models.User{ + Username: "test003", + Email: "234invalidmail@@@@@", + } + + auth.PostAuthenticate(user2) + + dbUser, err := dao.GetUser(queryCondition) + if err != nil { + t.Fatalf("Failed to get user, error %v", err) + } + assert.EqualValues("test003@vmware.com", dbUser.Email) + + user3 := &models.User{ + Username: "test003", + } + + auth.PostAuthenticate(user3) + dbUser, err = dao.GetUser(queryCondition) + if err != nil { + t.Fatalf("Failed to get user, error %v", err) + } + assert.EqualValues("test003@vmware.com", dbUser.Email) + + user4 := &models.User{ + Username: "test003", + Email: "test003@example.com", + } + + auth.PostAuthenticate(user4) + + dbUser, err = dao.GetUser(queryCondition) + if err != nil { + t.Fatalf("Failed to get user, error %v", err) + } + assert.EqualValues("test003@example.com", dbUser.Email) + dao.CleanUser(int64(dbUser.UserID)) +} diff --git a/src/ui/auth/uaa/uaa.go b/src/ui/auth/uaa/uaa.go index 22557a9bf..f59cda2f4 100644 --- a/src/ui/auth/uaa/uaa.go +++ b/src/ui/auth/uaa/uaa.go @@ -46,6 +46,7 @@ func CreateClient() (uaa.Client, error) { type Auth struct { sync.Mutex client uaa.Client + auth.DefaultAuthenticateHelper } //Authenticate ... From a89678466e71c9ff6605a08d69140f4b6ddf6d64 Mon Sep 17 00:00:00 2001 From: "Deng, Qian" Date: Wed, 3 Jan 2018 15:33:09 +0800 Subject: [PATCH 3/8] Enhancement of Metadata Descrition --- src/ui_ng/lib/package.json | 2 +- src/ui_ng/lib/pkg/package.json | 2 +- src/ui_ng/lib/src/harbor-library.module.ts | 2 +- .../repository/repository.component.css.ts | 21 +++++++++++++++++-- .../repository/repository.component.html.ts | 16 +++++++++----- src/ui_ng/lib/src/shared/shared.module.ts | 8 +++---- src/ui_ng/package.json | 4 ++-- src/ui_ng/rollup-config.js | 2 +- 8 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/ui_ng/lib/package.json b/src/ui_ng/lib/package.json index cfc448ae9..e7c4f3397 100644 --- a/src/ui_ng/lib/package.json +++ b/src/ui_ng/lib/package.json @@ -1,6 +1,6 @@ { "name": "harbor-ui", - "version": "0.6.2", + "version": "0.6.6", "description": "Harbor shared UI components based on Clarity and Angular4", "scripts": { "start": "ng serve --host 0.0.0.0 --port 4500 --proxy-config proxy.config.json", diff --git a/src/ui_ng/lib/pkg/package.json b/src/ui_ng/lib/pkg/package.json index 3d406f77b..0853af114 100644 --- a/src/ui_ng/lib/pkg/package.json +++ b/src/ui_ng/lib/pkg/package.json @@ -1,6 +1,6 @@ { "name": "harbor-ui", - "version": "0.6.2", + "version": "0.6.6", "description": "Harbor shared UI components based on Clarity and Angular4", "author": "VMware", "module": "index.js", diff --git a/src/ui_ng/lib/src/harbor-library.module.ts b/src/ui_ng/lib/src/harbor-library.module.ts index dca5fb101..16bac5064 100644 --- a/src/ui_ng/lib/src/harbor-library.module.ts +++ b/src/ui_ng/lib/src/harbor-library.module.ts @@ -214,7 +214,7 @@ export class HarborLibraryModule { config.configService || { provide: ConfigurationService, useClass: ConfigurationDefaultService }, config.jobLogService || { provide: JobLogService, useClass: JobLogDefaultService }, config.projectPolicyService || { provide: ProjectService, useClass: ProjectDefaultService }, - //Do initializing + // Do initializing TranslateServiceInitializer, { provide: APP_INITIALIZER, diff --git a/src/ui_ng/lib/src/repository/repository.component.css.ts b/src/ui_ng/lib/src/repository/repository.component.css.ts index 7e4812f15..0b1f34ccf 100644 --- a/src/ui_ng/lib/src/repository/repository.component.css.ts +++ b/src/ui_ng/lib/src/repository/repository.component.css.ts @@ -26,8 +26,25 @@ export const REPOSITORY_STYLE = `.option-right { font-size: 32px; } -pre { - white-space: pre-wrap; +.no-info-div { + background: white; + border: 1px; + border-style: solid; + border-color: #CCCCCC; + padding: 12px 12px 12px 12px; +} + +.info-div { + background: white; + border: 1px; + border-style: solid; + border-color: #CCCCCC; + padding: 0px 12px 24px 12px; +} + +.info-pre { + border: 0px; + max-height: fit-content; } #info-edit-button { diff --git a/src/ui_ng/lib/src/repository/repository.component.html.ts b/src/ui_ng/lib/src/repository/repository.component.html.ts index a2d021aab..2bb443602 100644 --- a/src/ui_ng/lib/src/repository/repository.component.html.ts +++ b/src/ui_ng/lib/src/repository/repository.component.html.ts @@ -24,12 +24,18 @@ export const REPOSITORY_TEMPLATE = `
- +
-
-

{{'REPOSITORY.NO_INFO' | translate }}

-
{{ imageInfo }}
- +
+
+

{{'REPOSITORY.NO_INFO' | translate }}

+

+
+
{{ imageInfo }}
+
+
+
+
diff --git a/src/ui_ng/lib/src/shared/shared.module.ts b/src/ui_ng/lib/src/shared/shared.module.ts index 95a1bd85c..365b14ee9 100644 --- a/src/ui_ng/lib/src/shared/shared.module.ts +++ b/src/ui_ng/lib/src/shared/shared.module.ts @@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'; import { HttpModule, Http } from '@angular/http'; import { ClarityModule } from 'clarity-angular'; import { FormsModule } from '@angular/forms'; -import { TranslateModule, TranslateLoader, TranslateService, MissingTranslationHandler } from "@ngx-translate/core"; +import { TranslateModule, TranslateLoader, TranslateService, MissingTranslationHandler } from '@ngx-translate/core'; import { MyMissingTranslationHandler } from '../i18n/missing-trans.handler'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { TranslatorJsonLoader } from '../i18n/local-json.loader'; @@ -30,9 +30,9 @@ export function GeneralTranslatorLoader(http: Http, config: IServiceConfig) { } /** - * + * * Module for sharing common modules - * + * * @export * @class SharedModule */ @@ -68,4 +68,4 @@ export function GeneralTranslatorLoader(http: Http, config: IServiceConfig) { providers: [CookieService] }) -export class SharedModule { } \ No newline at end of file +export class SharedModule { } diff --git a/src/ui_ng/package.json b/src/ui_ng/package.json index 7b608e743..aa3936f82 100644 --- a/src/ui_ng/package.json +++ b/src/ui_ng/package.json @@ -1,6 +1,6 @@ { "name": "harbor", - "version": "1.2.0", + "version": "1.3.0", "description": "Harbor UI with Clarity", "angular-cli": {}, "scripts": { @@ -31,7 +31,7 @@ "clarity-icons": "^0.10.17", "clarity-ui": "^0.10.17", "core-js": "^2.4.1", - "harbor-ui": "0.6.5", + "harbor-ui": "0.6.6-dev.0", "intl": "^1.2.5", "mutationobserver-shim": "^0.3.2", "ngx-cookie": "^1.0.0", diff --git a/src/ui_ng/rollup-config.js b/src/ui_ng/rollup-config.js index 36cedf305..bb5ba936f 100644 --- a/src/ui_ng/rollup-config.js +++ b/src/ui_ng/rollup-config.js @@ -21,7 +21,7 @@ export default { plugins: [ nodeResolve({jsnext: true, module: true, browser: true}), commonjs({ - include: ['node_modules/**'] + include: ['node_modules/**'], }), uglify() ] From e02de2068ac6968e31f008bc8f6066df091db39c Mon Sep 17 00:00:00 2001 From: Tan Jiang Date: Wed, 3 Jan 2018 16:21:29 +0800 Subject: [PATCH 4/8] Enable configuring the CA Certificate for UAA Enable configuring the path of root cert of UAA in harbor.cfg. It only takes effects if the verify_cert is set to "true" If the file does not exist, the configuration is skipped. The intention for this commit is to support integration with nested UAA in PAS or PKS, we don't expect user to manually configure this value, though he can do it if he wants. --- make/docker-compose.tpl | 2 +- make/harbor.cfg | 1 + make/prepare | 12 ++++++++++++ src/common/utils/uaa/client.go | 23 ++++++++++++++--------- src/common/utils/uaa/client_test.go | 2 +- src/ui/auth/uaa/uaa.go | 2 ++ 6 files changed, 31 insertions(+), 11 deletions(-) diff --git a/make/docker-compose.tpl b/make/docker-compose.tpl index 26a87a6e3..a6755236b 100644 --- a/make/docker-compose.tpl +++ b/make/docker-compose.tpl @@ -76,7 +76,7 @@ services: volumes: - ./common/config/ui/app.conf:/etc/ui/app.conf:z - ./common/config/ui/private_key.pem:/etc/ui/private_key.pem:z - - ./common/config/ui/certificates/:/etc/ui/certifates/ + - ./common/config/ui/certificates/:/etc/ui/certificates/ - /data/secretkey:/etc/ui/key:z - /data/ca_download/:/etc/ui/ca/:z - /data/psc/:/etc/ui/token/:z diff --git a/make/harbor.cfg b/make/harbor.cfg index 8df07e977..156a6acfa 100644 --- a/make/harbor.cfg +++ b/make/harbor.cfg @@ -142,4 +142,5 @@ uaa_endpoint = uaa.mydomain.org uaa_clientid = id uaa_clientsecret = secret uaa_verify_cert = true +uaa_ca_cert = /path/to/ca.pem ############# diff --git a/make/prepare b/make/prepare index 017162e3e..ba5e7476c 100755 --- a/make/prepare +++ b/make/prepare @@ -250,6 +250,7 @@ uaa_endpoint = rcp.get("configuration", "uaa_endpoint") uaa_clientid = rcp.get("configuration", "uaa_clientid") uaa_clientsecret = rcp.get("configuration", "uaa_clientsecret") uaa_verify_cert = rcp.get("configuration", "uaa_verify_cert") +uaa_ca_cert = rcp.get("configuration", "uaa_ca_cert") secret_key = get_secret_key(secretkey_path) log_rotate_count = rcp.get("configuration", "log_rotate_count") @@ -280,6 +281,7 @@ log_config_dir = prep_conf_dir (config_dir, "log") adminserver_conf_env = os.path.join(config_dir, "adminserver", "env") ui_conf_env = os.path.join(config_dir, "ui", "env") ui_conf = os.path.join(config_dir, "ui", "app.conf") +ui_cert_dir = os.path.join(config_dir, "ui", "certificates") jobservice_conf = os.path.join(config_dir, "jobservice", "app.conf") registry_conf = os.path.join(config_dir, "registry", "config.yml") db_conf_env = os.path.join(config_dir, "db", "env") @@ -387,6 +389,16 @@ shutil.copyfile(os.path.join(templates_dir, "jobservice", "app.conf"), jobservic print("Generated configuration file: %s" % ui_conf) shutil.copyfile(os.path.join(templates_dir, "ui", "app.conf"), ui_conf) +if auth_mode == "uaa_auth": + if os.path.isfile(uaa_ca_cert): + if not os.path.isdir(ui_cert_dir): + os.makedirs(ui_cert_dir, mode=0o600) + ui_uaa_ca = os.path.join(ui_cert_dir, "uaa_ca.pem") + print("Copying UAA CA cert to %s" % ui_uaa_ca) + shutil.copyfile(uaa_ca_cert, ui_uaa_ca) + else: + print("Can not find UAA CA cert: %s, skip" % uaa_ca_cert) + def validate_crt_subj(dirty_subj): subj_list = [item for item in dirty_subj.strip().split("/") \ diff --git a/src/common/utils/uaa/client.go b/src/common/utils/uaa/client.go index 8aab55027..07da2e792 100644 --- a/src/common/utils/uaa/client.go +++ b/src/common/utils/uaa/client.go @@ -22,6 +22,7 @@ import ( "fmt" "io/ioutil" "net/http" + "os" "strings" "github.com/vmware/harbor/src/common/utils/log" @@ -179,16 +180,20 @@ func NewDefaultClient(cfg *ClientConfig) (Client, error) { InsecureSkipVerify: cfg.SkipTLSVerify, } if !cfg.SkipTLSVerify && len(cfg.CARootPath) > 0 { - content, err := ioutil.ReadFile(cfg.CARootPath) - if err != nil { - return nil, err - } - pool := x509.NewCertPool() - //Do not throw error if the certificate is malformed, so we can put a place holder. - if ok := pool.AppendCertsFromPEM(content); !ok { - log.Warningf("Failed to append certificate to cert pool, cert path: %s", cfg.CARootPath) + if _, err := os.Stat(cfg.CARootPath); !os.IsNotExist(err) { + content, err := ioutil.ReadFile(cfg.CARootPath) + if err != nil { + return nil, err + } + pool := x509.NewCertPool() + //Do not throw error if the certificate is malformed, so we can put a place holder. + if ok := pool.AppendCertsFromPEM(content); !ok { + log.Warningf("Failed to append certificate to cert pool, cert path: %s", cfg.CARootPath) + } else { + tc.RootCAs = pool + } } else { - tc.RootCAs = pool + log.Warningf("The root certificate file %s is not found, skip configuring root cert in UAA client.", cfg.CARootPath) } } hc := &http.Client{ diff --git a/src/common/utils/uaa/client_test.go b/src/common/utils/uaa/client_test.go index 774392106..f56440a56 100644 --- a/src/common/utils/uaa/client_test.go +++ b/src/common/utils/uaa/client_test.go @@ -98,7 +98,7 @@ func TestNewClientWithCACert(t *testing.T) { CARootPath: "/notexist", } _, err := NewDefaultClient(cfg) - assert.NotNil(err) + assert.Nil(err) //Skip if it's malformed. cfg.CARootPath = path.Join(currPath(), "test", "non-ca.pem") _, err = NewDefaultClient(cfg) diff --git a/src/ui/auth/uaa/uaa.go b/src/ui/auth/uaa/uaa.go index 22557a9bf..62ae1e319 100644 --- a/src/ui/auth/uaa/uaa.go +++ b/src/ui/auth/uaa/uaa.go @@ -16,6 +16,7 @@ package uaa import ( "fmt" + "os" "strings" "sync" @@ -38,6 +39,7 @@ func CreateClient() (uaa.Client, error) { ClientSecret: UAASettings.ClientSecret, Endpoint: UAASettings.Endpoint, SkipTLSVerify: !UAASettings.VerifyCert, + CARootPath: os.Getenv("UAA_CA_ROOT"), } return uaa.NewDefaultClient(cfg) } From 4fb947a1550349003ba42342eb1d30bf0d10bbef Mon Sep 17 00:00:00 2001 From: Jesse Hu Date: Wed, 3 Jan 2018 18:28:22 +0800 Subject: [PATCH 5/8] Fix indentation in registry yaml files --- make/common/templates/registry/config.yml | 39 ++++++++++---------- make/common/templates/registry/config_ha.yml | 36 +++++++++--------- 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/make/common/templates/registry/config.yml b/make/common/templates/registry/config.yml index 4fd13d26d..c49805a04 100644 --- a/make/common/templates/registry/config.yml +++ b/make/common/templates/registry/config.yml @@ -4,32 +4,31 @@ log: fields: service: registry storage: - cache: - layerinfo: inmemory - filesystem: - rootdirectory: /storage - maintenance: - uploadpurging: - enabled: false - delete: - enabled: true + cache: + layerinfo: inmemory + filesystem: + rootdirectory: /storage + maintenance: + uploadpurging: + enabled: false + delete: + enabled: true http: - addr: :5000 - secret: placeholder - debug: - addr: localhost:5001 + addr: :5000 + secret: placeholder + debug: + addr: localhost:5001 auth: token: issuer: harbor-token-issuer realm: $ui_url/service/token rootcertbundle: /etc/registry/root.crt service: harbor-registry - notifications: endpoints: - - name: harbor - disabled: false - url: http://ui:8080/service/notifications - timeout: 3000ms - threshold: 5 - backoff: 1s + - name: harbor + disabled: false + url: http://ui:8080/service/notifications + timeout: 3000ms + threshold: 5 + backoff: 1s diff --git a/make/common/templates/registry/config_ha.yml b/make/common/templates/registry/config_ha.yml index bedf6f482..f3b04fcb1 100644 --- a/make/common/templates/registry/config_ha.yml +++ b/make/common/templates/registry/config_ha.yml @@ -4,14 +4,14 @@ log: fields: service: registry storage: - cache: - layerinfo: redis - Place_holder_for_Storage_configureation - maintenance: - uploadpurging: - enabled: false - delete: - enabled: true + cache: + layerinfo: redis + Place_holder_for_Storage_configureation + maintenance: + uploadpurging: + enabled: false + delete: + enabled: true redis: addr: $redis_url db: 0 @@ -24,10 +24,10 @@ redis: idletimeout: 300s http: - addr: :5000 - secret: placeholder - debug: - addr: localhost:5001 + addr: :5000 + secret: placeholder + debug: + addr: localhost:5001 auth: token: issuer: harbor-token-issuer @@ -37,9 +37,9 @@ auth: notifications: endpoints: - - name: harbor - disabled: false - url: http://ui:8080/service/notifications - timeout: 3000ms - threshold: 5 - backoff: 1s + - name: harbor + disabled: false + url: http://ui:8080/service/notifications + timeout: 3000ms + threshold: 5 + backoff: 1s From 3448fd9a2d554bb1550b7bdf31dd6057c39045e4 Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Wed, 27 Dec 2017 14:33:31 +0800 Subject: [PATCH 6/8] Fix SSRF security issue #3755 in ping target, email server and LDAP server APIs --- src/common/models/models_test.go | 21 ---- src/common/models/replication_job.go | 17 +-- src/common/models/target_test.go | 131 +++++++++++++++++++++ src/common/utils/registry/registry.go | 2 +- src/common/utils/registry/registry_test.go | 2 +- src/common/utils/utils.go | 31 +++-- src/common/utils/utils_test.go | 45 +++---- src/ui/api/email.go | 6 +- src/ui/api/email_test.go | 16 +-- src/ui/api/ldap.go | 3 +- src/ui/api/target.go | 43 +++---- 11 files changed, 201 insertions(+), 116 deletions(-) delete mode 100644 src/common/models/models_test.go create mode 100644 src/common/models/target_test.go diff --git a/src/common/models/models_test.go b/src/common/models/models_test.go deleted file mode 100644 index 822312399..000000000 --- a/src/common/models/models_test.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2017 VMware, Inc. All Rights Reserved. -// -// 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 models - -import ( - "testing" -) - -func TestMain(m *testing.M) { -} diff --git a/src/common/models/replication_job.go b/src/common/models/replication_job.go index 255fc1d9a..262ff2473 100644 --- a/src/common/models/replication_job.go +++ b/src/common/models/replication_job.go @@ -120,14 +120,15 @@ func (r *RepTarget) Valid(v *validation.Validation) { v.SetError("name", "max length is 64") } - if len(r.URL) == 0 { - v.SetError("endpoint", "can not be empty") - } - - r.URL = utils.FormatEndpoint(r.URL) - - if len(r.URL) > 64 { - v.SetError("endpoint", "max length is 64") + url, err := utils.ParseEndpoint(r.URL) + if err != nil { + v.SetError("endpoint", err.Error()) + } else { + // Prevent SSRF security issue #3755 + r.URL = url.Scheme + "://" + url.Host + url.Path + if len(r.URL) > 64 { + v.SetError("endpoint", "max length is 64") + } } // password is encoded using base64, the length of this field diff --git a/src/common/models/target_test.go b/src/common/models/target_test.go new file mode 100644 index 000000000..f8bf2ea92 --- /dev/null +++ b/src/common/models/target_test.go @@ -0,0 +1,131 @@ +// Copyright (c) 2017 VMware, Inc. All Rights Reserved. +// +// 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 models + +import ( + "testing" + + "github.com/astaxie/beego/validation" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestValidOfTarget(t *testing.T) { + cases := []struct { + target RepTarget + err bool + expected RepTarget + }{ + // name is null + { + RepTarget{ + Name: "", + }, + true, + RepTarget{}}, + + // url is null + { + RepTarget{ + Name: "endpoint01", + URL: "", + }, + true, + RepTarget{}, + }, + + // invalid url + { + RepTarget{ + Name: "endpoint01", + URL: "ftp://example.com", + }, + true, + RepTarget{}, + }, + + // invalid url + { + RepTarget{ + Name: "endpoint01", + URL: "ftp://example.com", + }, + true, + RepTarget{}, + }, + + // valid url + { + RepTarget{ + Name: "endpoint01", + URL: "example.com", + }, + false, + RepTarget{ + Name: "endpoint01", + URL: "http://example.com", + }, + }, + + // valid url + { + RepTarget{ + Name: "endpoint01", + URL: "http://example.com", + }, + false, + RepTarget{ + Name: "endpoint01", + URL: "http://example.com", + }, + }, + + // valid url + { + RepTarget{ + Name: "endpoint01", + URL: "https://example.com", + }, + false, + RepTarget{ + Name: "endpoint01", + URL: "https://example.com", + }, + }, + + // valid url + { + RepTarget{ + Name: "endpoint01", + URL: "http://example.com/redirect?key=value", + }, + false, + RepTarget{ + Name: "endpoint01", + URL: "http://example.com/redirect", + }}, + } + + for _, c := range cases { + v := &validation.Validation{} + c.target.Valid(v) + if c.err { + require.True(t, v.HasErrors()) + continue + } + require.False(t, v.HasErrors()) + assert.Equal(t, c.expected, c.target) + } +} diff --git a/src/common/utils/registry/registry.go b/src/common/utils/registry/registry.go index b84dabb97..bbd351309 100644 --- a/src/common/utils/registry/registry.go +++ b/src/common/utils/registry/registry.go @@ -129,7 +129,7 @@ func (r *Registry) Catalog() ([]string, error) { // Ping ... func (r *Registry) Ping() error { - req, err := http.NewRequest("GET", buildPingURL(r.Endpoint.String()), nil) + req, err := http.NewRequest(http.MethodHead, buildPingURL(r.Endpoint.String()), nil) if err != nil { return err } diff --git a/src/common/utils/registry/registry_test.go b/src/common/utils/registry/registry_test.go index 4f62ba595..b4204aaa3 100644 --- a/src/common/utils/registry/registry_test.go +++ b/src/common/utils/registry/registry_test.go @@ -28,7 +28,7 @@ import ( func TestPing(t *testing.T) { server := test.NewServer( &test.RequestHandlerMapping{ - Method: "GET", + Method: http.MethodHead, Pattern: "/v2/", Handler: test.Handler(nil), }) diff --git a/src/common/utils/utils.go b/src/common/utils/utils.go index 01be8ac45..0cc64b402 100644 --- a/src/common/utils/utils.go +++ b/src/common/utils/utils.go @@ -29,27 +29,24 @@ import ( "github.com/vmware/harbor/src/common/utils/log" ) -// FormatEndpoint formats endpoint -func FormatEndpoint(endpoint string) string { - endpoint = strings.TrimSpace(endpoint) +// ParseEndpoint parses endpoint to a URL +func ParseEndpoint(endpoint string) (*url.URL, error) { + endpoint = strings.Trim(endpoint, " ") endpoint = strings.TrimRight(endpoint, "/") - if !strings.HasPrefix(endpoint, "http://") && - !strings.HasPrefix(endpoint, "https://") { + if len(endpoint) == 0 { + return nil, fmt.Errorf("empty URL") + } + i := strings.Index(endpoint, "://") + if i >= 0 { + scheme := endpoint[:i] + if scheme != "http" && scheme != "https" { + return nil, fmt.Errorf("invalid scheme: %s", scheme) + } + } else { endpoint = "http://" + endpoint } - return endpoint -} - -// ParseEndpoint parses endpoint to a URL -func ParseEndpoint(endpoint string) (*url.URL, error) { - endpoint = FormatEndpoint(endpoint) - - u, err := url.Parse(endpoint) - if err != nil { - return nil, err - } - return u, nil + return url.ParseRequestURI(endpoint) } // ParseRepository splits a repository into two parts: project and rest diff --git a/src/common/utils/utils_test.go b/src/common/utils/utils_test.go index 24539fc54..7dcabc527 100644 --- a/src/common/utils/utils_test.go +++ b/src/common/utils/utils_test.go @@ -23,37 +23,30 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestParseEndpoint(t *testing.T) { - endpoint := "example.com" - u, err := ParseEndpoint(endpoint) - if err != nil { - t.Fatalf("failed to parse endpoint %s: %v", endpoint, err) + cases := []struct { + input string + err bool + expected string + }{ + {" example.com/ ", false, "http://example.com"}, + {"ftp://example.com", true, ""}, + {"http://example.com", false, "http://example.com"}, + {"https://example.com", false, "https://example.com"}, + {"http://example!@#!?//#", true, ""}, } - if u.String() != "http://example.com" { - t.Errorf("unexpected endpoint: %s != %s", endpoint, "http://example.com") - } - - endpoint = "https://example.com" - u, err = ParseEndpoint(endpoint) - if err != nil { - t.Fatalf("failed to parse endpoint %s: %v", endpoint, err) - } - - if u.String() != "https://example.com" { - t.Errorf("unexpected endpoint: %s != %s", endpoint, "https://example.com") - } - - endpoint = " example.com/ " - u, err = ParseEndpoint(endpoint) - if err != nil { - t.Fatalf("failed to parse endpoint %s: %v", endpoint, err) - } - - if u.String() != "http://example.com" { - t.Errorf("unexpected endpoint: %s != %s", endpoint, "http://example.com") + for _, c := range cases { + u, err := ParseEndpoint(c.input) + if c.err { + require.NotNil(t, err) + continue + } + require.Nil(t, err) + assert.Equal(t, c.expected, u.String()) } } diff --git a/src/ui/api/email.go b/src/ui/api/email.go index 9dd3d67b8..66a666a46 100644 --- a/src/ui/api/email.go +++ b/src/ui/api/email.go @@ -106,7 +106,9 @@ func (e *EmailAPI) Ping() { addr := net.JoinHostPort(host, strconv.Itoa(port)) if err := email.Ping(addr, identity, username, password, pingEmailTimeout, ssl, insecure); err != nil { - log.Debugf("ping %s failed: %v", addr, err) - e.CustomAbort(http.StatusBadRequest, err.Error()) + log.Errorf("failed to ping email server: %v", err) + // do not return any detail information of the error, or may cause SSRF security issue #3755 + e.RenderError(http.StatusBadRequest, "failed to ping email server") + return } } diff --git a/src/ui/api/email_test.go b/src/ui/api/email_test.go index bf5d26111..154fb69e2 100644 --- a/src/ui/api/email_test.go +++ b/src/ui/api/email_test.go @@ -16,7 +16,6 @@ package api import ( "fmt" - "strings" "testing" "github.com/stretchr/testify/assert" @@ -36,7 +35,7 @@ func TestPingEmail(t *testing.T) { assert.Equal(401, code, "the status code of ping email server with non-admin user should be 401") - //case 2: bad request + //case 2: empty email host settings := `{ "email_host": "" }` @@ -47,7 +46,7 @@ func TestPingEmail(t *testing.T) { return } - assert.Equal(400, code, "the status code of ping email server should be 400") + assert.Equal(400, code) //case 3: secure connection with admin role settings = `{ @@ -58,18 +57,13 @@ func TestPingEmail(t *testing.T) { "email_ssl": true }` - code, body, err := apiTest.PingEmail(*admin, []byte(settings)) + code, _, err = apiTest.PingEmail(*admin, []byte(settings)) if err != nil { t.Errorf("failed to test ping email server: %v", err) return } - assert.Equal(400, code, "the status code of ping email server should be 400") - - if !strings.Contains(body, "535") { - t.Errorf("unexpected error: %s does not contains 535", body) - return - } + assert.Equal(400, code) //case 4: ping email server whose settings are read from config code, _, err = apiTest.PingEmail(*admin, nil) @@ -78,5 +72,5 @@ func TestPingEmail(t *testing.T) { return } - assert.Equal(400, code, "the status code of ping email server should be 400") + assert.Equal(400, code) } diff --git a/src/ui/api/ldap.go b/src/ui/api/ldap.go index f3f2005a5..42ffb058c 100644 --- a/src/ui/api/ldap.go +++ b/src/ui/api/ldap.go @@ -29,7 +29,7 @@ type LdapAPI struct { } const ( - pingErrorMessage = "LDAP connection test failed!" + pingErrorMessage = "LDAP connection test failed" loadSystemErrorMessage = "Can't load system configuration!" canNotOpenLdapSession = "Can't open LDAP session!" searchLdapFailMessage = "LDAP search failed!" @@ -72,6 +72,7 @@ func (l *LdapAPI) Ping() { if err != nil { log.Errorf("ldap connect fail, error: %v", err) + // do not return any detail information of the error, or may cause SSRF security issue #3755 l.RenderError(http.StatusBadRequest, pingErrorMessage) return } diff --git a/src/ui/api/target.go b/src/ui/api/target.go index 68c3c2689..22d68fdf0 100644 --- a/src/ui/api/target.go +++ b/src/ui/api/target.go @@ -16,15 +16,12 @@ package api import ( "fmt" - "net" "net/http" - "net/url" "strconv" "github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/utils" - registry_error "github.com/vmware/harbor/src/common/utils/error" "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/registry" "github.com/vmware/harbor/src/common/utils/registry/auth" @@ -60,27 +57,15 @@ func (t *TargetAPI) Prepare() { func (t *TargetAPI) ping(endpoint, username, password string, insecure bool) { registry, err := newRegistryClient(endpoint, insecure, username, password) - if err != nil { - // timeout, dns resolve error, connection refused, etc. - if urlErr, ok := err.(*url.Error); ok { - if netErr, ok := urlErr.Err.(net.Error); ok { - t.CustomAbort(http.StatusBadRequest, netErr.Error()) - } - - t.CustomAbort(http.StatusBadRequest, urlErr.Error()) - } - - log.Errorf("failed to create registry client: %#v", err) - t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + if err == nil { + err = registry.Ping() } - if err = registry.Ping(); err != nil { - if regErr, ok := err.(*registry_error.HTTPError); ok { - t.CustomAbort(regErr.StatusCode, regErr.Detail) - } - - log.Errorf("failed to ping registry %s: %v", registry.Endpoint.String(), err) - t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + if err != nil { + log.Errorf("failed to ping target: %v", err) + // do not return any detail information of the error, or may cause SSRF security issue #3755 + t.RenderError(http.StatusBadRequest, "failed to ping target") + return } } @@ -117,7 +102,14 @@ func (t *TargetAPI) Ping() { } if req.Endpoint != nil { - target.URL = *req.Endpoint + url, err := utils.ParseEndpoint(*req.Endpoint) + if err != nil { + t.HandleBadRequest(err.Error()) + return + } + + // Prevent SSRF security issue #3755 + target.URL = url.Scheme + "://" + url.Host + url.Path } if req.Username != nil { target.Username = *req.Username @@ -129,11 +121,6 @@ func (t *TargetAPI) Ping() { target.Insecure = *req.Insecure } - if len(target.URL) == 0 { - t.HandleBadRequest("empty endpoint") - return - } - t.ping(target.URL, target.Username, target.Password, target.Insecure) } From 7845405862a1f762d35b60768cdad039fba96e99 Mon Sep 17 00:00:00 2001 From: "Fuhui Peng (c)" Date: Thu, 4 Jan 2018 10:16:49 +0800 Subject: [PATCH 7/8] Modify skinnable json file and modify image path about #3909 #3879 --- .../src/app/account/sign-in/sign-in.component.css | 3 ++- .../src/app/account/sign-in/sign-in.component.html | 3 ++- .../app/base/global-search/global-search.component.ts | 2 +- .../src/app/base/navigator/navigator.component.html | 2 +- .../app/shared/about-dialog/about-dialog.component.ts | 2 +- src/ui_ng/src/app/skinable-config.service.ts | 6 +++--- src/ui_ng/src/setting.json | 10 +++++----- 7 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/ui_ng/src/app/account/sign-in/sign-in.component.css b/src/ui_ng/src/app/account/sign-in/sign-in.component.css index f9251f47c..ed6572137 100644 --- a/src/ui_ng/src/app/account/sign-in/sign-in.component.css +++ b/src/ui_ng/src/app/account/sign-in/sign-in.component.css @@ -47,4 +47,5 @@ font-size: 14px !important; position: relative; top: -9px; -} \ No newline at end of file +} +.bg{position: absolute;top: 60px; right: 0px;width: 100%; height: 100%; background-size: cover;} \ No newline at end of file diff --git a/src/ui_ng/src/app/account/sign-in/sign-in.component.html b/src/ui_ng/src/app/account/sign-in/sign-in.component.html index e19d06e01..445233195 100644 --- a/src/ui_ng/src/app/account/sign-in/sign-in.component.html +++ b/src/ui_ng/src/app/account/sign-in/sign-in.component.html @@ -1,4 +1,5 @@ - diff --git a/src/ui_ng/src/app/harbor-routing.module.ts b/src/ui_ng/src/app/harbor-routing.module.ts index fa01bf976..934e914ca 100644 --- a/src/ui_ng/src/app/harbor-routing.module.ts +++ b/src/ui_ng/src/app/harbor-routing.module.ts @@ -78,25 +78,15 @@ const harborRoutes: Routes = [ component: UserComponent, canActivate: [SystemAdminGuard] }, + { + path: 'registries', + component: DestinationPageComponent, + canActivate: [SystemAdminGuard] + }, { path: 'replications', - component: ReplicationManagementComponent, + component: TotalReplicationPageComponent, canActivate: [SystemAdminGuard], - canActivateChild: [SystemAdminGuard], - children: [ - { - path: 'rules', - component: TotalReplicationPageComponent - }, - { - path: 'endpoints', - component: DestinationPageComponent - }, - { - path: '**', - redirectTo: 'endpoints' - } - ] }, { path: 'tags/:id/:repo', diff --git a/src/ui_ng/src/app/replication/destination/destination-page.component.html b/src/ui_ng/src/app/replication/destination/destination-page.component.html index 3d0c138d2..612bae5f8 100644 --- a/src/ui_ng/src/app/replication/destination/destination-page.component.html +++ b/src/ui_ng/src/app/replication/destination/destination-page.component.html @@ -1,3 +1,4 @@ -
+

{{'SIDE_NAV.SYSTEM_MGMT.REGISTRY' | translate}}

+
\ No newline at end of file diff --git a/src/ui_ng/src/app/replication/replication-management/replication-management.component.ts b/src/ui_ng/src/app/replication/replication-management/replication-management.component.ts index a81236c8b..1996213d7 100644 --- a/src/ui_ng/src/app/replication/replication-management/replication-management.component.ts +++ b/src/ui_ng/src/app/replication/replication-management/replication-management.component.ts @@ -11,6 +11,10 @@ // 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. + +// This Module is used as Container For Endpoint and Replication Rules +// Will deprecated on Harbor 1.4.0 + import { Component } from '@angular/core'; @Component({ diff --git a/src/ui_ng/src/app/replication/total-replication/total-replication-page.component.html b/src/ui_ng/src/app/replication/total-replication/total-replication-page.component.html index b93ec2ca3..9b0e1fb7f 100644 --- a/src/ui_ng/src/app/replication/total-replication/total-replication-page.component.html +++ b/src/ui_ng/src/app/replication/total-replication/total-replication-page.component.html @@ -1,3 +1,4 @@ -
+

{{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}

+
\ No newline at end of file diff --git a/src/ui_ng/src/i18n/lang/en-us-lang.json b/src/ui_ng/src/i18n/lang/en-us-lang.json index 071639669..a3a4de2b4 100644 --- a/src/ui_ng/src/i18n/lang/en-us-lang.json +++ b/src/ui_ng/src/i18n/lang/en-us-lang.json @@ -100,7 +100,8 @@ "SYSTEM_MGMT": { "NAME": "Administration", "USER": "Users", - "REPLICATION": "Replication", + "REGISTRY": "Registries", + "REPLICATION": "Replications", "CONFIG": "Configuration" }, "LOGS": "Logs" diff --git a/src/ui_ng/src/i18n/lang/es-es-lang.json b/src/ui_ng/src/i18n/lang/es-es-lang.json index 1c0a4b161..d74968509 100644 --- a/src/ui_ng/src/i18n/lang/es-es-lang.json +++ b/src/ui_ng/src/i18n/lang/es-es-lang.json @@ -96,7 +96,8 @@ "SYSTEM_MGMT": { "NAME": "Administración", "USER": "Usuarios", - "REPLICATION": "Replicación", + "REGISTRY": "Registries", + "REPLICATION": "Replicacións", "CONFIG": "Configuración" }, "LOGS": "Logs" diff --git a/src/ui_ng/src/i18n/lang/zh-cn-lang.json b/src/ui_ng/src/i18n/lang/zh-cn-lang.json index f498730c5..483d94e75 100644 --- a/src/ui_ng/src/i18n/lang/zh-cn-lang.json +++ b/src/ui_ng/src/i18n/lang/zh-cn-lang.json @@ -96,6 +96,7 @@ "SYSTEM_MGMT": { "NAME": "系统管理", "USER": "用户管理", + "REGISTRY": "仓库管理", "REPLICATION": "复制管理", "CONFIG": "配置管理" }, diff --git a/tests/resources/Harbor-Pages/Administration-Users_Elements.robot b/tests/resources/Harbor-Pages/Administration-Users_Elements.robot index b1aad665f..b278ae6b6 100644 --- a/tests/resources/Harbor-Pages/Administration-Users_Elements.robot +++ b/tests/resources/Harbor-Pages/Administration-Users_Elements.robot @@ -16,5 +16,5 @@ Documentation This resource provides any keywords related to the Harbor private registry appliance *** Variables *** -${administration_user_tag_xpath} /html/body/harbor-app/harbor-shell/clr-main-container/div/nav/section/section/ul/li[1]/a -${administration_tag_xpath} /html/body/harbor-app/harbor-shell/clr-main-container/div/nav/section/section/label \ No newline at end of file +${administration_user_tag_xpath} //clr-vertical-nav-group-children/a[contains(.,'Users')] +${administration_tag_xpath} //clr-vertical-nav-group[contains(.,'Admin')] \ No newline at end of file diff --git a/tests/resources/Harbor-Pages/Configuration.robot b/tests/resources/Harbor-Pages/Configuration.robot index 14a6a928d..e4c1cec4c 100644 --- a/tests/resources/Harbor-Pages/Configuration.robot +++ b/tests/resources/Harbor-Pages/Configuration.robot @@ -38,14 +38,14 @@ Init LDAP Sleep 1 Capture Page Screenshot Disable Ldap Verify Cert Checkbox - Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/config/div/div/div/button[1] + Click Element xpath=${config_save_button_xpath} Sleep 2 Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/config/div/div/div/button[3] Sleep 1 Capture Page Screenshot Switch To Configure - Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/nav/section/section/ul/li[3]/a + Click Element xpath=${configuration_xpath} Sleep 2 Test Ldap Connection @@ -89,32 +89,32 @@ Ldap Verify Cert Checkbox Should Be Disabled Set Pro Create Admin Only #set limit to admin only Sleep 2 - Click Element xpath=//clr-main-container//nav//ul/li[3] + Click Element xpath=${configuration_xpath} Sleep 1 Click Element xpath=//select[@id="proCreation"] Click Element xpath=//select[@id="proCreation"]//option[@value="adminonly"] Sleep 1 - Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/config/div/div/div/button[1] + Click Element xpath=${config_save_button_xpath} Capture Page Screenshot AdminCreateOnly.png Set Pro Create Every One #set limit to Every One - Click Element xpath=//clr-main-container//nav//ul/li[3] + Click Element xpath=${configuration_xpath} Sleep 1 Click Element xpath=//select[@id="proCreation"] Click Element xpath=//select[@id="proCreation"]//option[@value="everyone"] Sleep 1 - Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/config/div/div/div/button[1] + Click Element xpath=${config_save_button_xpath} Sleep 2 Capture Page Screenshot EveryoneCreate.png Disable Self Reg - Click Element xpath=//clr-main-container//nav//ul/li[3] + Click Element xpath=${configuration_xpath} Mouse Down xpath=${self_reg_xpath} Mouse Up xpath=${self_reg_xpath} Sleep 1 Self Reg Should Be Disabled - Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/config/div/div/div/button[1] + Click Element xpath=${config_save_button_xpath} Capture Page Screenshot DisableSelfReg.png Sleep 1 @@ -123,7 +123,7 @@ Enable Self Reg Mouse Up xpath=${self_reg_xpath} Sleep 1 Self Reg Should Be Enabled - Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/config/div/div/div/button[1] + Click Element xpath=${config_save_button_xpath} Capture Page Screenshot EnableSelfReg.png Sleep 1 @@ -142,13 +142,13 @@ Project Creation Should Not Display ## System settings Switch To System Settings Sleep 1 - Click Element xpath=//clr-main-container//nav//ul/li[3] + Click Element xpath=${configuration_xpath} Click Element xpath=//*[@id="config-system"] Modify Token Expiration [Arguments] ${minutes} Input Text xpath=//*[@id="tokenExpiration"] ${minutes} - Click Button xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/config/div/div/div/button[1] + Click Button xpath=${config_save_button_xpath} Sleep 1 Token Must Be Match @@ -159,7 +159,7 @@ Token Must Be Match Check Verify Remote Cert Mouse Down xpath=//*[@id="clr-checkbox-verifyRemoteCert"] Mouse Up xpath=//*[@id="clr-checkbox-verifyRemoteCert"] - Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/config/div/div/div/button[1] + Click Element xpath=${config_save_button_xpath} Capture Page Screenshot RemoteCert.png Sleep 1 @@ -191,7 +191,7 @@ Config Email Mouse Down xpath=//*[@id="clr-checkbox-emailInsecure"] Mouse Up xpath=//*[@id="clr-checkbox-emailInsecure"] Sleep 1 - Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/config/div/div/div/button[1] + Click Element xpath=${config_save_button_xpath} Sleep 6 Verify Email @@ -206,11 +206,11 @@ Set Scan All To None click element //vulnerability-config//select click element //vulnerability-config//select/option[@value='none'] sleep 1 - click element //config//div/button[contains(.,'SAVE')] + click element ${config_save_button_xpath} Set Scan All To Daily click element //vulnerability-config//select click element //vulnerability-config//select/option[@value='daily'] sleep 1 - click element //config//div/button[contains(.,'SAVE')] + click element ${config_save_button_xpath} Click Scan Now click element //vulnerability-config//button[contains(.,'SCAN')] \ No newline at end of file diff --git a/tests/resources/Harbor-Pages/Configuration_Elements.robot b/tests/resources/Harbor-Pages/Configuration_Elements.robot index 04bd2ffa2..8cb6649b5 100644 --- a/tests/resources/Harbor-Pages/Configuration_Elements.robot +++ b/tests/resources/Harbor-Pages/Configuration_Elements.robot @@ -19,3 +19,5 @@ Documentation This resource provides any keywords related to the Harbor private ${project_create_xpath} //clr-dg-action-bar//button[contains(.,'New')] ${self_reg_xpath} //input[@id="clr-checkbox-selfReg"] ${test_ldap_xpath} /html/body/harbor-app/harbor-shell/clr-main-container/div/div/config/div/div/div/button[3] +${config_save_button_xpath} //config//div/button[contains(.,'SAVE')] +${configuration_xpath} //clr-vertical-nav-group-children/a[contains(.,'Configuration')] \ No newline at end of file diff --git a/tests/resources/Harbor-Pages/Project.robot b/tests/resources/Harbor-Pages/Project.robot index 196a493c4..0ee7c2e5a 100644 --- a/tests/resources/Harbor-Pages/Project.robot +++ b/tests/resources/Harbor-Pages/Project.robot @@ -56,7 +56,7 @@ Switch To Log Sleep 1 Switch To Replication - Click Element xpath=${replication_xpath} + Click Element xpath=${project_replication_xpath} Sleep 1 Back To projects diff --git a/tests/resources/Harbor-Pages/Project_Elements.robot b/tests/resources/Harbor-Pages/Project_Elements.robot index 0902c3ecf..57175ee05 100644 --- a/tests/resources/Harbor-Pages/Project_Elements.robot +++ b/tests/resources/Harbor-Pages/Project_Elements.robot @@ -20,6 +20,6 @@ ${create_project_button_css} .btn ${project_name_xpath} //*[@id="create_project_name"] ${project_public_xpath} //input[@name='public']/..//label ${project_save_css} html body.no-scrolling harbor-app harbor-shell clr-main-container.main-container div.content-container div.content-area.content-area-override project div.row div.col-lg-12.col-md-12.col-sm-12.col-xs-12 div.row.flex-items-xs-between div.option-left create-project clr-modal div.modal div.modal-dialog div.modal-content div.modal-footer button.btn.btn-primary -${log_xpath} /html/body/harbor-app/harbor-shell/clr-main-container/div/nav/section/a[2] -${projects_xpath} /html/body/harbor-app/harbor-shell/clr-main-container/div/nav/section/a[1] -${replication_xpath} /html/body/harbor-app/harbor-shell/clr-main-container/div/div/project-detail/nav/ul/li[4]/a +${log_xpath} //clr-main-container/div/clr-vertical-nav/div/a[contains(.,'Logs')] +${projects_xpath} //clr-main-container/div/clr-vertical-nav/div/a[contains(.,'Projects')] +${project_replication_xpath} //project-detail//a[contains(.,'Replication')] \ No newline at end of file diff --git a/tests/resources/Harbor-Pages/Replication_Elements.robot b/tests/resources/Harbor-Pages/Replication_Elements.robot index 3265678a2..0976ddf1c 100644 --- a/tests/resources/Harbor-Pages/Replication_Elements.robot +++ b/tests/resources/Harbor-Pages/Replication_Elements.robot @@ -26,3 +26,4 @@ ${destination_url_xpath} //*[@id='destination_url'] ${destination_username_xpath} //*[@id='destination_username'] ${destination_password_xpath} //*[@id='destination_password'] ${replicaton_save_xpath} //button[contains(.,'OK')] +${replication_xpath} //clr-vertical-nav-group-children/a[contains(.,'Replication')] \ No newline at end of file diff --git a/tests/resources/Harbor-Pages/UserProfile.robot b/tests/resources/Harbor-Pages/UserProfile.robot index b8d0990f4..6e20cf16f 100644 --- a/tests/resources/Harbor-Pages/UserProfile.robot +++ b/tests/resources/Harbor-Pages/UserProfile.robot @@ -31,7 +31,7 @@ Change Password Sleep 1 Click Element xpath=//password-setting/clr-modal//button[2] Sleep 2 - Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/nav/section/a[2] + Click Element xpath=${log_xpath} Sleep 1 Update User Comment