mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-23 02:35:17 +01:00
Merge pull request #1780 from ywk253100/170322_refactor_adminserver_test
Refactor uts of adminserver
This commit is contained in:
commit
8cfdd1f5cc
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
61
src/adminserver/systemcfg/encrypt/encrypt.go
Normal file
61
src/adminserver/systemcfg/encrypt/encrypt.go
Normal file
@ -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)
|
||||
}
|
93
src/adminserver/systemcfg/encrypt/encrypt_test.go
Normal file
93
src/adminserver/systemcfg/encrypt/encrypt_test.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
98
src/adminserver/systemcfg/store/encrypt/driver.go
Normal file
98
src/adminserver/systemcfg/store/encrypt/driver.go
Normal file
@ -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)
|
||||
}
|
82
src/adminserver/systemcfg/store/encrypt/driver_test.go
Normal file
82
src/adminserver/systemcfg/store/encrypt/driver_test.go
Normal file
@ -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"])
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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])
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user