diff --git a/src/adminserver/api/cfg.go b/src/adminserver/api/cfg.go index 94f44e87d..285f85d8b 100644 --- a/src/adminserver/api/cfg.go +++ b/src/adminserver/api/cfg.go @@ -20,13 +20,13 @@ import ( "io/ioutil" "net/http" - cfg "github.com/vmware/harbor/src/adminserver/systemcfg" + "github.com/vmware/harbor/src/adminserver/systemcfg" "github.com/vmware/harbor/src/common/utils/log" ) // ListCfgs lists configurations func ListCfgs(w http.ResponseWriter, r *http.Request) { - cfg, err := cfg.GetSystemCfg() + cfg, err := systemcfg.CfgStore.Read() if err != nil { log.Errorf("failed to get system configurations: %v", err) handleInternalServerError(w) @@ -54,7 +54,7 @@ func UpdateCfgs(w http.ResponseWriter, r *http.Request) { return } - if err = cfg.UpdateSystemCfg(m); err != nil { + if err = systemcfg.CfgStore.Write(m); err != nil { log.Errorf("failed to update system configurations: %v", err) handleInternalServerError(w) return @@ -63,9 +63,16 @@ func UpdateCfgs(w http.ResponseWriter, r *http.Request) { // ResetCfgs resets configurations from environment variables func ResetCfgs(w http.ResponseWriter, r *http.Request) { - if err := cfg.Reset(); err != nil { + cfgs := map[string]interface{}{} + if err := systemcfg.LoadFromEnv(cfgs, true); err != nil { log.Errorf("failed to reset system configurations: %v", err) handleInternalServerError(w) return } + + if err := systemcfg.CfgStore.Write(cfgs); err != nil { + log.Errorf("failed to write system configurations to storage: %v", err) + handleInternalServerError(w) + return + } } diff --git a/src/adminserver/api/cfg_test.go b/src/adminserver/api/cfg_test.go index 5e07c4b1e..92ebaba4f 100644 --- a/src/adminserver/api/cfg_test.go +++ b/src/adminserver/api/cfg_test.go @@ -18,6 +18,8 @@ package api import ( "bytes" "encoding/json" + "errors" + "fmt" "io" "io/ioutil" "net/http" @@ -25,189 +27,132 @@ import ( "os" "testing" + "github.com/stretchr/testify/assert" "github.com/vmware/harbor/src/adminserver/systemcfg" "github.com/vmware/harbor/src/common" - "github.com/vmware/harbor/src/common/utils/test" ) -func TestConfigAPI(t *testing.T) { - configPath := "/tmp/config.json" - secretKeyPath := "/tmp/secretkey" +type fakeCfgStore struct { + cfgs map[string]interface{} + err error +} - _, err := test.GenerateKey(secretKeyPath) - if err != nil { - t.Errorf("failed to generate secret key: %v", err) - return +func (f *fakeCfgStore) Name() string { + return "fake" +} + +func (f *fakeCfgStore) Read() (map[string]interface{}, error) { + return f.cfgs, f.err +} + +func (f *fakeCfgStore) Write(cfgs map[string]interface{}) error { + f.cfgs = cfgs + return f.err +} + +func TestListCfgs(t *testing.T) { + // 500 + systemcfg.CfgStore = &fakeCfgStore{ + cfgs: nil, + err: errors.New("error"), } - defer os.Remove(secretKeyPath) - - secret := "secret" - envs := map[string]string{ - "AUTH_MODE": common.DBAuth, - "JSON_CFG_STORE_PATH": configPath, - "KEY_PATH": secretKeyPath, - "UI_SECRET": secret, - "MYSQL_PORT": "3306", - "TOKEN_EXPIRATION": "30", - "CFG_EXPIRATION": "5", - "MAX_JOB_WORKERS": "3", - "LDAP_SCOPE": "3", - "LDAP_TIMEOUT": "30", - "EMAIL_PORT": "25", - "MYSQL_PWD": "", - "LDAP_SEARCH_PWD": "", - "EMAIL_PWD": "", - "HARBOR_ADMIN_PASSWORD": "", - } - - for k, v := range envs { - if err := os.Setenv(k, v); err != nil { - t.Errorf("failed to set env %s: %v", k, err) - return - } - } - defer os.Remove(configPath) - - if err := systemcfg.Init(); err != nil { - t.Errorf("failed to initialize system configurations: %v", err) - return - } - - r, err := http.NewRequest("GET", "", nil) - if err != nil { - t.Errorf("failed to create request: %v", err) - return - } - - r.AddCookie(&http.Cookie{ - Name: "secret", - Value: secret, - }) w := httptest.NewRecorder() - ListCfgs(w, r) - if w.Code != http.StatusOK { - t.Errorf("unexpected status code: %d != %d", w.Code, http.StatusOK) - return - } + ListCfgs(w, nil) + assert.Equal(t, http.StatusInternalServerError, w.Code) - m, err := parse(w.Body) - if err != nil { - t.Errorf("failed to parse response body: %v", err) - return + // 200 + key := "key" + value := "value" + cfgs := map[string]interface{}{ + key: value, } - - scope := int(m[common.LDAPScope].(float64)) - if scope != 3 { - t.Errorf("unexpected ldap scope: %d != %d", scope, 3) - return + systemcfg.CfgStore = &fakeCfgStore{ + cfgs: cfgs, + err: nil, } - - // modify configurations - c := map[string]interface{}{ - common.AUTHMode: common.LDAPAuth, - } - - b, err := json.Marshal(c) - if err != nil { - t.Errorf("failed to marshal configuartions: %v", err) - return - } - w = httptest.NewRecorder() - r, err = http.NewRequest("GET", "", bytes.NewReader(b)) + ListCfgs(w, nil) + assert.Equal(t, http.StatusOK, w.Code) + result, err := parse(w.Body) if err != nil { - t.Errorf("failed to create request: %v", err) - return + t.Fatalf("failed to parse response body: %v", err) + } + assert.Equal(t, value, result[key]) +} + +func TestUpdateCfgs(t *testing.T) { + // 400 + w := httptest.NewRecorder() + r, err := http.NewRequest("", "", bytes.NewReader([]byte{'a'})) + if err != nil { + t.Fatalf("failed to create request: %v", err) } - r.AddCookie(&http.Cookie{ - Name: "secret", - Value: secret, - }) UpdateCfgs(w, r) + assert.Equal(t, http.StatusBadRequest, w.Code) - if w.Code != http.StatusOK { - t.Errorf("unexpected status code: %d != %d", w.Code, http.StatusOK) - return + // 500 + systemcfg.CfgStore = &fakeCfgStore{ + cfgs: nil, + err: errors.New("error"), } - - // confirm the modification is done - r, err = http.NewRequest("GET", "", nil) - if err != nil { - t.Errorf("failed to create request: %v", err) - return - } - r.AddCookie(&http.Cookie{ - Name: "secret", - Value: secret, - }) w = httptest.NewRecorder() - ListCfgs(w, r) - if w.Code != http.StatusOK { - t.Errorf("unexpected status code: %d != %d", w.Code, http.StatusOK) - return - } - - m, err = parse(w.Body) + r, err = http.NewRequest("", "", bytes.NewBufferString("{}")) if err != nil { - t.Errorf("failed to parse response body: %v", err) - return + t.Fatalf("failed to create request: %v", err) } - mode := m[common.AUTHMode].(string) - if mode != common.LDAPAuth { - t.Errorf("unexpected auth mode: %s != %s", mode, common.LDAPAuth) - return - } + UpdateCfgs(w, r) + assert.Equal(t, http.StatusInternalServerError, w.Code) - // reset configurations + // 200 + key := "key" + value := "value" + systemcfg.CfgStore = &fakeCfgStore{ + cfgs: nil, + err: nil, + } w = httptest.NewRecorder() - r, err = http.NewRequest("POST", "", nil) + r, err = http.NewRequest("", "", + bytes.NewBufferString(fmt.Sprintf(`{"%s":"%s"}`, key, value))) if err != nil { - t.Errorf("failed to create request: %v", err) - return - } - r.AddCookie(&http.Cookie{ - Name: "secret", - Value: secret, - }) - - ResetCfgs(w, r) - - if w.Code != http.StatusOK { - t.Errorf("unexpected status code: %d != %d", w.Code, http.StatusOK) - return + t.Fatalf("failed to create request: %v", err) } - // confirm the reset - r, err = http.NewRequest("GET", "", nil) - if err != nil { - t.Errorf("failed to create request: %v", err) - return + UpdateCfgs(w, r) + assert.Equal(t, http.StatusOK, w.Code) + +} + +func TestResetCfgs(t *testing.T) { + // 500 + systemcfg.CfgStore = &fakeCfgStore{ + cfgs: nil, + err: errors.New("error"), } - r.AddCookie(&http.Cookie{ - Name: "secret", - Value: secret, - }) + w := httptest.NewRecorder() + + ResetCfgs(w, nil) + assert.Equal(t, http.StatusInternalServerError, w.Code) + + // 200 + os.Clearenv() + key := "LDAP_URL" + value := "ldap://ldap.com" + if err := os.Setenv(key, value); err != nil { + t.Fatalf("failed to set env: %v", err) + } + store := &fakeCfgStore{ + cfgs: nil, + err: nil, + } + systemcfg.CfgStore = store w = httptest.NewRecorder() - ListCfgs(w, r) - if w.Code != http.StatusOK { - t.Errorf("unexpected status code: %d != %d", w.Code, http.StatusOK) - return - } - m, err = parse(w.Body) - if err != nil { - t.Errorf("failed to parse response body: %v", err) - return - } - - mode = m[common.AUTHMode].(string) - if mode != common.DBAuth { - t.Errorf("unexpected auth mode: %s != %s", mode, common.LDAPAuth) - return - } + ResetCfgs(w, nil) + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, value, store.cfgs[common.LDAPURL]) } func parse(reader io.Reader) (map[string]interface{}, error) { diff --git a/src/adminserver/systemcfg/encrypt/encrypt.go b/src/adminserver/systemcfg/encrypt/encrypt.go new file mode 100644 index 000000000..e00f65b93 --- /dev/null +++ b/src/adminserver/systemcfg/encrypt/encrypt.go @@ -0,0 +1,61 @@ +/* + Copyright (c) 2016 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 encrypt + +import ( + comcfg "github.com/vmware/harbor/src/common/config" + "github.com/vmware/harbor/src/common/utils" +) + +// Encryptor encrypts or decrypts a strings +type Encryptor interface { + // Encrypt encrypts plaintext + Encrypt(string) (string, error) + // Decrypt decrypts ciphertext + Decrypt(string) (string, error) +} + +// AESEncryptor uses AES to encrypt or decrypt string +type AESEncryptor struct { + keyProvider comcfg.KeyProvider + keyParams map[string]interface{} +} + +// NewAESEncryptor returns an instance of an AESEncryptor +func NewAESEncryptor(keyProvider comcfg.KeyProvider, + keyParams map[string]interface{}) Encryptor { + return &AESEncryptor{ + keyProvider: keyProvider, + } +} + +// Encrypt ... +func (a *AESEncryptor) Encrypt(plaintext string) (string, error) { + key, err := a.keyProvider.Get(a.keyParams) + if err != nil { + return "", err + } + return utils.ReversibleEncrypt(plaintext, key) +} + +// Decrypt ... +func (a *AESEncryptor) Decrypt(ciphertext string) (string, error) { + key, err := a.keyProvider.Get(a.keyParams) + if err != nil { + return "", err + } + return utils.ReversibleDecrypt(ciphertext, key) +} diff --git a/src/adminserver/systemcfg/encrypt/encrypt_test.go b/src/adminserver/systemcfg/encrypt/encrypt_test.go new file mode 100644 index 000000000..a07d7385e --- /dev/null +++ b/src/adminserver/systemcfg/encrypt/encrypt_test.go @@ -0,0 +1,93 @@ +/* + Copyright (c) 2016 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 encrypt + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" + comcfg "github.com/vmware/harbor/src/common/config" +) + +type fakeKeyProvider struct { + key string + err error +} + +func (f *fakeKeyProvider) Get(params map[string]interface{}) ( + string, error) { + return f.key, f.err +} + +func TestEncrypt(t *testing.T) { + cases := []struct { + plaintext string + keyProvider comcfg.KeyProvider + err bool + }{ + {"", &fakeKeyProvider{"", errors.New("error")}, true}, + {"text", &fakeKeyProvider{"1234567890123456", nil}, false}, + } + + for _, c := range cases { + encrptor := NewAESEncryptor(c.keyProvider, nil) + ciphertext, err := encrptor.Encrypt(c.plaintext) + if c.err { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) + str, err := encrptor.Decrypt(ciphertext) + assert.Nil(t, err) + assert.Equal(t, c.plaintext, str) + } + } +} + +func TestDecrypt(t *testing.T) { + plaintext := "text" + key := "1234567890123456" + + encrptor := NewAESEncryptor(&fakeKeyProvider{ + key: key, + err: nil, + }, nil) + + ciphertext, err := encrptor.Encrypt(plaintext) + if err != nil { + t.Fatalf("failed to encrpt %s: %v", plaintext, err) + } + + cases := []struct { + ciphertext string + keyProvider comcfg.KeyProvider + err bool + }{ + {"", &fakeKeyProvider{"", errors.New("error")}, true}, + {ciphertext, &fakeKeyProvider{key, nil}, false}, + } + + for _, c := range cases { + encrptor := NewAESEncryptor(c.keyProvider, nil) + str, err := encrptor.Decrypt(c.ciphertext) + if c.err { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) + assert.Equal(t, plaintext, str) + } + } +} diff --git a/src/adminserver/systemcfg/store/encrypt/driver.go b/src/adminserver/systemcfg/store/encrypt/driver.go new file mode 100644 index 000000000..79216586f --- /dev/null +++ b/src/adminserver/systemcfg/store/encrypt/driver.go @@ -0,0 +1,98 @@ +/* + Copyright (c) 2016 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 encrypt + +import ( + "github.com/vmware/harbor/src/adminserver/systemcfg/encrypt" + "github.com/vmware/harbor/src/adminserver/systemcfg/store" + "github.com/vmware/harbor/src/common/utils/log" +) + +const ( + name = "encrypt" +) + +// cfgStore wraps a store.Driver with an encryptor +type cfgStore struct { + // attrs need to be encrypted and decrypted + keys []string + encryptor encrypt.Encryptor + store store.Driver +} + +// NewCfgStore returns an instance of cfgStore +// keys are the attrs need to be encrypted or decrypted +func NewCfgStore(encryptor encrypt.Encryptor, + keys []string, store store.Driver) store.Driver { + return &cfgStore{ + keys: keys, + encryptor: encryptor, + store: store, + } +} + +func (c *cfgStore) Name() string { + return name +} + +func (c *cfgStore) Read() (map[string]interface{}, error) { + m, err := c.store.Read() + if err != nil { + return nil, err + } + + for _, key := range c.keys { + v, ok := m[key] + if !ok { + continue + } + + str, ok := v.(string) + if !ok { + log.Warningf("%v is not string, skip decrypt", v) + continue + } + + text, err := c.encryptor.Decrypt(str) + if err != nil { + return nil, err + } + m[key] = text + } + return m, nil +} + +func (c *cfgStore) Write(m map[string]interface{}) error { + for _, key := range c.keys { + v, ok := m[key] + if !ok { + continue + } + + str, ok := v.(string) + if !ok { + log.Warningf("%v is not string, skip encrypt", v) + continue + } + + ciphertext, err := c.encryptor.Encrypt(str) + if err != nil { + return err + } + m[key] = ciphertext + } + return c.store.Write(m) +} diff --git a/src/adminserver/systemcfg/store/encrypt/driver_test.go b/src/adminserver/systemcfg/store/encrypt/driver_test.go new file mode 100644 index 000000000..ad1b4a820 --- /dev/null +++ b/src/adminserver/systemcfg/store/encrypt/driver_test.go @@ -0,0 +1,82 @@ +/* + Copyright (c) 2016 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 encrypt + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type fakeCfgStore struct { + cfgs map[string]interface{} +} + +func (f *fakeCfgStore) Name() string { + return "fake" +} + +func (f *fakeCfgStore) Read() (map[string]interface{}, error) { + return f.cfgs, nil +} + +func (f *fakeCfgStore) Write(cfgs map[string]interface{}) error { + f.cfgs = cfgs + return nil +} + +type fakeEncryptor struct { +} + +func (f *fakeEncryptor) Encrypt(plaintext string) (string, error) { + return "encrypted" + plaintext, nil +} + +func (f *fakeEncryptor) Decrypt(ciphertext string) (string, error) { + return "decrypted" + ciphertext, nil +} + +func TestName(t *testing.T) { + driver := NewCfgStore(nil, nil, nil) + assert.Equal(t, name, driver.Name()) +} + +func TestRead(t *testing.T) { + keys := []string{"key"} + driver := NewCfgStore(&fakeEncryptor{}, keys, &fakeCfgStore{ + cfgs: map[string]interface{}{"key": "value"}, + }) + + cfgs, err := driver.Read() + assert.Nil(t, err) + assert.Equal(t, "decryptedvalue", cfgs["key"]) +} + +func TestWrite(t *testing.T) { + keys := []string{"key"} + store := &fakeCfgStore{ + cfgs: map[string]interface{}{}, + } + driver := NewCfgStore(&fakeEncryptor{}, keys, store) + + cfgs := map[string]interface{}{ + "key": "value", + } + + err := driver.Write(cfgs) + assert.Nil(t, err) + assert.Equal(t, "encryptedvalue", store.cfgs["key"]) +} diff --git a/src/adminserver/systemcfg/systemcfg.go b/src/adminserver/systemcfg/systemcfg.go index 51de78d34..577221a81 100644 --- a/src/adminserver/systemcfg/systemcfg.go +++ b/src/adminserver/systemcfg/systemcfg.go @@ -21,23 +21,24 @@ import ( "strconv" "strings" + enpt "github.com/vmware/harbor/src/adminserver/systemcfg/encrypt" "github.com/vmware/harbor/src/adminserver/systemcfg/store" + "github.com/vmware/harbor/src/adminserver/systemcfg/store/encrypt" "github.com/vmware/harbor/src/adminserver/systemcfg/store/json" "github.com/vmware/harbor/src/common" comcfg "github.com/vmware/harbor/src/common/config" - "github.com/vmware/harbor/src/common/utils" "github.com/vmware/harbor/src/common/utils/log" ) const ( - defaultCfgStoreDriver string = "json" defaultJSONCfgStorePath string = "/etc/adminserver/config.json" defaultKeyPath string = "/etc/adminserver/key" ) var ( - cfgStore store.Driver - keyProvider comcfg.KeyProvider + // CfgStore is a storage driver that configurations + // can be read from and wrote to + CfgStore store.Driver // attrs need to be encrypted or decrypted attrs = []string{ @@ -157,6 +158,9 @@ type parser struct { } func parseStringToInt(str string) (interface{}, error) { + if len(str) == 0 { + return 0, nil + } return strconv.Atoi(str) } @@ -165,80 +169,65 @@ func parseStringToBool(str string) (interface{}, error) { strings.ToLower(str) == "on", nil } -// Init system configurations. Read from config store first, -// if null read from env +// Init system configurations. If env RESET is set or configurations +// read from storage driver is null, load all configurations from env func Init() (err error) { - //init configuation store if err = initCfgStore(); err != nil { return err } - //init key provider - initKeyProvider() + loadAll := false + cfgs := map[string]interface{}{} if os.Getenv("RESET") == "true" { - log.Info("RESET is set, resetting system configurations...") - return Reset() + log.Info("RESET is set, will load all configurations from environment variables") + loadAll = true } - cfg, err := GetSystemCfg() - if err != nil { + if !loadAll { + cfgs, err = CfgStore.Read() + if cfgs == nil { + log.Info("configurations read from storage driver are null, will load them from environment variables") + loadAll = true + cfgs = map[string]interface{}{} + } + } + + if err = LoadFromEnv(cfgs, loadAll); err != nil { return err } - if cfg != nil { - if err = loadFromEnv(cfg, false); err != nil { - return err - } - } else { - log.Info("configurations read from store driver are null, initializing system from environment variables...") - cfg = make(map[string]interface{}) - if err = loadFromEnv(cfg, true); err != nil { - return err - } - } - - //sync configurations into cfg store - log.Info("updating system configurations...") - return UpdateSystemCfg(cfg) + return CfgStore.Write(cfgs) } func initCfgStore() (err error) { - t := os.Getenv("CFG_STORE_DRIVER") - if len(t) == 0 { - t = defaultCfgStoreDriver - } - log.Infof("configuration store driver: %s", t) - - switch t { - case "json": - path := os.Getenv("JSON_CFG_STORE_PATH") - if len(path) == 0 { - path = defaultJSONCfgStorePath - } - log.Infof("json configuration store path: %s", path) - - cfgStore, err = json.NewCfgStore(path) - default: - err = fmt.Errorf("unsupported configuration store driver %s", t) - } - - return err -} - -func initKeyProvider() { - path := os.Getenv("KEY_PATH") + path := os.Getenv("JSON_CFG_STORE_PATH") if len(path) == 0 { - path = defaultKeyPath + path = defaultJSONCfgStorePath } - log.Infof("key path: %s", path) + log.Infof("the path of json configuration storage: %s", path) - keyProvider = comcfg.NewFileKeyProvider(path) + CfgStore, err = json.NewCfgStore(path) + if err != nil { + return + } + + kp := os.Getenv("KEY_PATH") + if len(kp) == 0 { + kp = defaultKeyPath + } + log.Infof("the path of key used by key provider: %s", kp) + + encryptor := enpt.NewAESEncryptor( + comcfg.NewFileKeyProvider(kp), nil) + + CfgStore = encrypt.NewCfgStore(encryptor, attrs, CfgStore) + return nil } -// load the configurations from allEnvs, if all is false, it just loads +// LoadFromEnv loads the configurations from allEnvs, if all is false, it just loads // the repeatLoadEnvs -func loadFromEnv(cfg map[string]interface{}, all bool) error { +func LoadFromEnv(cfgs map[string]interface{}, all bool) error { envs := repeatLoadEnvs if all { envs = allEnvs @@ -246,7 +235,7 @@ func loadFromEnv(cfg map[string]interface{}, all bool) error { for k, v := range envs { if str, ok := v.(string); ok { - cfg[k] = os.Getenv(str) + cfgs[k] = os.Getenv(str) continue } @@ -255,7 +244,7 @@ func loadFromEnv(cfg map[string]interface{}, all bool) error { if err != nil { return err } - cfg[k] = i + cfgs[k] = i continue } @@ -264,90 +253,3 @@ func loadFromEnv(cfg map[string]interface{}, all bool) error { return nil } - -// GetSystemCfg returns the system configurations -func GetSystemCfg() (map[string]interface{}, error) { - m, err := cfgStore.Read() - if err != nil { - return nil, err - } - - key, err := keyProvider.Get(nil) - if err != nil { - return nil, fmt.Errorf("failed to get key: %v", err) - } - - if err = decrypt(m, attrs, key); err != nil { - return nil, err - } - - return m, nil -} - -// UpdateSystemCfg updates the system configurations -func UpdateSystemCfg(cfg map[string]interface{}) error { - - key, err := keyProvider.Get(nil) - if err != nil { - return fmt.Errorf("failed to get key: %v", err) - } - - if err := encrypt(cfg, attrs, key); err != nil { - return err - } - - return cfgStore.Write(cfg) -} - -func encrypt(m map[string]interface{}, keys []string, secretKey string) error { - for _, key := range keys { - v, ok := m[key] - if !ok { - continue - } - - if len(v.(string)) == 0 { - continue - } - - cipherText, err := utils.ReversibleEncrypt(v.(string), secretKey) - if err != nil { - return err - } - m[key] = cipherText - } - return nil -} - -func decrypt(m map[string]interface{}, keys []string, secretKey string) error { - for _, key := range keys { - v, ok := m[key] - if !ok { - continue - } - - if len(v.(string)) == 0 { - continue - } - - text, err := utils.ReversibleDecrypt(v.(string), secretKey) - if err != nil { - return err - } - m[key] = text - } - return nil -} - -// Reset clears old system configurations and reloads them -// from environment variables -func Reset() error { - cfg := map[string]interface{}{} - if err := loadFromEnv(cfg, true); err != nil { - return err - } - - //sync configurations into cfg store - log.Info("updating system configurations...") - return UpdateSystemCfg(cfg) -} diff --git a/src/adminserver/systemcfg/systemcfg_test.go b/src/adminserver/systemcfg/systemcfg_test.go index 1d41fe7bf..8ccd7e33b 100644 --- a/src/adminserver/systemcfg/systemcfg_test.go +++ b/src/adminserver/systemcfg/systemcfg_test.go @@ -19,119 +19,81 @@ import ( "os" "testing" + "github.com/stretchr/testify/assert" "github.com/vmware/harbor/src/common" - "github.com/vmware/harbor/src/common/utils/test" ) -// test functions in adminserver/systemcfg/systemcfg.go -func TestSystemcfg(t *testing.T) { - configPath := "/tmp/config.json" - if _, err := os.Stat(configPath); err == nil { - if err := os.Remove(configPath); err != nil { - t.Errorf("failed to remove %s: %v", configPath, err) - return - } - } else if !os.IsNotExist(err) { - t.Errorf("failed to check the existence of %s: %v", configPath, err) - return +func TestParseStringToInt(t *testing.T) { + cases := []struct { + input string + result int + }{ + {"1", 1}, + {"-1", -1}, + {"0", 0}, + {"", 0}, } - if err := os.Setenv("JSON_CFG_STORE_PATH", configPath); err != nil { - t.Errorf("failed to set env: %v", err) - return - } - - keyPath := "/tmp/secretkey" - if _, err := test.GenerateKey(keyPath); err != nil { - t.Errorf("failed to generate key: %v", err) - return - } - defer os.Remove(keyPath) - - if err := os.Setenv("KEY_PATH", keyPath); err != nil { - t.Errorf("failed to set env: %v", err) - return - } - - m := map[string]string{ - "AUTH_MODE": common.DBAuth, - "LDAP_SCOPE": "1", - "LDAP_TIMEOUT": "30", - "MYSQL_PORT": "3306", - "MAX_JOB_WORKERS": "3", - "TOKEN_EXPIRATION": "30", - "CFG_EXPIRATION": "5", - "EMAIL_PORT": "25", - "MYSQL_PWD": "", - "LDAP_SEARCH_PWD": "", - "EMAIL_PWD": "", - "HARBOR_ADMIN_PASSWORD": "", - } - - for k, v := range m { - if err := os.Setenv(k, v); err != nil { - t.Fatalf("failed to set env %s: %v", k, err) - } - } - - if err := Init(); err != nil { - t.Errorf("failed to initialize system configurations: %v", err) - return - } - defer os.Remove(configPath) - - // run Init again to make sure it works well when the configuration file - // already exists - if err := Init(); err != nil { - t.Errorf("failed to initialize system configurations: %v", err) - return - } - - cfg, err := GetSystemCfg() - if err != nil { - t.Errorf("failed to get system configurations: %v", err) - return - } - - if cfg[common.AUTHMode] != common.DBAuth { - t.Errorf("unexpected auth mode: %s != %s", - cfg[common.AUTHMode], common.DBAuth) - return - } - - cfg[common.AUTHMode] = common.LDAPAuth - - if err = UpdateSystemCfg(cfg); err != nil { - t.Errorf("failed to update system configurations: %v", err) - return - } - - cfg, err = GetSystemCfg() - if err != nil { - t.Errorf("failed to get system configurations: %v", err) - return - } - - if cfg[common.AUTHMode] != common.LDAPAuth { - t.Errorf("unexpected auth mode: %s != %s", - cfg[common.AUTHMode], common.DBAuth) - return - } - - if err = Reset(); err != nil { - t.Errorf("failed to reset system configurations: %v", err) - return - } - - cfg, err = GetSystemCfg() - if err != nil { - t.Errorf("failed to get system configurations: %v", err) - return - } - - if cfg[common.AUTHMode] != common.DBAuth { - t.Errorf("unexpected auth mode: %s != %s", - cfg[common.AUTHMode], common.DBAuth) - return + for _, c := range cases { + i, err := parseStringToInt(c.input) + assert.Nil(t, err) + assert.Equal(t, c.result, i) } } + +func TestParseStringToBool(t *testing.T) { + cases := []struct { + input string + result bool + }{ + {"true", true}, + {"on", true}, + {"TRUE", true}, + {"ON", true}, + {"other", false}, + {"", false}, + } + + for _, c := range cases { + b, _ := parseStringToBool(c.input) + assert.Equal(t, c.result, b) + } +} + +func TestInitCfgStore(t *testing.T) { + os.Clearenv() + path := "/tmp/config.json" + if err := os.Setenv("JSON_CFG_STORE_PATH", path); err != nil { + t.Fatalf("failed to set env: %v", err) + } + defer os.RemoveAll(path) + err := initCfgStore() + assert.Nil(t, err) +} + +func TestLoadFromEnv(t *testing.T) { + os.Clearenv() + ldapURL := "ldap://ldap.com" + extEndpoint := "http://harbor.com" + if err := os.Setenv("LDAP_URL", ldapURL); err != nil { + t.Fatalf("failed to set env: %v", err) + } + cfgs := map[string]interface{}{} + err := LoadFromEnv(cfgs, true) + assert.Nil(t, err) + assert.Equal(t, ldapURL, cfgs[common.LDAPURL]) + + os.Clearenv() + if err := os.Setenv("LDAP_URL", ldapURL); err != nil { + t.Fatalf("failed to set env: %v", err) + } + if err := os.Setenv("EXT_ENDPOINT", extEndpoint); err != nil { + t.Fatalf("failed to set env: %v", err) + } + + cfgs = map[string]interface{}{} + err = LoadFromEnv(cfgs, false) + assert.Nil(t, err) + assert.Equal(t, extEndpoint, cfgs[common.ExtEndpoint]) + assert.Equal(t, nil, cfgs[common.LDAPURL]) +}