diff --git a/.travis.yml b/.travis.yml index e463cfc70..a80c95942 100644 --- a/.travis.yml +++ b/.travis.yml @@ -73,6 +73,8 @@ before_script: - sudo sqlite3 /registry.db < make/common/db/registry_sqlite.sql script: + - sudo mkdir -p /harbor_storage/ca_download + - sudo mv ./tests/ca.crt /harbor_storage/ca_download - sudo service mysql stop - sudo ./tests/testprepare.sh - docker-compose -f ./make/docker-compose.test.yml up -d diff --git a/Makefile b/Makefile index d6c7d442a..974118b9c 100644 --- a/Makefile +++ b/Makefile @@ -5,14 +5,11 @@ # all: prepare env, compile binarys, build images and install images # prepare: prepare env # compile: compile ui and jobservice code -# compile_buildgolangimage: -# compile local building golang image -# forexample : make compile_buildgolangimage -e \ -# GOBUILDIMAGE=harborgo:1.6.2 +# # compile_golangimage: # compile from golang image # for example: make compile_golangimage -e GOBUILDIMAGE= \ -# harborgo:1.6.2 +# golang:1.7.3 # compile_ui, compile_jobservice: compile specific binary # # build: build Harbor docker images (defuault: build_photon) @@ -192,11 +189,6 @@ compile_jobservice: compile_normal: compile_ui compile_jobservice -compile_buildgolangimage: - @echo "compiling golang image for harbor ..." - @$(DOCKERBUILD) -t $(GOBUILDIMAGE) -f $(TOOLSPATH)/$(GOLANGDOCKERFILENAME) . - @echo "Done." - compile_golangimage: @echo "compiling binary for ui (golang image)..." @echo $(GOBASEPATH) diff --git a/docs/compile_guide.md b/docs/compile_guide.md index 39530e3d6..76a973647 100644 --- a/docs/compile_guide.md +++ b/docs/compile_guide.md @@ -24,26 +24,7 @@ golang* | 1.6.0 + $ git clone https://github.com/vmware/harbor ``` -## Step 3: Resolving dependencies of Go language -You can compile the source code by using a Golang dev image. In this case, you can skip this step. - -If you are building Harbor using your own Go compiling environment. You need to install LDAP packages manually. - -For PhotonOS: - - ```sh - $ tdnf install -y sed apr-util-ldap - ``` - -For Ubuntu: - - ```sh - $ apt-get update && apt-get install -y libldap2-dev - ``` - -For other platforms, please consult the relevant documentation of installing LDAP package. - -## Step 4: Building and installing Harbor +## Step 3: Building and installing Harbor ### Configuration @@ -58,18 +39,18 @@ Edit the file **make/harbor.cfg** and make necessary configuration changes such You can compile the code by one of the three approaches: -#### I. Create a Golang dev image, then build Harbor +#### I. Build with offical Golang image -* Build Golang dev image: +* Get offcial Golang image from docker hub: ```sh - $ make compile_buildgolangimage -e GOBUILDIMAGE=harborgo:1.7.3 + $ docker pull golang:1.7.3 ``` * Build, install and bring up Harbor: ```sh - $ make install -e GOBUILDIMAGE=harborgo:1.7.3 COMPILETAG=compile_golangimage + $ make install -e GOBUILDIMAGE=golang:1.7.3 COMPILETAG=compile_golangimage ``` #### II. Compile code with your own Golang environment, then build Harbor @@ -142,7 +123,6 @@ Target | Description all | prepare env, compile binaries, build images and install images prepare | prepare env compile | compile ui and jobservice code -compile_golangimage | compile local golang image compile_ui | compile ui binary compile_jobservice | compile jobservice binary build | build Harbor docker images (default: using build_photon) @@ -163,13 +143,6 @@ cleanpackage | remove online/offline install package #### EXAMPLE: -#### Build a golang dev image (for building Harbor): - - ```sh - $ make compile_golangimage -e GOBUILDIMAGE= [$YOURIMAGE] - - ``` - #### Build Harbor images based on Ubuntu ```sh diff --git a/make/common/templates/adminserver/env b/make/common/templates/adminserver/env index 7c88ba28a..959082ce3 100644 --- a/make/common/templates/adminserver/env +++ b/make/common/templates/adminserver/env @@ -9,6 +9,7 @@ LDAP_BASE_DN=$ldap_basedn LDAP_FILTER=$ldap_filter LDAP_UID=$ldap_uid LDAP_SCOPE=$ldap_scope +LDAP_TIMEOUT=$ldap_timeout DATABASE_TYPE=mysql MYSQL_HOST=mysql MYSQL_PORT=3306 diff --git a/make/dev/jobservice/Dockerfile b/make/dev/jobservice/Dockerfile index 8cd8467e1..87bc159fb 100644 --- a/make/dev/jobservice/Dockerfile +++ b/make/dev/jobservice/Dockerfile @@ -2,10 +2,6 @@ FROM golang:1.7.3 MAINTAINER jiangd@vmware.com -RUN apt-get update \ - && apt-get install -y libldap2-dev \ - && rm -r /var/lib/apt/lists/* - COPY . /go/src/github.com/vmware/harbor WORKDIR /go/src/github.com/vmware/harbor/src/jobservice diff --git a/make/dev/ui/Dockerfile b/make/dev/ui/Dockerfile index 822797d76..db2f0aa04 100644 --- a/make/dev/ui/Dockerfile +++ b/make/dev/ui/Dockerfile @@ -2,10 +2,6 @@ FROM golang:1.7.3 MAINTAINER jiangd@vmware.com -RUN apt-get update \ - && apt-get install -y libldap2-dev \ - && rm -r /var/lib/apt/lists/* - COPY src/. /go/src/github.com/vmware/harbor/src WORKDIR /go/src/github.com/vmware/harbor/src/ui @@ -21,8 +17,6 @@ COPY src/favicon.ico /go/bin/favicon.ico COPY make/jsminify.sh /tmp/jsminify.sh RUN chmod u+x /go/bin/harbor_ui \ - && sed -i 's/TLS_CACERT/#TLS_CAERT/g' /etc/ldap/ldap.conf \ - && sed -i '$a\TLS_REQCERT allow' /etc/ldap/ldap.conf \ && timestamp=`date '+%s'` \ && /tmp/jsminify.sh /go/bin/views/sections/script-include.htm /go/bin/static/resources/js/harbor.app.min.$timestamp.js /go/bin/ \ && sed -i "s/harbor\.app\.min\.js/harbor\.app\.min\.$timestamp\.js/g" /go/bin/views/sections/script-min-include.htm diff --git a/make/harbor.cfg b/make/harbor.cfg index 8b8c6cd37..69118dbf3 100644 --- a/make/harbor.cfg +++ b/make/harbor.cfg @@ -52,6 +52,9 @@ ldap_uid = uid #the scope to search for users, 1-LDAP_SCOPE_BASE, 2-LDAP_SCOPE_ONELEVEL, 3-LDAP_SCOPE_SUBTREE ldap_scope = 3 +#Timeout (in seconds) when connecting to an LDAP Server. The default value (and most reasonable) is 5 seconds. +ldap_connect_timeout = 5 + #The password for the root user of mysql db, change this before any production use. db_password = root123 diff --git a/make/photon/ui/Dockerfile b/make/photon/ui/Dockerfile index 29722d4dc..e52164471 100644 --- a/make/photon/ui/Dockerfile +++ b/make/photon/ui/Dockerfile @@ -1,7 +1,7 @@ FROM library/photon:1.0 RUN mkdir /harbor/ -RUN tdnf install -y sed apr-util-ldap +RUN tdnf install -y sed COPY ./make/dev/ui/harbor_ui /harbor/ @@ -13,8 +13,7 @@ COPY ./make/jsminify.sh /tmp/jsminify.sh RUN chmod u+x /harbor/harbor_ui \ && timestamp=`date '+%s'` \ && /tmp/jsminify.sh /harbor/views/sections/script-include.htm /harbor/static/resources/js/harbor.app.min.$timestamp.js /harbor/ \ - && sed -i "s/harbor\.app\.min\.js/harbor\.app\.min\.$timestamp\.js/g" /harbor/views/sections/script-min-include.htm \ - && echo "TLS_REQCERT allow" >> /etc/openldap/ldap.conf + && sed -i "s/harbor\.app\.min\.js/harbor\.app\.min\.$timestamp\.js/g" /harbor/views/sections/script-min-include.htm WORKDIR /harbor/ ENTRYPOINT ["/harbor/harbor_ui"] diff --git a/make/prepare b/make/prepare index 5e1b6b311..2610d5fa2 100755 --- a/make/prepare +++ b/make/prepare @@ -101,6 +101,7 @@ else: ldap_filter = "" ldap_uid = rcp.get("configuration", "ldap_uid") ldap_scope = rcp.get("configuration", "ldap_scope") +ldap_connect_timeout = rcp.get("configuration", "ldap_connect_timeout") db_password = rcp.get("configuration", "db_password") self_registration = rcp.get("configuration", "self_registration") use_compressed_js = rcp.get("configuration", "use_compressed_js") @@ -201,6 +202,7 @@ render(os.path.join(templates_dir, "ui", "env"), ldap_filter=ldap_filter, ldap_uid=ldap_uid, ldap_scope=ldap_scope, + ldap_connect_timeout=ldap_connect_timeout, self_registration=self_registration, use_compressed_js=use_compressed_js, ui_secret=ui_secret, diff --git a/make/ubuntu/jobservice/Dockerfile b/make/ubuntu/jobservice/Dockerfile index 0528c9ae4..29814703d 100644 --- a/make/ubuntu/jobservice/Dockerfile +++ b/make/ubuntu/jobservice/Dockerfile @@ -1,10 +1,7 @@ -FROM golang:1.7.3 +FROM library/ubuntu:14.04 MAINTAINER jiangd@vmware.com -RUN apt-get update && apt-get install -y libldap2-dev \ - && rm -r /var/lib/apt/lists/* - RUN mkdir /harbor/ COPY ./make/dev/jobservice/harbor_jobservice /harbor/ diff --git a/make/ubuntu/ui/Dockerfile b/make/ubuntu/ui/Dockerfile index 067741a4e..f44d4fdf9 100644 --- a/make/ubuntu/ui/Dockerfile +++ b/make/ubuntu/ui/Dockerfile @@ -1,10 +1,7 @@ -FROM golang:1.7.3 +FROM library/ubuntu:14.04 MAINTAINER jiangd@vmware.com -RUN apt-get update && apt-get install -y libldap2-dev \ - && rm -r /var/lib/apt/lists/* - ENV MYSQL_USR root \ MYSQL_PWD root \ REGISTRY_URL localhost:5000 @@ -18,8 +15,6 @@ COPY ./src/favicon.ico /harbor/favicon.ico COPY ./make/jsminify.sh /tmp/jsminify.sh RUN chmod u+x /harbor/harbor_ui \ - && sed -i 's/TLS_CACERT/#TLS_CAERT/g' /etc/ldap/ldap.conf \ - && sed -i '$a\TLS_REQCERT allow' /etc/ldap/ldap.conf \ && timestamp=`date '+%s'` \ && /tmp/jsminify.sh /harbor/views/sections/script-include.htm /harbor/static/resources/js/harbor.app.min.$timestamp.js /harbor/ \ && sed -i "s/harbor\.app\.min\.js/harbor\.app\.min\.$timestamp\.js/g" /harbor/views/sections/script-min-include.htm diff --git a/src/adminserver/systemcfg/systemcfg.go b/src/adminserver/systemcfg/systemcfg.go index dda9cf144..4bb50cf4b 100644 --- a/src/adminserver/systemcfg/systemcfg.go +++ b/src/adminserver/systemcfg/systemcfg.go @@ -103,6 +103,11 @@ func initFromEnv() (*models.SystemCfg, error) { return nil, err } cfg.Authentication.LDAP.Scope = scope + timeout, err := strconv.Atoi(os.Getenv("LDAP_TIMEOUT")) + if err != nil { + return nil, err + } + cfg.Authentication.LDAP.Timeout = timeout cfg.Database = &models.Database{ Type: os.Getenv("DATABASE_TYPE"), MySQL: &models.MySQL{ diff --git a/src/common/dao/dao_test.go b/src/common/dao/dao_test.go index dd3594c79..26056116f 100644 --- a/src/common/dao/dao_test.go +++ b/src/common/dao/dao_test.go @@ -998,13 +998,6 @@ func TestGetRecentLogs(t *testing.T) { } } -func TestGetTopRepos(t *testing.T) { - _, err := GetTopRepos(10) - if err != nil { - t.Fatalf("error occured in getting top repos, error: %v", err) - } -} - var targetID, policyID, policyID2, policyID3, jobID, jobID2, jobID3 int64 func TestAddRepTarget(t *testing.T) { diff --git a/src/common/dao/repository.go b/src/common/dao/repository.go index cda2f5cba..5d9353a0c 100644 --- a/src/common/dao/repository.go +++ b/src/common/dao/repository.go @@ -102,12 +102,22 @@ func GetRepositoryByProjectName(name string) ([]*models.RepoRecord, error) { } //GetTopRepos returns the most popular repositories -func GetTopRepos(count int) ([]models.TopRepo, error) { +func GetTopRepos(userID int, count int) ([]models.TopRepo, error) { topRepos := []models.TopRepo{} + sql := `select r.name, r.pull_count from repository r + inner join project p on r.project_id = p.project_id + where ( + p.deleted = 0 and ( + p.public = 1 or ( + ? <> ? and exists ( + select 1 from project_member pm + where pm.project_id = p.project_id and pm.user_id = ? + )))) + order by r.pull_count desc, r.name limit ?` repositories := []*models.RepoRecord{} - if _, err := GetOrmer().QueryTable(&models.RepoRecord{}). - OrderBy("-PullCount", "Name").Limit(count).All(&repositories); err != nil { + _, err := GetOrmer().Raw(sql, userID, NonExistUserID, userID, count).QueryRows(&repositories) + if err != nil { return topRepos, err } diff --git a/src/common/dao/repository_test.go b/src/common/dao/repository_test.go index 1cc251f60..e8efc7e6b 100644 --- a/src/common/dao/repository_test.go +++ b/src/common/dao/repository_test.go @@ -16,8 +16,11 @@ package dao import ( + "fmt" "testing" + "time" + "github.com/stretchr/testify/require" "github.com/vmware/harbor/src/common/models" ) @@ -160,6 +163,161 @@ func TestGetTotalOfUserRelevantRepositories(t *testing.T) { } } +func TestGetTopRepos(t *testing.T) { + var err error + require := require.New(t) + + require.NoError(GetOrmer().Begin()) + defer func() { + require.NoError(GetOrmer().Rollback()) + }() + + admin, err := GetUser(models.User{Username: "admin"}) + require.NoError(err) + + user := models.User{ + Username: "user", + Password: "user", + Email: "user@test.com", + } + userID, err := Register(user) + require.NoError(err) + user.UserID = int(userID) + + // + // public project with 1 repository + // non-public project with 2 repositories visible by admin + // non-public project with 1 repository visible by admin and user + // deleted public project with 1 repository + // + + project1 := models.Project{ + OwnerID: admin.UserID, + Name: "project1", + CreationTime: time.Now(), + OwnerName: admin.Username, + Public: 0, + } + project1.ProjectID, err = AddProject(project1) + require.NoError(err) + + project2 := models.Project{ + OwnerID: admin.UserID, + Name: "project2", + CreationTime: time.Now(), + OwnerName: admin.Username, + Public: 0, + } + project2.ProjectID, err = AddProject(project2) + require.NoError(err) + + require.NoError(AddProjectMember(project2.ProjectID, user.UserID, models.PROJECTADMIN)) + + err = AddRepository(*repository) + require.NoError(err) + + repository1 := &models.RepoRecord{ + Name: fmt.Sprintf("%v/repository1", project1.Name), + OwnerName: admin.Username, + ProjectName: project1.Name, + } + err = AddRepository(*repository1) + require.NoError(err) + require.NoError(IncreasePullCount(repository1.Name)) + repository1, err = GetRepositoryByName(repository1.Name) + require.NoError(err) + + repository2 := &models.RepoRecord{ + Name: fmt.Sprintf("%v/repository2", project1.Name), + OwnerName: admin.Username, + ProjectName: project1.Name, + } + err = AddRepository(*repository2) + require.NoError(err) + require.NoError(IncreasePullCount(repository2.Name)) + require.NoError(IncreasePullCount(repository2.Name)) + repository2, err = GetRepositoryByName(repository2.Name) + require.NoError(err) + + repository3 := &models.RepoRecord{ + Name: fmt.Sprintf("%v/repository3", project2.Name), + OwnerName: admin.Username, + ProjectName: project2.Name, + } + err = AddRepository(*repository3) + require.NoError(err) + require.NoError(IncreasePullCount(repository3.Name)) + require.NoError(IncreasePullCount(repository3.Name)) + require.NoError(IncreasePullCount(repository3.Name)) + repository3, err = GetRepositoryByName(repository3.Name) + require.NoError(err) + + deletedPublicProject := models.Project{ + OwnerID: admin.UserID, + Name: "public-deleted", + CreationTime: time.Now(), + OwnerName: admin.Username, + Public: 1, + } + deletedPublicProject.ProjectID, err = AddProject(deletedPublicProject) + require.NoError(err) + deletedPublicRepository1 := &models.RepoRecord{ + Name: fmt.Sprintf("%v/repository1", deletedPublicProject.Name), + OwnerName: admin.Username, + ProjectName: deletedPublicProject.Name, + } + err = AddRepository(*deletedPublicRepository1) + require.NoError(err) + DeleteProject(deletedPublicProject.ProjectID) + + var topRepos []models.TopRepo + + // anonymous should retrieve public non-deleted repositories + topRepos, err = GetTopRepos(NonExistUserID, 3) + require.NoError(err) + require.Len(topRepos, 1) + require.Equal(topRepos, []models.TopRepo{ + models.TopRepo{ + RepoName: repository.Name, + AccessCount: repository.PullCount, + }, + }) + + // admin should retrieve all visible repositories limited by count + topRepos, err = GetTopRepos(admin.UserID, 3) + require.NoError(err) + require.Len(topRepos, 3) + require.Equal(topRepos, []models.TopRepo{ + models.TopRepo{ + RepoName: repository3.Name, + AccessCount: repository3.PullCount, + }, + models.TopRepo{ + RepoName: repository2.Name, + AccessCount: repository2.PullCount, + }, + models.TopRepo{ + RepoName: repository1.Name, + AccessCount: repository1.PullCount, + }, + }) + + // user should retrieve all visible repositories + topRepos, err = GetTopRepos(user.UserID, 3) + require.NoError(err) + require.Len(topRepos, 2) + require.Equal(topRepos, []models.TopRepo{ + models.TopRepo{ + RepoName: repository3.Name, + AccessCount: repository3.PullCount, + }, + models.TopRepo{ + RepoName: repository.Name, + AccessCount: repository.PullCount, + }, + }) +} + func addRepository(repository *models.RepoRecord) error { return AddRepository(*repository) } diff --git a/src/common/models/config.go b/src/common/models/config.go index 4039ea7ef..9697a32ba 100644 --- a/src/common/models/config.go +++ b/src/common/models/config.go @@ -31,6 +31,7 @@ type LDAP struct { Filter string `json:"filter"` UID string `json:"uid"` Scope int `json:"scope"` + Timeout int `json:"timeout"` // in second } // Database ... diff --git a/src/ui/api/harborapi_test.go b/src/ui/api/harborapi_test.go index 749927438..4fd57a1c3 100644 --- a/src/ui/api/harborapi_test.go +++ b/src/ui/api/harborapi_test.go @@ -88,6 +88,8 @@ func init() { beego.Router("/api/policies/replication", &RepPolicyAPI{}, "get:List") beego.Router("/api/policies/replication", &RepPolicyAPI{}, "post:Post;delete:Delete") beego.Router("/api/policies/replication/:id([0-9]+)/enablement", &RepPolicyAPI{}, "put:UpdateEnablement") + beego.Router("/api/systeminfo/volumes", &SystemInfoAPI{}, "get:GetVolumeInfo") + beego.Router("/api/systeminfo/getcert", &SystemInfoAPI{}, "get:GetCert") _ = updateInitPassword(1, "Harbor12345") @@ -872,3 +874,26 @@ func updateInitPassword(userID int, password string) error { } return nil } + +//Get system volume info +func (a testapi) VolumeInfoGet(authInfo usrInfo) (int, apilib.SystemInfo, error) { + _sling := sling.New().Get(a.basePath) + path := "/api/systeminfo/volumes" + _sling = _sling.Path(path) + httpStatusCode, body, err := request(_sling, jsonAcceptHeader, authInfo) + var successPayLoad apilib.SystemInfo + if 200 == httpStatusCode && nil == err { + err = json.Unmarshal(body, &successPayLoad) + } + + return httpStatusCode, successPayLoad, err +} + +//Get system cert +func (a testapi) CertGet(authInfo usrInfo) (int, []byte, error) { + _sling := sling.New().Get(a.basePath) + path := "/api/systeminfo/getcert" + _sling = _sling.Path(path) + httpStatusCode, body, err := request(_sling, jsonAcceptHeader, authInfo) + return httpStatusCode, body, err +} diff --git a/src/ui/api/project.go b/src/ui/api/project.go index 58ec9b96f..b28e90600 100644 --- a/src/ui/api/project.go +++ b/src/ui/api/project.go @@ -44,7 +44,8 @@ type projectReq struct { } const projectNameMaxLen int = 30 -const projectNameMinLen int = 4 +const projectNameMinLen int = 2 +const restrictedNameChars = `[a-z0-9]+(?:[._-][a-z0-9]+)*` const dupProjectPattern = `Duplicate entry '\w+' for key 'name'` // Prepare validates the URL and the user @@ -423,9 +424,9 @@ func isProjectAdmin(userID int, pid int64) bool { func validateProjectReq(req projectReq) error { pn := req.ProjectName if isIllegalLength(req.ProjectName, projectNameMinLen, projectNameMaxLen) { - return fmt.Errorf("Project name is illegal in length. (greater than 4 or less than 30)") + return fmt.Errorf("Project name is illegal in length. (greater than 2 or less than 30)") } - validProjectName := regexp.MustCompile(`^[a-z0-9](?:-*[a-z0-9])*(?:[._][a-z0-9](?:-*[a-z0-9])*)*$`) + validProjectName := regexp.MustCompile(`^` + restrictedNameChars + `$`) legal := validProjectName.MatchString(pn) if !legal { return fmt.Errorf("project name is not in lower case or contains illegal characters") diff --git a/src/ui/api/repository.go b/src/ui/api/repository.go index 9bc7036c0..5eca32805 100644 --- a/src/ui/api/repository.go +++ b/src/ui/api/repository.go @@ -424,7 +424,12 @@ func (ra *RepositoryAPI) GetTopRepos() { ra.CustomAbort(http.StatusBadRequest, "invalid count") } - repos, err := dao.GetTopRepos(count) + userID, _, ok := ra.GetUserIDForRequest() + if !ok { + userID = dao.NonExistUserID + } + + repos, err := dao.GetTopRepos(userID, count) if err != nil { log.Errorf("failed to get top repos: %v", err) ra.CustomAbort(http.StatusInternalServerError, "internal server error") diff --git a/src/ui/api/systeminfo_test.go b/src/ui/api/systeminfo_test.go new file mode 100644 index 000000000..933e47640 --- /dev/null +++ b/src/ui/api/systeminfo_test.go @@ -0,0 +1,64 @@ +package api + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestGetVolumeInfo(t *testing.T) { + fmt.Println("Testing Get Volume Info") + assert := assert.New(t) + apiTest := newHarborAPI() + + //case 1: get volume info without admin role + CommonAddUser() + code, _, err := apiTest.VolumeInfoGet(*testUser) + if err != nil { + t.Error("Error occured while get system volume info") + t.Log(err) + } else { + assert.Equal(403, code, "Get system volume info should be 403") + } + //case 2: get volume info with admin role + code, info, err := apiTest.VolumeInfoGet(*admin) + if err != nil { + t.Error("Error occured while get system volume info") + t.Log(err) + } else { + assert.Equal(200, code, "Get system volume info should be 200") + if info.HarborStorage.Total <= 0 { + assert.Equal(1, info.HarborStorage.Total, "Total storage of system should be larger than 0") + } + if info.HarborStorage.Free <= 0 { + assert.Equal(1, info.HarborStorage.Free, "Free storage of system should be larger than 0") + } + } + +} + +func TestGetCert(t *testing.T) { + fmt.Println("Testing Get Cert") + assert := assert.New(t) + apiTest := newHarborAPI() + + //case 1: get cert without admin role + code, _, err := apiTest.CertGet(*testUser) + if err != nil { + t.Error("Error occured while get system cert") + t.Log(err) + } else { + assert.Equal(403, code, "Get system cert should be 403") + } + //case 2: get cert with admin role + code, content, err := apiTest.CertGet(*admin) + if err != nil { + t.Error("Error occured while get system cert") + t.Log(err) + } else { + assert.Equal(200, code, "Get system cert should be 200") + assert.Equal("test for ca.crt.\n", string(content), "Get system cert content should be equal") + + } + CommonDelUser() +} diff --git a/src/ui/auth/ldap/ldap.go b/src/ui/auth/ldap/ldap.go index df27ba837..e3928a201 100644 --- a/src/ui/auth/ldap/ldap.go +++ b/src/ui/auth/ldap/ldap.go @@ -16,18 +16,19 @@ package ldap import ( + "crypto/tls" "errors" "fmt" "strings" - - "github.com/vmware/harbor/src/common/utils/log" + "time" "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/auth" "github.com/vmware/harbor/src/ui/config" - "github.com/mqu/openldap" + goldap "gopkg.in/ldap.v2" ) // Auth implements Authenticator interface to authenticate against LDAP @@ -35,6 +36,55 @@ type Auth struct{} const metaChars = "&|!=~*<>()" +// Connect checks the LDAP configuration directives, and connects to the LDAP URL +// Returns an LDAP connection +func Connect(settings *models.LDAP) (*goldap.Conn, error) { + ldapURL := settings.URL + if ldapURL == "" { + return nil, errors.New("can not get any available LDAP_URL") + } + log.Debug("ldapURL:", ldapURL) + + // This routine keeps compability with the old format used on harbor.cfg + splitLdapURL := strings.Split(ldapURL, "://") + protocol, hostport := splitLdapURL[0], splitLdapURL[1] + + var host, port string + + // This tries to detect the used port, if not defined + if strings.Contains(hostport, ":") { + splitHostPort := strings.Split(hostport, ":") + host, port = splitHostPort[0], splitHostPort[1] + } else { + host = hostport + switch protocol { + case "ldap": + port = "389" + case "ldaps": + port = "636" + } + } + + // Sets a Dial Timeout for LDAP + goldap.DefaultTimeout = time.Duration(settings.Timeout) * time.Second + + var ldap *goldap.Conn + var err error + switch protocol { + case "ldap": + ldap, err = goldap.Dial("tcp", fmt.Sprintf("%s:%s", host, port)) + case "ldaps": + ldap, err = goldap.DialTLS("tcp", fmt.Sprintf("%s:%s", host, port), &tls.Config{InsecureSkipVerify: true}) + } + + if err != nil { + return nil, err + } + + return ldap, nil + +} + // 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 // be associated to other entities in the system. @@ -52,16 +102,10 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) { return nil, err } - ldapURL := settings.URL - if ldapURL == "" { - return nil, errors.New("can not get any available LDAP_URL") - } - log.Debug("ldapURL:", ldapURL) - ldap, err := openldap.Initialize(ldapURL) + ldap, err := Connect(settings) if err != nil { return nil, err } - ldap.SetOption(openldap.LDAP_OPT_PROTOCOL_VERSION, openldap.LDAP_VERSION3) ldapBaseDn := settings.BaseDN if ldapBaseDn == "" { @@ -92,27 +136,42 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) { ldapScope := settings.Scope var scope int if ldapScope == 1 { - scope = openldap.LDAP_SCOPE_BASE + scope = goldap.ScopeBaseObject } else if ldapScope == 2 { - scope = openldap.LDAP_SCOPE_ONELEVEL + scope = goldap.ScopeSingleLevel } else { - scope = openldap.LDAP_SCOPE_SUBTREE + scope = goldap.ScopeWholeSubtree } attributes := []string{"uid", "cn", "mail", "email"} - result, err := ldap.SearchAll(ldapBaseDn, scope, filter, attributes) + + searchRequest := goldap.NewSearchRequest( + ldapBaseDn, + scope, + goldap.NeverDerefAliases, + 0, // Unlimited results. TODO: Limit this (as we expect only one result)? + 0, // Search Timeout. TODO: Limit this (check what is the unit of timeout) and make configurable + false, // Types Only + filter, + attributes, + nil, + ) + + result, err := ldap.Search(searchRequest) if err != nil { return nil, err } - if len(result.Entries()) == 0 { + + if len(result.Entries) == 0 { log.Warningf("Not found an entry.") return nil, nil - } else if len(result.Entries()) != 1 { + } else if len(result.Entries) != 1 { log.Warningf("Found more than one entry.") return nil, nil } - en := result.Entries()[0] - bindDN := en.Dn() - log.Debug("found entry:", en) + + entry := result.Entries[0] + bindDN := entry.DN + log.Debug("found entry:", bindDN) err = ldap.Bind(bindDN, m.Password) if err != nil { log.Debug("Bind user error", err) @@ -121,9 +180,10 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) { defer ldap.Close() u := models.User{} - for _, attr := range en.Attributes() { - val := attr.Values()[0] - switch attr.Name() { + + for _, attr := range entry.Attributes { + val := attr.Values[0] + switch attr.Name { case "uid": u.Realname = val case "cn": diff --git a/src/ui/config/config_test.go b/src/ui/config/config_test.go index 38df6641b..3f18b40b1 100644 --- a/src/ui/config/config_test.go +++ b/src/ui/config/config_test.go @@ -29,6 +29,7 @@ var ( "cn", "uid", "2", + "5", } tokenExp = "3" tokenExpRes = 3 @@ -52,6 +53,7 @@ func TestMain(m *testing.M) { os.Setenv("LDAP_UID", ldap.UID) os.Setenv("LDAP_SCOPE", ldap.Scope) os.Setenv("LDAP_FILTER", ldap.Filter) + os.Setenv("LDAP_CONNECT_TIMEOUT", ldap.ConnectTimeout) os.Setenv("TOKEN_EXPIRATION", tokenExp) os.Setenv("HARBOR_ADMIN_PASSWORD", adminPassword) os.Setenv("EXT_REG_URL", externalRegURL) @@ -76,6 +78,7 @@ func TestMain(m *testing.M) { os.Unsetenv("LDAP_UID") os.Unsetenv("LDAP_SCOPE") os.Unsetenv("LDAP_FILTER") + os.Unsetenv("LDAP_CONNECT_TIMEOUT") os.Unsetenv("TOKEN_EXPIRATION") os.Unsetenv("HARBOR_ADMIN_PASSWORD") os.Unsetenv("EXT_REG_URL") diff --git a/src/ui/views/sign-up.htm b/src/ui/views/sign-up.htm index 841c4d8d7..eb513eaec 100644 --- a/src/ui/views/sign-up.htm +++ b/src/ui/views/sign-up.htm @@ -30,7 +30,7 @@