diff --git a/.travis.yml b/.travis.yml index 8c734d966..05dc4a095 100644 --- a/.travis.yml +++ b/.travis.yml @@ -70,6 +70,7 @@ before_script: # create tables and load data # - mysql < ./make/db/registry.sql -uroot --verbose - sudo sqlite3 /tmp/registry.db < make/common/db/registry_sqlite.sql + - sudo chmod 777 /tmp/registry.db script: - sudo mkdir -p /harbor_storage/ca_download diff --git a/Makefile b/Makefile index 974118b9c..cd4fd9b98 100644 --- a/Makefile +++ b/Makefile @@ -4,18 +4,17 @@ # # all: prepare env, compile binarys, build images and install images # prepare: prepare env -# compile: compile ui and jobservice code +# compile: compile adminserver, ui and jobservice code # # compile_golangimage: # compile from golang image # for example: make compile_golangimage -e GOBUILDIMAGE= \ # golang:1.7.3 -# compile_ui, compile_jobservice: compile specific binary +# compile_adminserver, compile_ui, compile_jobservice: compile specific binary # # build: build Harbor docker images (defuault: build_photon) # for example: make build -e BASEIMAGE=photon -# build_photon: build Harbor docker images from photon bsaeimage -# build_ubuntu: build Harbor docker images from ubuntu baseimage +# build_photon: build Harbor docker images from photon baseimage # # install: include compile binarys, build images, prepare specific \ # version composefile and startup Harbor instance @@ -46,7 +45,7 @@ # # clean: remove binary, Harbor images, specific version docker-compose \ # file, specific version tag and online/offline install package -# cleanbinary: remove ui and jobservice binary +# cleanbinary: remove adminserver, ui and jobservice binary # cleanimage: remove Harbor images # cleandockercomposefile: # remove specific version docker-compose @@ -100,14 +99,19 @@ GOBUILDIMAGE=reg.mydomain.com/library/harborgo[:tag] GOBUILDPATH=$(GOBASEPATH)/harbor GOIMAGEBUILDCMD=/usr/local/go/bin/go GOIMAGEBUILD=$(GOIMAGEBUILDCMD) build +GOBUILDPATH_ADMINSERVER=$(GOBUILDPATH)/src/adminserver GOBUILDPATH_UI=$(GOBUILDPATH)/src/ui GOBUILDPATH_JOBSERVICE=$(GOBUILDPATH)/src/jobservice GOBUILDMAKEPATH=$(GOBUILDPATH)/make +GOBUILDMAKEPATH_ADMINSERVER=$(GOBUILDMAKEPATH)/dev/adminserver GOBUILDMAKEPATH_UI=$(GOBUILDMAKEPATH)/dev/ui GOBUILDMAKEPATH_JOBSERVICE=$(GOBUILDMAKEPATH)/dev/jobservice GOLANGDOCKERFILENAME=Dockerfile.golang # binary +ADMINSERVERSOURCECODE=$(SRCPATH)/adminserver +ADMINSERVERBINARYPATH=$(MAKEDEVPATH)/adminserver +ADMINSERVERBINARYNAME=harbor_adminserver UISOURCECODE=$(SRCPATH)/ui UIBINARYPATH=$(MAKEDEVPATH)/ui UIBINARYNAME=harbor_ui @@ -125,7 +129,6 @@ CONFIGFILE=harbor.cfg # makefile MAKEFILEPATH_PHOTON=$(MAKEPATH)/photon -MAKEFILEPATH_UBUNTU=$(MAKEPATH)/ubuntu # common dockerfile DOCKERFILEPATH_COMMON=$(MAKEPATH)/common @@ -133,6 +136,7 @@ DOCKERFILEPATH_DB=$(DOCKERFILEPATH_COMMON)/db DOCKERFILENAME_DB=Dockerfile # docker image name +DOCKERIMAGENAME_ADMINSERVER=vmware/harbor-adminserver DOCKERIMAGENAME_UI=vmware/harbor-ui DOCKERIMAGENAME_JOBSERVICE=vmware/harbor-jobservice DOCKERIMAGENAME_LOG=vmware/harbor-log @@ -177,6 +181,11 @@ version: check_environment: @$(MAKEPATH)/$(CHECKENVCMD) +compile_adminserver: + @echo "compiling binary for adminserver..." + @$(GOBUILD) -o $(ADMINSERVERBINARYPATH)/$(ADMINSERVERBINARYNAME) $(ADMINSERVERSOURCECODE) + @echo "Done." + compile_ui: @echo "compiling binary for ui..." @$(GOBUILD) -o $(UIBINARYPATH)/$(UIBINARYNAME) $(UISOURCECODE) @@ -187,9 +196,15 @@ compile_jobservice: @$(GOBUILD) -o $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) $(JOBSERVICESOURCECODE) @echo "Done." -compile_normal: compile_ui compile_jobservice +compile_normal: compile_adminserver compile_ui compile_jobservice compile_golangimage: + @echo "compiling binary for adminserver (golang image)..." + @echo $(GOBASEPATH) + @echo $(GOBUILDPATH) + @$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_ADMINSERVER) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_ADMINSERVER)/$(ADMINSERVERBINARYNAME) + @echo "Done." + @echo "compiling binary for ui (golang image)..." @echo $(GOBASEPATH) @echo $(GOBUILDPATH) @@ -214,9 +229,6 @@ build_common: version build_photon: build_common make -f $(MAKEFILEPATH_PHOTON)/Makefile build -e DEVFLAG=$(DEVFLAG) -build_ubuntu: build_common - make -f $(MAKEFILEPATH_UBUNTU)/Makefile build -e DEVFLAG=$(DEVFLAG) - build: build_$(BASEIMAGE) modify_composefile: @@ -240,7 +252,6 @@ package_online: modify_composefile @cp NOTICE $(HARBORPKG)/NOTICE @$(TARCMD) -zcvf harbor-online-installer-$(VERSIONTAG).tgz \ --exclude=$(HARBORPKG)/common/db --exclude=$(HARBORPKG)/common/config\ - --exclude=$(HARBORPKG)/common/log --exclude=$(HARBORPKG)/ubuntu \ --exclude=$(HARBORPKG)/photon --exclude=$(HARBORPKG)/kubernetes \ --exclude=$(HARBORPKG)/dev --exclude=$(DOCKERCOMPOSETPLFILENAME) \ --exclude=$(HARBORPKG)/checkenv.sh \ @@ -264,6 +275,7 @@ package_offline: compile build modify_composefile @echo "saving harbor docker image" @$(DOCKERSAVE) -o $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \ + $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \ $(DOCKERIMAGENAME_UI):$(VERSIONTAG) \ $(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \ $(DOCKERIMAGENAME_DB):$(VERSIONTAG) \ @@ -272,7 +284,6 @@ package_offline: compile build modify_composefile @$(TARCMD) -zcvf harbor-offline-installer-$(VERSIONTAG).tgz \ --exclude=$(HARBORPKG)/common/db --exclude=$(HARBORPKG)/common/config\ - --exclude=$(HARBORPKG)/common/log --exclude=$(HARBORPKG)/ubuntu \ --exclude=$(HARBORPKG)/photon --exclude=$(HARBORPKG)/kubernetes \ --exclude=$(HARBORPKG)/dev --exclude=$(DOCKERCOMPOSETPLFILENAME) \ --exclude=$(HARBORPKG)/checkenv.sh \ @@ -285,6 +296,11 @@ package_offline: compile build modify_composefile pushimage: @echo "pushing harbor images ..." + @$(DOCKERTAG) $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) + @$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \ + $(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER) + @$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) + @$(DOCKERTAG) $(DOCKERIMAGENAME_UI):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG) @$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \ $(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER) @@ -317,11 +333,13 @@ down: cleanbinary: @echo "cleaning binary..." + @if [ -f $(ADMINSERVERBINARYPATH)/$(ADMINSERVERBINARYNAME) ] ; then rm $(ADMINSERVERBINARYPATH)/$(ADMINSERVERBINARYNAME) ; fi @if [ -f $(UIBINARYPATH)/$(UIBINARYNAME) ] ; then rm $(UIBINARYPATH)/$(UIBINARYNAME) ; fi @if [ -f $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) ] ; then rm $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) ; fi cleanimage: @echo "cleaning image for photon..." + - $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) - $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_UI):$(VERSIONTAG) - $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_DB):$(VERSIONTAG) - $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) diff --git a/docs/compile_guide.md b/docs/compile_guide.md index 76a973647..7459a97d0 100644 --- a/docs/compile_guide.md +++ b/docs/compile_guide.md @@ -127,7 +127,6 @@ compile_ui | compile ui binary compile_jobservice | compile jobservice binary build | build Harbor docker images (default: using build_photon) build_photon | build Harbor docker images from Photon OS base image -build_ubuntu | build Harbor docker images from Ubuntu base image install | compile binaries, build images, prepare specific version of compose file and startup Harbor instance start | startup Harbor instance down | shutdown Harbor instance @@ -143,13 +142,6 @@ cleanpackage | remove online/offline install package #### EXAMPLE: -#### Build Harbor images based on Ubuntu - - ```sh - $ make build -e BASEIMAGE=ubuntu - - ``` - #### Push Harbor images to specific registry server ```sh diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 6bbd2ed21..460bd6a85 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1371,6 +1371,46 @@ paths: description: Inviald ldap configuration parameters. 500: description: Unexpected internal errors. + /configurations: + get: + summary: Get system configurations. + description: | + This endpoint is for retrieving system configurations that only provides for admin user. + tags: + - Products + responses: + 200: + description: Get system configurations successfully. The response body is a map. + schema: + type: object + 401: + description: User need to log in first. + 403: + description: User does not have permission of admin role. + 500: + description: Unexpected internal errors. + put: + summary: Modify system configurations. + description: | + This endpoint is for modifying system configurations that only provides for admin user. + tags: + - Products + parameters: + - name: configurations + in: body + required: true + schema: + type: object + description: The configurations map need to be modified, the following are keys "auth_mode", "email_from", "email_host", "email_identity", "email_password", "email_port", "email_ssl", "email_username", "ldap_base_dn", "ldap_filter", "ldap_scope", "ldap_search_dn", "ldap_search_password", "ldap_timeout", "ldap_uid", "ldap_url", "project_creation_restriction", "self_registration", "verify_remote_cert". + responses: + 200: + description: Modify system configurations successfully. + 401: + description: User need to log in first. + 403: + description: User does not have permission of admin role. + 500: + description: Unexpected internal errors. definitions: Search: type: object diff --git a/make/dev/docker-compose.yml b/make/dev/docker-compose.yml index c94a2d875..716f642ba 100644 --- a/make/dev/docker-compose.yml +++ b/make/dev/docker-compose.yml @@ -3,7 +3,7 @@ services: log: build: context: ../../ - dockerfile: make/ubuntu/log/Dockerfile + dockerfile: make/photon/log/Dockerfile restart: always volumes: - /var/log/harbor/:/var/log/docker/ diff --git a/make/ubuntu/Makefile b/make/ubuntu/Makefile deleted file mode 100644 index 81fac2acd..000000000 --- a/make/ubuntu/Makefile +++ /dev/null @@ -1,79 +0,0 @@ -# Makefile for a harbor project -# -# Targets: -# -# build: build harbor ubuntu images -# clean: clean ui and jobservice harbor images - -# common -SHELL := /bin/bash -BUILDPATH=$(CURDIR) -MAKEPATH=$(BUILDPATH)/make -MAKEDEVPATH=$(MAKEPATH)/dev -SRCPATH=./src -TOOLSPATH=$(BUILDPATH)/tools -CHECKENVCMD=checkenv.sh -DEVFLAG=true - -# docker parameters -DOCKERCMD=$(shell which docker) -DOCKERBUILD=$(DOCKERCMD) build -DOCKERRMIMAGE=$(DOCKERCMD) rmi -DOCKERIMASES=$(DOCKERCMD) images - -# binary -UISOURCECODE=$(SRCPATH)/ui -UIBINARYPATH=$(MAKEDEVPATH)/ui -UIBINARYNAME=harbor_ui -JOBSERVICESOURCECODE=$(SRCPATH)/jobservice -JOBSERVICEBINARYPATH=$(MAKEDEVPATH)/jobservice -JOBSERVICEBINARYNAME=harbor_jobservice - -# ubuntu dockerfile -DOCKERFILEPATH=$(MAKEPATH)/ubuntu -DOCKERFILEPATH_UI=$(DOCKERFILEPATH)/ui -DOCKERFILENAME_UI=Dockerfile -DOCKERIMAGENAME_UI=vmware/harbor-ui -DOCKERFILEPATH_JOBSERVICE=$(DOCKERFILEPATH)/jobservice -DOCKERFILENAME_JOBSERVICE=Dockerfile -DOCKERIMAGENAME_JOBSERVICE=vmware/harbor-jobservice -DOCKERFILEPATH_LOG=$(DOCKERFILEPATH)/log -DOCKERFILENAME_LOG=Dockerfile -DOCKERIMAGENAME_LOG=vmware/harbor-log - -# version prepare -VERSIONFILEPATH=$(SRCPATH)/views/sections -VERSIONFILENAME=header-content.htm -GITCMD=$(shell which git) -GITTAG=$(GITCMD) describe --tags -ifeq ($(DEVFLAG), true) - VERSIONTAG=dev -else - VERSIONTAG=$(shell $(GITTAG)) -endif - -check_environment: - @$(MAKEPATH)/$(CHECKENVCMD) - -build: - @echo "building ui container for ubuntu..." - $(DOCKERBUILD) -f $(DOCKERFILEPATH_UI)/$(DOCKERFILENAME_UI) -t $(DOCKERIMAGENAME_UI):$(VERSIONTAG) . - @echo "Done." - - @echo "building jobservice container for ubuntu..." - $(DOCKERBUILD) -f $(DOCKERFILEPATH_JOBSERVICE)/$(DOCKERFILENAME_JOBSERVICE) -t $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) . - @echo "Done." - - @echo "building log container for ubuntu..." - $(DOCKERBUILD) -f $(DOCKERFILEPATH_LOG)/$(DOCKERFILENAME_LOG) -t $(DOCKERIMAGENAME_LOG):$(VERSIONTAG) . - @echo "Done." - -cleanimage: - @echo "cleaning image for ubuntu..." - - $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_UI):$(VERSIONTAG) - - $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) - - $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_LOG):$(VERSIONTAG) - -.PHONY: clean -clean: cleanimage - diff --git a/make/ubuntu/jobservice/Dockerfile b/make/ubuntu/jobservice/Dockerfile deleted file mode 100644 index 29814703d..000000000 --- a/make/ubuntu/jobservice/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM library/ubuntu:14.04 - -MAINTAINER jiangd@vmware.com - -RUN mkdir /harbor/ -COPY ./make/dev/jobservice/harbor_jobservice /harbor/ - -RUN chmod u+x /harbor/harbor_jobservice - -WORKDIR /harbor/ -ENTRYPOINT ["/harbor/harbor_jobservice"] diff --git a/make/ubuntu/log/Dockerfile b/make/ubuntu/log/Dockerfile deleted file mode 100644 index 3e1c7520e..000000000 --- a/make/ubuntu/log/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM library/ubuntu:14.04 - -RUN rm /etc/rsyslog.d/* && rm /etc/rsyslog.conf - -ADD make/common/log/rsyslog.conf /etc/rsyslog.conf - -# rotate logs weekly -# notes: file name cannot contain dot, or the script will not run -ADD make/common/log/rotate.sh /etc/cron.weekly/rotate - -# rsyslog configuration file for docker -ADD make/common/log/rsyslog_docker.conf /etc/rsyslog.d/ - -VOLUME /var/log/docker/ - -EXPOSE 514 - -CMD cron && rsyslogd -n diff --git a/make/ubuntu/ui/Dockerfile b/make/ubuntu/ui/Dockerfile deleted file mode 100644 index f44d4fdf9..000000000 --- a/make/ubuntu/ui/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -FROM library/ubuntu:14.04 - -MAINTAINER jiangd@vmware.com - -ENV MYSQL_USR root \ - MYSQL_PWD root \ - REGISTRY_URL localhost:5000 - -RUN mkdir /harbor/ -COPY ./make/dev/ui/harbor_ui /harbor/ - -COPY ./src/ui/views /harbor/views -COPY ./src/ui/static /harbor/static -COPY ./src/favicon.ico /harbor/favicon.ico -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 - -WORKDIR /harbor/ -ENTRYPOINT ["/harbor/harbor_ui"] - -EXPOSE 80 - diff --git a/src/adminserver/systemcfg/systemcfg.go b/src/adminserver/systemcfg/systemcfg.go index d1d45c203..4d22adc22 100644 --- a/src/adminserver/systemcfg/systemcfg.go +++ b/src/adminserver/systemcfg/systemcfg.go @@ -78,7 +78,7 @@ func getCfgStore() string { //read the following attrs from env every time boots up func readFromEnv(cfg map[string]interface{}) error { - cfg[comcfg.DomainName] = os.Getenv("EXT_ENDPOINT") + cfg[comcfg.ExtEndpoint] = os.Getenv("EXT_ENDPOINT") cfg[comcfg.DatabaseType] = os.Getenv("DATABASE_TYPE") cfg[comcfg.MySQLHost] = os.Getenv("MYSQL_HOST") diff --git a/src/common/config/config.go b/src/common/config/config.go index 295564d66..29c59f948 100644 --- a/src/common/config/config.go +++ b/src/common/config/config.go @@ -40,7 +40,7 @@ const ( LDAPScopeOnelevel = "2" LDAPScopeSubtree = "3" - DomainName = "domain_name" + ExtEndpoint = "ext_endpoint" AUTHMode = "auth_mode" DatabaseType = "database_type" MySQLHost = "mysql_host" diff --git a/src/common/dao/config.go b/src/common/dao/config.go index eb700c8af..a0a2ab1fd 100644 --- a/src/common/dao/config.go +++ b/src/common/dao/config.go @@ -27,6 +27,6 @@ func AuthModeCanBeModified() (bool, error) { if err != nil { return false, err } - - return c == 1, nil + // admin and anonymous + return c == 2, nil } diff --git a/src/common/dao/config_test.go b/src/common/dao/config_test.go index a874a6c6e..b8eb43721 100644 --- a/src/common/dao/config_test.go +++ b/src/common/dao/config_test.go @@ -27,7 +27,7 @@ func TestAuthModeCanBeModified(t *testing.T) { t.Fatalf("failed to count users: %v", err) } - if c == 1 { + if c == 2 { flag, err := AuthModeCanBeModified() if err != nil { t.Fatalf("failed to determine whether auth mode can be modified: %v", err) diff --git a/src/common/dao/dao_test.go b/src/common/dao/dao_test.go index 2a1144d9b..26056116f 100644 --- a/src/common/dao/dao_test.go +++ b/src/common/dao/dao_test.go @@ -137,7 +137,7 @@ const publicityOn = 1 const publicityOff = 0 func TestMain(m *testing.M) { - databases := []string{"mysql"} + databases := []string{"mysql", "sqlite"} for _, database := range databases { log.Infof("run test cases for database: %s", database) diff --git a/src/common/utils/registry/auth/tokenauthorizer.go b/src/common/utils/registry/auth/tokenauthorizer.go index 917e43f38..b65474c9b 100644 --- a/src/common/utils/registry/auth/tokenauthorizer.go +++ b/src/common/utils/registry/auth/tokenauthorizer.go @@ -133,19 +133,24 @@ func (t *tokenAuthorizer) updateCachedToken(token string, expiresIn int) { // Implements interface Authorizer type standardTokenAuthorizer struct { tokenAuthorizer - client *http.Client - credential Credential + client *http.Client + credential Credential + tokenServiceEndpoint string } // NewStandardTokenAuthorizer returns a standard token authorizer. The authorizer will request a token // from token server and add it to the origin request -func NewStandardTokenAuthorizer(credential Credential, insecure bool, scopeType, scopeName string, scopeActions ...string) Authorizer { +// If tokenServiceEndpoint is set, the token request will be sent to it instead of the server get from authorizer +// The usage please refer to the function tokenURL +func NewStandardTokenAuthorizer(credential Credential, insecure bool, + tokenServiceEndpoint string, scopeType, scopeName string, scopeActions ...string) Authorizer { authorizer := &standardTokenAuthorizer{ client: &http.Client{ Transport: registry.GetHTTPTransport(insecure), Timeout: 30 * time.Second, }, - credential: credential, + credential: credential, + tokenServiceEndpoint: tokenServiceEndpoint, } if len(scopeType) != 0 || len(scopeName) != 0 { @@ -162,7 +167,7 @@ func NewStandardTokenAuthorizer(credential Credential, insecure bool, scopeType, } func (s *standardTokenAuthorizer) generateToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) { - realm = tokenURL(realm) + realm = s.tokenURL(realm) u, err := url.Parse(realm) if err != nil { @@ -229,20 +234,15 @@ func (s *standardTokenAuthorizer) generateToken(realm, service string, scopes [] // when the registry client is used inside Harbor, the token request // can be posted to token service directly rather than going through nginx. -// this solution can resolve two problems: +// If realm is set as the internal url of token service, this can resolve +// two problems: // 1. performance issue // 2. the realm field returned by registry is an IP which can not reachable // inside Harbor -func tokenURL(realm string) string { - //TODO - /* - extEndpoint := config.ExtEndpoint() - tokenEndpoint := config.TokenEndpoint() - if len(extEndpoint) != 0 && len(tokenEndpoint) != 0 && - strings.Contains(realm, extEndpoint) { - realm = strings.TrimRight(tokenEndpoint, "/") + "/service/token" - } - */ +func (s *standardTokenAuthorizer) tokenURL(realm string) string { + if len(s.tokenServiceEndpoint) != 0 { + return s.tokenServiceEndpoint + } return realm } diff --git a/src/common/utils/registry/auth/tokenauthorizer_test.go b/src/common/utils/registry/auth/tokenauthorizer_test.go index 2d843d887..3a582bb64 100644 --- a/src/common/utils/registry/auth/tokenauthorizer_test.go +++ b/src/common/utils/registry/auth/tokenauthorizer_test.go @@ -40,7 +40,7 @@ func TestAuthorizeOfStandardTokenAuthorizer(t *testing.T) { }) defer server.Close() - authorizer := NewStandardTokenAuthorizer(nil, false, "repository", "library/ubuntu", "pull") + authorizer := NewStandardTokenAuthorizer(nil, false, "", "repository", "library/ubuntu", "pull") req, err := http.NewRequest("GET", "http://registry", nil) if err != nil { t.Fatalf("failed to create request: %v", err) diff --git a/src/common/utils/test/adminserver.go b/src/common/utils/test/adminserver.go index 18e1b91fd..93621d0fc 100644 --- a/src/common/utils/test/adminserver.go +++ b/src/common/utils/test/adminserver.go @@ -23,47 +23,53 @@ import ( "github.com/vmware/harbor/src/common/config" ) +var adminServerDefaultConfig = map[string]interface{}{ + config.ExtEndpoint: "host01.com", + config.AUTHMode: config.DBAuth, + config.DatabaseType: "mysql", + config.MySQLHost: "127.0.0.1", + config.MySQLPort: 3306, + config.MySQLUsername: "user01", + config.MySQLPassword: "password", + config.MySQLDatabase: "registry", + config.SQLiteFile: "/tmp/registry.db", + config.SelfRegistration: true, + config.LDAPURL: "ldap://127.0.0.1", + config.LDAPSearchDN: "uid=searchuser,ou=people,dc=mydomain,dc=com", + config.LDAPSearchPwd: "password", + config.LDAPBaseDN: "ou=people,dc=mydomain,dc=com", + config.LDAPUID: "uid", + config.LDAPFilter: "", + config.LDAPScope: 3, + config.LDAPTimeout: 30, + config.TokenServiceURL: "http://token_service", + config.RegistryURL: "http://registry", + config.EmailHost: "127.0.0.1", + config.EmailPort: 25, + config.EmailUsername: "user01", + config.EmailPassword: "password", + config.EmailFrom: "from", + config.EmailSSL: true, + config.EmailIdentity: "", + config.ProjectCreationRestriction: config.ProCrtRestrAdmOnly, + config.VerifyRemoteCert: false, + config.MaxJobWorkers: 3, + config.TokenExpiration: 30, + config.CfgExpiration: 5, + config.JobLogDir: "/var/log/jobs", + config.UseCompressedJS: true, + config.SecretKey: "secret", + config.AdminInitialPassword: "password", +} + // NewAdminserver returns a mock admin server -func NewAdminserver() (*httptest.Server, error) { +func NewAdminserver(config map[string]interface{}) (*httptest.Server, error) { m := []*RequestHandlerMapping{} - b, err := json.Marshal(map[string]interface{}{ - config.DomainName: "host01.com", - config.AUTHMode: config.DBAuth, - config.DatabaseType: "mysql", - config.MySQLHost: "127.0.0.1", - config.MySQLPort: 3306, - config.MySQLUsername: "user01", - config.MySQLPassword: "password", - config.MySQLDatabase: "registry", - config.SQLiteFile: "/tmp/registry.db", - config.SelfRegistration: true, - config.LDAPURL: "ldap://127.0.0.1", - config.LDAPSearchDN: "uid=searchuser,ou=people,dc=mydomain,dc=com", - config.LDAPSearchPwd: "password", - config.LDAPBaseDN: "ou=people,dc=mydomain,dc=com", - config.LDAPUID: "uid", - config.LDAPFilter: "", - config.LDAPScope: 3, - config.LDAPTimeout: 30, - config.TokenServiceURL: "http://token_service", - config.RegistryURL: "http://registry", - config.EmailHost: "127.0.0.1", - config.EmailPort: 25, - config.EmailUsername: "user01", - config.EmailPassword: "password", - config.EmailFrom: "from", - config.EmailSSL: true, - config.EmailIdentity: "", - config.ProjectCreationRestriction: config.ProCrtRestrAdmOnly, - config.VerifyRemoteCert: false, - config.MaxJobWorkers: 3, - config.TokenExpiration: 30, - config.CfgExpiration: 5, - config.JobLogDir: "/var/log/jobs", - config.UseCompressedJS: true, - config.SecretKey: "secret", - config.AdminInitialPassword: "password", - }) + if config == nil { + config = adminServerDefaultConfig + } + + b, err := json.Marshal(config) if err != nil { return nil, err } diff --git a/src/jobservice/config/config.go b/src/jobservice/config/config.go index e4ba0f9b1..164f07f91 100644 --- a/src/jobservice/config/config.go +++ b/src/jobservice/config/config.go @@ -120,3 +120,17 @@ func SecretKey() (string, error) { func UISecret() string { return os.Getenv("UI_SECRET") } + +// ExtEndpoint ... +func ExtEndpoint() (string, error) { + cfg, err := mg.Get() + if err != nil { + return "", err + } + return cfg[comcfg.ExtEndpoint].(string), nil +} + +// InternalTokenServiceEndpoint ... +func InternalTokenServiceEndpoint() string { + return "http://ui/service/token" +} diff --git a/src/jobservice/config/config_test.go b/src/jobservice/config/config_test.go index 0abcb1655..c70af060c 100644 --- a/src/jobservice/config/config_test.go +++ b/src/jobservice/config/config_test.go @@ -24,7 +24,7 @@ import ( // test functions under package jobservice/config func TestConfig(t *testing.T) { - server, err := test.NewAdminserver() + server, err := test.NewAdminserver(nil) if err != nil { t.Fatalf("failed to create a mock admin server: %v", err) } @@ -53,8 +53,6 @@ func TestConfig(t *testing.T) { t.Fatalf("failed to get max job workers: %v", err) } - LocalUIURL() - if _, err := LocalRegURL(); err != nil { t.Fatalf("failed to get registry URL: %v", err) } @@ -67,5 +65,11 @@ func TestConfig(t *testing.T) { t.Fatalf("failed to get secret key: %v", err) } - UISecret() + if len(InternalTokenServiceEndpoint()) == 0 { + t.Error("the internal token service endpoint is null") + } + + if _, err := ExtEndpoint(); err != nil { + t.Fatalf("failed to get ext endpoint: %v", err) + } } diff --git a/src/jobservice/replication/transfer.go b/src/jobservice/replication/transfer.go index db70003cc..970de022f 100644 --- a/src/jobservice/replication/transfer.go +++ b/src/jobservice/replication/transfer.go @@ -33,6 +33,7 @@ import ( "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" + "github.com/vmware/harbor/src/jobservice/config" ) const ( @@ -137,7 +138,7 @@ func (i *Initializer) enter() (string, error) { c := &http.Cookie{Name: models.UISecretCookie, Value: i.srcSecret} srcCred := auth.NewCookieCredential(c) srcClient, err := newRepositoryClient(i.srcURL, i.insecure, srcCred, - i.repository, "repository", i.repository, "pull", "push", "*") + config.InternalTokenServiceEndpoint(), i.repository, "repository", i.repository, "pull", "push", "*") if err != nil { i.logger.Errorf("an error occurred while creating source repository client: %v", err) return "", err @@ -146,7 +147,7 @@ func (i *Initializer) enter() (string, error) { dstCred := auth.NewBasicAuthCredential(i.dstUsr, i.dstPwd) dstClient, err := newRepositoryClient(i.dstURL, i.insecure, dstCred, - i.repository, "repository", i.repository, "pull", "push", "*") + "", i.repository, "repository", i.repository, "pull", "push", "*") if err != nil { i.logger.Errorf("an error occurred while creating destination repository client: %v", err) return "", err @@ -457,10 +458,12 @@ func (m *ManifestPusher) enter() (string, error) { return StatePullManifest, nil } -func newRepositoryClient(endpoint string, insecure bool, credential auth.Credential, repository, scopeType, scopeName string, +func newRepositoryClient(endpoint string, insecure bool, credential auth.Credential, + tokenServiceEndpoint, repository, scopeType, scopeName string, scopeActions ...string) (*registry.Repository, error) { - authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, scopeType, scopeName, scopeActions...) + authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, + tokenServiceEndpoint, scopeType, scopeName, scopeActions...) store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer) if err != nil { diff --git a/src/ui/api/config.go b/src/ui/api/config.go index 89f5371a0..98657f18a 100644 --- a/src/ui/api/config.go +++ b/src/ui/api/config.go @@ -119,7 +119,14 @@ func (c *ConfigAPI) Put() { } } - if err := validateCfg(cfg); err != nil { + isSysErr, err := validateCfg(cfg) + + if err != nil { + if isSysErr { + log.Errorf("failed to validate configurations: %v", err) + c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + c.CustomAbort(http.StatusBadRequest, err.Error()) } @@ -162,42 +169,68 @@ func (c *ConfigAPI) Put() { } } -func validateCfg(c map[string]string) error { +func validateCfg(c map[string]string) (bool, error) { + isSysErr := false + + mode, err := config.AuthMode() + if err != nil { + isSysErr = true + return isSysErr, err + } + if value, ok := c[comcfg.AUTHMode]; ok { if value != comcfg.DBAuth && value != comcfg.LDAPAuth { - return fmt.Errorf("invalid %s, shoud be %s or %s", comcfg.AUTHMode, comcfg.DBAuth, comcfg.LDAPAuth) + return isSysErr, fmt.Errorf("invalid %s, shoud be %s or %s", comcfg.AUTHMode, comcfg.DBAuth, comcfg.LDAPAuth) + } + mode = value + } + + if mode == comcfg.LDAPAuth { + ldap, err := config.LDAP() + if err != nil { + isSysErr = true + return isSysErr, err } - if value == comcfg.LDAPAuth { + if len(ldap.URL) == 0 { if _, ok := c[comcfg.LDAPURL]; !ok { - return fmt.Errorf("%s is missing", comcfg.LDAPURL) + return isSysErr, fmt.Errorf("%s is missing", comcfg.LDAPURL) } + } + + if len(ldap.BaseDN) == 0 { if _, ok := c[comcfg.LDAPBaseDN]; !ok { - return fmt.Errorf("%s is missing", comcfg.LDAPBaseDN) + return isSysErr, fmt.Errorf("%s is missing", comcfg.LDAPBaseDN) } + } + if len(ldap.UID) == 0 { if _, ok := c[comcfg.LDAPUID]; !ok { - return fmt.Errorf("%s is missing", comcfg.LDAPUID) + return isSysErr, fmt.Errorf("%s is missing", comcfg.LDAPUID) } + } + if ldap.Scope == 0 { if _, ok := c[comcfg.LDAPScope]; !ok { - return fmt.Errorf("%s is missing", comcfg.LDAPScope) + return isSysErr, fmt.Errorf("%s is missing", comcfg.LDAPScope) } } } + log.Infof("===========%v", c) + if ldapURL, ok := c[comcfg.LDAPURL]; ok && len(ldapURL) == 0 { - return fmt.Errorf("%s is empty", comcfg.LDAPURL) + return isSysErr, fmt.Errorf("%s is empty", comcfg.LDAPURL) } if baseDN, ok := c[comcfg.LDAPBaseDN]; ok && len(baseDN) == 0 { - return fmt.Errorf("%s is empty", comcfg.LDAPBaseDN) + return isSysErr, fmt.Errorf("%s is empty", comcfg.LDAPBaseDN) } if uID, ok := c[comcfg.LDAPUID]; ok && len(uID) == 0 { - return fmt.Errorf("%s is empty", comcfg.LDAPUID) + return isSysErr, fmt.Errorf("%s is empty", comcfg.LDAPUID) } if scope, ok := c[comcfg.LDAPScope]; ok && scope != comcfg.LDAPScopeBase && scope != comcfg.LDAPScopeOnelevel && scope != comcfg.LDAPScopeSubtree { - return fmt.Errorf("invalid %s, should be %s, %s or %s", + return isSysErr, fmt.Errorf("invalid %s, should be %s, %s or %s", comcfg.LDAPScope, comcfg.LDAPScopeBase, comcfg.LDAPScopeOnelevel, @@ -205,41 +238,41 @@ func validateCfg(c map[string]string) error { } if timeout, ok := c[comcfg.LDAPTimeout]; ok { if t, err := strconv.Atoi(timeout); err != nil || t < 0 { - return fmt.Errorf("invalid %s", comcfg.LDAPTimeout) + return isSysErr, fmt.Errorf("invalid %s", comcfg.LDAPTimeout) } } if self, ok := c[comcfg.SelfRegistration]; ok && self != "0" && self != "1" { - return fmt.Errorf("%s should be %s or %s", + return isSysErr, fmt.Errorf("%s should be %s or %s", comcfg.SelfRegistration, "0", "1") } if port, ok := c[comcfg.EmailPort]; ok { if p, err := strconv.Atoi(port); err != nil || p < 0 || p > 65535 { - return fmt.Errorf("invalid %s", comcfg.EmailPort) + return isSysErr, fmt.Errorf("invalid %s", comcfg.EmailPort) } } if ssl, ok := c[comcfg.EmailSSL]; ok && ssl != "0" && ssl != "1" { - return fmt.Errorf("%s should be %s or %s", comcfg.EmailSSL, "0", "1") + return isSysErr, fmt.Errorf("%s should be %s or %s", comcfg.EmailSSL, "0", "1") } if crt, ok := c[comcfg.ProjectCreationRestriction]; ok && crt != comcfg.ProCrtRestrEveryone && crt != comcfg.ProCrtRestrAdmOnly { - return fmt.Errorf("invalid %s, should be %s or %s", + return isSysErr, fmt.Errorf("invalid %s, should be %s or %s", comcfg.ProjectCreationRestriction, comcfg.ProCrtRestrAdmOnly, comcfg.ProCrtRestrEveryone) } if verify, ok := c[comcfg.VerifyRemoteCert]; ok && verify != "0" && verify != "1" { - return fmt.Errorf("invalid %s, should be %s or %s", + return isSysErr, fmt.Errorf("invalid %s, should be %s or %s", comcfg.VerifyRemoteCert, "0", "1") } - return nil + return isSysErr, nil } //encode passwords and convert map[string]string to map[string]interface{} diff --git a/src/ui/api/repository.go b/src/ui/api/repository.go index b229c0e96..012ced2d3 100644 --- a/src/ui/api/repository.go +++ b/src/ui/api/repository.go @@ -442,7 +442,9 @@ func newRepositoryClient(endpoint string, insecure bool, username, password, rep scopeActions ...string) (*registry.Repository, error) { credential := auth.NewBasicAuthCredential(username, password) - authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, scopeType, scopeName, scopeActions...) + + authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, + config.InternalTokenServiceEndpoint(), scopeType, scopeName, scopeActions...) store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer) if err != nil { diff --git a/src/ui/api/target.go b/src/ui/api/target.go index becda414e..0fc039f57 100644 --- a/src/ui/api/target.go +++ b/src/ui/api/target.go @@ -340,7 +340,9 @@ func (t *TargetAPI) Delete() { func newRegistryClient(endpoint string, insecure bool, username, password, scopeType, scopeName string, scopeActions ...string) (*registry.Registry, error) { credential := auth.NewBasicAuthCredential(username, password) - authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, scopeType, scopeName, scopeActions...) + + authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, + "", scopeType, scopeName, scopeActions...) store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer) if err != nil { diff --git a/src/ui/config/config.go b/src/ui/config/config.go index 5833b16e7..21bbe288b 100644 --- a/src/ui/config/config.go +++ b/src/ui/config/config.go @@ -114,13 +114,13 @@ func TokenExpiration() (int, error) { return int(cfg[comcfg.TokenExpiration].(float64)), nil } -// DomainName returns the external URL of Harbor: protocal://host:port -func DomainName() (string, error) { +// ExtEndpoint returns the external URL of Harbor: protocal://host:port +func ExtEndpoint() (string, error) { cfg, err := mg.Get() if err != nil { return "", err } - return cfg[comcfg.DomainName].(string), nil + return cfg[comcfg.ExtEndpoint].(string), nil } // SecretKey returns the secret key to encrypt the password of target @@ -155,6 +155,11 @@ func InternalJobServiceURL() string { return "http://jobservice" } +// InternalTokenServiceEndpoint returns token service endpoint for internal communication between Harbor containers +func InternalTokenServiceEndpoint() string { + return "http://ui/service/token" +} + // InitialAdminPassword returns the initial password for administrator func InitialAdminPassword() (string, error) { cfg, err := mg.Get() diff --git a/src/ui/config/config_test.go b/src/ui/config/config_test.go index 4846dd952..e06798c52 100644 --- a/src/ui/config/config_test.go +++ b/src/ui/config/config_test.go @@ -23,7 +23,7 @@ import ( // test functions under package ui/config func TestConfig(t *testing.T) { - server, err := test.NewAdminserver() + server, err := test.NewAdminserver(nil) if err != nil { t.Fatalf("failed to create a mock admin server: %v", err) } @@ -68,7 +68,7 @@ func TestConfig(t *testing.T) { t.Fatalf("failed to get token expiration: %v", err) } - if _, err := DomainName(); err != nil { + if _, err := ExtEndpoint(); err != nil { t.Fatalf("failed to get domain name: %v", err) } @@ -84,9 +84,17 @@ func TestConfig(t *testing.T) { t.Fatalf("failed to get registry URL: %v", err) } - InternalJobServiceURL() + if len(InternalJobServiceURL()) == 0 { + t.Error("the internal job service url is null") + } - InitialAdminPassword() + if len(InternalTokenServiceEndpoint()) == 0 { + t.Error("the internal token service endpoint is null") + } + + if _, err := InitialAdminPassword(); err != nil { + t.Fatalf("failed to get initial admin password: %v", err) + } if _, err := OnlyAdminCreateProject(); err != nil { t.Fatalf("failed to get onldy admin create project: %v", err) @@ -103,6 +111,4 @@ func TestConfig(t *testing.T) { if _, err := Database(); err != nil { t.Fatalf("failed to get database: %v", err) } - - UISecret() } diff --git a/src/ui/controllers/password.go b/src/ui/controllers/password.go index 786b010a2..60a3140ee 100644 --- a/src/ui/controllers/password.go +++ b/src/ui/controllers/password.go @@ -49,7 +49,7 @@ func (cc *CommonController) SendEmail() { message := new(bytes.Buffer) - harborURL, err := config.DomainName() + harborURL, err := config.ExtEndpoint() if err != nil { log.Errorf("failed to get domain name: %v", err) cc.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) diff --git a/src/ui/controllers/repository.go b/src/ui/controllers/repository.go index 0fddff097..a3ada104d 100644 --- a/src/ui/controllers/repository.go +++ b/src/ui/controllers/repository.go @@ -15,7 +15,7 @@ type RepositoryController struct { // Get renders repository page func (rc *RepositoryController) Get() { - url, err := config.DomainName() + url, err := config.ExtEndpoint() if err != nil { log.Errorf("failed to get domain name: %v", err) rc.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) diff --git a/src/ui/service/token/authutils.go b/src/ui/service/token/authutils.go index 3bda33ca4..8c14df5d6 100644 --- a/src/ui/service/token/authutils.go +++ b/src/ui/service/token/authutils.go @@ -84,7 +84,7 @@ func FilterAccess(username string, a *token.ResourceActions) { repoLength := len(repoSplit) if repoLength > 1 { //Only check the permission when the requested image has a namespace, i.e. project var projectName string - registryURL, err := config.DomainName() + registryURL, err := config.ExtEndpoint() if err != nil { log.Errorf("failed to get domain name: %v", err) return