From d6b4330cc848c3987b902320ba75ba6285db9b96 Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Thu, 29 Jun 2017 23:54:39 +0800 Subject: [PATCH 1/2] create a global project manager --- make/dev/docker-compose.yml | 1 + make/docker-compose.tpl | 1 + src/ui/config/config.go | 24 +++++- src/ui/filter/security.go | 17 +++-- src/ui/projectmanager/pms/pm.go | 36 +++++---- src/ui/projectmanager/pms/pm_test.go | 32 ++++---- src/ui/projectmanager/pms/token.go | 83 +++++++++++++++++++++ src/ui/projectmanager/pms/token_test.go | 98 +++++++++++++++++++++++++ src/ui/proxy/interceptor_test.go | 3 +- src/ui/proxy/interceptors.go | 3 +- src/ui/service/notification.go | 5 +- 11 files changed, 258 insertions(+), 45 deletions(-) create mode 100644 src/ui/projectmanager/pms/token.go create mode 100644 src/ui/projectmanager/pms/token_test.go diff --git a/make/dev/docker-compose.yml b/make/dev/docker-compose.yml index 6f54d5757..0a5b4a1b3 100644 --- a/make/dev/docker-compose.yml +++ b/make/dev/docker-compose.yml @@ -83,6 +83,7 @@ services: - ./common/config/ui/private_key.pem:/etc/ui/private_key.pem:z - /data/secretkey:/etc/ui/key:z - /data/ca_download/:/etc/ui/ca/:z + - /data/token:/etc/ui/token:z networks: - harbor depends_on: diff --git a/make/docker-compose.tpl b/make/docker-compose.tpl index 22eba44f7..697948876 100644 --- a/make/docker-compose.tpl +++ b/make/docker-compose.tpl @@ -77,6 +77,7 @@ services: - ./common/config/ui/private_key.pem:/etc/ui/private_key.pem:z - /data/secretkey:/etc/ui/key:z - /data/ca_download/:/etc/ui/ca/:z + - /data/token:/etc/ui/token:z networks: - harbor depends_on: diff --git a/src/ui/config/config.go b/src/ui/config/config.go index 0b139eb48..de14f12f4 100644 --- a/src/ui/config/config.go +++ b/src/ui/config/config.go @@ -15,7 +15,9 @@ package config import ( + "crypto/tls" "fmt" + "net/http" "os" "strings" @@ -32,8 +34,9 @@ import ( ) const ( - defaultKeyPath string = "/etc/ui/key" - secretCookieName string = "secret" + defaultKeyPath string = "/etc/ui/key" + defaultTokenFilePath string = "/etc/ui/token" + secretCookieName string = "secret" ) var ( @@ -45,6 +48,9 @@ var ( GlobalProjectMgr projectmanager.ProjectManager mg *comcfg.Manager keyProvider comcfg.KeyProvider + // AdmiralClient is initialized only under integration deploy mode + // and can be passed to project manager as a parameter + AdmiralClient *http.Client ) // Init configurations @@ -104,9 +110,19 @@ func initProjectManager() { } // integration with admiral - // TODO create project manager based on pms using service account log.Info("initializing the project manager based on PMS...") - GlobalProjectMgr = pms.NewProjectManager(AdmiralEndpoint(), "") + // TODO read ca/cert file and pass it to the TLS config + AdminserverClient := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + }, + } + GlobalProjectMgr = pms.NewProjectManager(AdminserverClient, + AdmiralEndpoint(), &pms.FileTokenReader{ + Path: defaultTokenFilePath, + }) } // Load configurations diff --git a/src/ui/filter/security.go b/src/ui/filter/security.go index 990c7d5b8..6b56ec3bc 100644 --- a/src/ui/filter/security.go +++ b/src/ui/filter/security.go @@ -51,8 +51,8 @@ func Init() { if config.WithAdmiral() { reqCtxModifiers = []ReqCtxModifier{ &secretReqCtxModifier{config.SecretStore}, - &basicAuthReqCtxModifier{}, &tokenReqCtxModifier{}, + &basicAuthReqCtxModifier{}, &unauthorizedReqCtxModifier{}} return } @@ -131,15 +131,14 @@ func (b *basicAuthReqCtxModifier) Modify(ctx *beegoctx.Context) bool { if config.WithAdmiral() { // integration with admiral - token, authCtx, err := authcontext.Login(config.AdmiralEndpoint(), username, password) + _, authCtx, err := authcontext.Login(config.AdmiralEndpoint(), username, password) if err != nil { log.Errorf("failed to authenticate %s: %v", username, err) return false } - log.Debug("creating PMS project manager...") - pm = pms.NewProjectManager(config.AdmiralEndpoint(), token) - + log.Debug("using glocal project manager...") + pm = config.GlobalProjectMgr log.Debug("creating admiral security context...") securCtx = admiral.NewSecurityContext(authCtx, pm) } else { @@ -211,7 +210,10 @@ func (t *tokenReqCtxModifier) Modify(ctx *beegoctx.Context) bool { } log.Debug("creating PMS project manager...") - pm := pms.NewProjectManager(config.AdmiralEndpoint(), token) + pm := pms.NewProjectManager(config.AdmiralClient, + config.AdmiralEndpoint(), &pms.RawTokenReader{ + Token: token, + }) log.Debug("creating admiral security context...") securCtx := admiral.NewSecurityContext(authContext, pm) setSecurCtxAndPM(ctx.Request, securCtx, pm) @@ -230,7 +232,8 @@ func (u *unauthorizedReqCtxModifier) Modify(ctx *beegoctx.Context) bool { if config.WithAdmiral() { // integration with admiral log.Debug("creating PMS project manager...") - pm = pms.NewProjectManager(config.AdmiralEndpoint(), "") + pm = pms.NewProjectManager(config.AdmiralClient, + config.AdmiralEndpoint(), nil) log.Debug("creating admiral security context...") securCtx = admiral.NewSecurityContext(nil, pm) } else { diff --git a/src/ui/projectmanager/pms/pm.go b/src/ui/projectmanager/pms/pm.go index 174fb961e..4f8479797 100644 --- a/src/ui/projectmanager/pms/pm.go +++ b/src/ui/projectmanager/pms/pm.go @@ -32,14 +32,12 @@ import ( "github.com/vmware/harbor/src/common/utils/log" ) -var transport = &http.Transport{} - // ProjectManager implements projectmanager.ProjecdtManager interface // base on project management service type ProjectManager struct { - endpoint string - token string - client *http.Client + client *http.Client + endpoint string + tokenReader TokenReader } type user struct { @@ -58,13 +56,12 @@ type project struct { } // NewProjectManager returns an instance of ProjectManager -func NewProjectManager(endpoint, token string) *ProjectManager { +func NewProjectManager(client *http.Client, endpoint string, + tokenReader TokenReader) *ProjectManager { return &ProjectManager{ - endpoint: strings.TrimRight(endpoint, "/"), - token: token, - client: &http.Client{ - Transport: transport, - }, + client: client, + endpoint: strings.TrimRight(endpoint, "/"), + tokenReader: tokenReader, } } @@ -307,7 +304,7 @@ func (p *ProjectManager) GetPublic() ([]*models.Project, error) { // GetByMember ... func (p *ProjectManager) GetByMember(username string) ([]*models.Project, error) { projects := []*models.Project{} - ctx, err := authcontext.GetAuthCtxOfUser(p.endpoint, p.token, username) + ctx, err := authcontext.GetAuthCtxOfUser(p.endpoint, p.getToken(), username) if err != nil { return projects, err } @@ -425,7 +422,7 @@ func (p *ProjectManager) send(method, path string, body io.Reader) ([]byte, erro return nil, err } - req.Header.Add("x-xenon-auth-token", p.token) + req.Header.Add("x-xenon-auth-token", p.getToken()) url := req.URL.String() @@ -452,3 +449,16 @@ func (p *ProjectManager) send(method, path string, body io.Reader) ([]byte, erro return b, nil } + +func (p *ProjectManager) getToken() string { + if p.tokenReader == nil { + return "" + } + + token, err := p.tokenReader.ReadToken() + if err != nil { + token = "" + log.Errorf("failed to read token: %v", err) + } + return token +} diff --git a/src/ui/projectmanager/pms/pm_test.go b/src/ui/projectmanager/pms/pm_test.go index 17ded36af..aa14d6fde 100644 --- a/src/ui/projectmanager/pms/pm_test.go +++ b/src/ui/projectmanager/pms/pm_test.go @@ -15,6 +15,7 @@ package pms import ( + "net/http" "sort" "testing" @@ -24,8 +25,11 @@ import ( ) var ( - endpoint = "http://127.0.0.1:8282" - token = "" + client = http.DefaultClient + endpoint = "http://127.0.0.1:8282" + tokenReader = &RawTokenReader{ + Token: "", + } ) func TestConvert(t *testing.T) { @@ -177,7 +181,7 @@ func TestParse(t *testing.T) { } func TestGet(t *testing.T) { - pm := NewProjectManager(endpoint, token) + pm := NewProjectManager(client, endpoint, tokenReader) name := "project_for_test_get" id, err := pm.Create(&models.Project{ Name: name, @@ -211,7 +215,7 @@ func TestGet(t *testing.T) { } func TestIsPublic(t *testing.T) { - pm := NewProjectManager(endpoint, token) + pm := NewProjectManager(client, endpoint, tokenReader) // invalid input type public, err := pm.IsPublic([]string{}) @@ -259,7 +263,7 @@ func TestIsPublic(t *testing.T) { } func TestExist(t *testing.T) { - pm := NewProjectManager(endpoint, token) + pm := NewProjectManager(client, endpoint, tokenReader) // invalid input type exist, err := pm.Exist([]string{}) @@ -289,7 +293,7 @@ func TestExist(t *testing.T) { } func TestGetRoles(t *testing.T) { - pm := NewProjectManager(endpoint, token) + pm := NewProjectManager(client, endpoint, tokenReader) // nil username, nil project roles, err := pm.GetRoles("", nil) @@ -316,7 +320,7 @@ func TestGetRoles(t *testing.T) { } func TestGetPublic(t *testing.T) { - pm := NewProjectManager(endpoint, token) + pm := NewProjectManager(client, endpoint, tokenReader) projects, err := pm.GetPublic() assert.Nil(t, nil) @@ -350,7 +354,7 @@ func TestGetByMember(t *testing.T) { } func TestCreate(t *testing.T) { - pm := NewProjectManager(endpoint, token) + pm := NewProjectManager(client, endpoint, tokenReader) name := "project_for_test_create" id, err := pm.Create(&models.Project{ @@ -375,7 +379,7 @@ func TestCreate(t *testing.T) { } func TestDelete(t *testing.T) { - pm := NewProjectManager(endpoint, token) + pm := NewProjectManager(client, endpoint, tokenReader) // non-exist project err := pm.Delete(int64(0)) @@ -401,13 +405,13 @@ func TestDelete(t *testing.T) { } func TestUpdate(t *testing.T) { - pm := NewProjectManager(endpoint, token) + pm := NewProjectManager(client, endpoint, tokenReader) err := pm.Update(nil, nil) assert.NotNil(t, err) } func TestGetAll(t *testing.T) { - pm := NewProjectManager(endpoint, token) + pm := NewProjectManager(client, endpoint, tokenReader) name1 := "project_for_test_get_all_01" id1, err := pm.Create(&models.Project{ @@ -471,7 +475,7 @@ func TestGetAll(t *testing.T) { } func TestGetTotal(t *testing.T) { - pm := NewProjectManager(endpoint, token) + pm := NewProjectManager(client, endpoint, tokenReader) total1, err := pm.GetTotal(nil) require.Nil(t, err) @@ -489,13 +493,13 @@ func TestGetTotal(t *testing.T) { } func TestGetHasReadPerm(t *testing.T) { - pm := NewProjectManager(endpoint, token) + pm := NewProjectManager(client, endpoint, tokenReader) _, err := pm.GetHasReadPerm() assert.NotNil(t, err) } func delete(t *testing.T, id int64) { - pm := NewProjectManager(endpoint, token) + pm := NewProjectManager(client, endpoint, tokenReader) if err := pm.Delete(id); err != nil { t.Logf("failed to delete project %d: %v", id, err) } diff --git a/src/ui/projectmanager/pms/token.go b/src/ui/projectmanager/pms/token.go new file mode 100644 index 000000000..6b7b7a094 --- /dev/null +++ b/src/ui/projectmanager/pms/token.go @@ -0,0 +1,83 @@ +// 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 pms + +import ( + "bufio" + "fmt" + "io" + "os" + "strings" +) + +const ( + key = "access_token" +) + +// TokenReader is an interface used to wrap the way how to get token +type TokenReader interface { + // ReadToken reads token + ReadToken() (string, error) +} + +// RawTokenReader just returns the token contained by field Token +type RawTokenReader struct { + Token string +} + +// ReadToken ... +func (r *RawTokenReader) ReadToken() (string, error) { + return r.Token, nil +} + +// FileTokenReader reads token from file +type FileTokenReader struct { + Path string +} + +// ReadToken ... +func (f *FileTokenReader) ReadToken() (string, error) { + file, err := os.Open(f.Path) + if err != nil { + return "", err + } + + return readToken(file) +} + +func readToken(reader io.Reader) (string, error) { + if reader == nil { + return "", fmt.Errorf("reader is nil") + } + + r := bufio.NewReader(reader) + for { + line, _, err := r.ReadLine() + if err != nil { + if err == io.EOF { + err = fmt.Errorf("%s not found", key) + } + return "", err + } + + strs := strings.SplitN(string(line), "=", 2) + if len(strs) != 2 { + continue + } + if strs[0] == key { + return strs[1], nil + } + } +} diff --git a/src/ui/projectmanager/pms/token_test.go b/src/ui/projectmanager/pms/token_test.go new file mode 100644 index 000000000..04c1ac6e2 --- /dev/null +++ b/src/ui/projectmanager/pms/token_test.go @@ -0,0 +1,98 @@ +// 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 pms + +import ( + "bytes" + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRawTokenReader(t *testing.T) { + raw := "token" + reader := &RawTokenReader{ + Token: raw, + } + + token, err := reader.ReadToken() + require.Nil(t, err) + assert.Equal(t, raw, token) +} + +func TestReadToken(t *testing.T) { + // nil reader + _, err := readToken(nil) + assert.NotNil(t, err) + + // empty + reader := bytes.NewReader([]byte{}) + _, err = readToken(reader) + assert.NotNil(t, err) + + // contains no "access_token" + content := "key1=value\nkey2=value2" + reader = bytes.NewReader([]byte(content)) + _, err = readToken(reader) + assert.NotNil(t, err) + + // contains "access_token" but no "=" + content = "access_token value\nkey2=value2" + reader = bytes.NewReader([]byte(content)) + _, err = readToken(reader) + assert.NotNil(t, err) + + // contains "access_token" and "=", but no value + content = "access_token=\nkey2=value2" + reader = bytes.NewReader([]byte(content)) + token, err := readToken(reader) + require.Nil(t, err) + assert.Len(t, token, 0) + + // valid "access_token" + content = "access_token=token\nkey2=value2" + reader = bytes.NewReader([]byte(content)) + token, err = readToken(reader) + require.Nil(t, err) + assert.Equal(t, "token", token) +} + +func TestFileTokenReader(t *testing.T) { + // file not exist + path := "/tmp/not_exist_file" + reader := &FileTokenReader{ + Path: path, + } + + _, err := reader.ReadToken() + assert.NotNil(t, err) + + // file exist + path = "/tmp/exist_file" + err = ioutil.WriteFile(path, []byte("access_token=token"), 0x0666) + require.Nil(t, err) + defer os.Remove(path) + + reader = &FileTokenReader{ + Path: path, + } + + token, err := reader.ReadToken() + require.Nil(t, err) + assert.Equal(t, "token", token) +} diff --git a/src/ui/proxy/interceptor_test.go b/src/ui/proxy/interceptor_test.go index 1544ef4fd..bd739fb3a 100644 --- a/src/ui/proxy/interceptor_test.go +++ b/src/ui/proxy/interceptor_test.go @@ -102,7 +102,8 @@ func TestEnvPolicyChecker(t *testing.T) { } func TestPMSPolicyChecker(t *testing.T) { - pm := pms.NewProjectManager(admiralEndpoint, token) + pm := pms.NewProjectManager(http.DefaultClient, + admiralEndpoint, nil) name := "project_for_test_get_true" id, err := pm.Create(&models.Project{ Name: name, diff --git a/src/ui/proxy/interceptors.go b/src/ui/proxy/interceptors.go index 536be11f5..49f4bc64c 100644 --- a/src/ui/proxy/interceptors.go +++ b/src/ui/proxy/interceptors.go @@ -6,7 +6,6 @@ import ( "github.com/vmware/harbor/src/common/utils/notary" "github.com/vmware/harbor/src/ui/config" "github.com/vmware/harbor/src/ui/projectmanager" - "github.com/vmware/harbor/src/ui/projectmanager/pms" "context" "fmt" @@ -92,7 +91,7 @@ func newPMSPolicyChecker(pm projectmanager.ProjectManager) policyChecker { // TODO: Get project manager with PM factory. func getPolicyChecker() policyChecker { if config.WithAdmiral() { - return newPMSPolicyChecker(pms.NewProjectManager(config.AdmiralEndpoint(), "")) + return newPMSPolicyChecker(config.GlobalProjectMgr) } return EnvChecker } diff --git a/src/ui/service/notification.go b/src/ui/service/notification.go index cf1a846b5..6aef9938f 100644 --- a/src/ui/service/notification.go +++ b/src/ui/service/notification.go @@ -27,7 +27,6 @@ import ( "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/ui/api" "github.com/vmware/harbor/src/ui/config" - "github.com/vmware/harbor/src/ui/projectmanager/pms" uiutils "github.com/vmware/harbor/src/ui/utils" ) @@ -161,9 +160,7 @@ func autoScanEnabled(projectName string) bool { return false } if config.WithAdmiral() { - //TODO get a project manager based on service account. - var pm *pms.ProjectManager = pms.NewProjectManager("", "") - p, err := pm.Get(projectName) + p, err := config.GlobalProjectMgr.Get(projectName) if err != nil { log.Warningf("failed to get project, error: %v", err) return false From bdbdb383ac77af600a0ee774cdf56d692dbe5458 Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Fri, 30 Jun 2017 16:21:55 +0800 Subject: [PATCH 2/2] update --- make/dev/docker-compose.yml | 2 +- make/docker-compose.tpl | 2 +- src/ui/config/config.go | 2 +- src/ui/projectmanager/pms/token.go | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/make/dev/docker-compose.yml b/make/dev/docker-compose.yml index 0a5b4a1b3..17ac4c552 100644 --- a/make/dev/docker-compose.yml +++ b/make/dev/docker-compose.yml @@ -83,7 +83,7 @@ services: - ./common/config/ui/private_key.pem:/etc/ui/private_key.pem:z - /data/secretkey:/etc/ui/key:z - /data/ca_download/:/etc/ui/ca/:z - - /data/token:/etc/ui/token:z + - /data/service_token:/etc/ui/service_token:z networks: - harbor depends_on: diff --git a/make/docker-compose.tpl b/make/docker-compose.tpl index 697948876..8994ba776 100644 --- a/make/docker-compose.tpl +++ b/make/docker-compose.tpl @@ -77,7 +77,7 @@ services: - ./common/config/ui/private_key.pem:/etc/ui/private_key.pem:z - /data/secretkey:/etc/ui/key:z - /data/ca_download/:/etc/ui/ca/:z - - /data/token:/etc/ui/token:z + - /data/service_token:/etc/ui/service_token:z networks: - harbor depends_on: diff --git a/src/ui/config/config.go b/src/ui/config/config.go index ca4a61e45..f4e7a394e 100644 --- a/src/ui/config/config.go +++ b/src/ui/config/config.go @@ -36,7 +36,7 @@ import ( const ( defaultKeyPath string = "/etc/ui/key" - defaultTokenFilePath string = "/etc/ui/token" + defaultTokenFilePath string = "/etc/ui/service_token" secretCookieName string = "secret" ) diff --git a/src/ui/projectmanager/pms/token.go b/src/ui/projectmanager/pms/token.go index 6b7b7a094..42ddbe1bf 100644 --- a/src/ui/projectmanager/pms/token.go +++ b/src/ui/projectmanager/pms/token.go @@ -53,6 +53,7 @@ func (f *FileTokenReader) ReadToken() (string, error) { if err != nil { return "", err } + defer file.Close() return readToken(file) }