From a392a8dc293d09696e314aac1584ff7da1b80411 Mon Sep 17 00:00:00 2001 From: Tan Jiang Date: Wed, 10 Jan 2018 15:58:01 +0800 Subject: [PATCH] Provide API to rename admin This is to provide a workaround for very corner case that in user's authentication backend (LDAP, UAA) has a user called "admin" and because Harbor's super user is hard coded to "admin" it's not possible to login the "admin" with credentials in LDAP or UAA. To minimize the impact, we'll provide an internal API for user to update the super user's username from "admin" to "admin@harbor.local", this API can be called by "admin" only, and is not reversible. --- src/common/const.go | 1 + src/common/dao/dao_test.go | 10 ++++++++-- src/common/dao/user.go | 13 +++++++++++++ src/ui/api/internal.go | 22 ++++++++++++++++++++++ src/ui/auth/authenticator.go | 6 ++++-- src/ui/controllers/controllers_test.go | 8 ++++++++ src/ui/router.go | 4 +++- 7 files changed, 59 insertions(+), 5 deletions(-) diff --git a/src/common/const.go b/src/common/const.go index 82be6df84..84330b267 100644 --- a/src/common/const.go +++ b/src/common/const.go @@ -82,4 +82,5 @@ const ( DefaultClairEndpoint = "http://clair:6060" CfgDriverDB = "db" CfgDriverJSON = "json" + NewHarborAdminName = "admin@harbor.local" ) diff --git a/src/common/dao/dao_test.go b/src/common/dao/dao_test.go index 84f159608..a8decc18a 100644 --- a/src/common/dao/dao_test.go +++ b/src/common/dao/dao_test.go @@ -40,7 +40,7 @@ func execUpdate(o orm.Ormer, sql string, params ...interface{}) error { return nil } -func clearUp(username string) { +func cleanByUser(username string) { var err error o := GetOrmer() @@ -155,7 +155,7 @@ func TestMain(m *testing.M) { } func testForAll(m *testing.M) int { - clearUp(username) + cleanByUser(username) return m.Run() } @@ -1586,6 +1586,12 @@ func TestGetScanJobsByStatus(t *testing.T) { assert.Equal(sj1.Repository, r2[0].Repository) } +func TestIsSuperUser(t *testing.T) { + assert := assert.New(t) + assert.True(IsSuperUser("admin")) + assert.False(IsSuperUser("none")) +} + func TestSaveConfigEntries(t *testing.T) { configEntries := []models.ConfigEntry{ { diff --git a/src/common/dao/user.go b/src/common/dao/user.go index 57d8ab693..c79775174 100644 --- a/src/common/dao/user.go +++ b/src/common/dao/user.go @@ -296,6 +296,19 @@ func OnBoardUser(u *models.User) error { return nil } +//IsSuperUser checks if the user is super user(conventionally id == 1) of Harbor +func IsSuperUser(username string) bool { + u, err := GetUser(models.User{ + Username: username, + }) + log.Debugf("Check if user %s is super user", username) + if err != nil { + log.Errorf("Failed to get user from DB, username: %s, error: %v", username, err) + return false + } + return u != nil && u.UserID == 1 +} + //CleanUser - Clean this user information from DB func CleanUser(id int64) error { if _, err := GetOrmer().QueryTable(&models.User{}). diff --git a/src/ui/api/internal.go b/src/ui/api/internal.go index 266b40e3d..3ec3e5921 100644 --- a/src/ui/api/internal.go +++ b/src/ui/api/internal.go @@ -15,6 +15,10 @@ package api import ( + "github.com/vmware/harbor/src/common" + "github.com/vmware/harbor/src/common/dao" + "github.com/vmware/harbor/src/common/models" + "github.com/vmware/harbor/src/common/utils/log" "net/http" ) @@ -43,3 +47,21 @@ func (ia *InternalAPI) SyncRegistry() { ia.CustomAbort(http.StatusInternalServerError, "internal error") } } + +// RenameAdmin we don't provide flexibility in this API, as this is a workaround. +func (ia *InternalAPI) RenameAdmin() { + if !dao.IsSuperUser(ia.SecurityCtx.GetUsername()) { + log.Errorf("User %s is not super user, not allow to rename admin.", ia.SecurityCtx.GetUsername()) + ia.CustomAbort(http.StatusForbidden, "") + } + newName := common.NewHarborAdminName + if err := dao.ChangeUserProfile(models.User{ + UserID: 1, + Username: newName, + }, "username"); err != nil { + log.Errorf("Failed to change admin's username, error: %v", err) + ia.CustomAbort(http.StatusInternalServerError, "Failed to rename admin user.") + } + log.Debugf("The super user has been renamed to: %s", newName) + ia.DestroySession() +} diff --git a/src/ui/auth/authenticator.go b/src/ui/auth/authenticator.go index 4d3474b18..95c5c75e6 100644 --- a/src/ui/auth/authenticator.go +++ b/src/ui/auth/authenticator.go @@ -18,6 +18,8 @@ import ( "fmt" "time" + "github.com/vmware/harbor/src/common" + "github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/ui/config" @@ -88,8 +90,8 @@ func Login(m models.AuthModel) (*models.User, error) { if err != nil { return nil, err } - if authMode == "" || m.Principal == "admin" { - authMode = "db_auth" + if authMode == "" || dao.IsSuperUser(m.Principal) { + authMode = common.DBAuth } log.Debug("Current AUTH_MODE is ", authMode) diff --git a/src/ui/controllers/controllers_test.go b/src/ui/controllers/controllers_test.go index f8bb7ec7f..e252d4953 100644 --- a/src/ui/controllers/controllers_test.go +++ b/src/ui/controllers/controllers_test.go @@ -28,6 +28,7 @@ import ( "github.com/astaxie/beego" "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" "github.com/vmware/harbor/src/common/utils/test" "github.com/vmware/harbor/src/ui/config" @@ -126,6 +127,13 @@ func TestAll(t *testing.T) { if err := proxy.Init(); err != nil { panic(err) } + database, err := config.Database() + if err != nil { + panic(err) + } + if err := dao.InitDatabase(database); err != nil { + panic(err) + } assert := assert.New(t) diff --git a/src/ui/router.go b/src/ui/router.go index fd6d34c78..4d5637a76 100644 --- a/src/ui/router.go +++ b/src/ui/router.go @@ -86,7 +86,6 @@ func initRouters() { beego.Router("/api/projects/:id([0-9]+)/metadatas/?:name", &api.MetadataAPI{}, "get:Get") beego.Router("/api/projects/:id([0-9]+)/metadatas/", &api.MetadataAPI{}, "post:Post") beego.Router("/api/projects/:id([0-9]+)/metadatas/:name", &api.MetadataAPI{}, "put:Put;delete:Delete") - beego.Router("/api/internal/syncregistry", &api.InternalAPI{}, "post:SyncRegistry") beego.Router("/api/repositories", &api.RepositoryAPI{}, "get:Get") beego.Router("/api/repositories/scanAll", &api.RepositoryAPI{}, "post:ScanAll") beego.Router("/api/repositories/*", &api.RepositoryAPI{}, "delete:Delete;put:Put") @@ -119,6 +118,9 @@ func initRouters() { beego.Router("/api/systeminfo/volumes", &api.SystemInfoAPI{}, "get:GetVolumeInfo") beego.Router("/api/systeminfo/getcert", &api.SystemInfoAPI{}, "get:GetCert") + beego.Router("/api/internal/syncregistry", &api.InternalAPI{}, "post:SyncRegistry") + beego.Router("/api/internal/renameadmin", &api.InternalAPI{}, "post:RenameAdmin") + //external service that hosted on harbor process: beego.Router("/service/notifications", ®istry.NotificationHandler{}) beego.Router("/service/notifications/clair", &clair.Handler{}, "post:Handle")