harbor/src/adminserver/systemcfg/systemcfg.go

354 lines
8.2 KiB
Go

/*
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 systemcfg
import (
"fmt"
"os"
"strconv"
"strings"
"github.com/vmware/harbor/src/adminserver/systemcfg/store"
"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
// attrs need to be encrypted or decrypted
attrs = []string{
common.EmailPassword,
common.LDAPSearchPwd,
common.MySQLPassword,
common.AdminInitialPassword,
}
// all configurations need read from environment variables
allEnvs = map[string]interface{}{
common.ExtEndpoint: "EXT_ENDPOINT",
common.AUTHMode: "AUTH_MODE",
common.SelfRegistration: &parser{
env: "SELF_REGISTRATION",
parse: parseStringToBool,
},
common.DatabaseType: "DATABASE_TYPE",
common.MySQLHost: "MYSQL_HOST",
common.MySQLPort: &parser{
env: "MYSQL_PORT",
parse: parseStringToInt,
},
common.MySQLUsername: "MYSQL_USR",
common.MySQLPassword: "MYSQL_PWD",
common.MySQLDatabase: "MYSQL_DATABASE",
common.SQLiteFile: "SQLITE_FILE",
common.LDAPURL: "LDAP_URL",
common.LDAPSearchDN: "LDAP_SEARCH_DN",
common.LDAPSearchPwd: "LDAP_SEARCH_PWD",
common.LDAPBaseDN: "LDAP_BASE_DN",
common.LDAPFilter: "LDAP_FILTER",
common.LDAPUID: "LDAP_UID",
common.LDAPScope: &parser{
env: "LDAP_SCOPE",
parse: parseStringToInt,
},
common.LDAPTimeout: &parser{
env: "LDAP_TIMEOUT",
parse: parseStringToInt,
},
common.EmailHost: "EMAIL_HOST",
common.EmailPort: &parser{
env: "EMAIL_PORT",
parse: parseStringToInt,
},
common.EmailUsername: "EMAIL_USR",
common.EmailPassword: "EMAIL_PWD",
common.EmailSSL: &parser{
env: "EMAIL_SSL",
parse: parseStringToBool,
},
common.EmailFrom: "EMAIL_FROM",
common.EmailIdentity: "EMAIL_IDENTITY",
common.RegistryURL: "REGISTRY_URL",
common.TokenExpiration: &parser{
env: "TOKEN_EXPIRATION",
parse: parseStringToInt,
},
common.UseCompressedJS: &parser{
env: "USE_COMPRESSED_JS",
parse: parseStringToBool,
},
common.CfgExpiration: &parser{
env: "CFG_EXPIRATION",
parse: parseStringToInt,
},
common.MaxJobWorkers: &parser{
env: "MAX_JOB_WORKERS",
parse: parseStringToInt,
},
common.VerifyRemoteCert: &parser{
env: "VERIFY_REMOTE_CERT",
parse: parseStringToBool,
},
common.ProjectCreationRestriction: "PROJECT_CREATION_RESTRICTION",
common.AdminInitialPassword: "HARBOR_ADMIN_PASSWORD",
common.AdmiralEndpoint: "ADMIRAL_URL",
common.WithNotary: &parser{
env: "WITH_NOTARY",
parse: parseStringToBool,
},
}
// configurations need read from environment variables
// every time the system startup
repeatLoadEnvs = map[string]interface{}{
common.ExtEndpoint: "EXT_ENDPOINT",
common.MySQLPassword: "MYSQL_PWD",
common.MaxJobWorkers: &parser{
env: "MAX_JOB_WORKERS",
parse: parseStringToInt,
},
// TODO remove this config?
common.UseCompressedJS: &parser{
env: "USE_COMPRESSED_JS",
parse: parseStringToBool,
},
common.CfgExpiration: &parser{
env: "CFG_EXPIRATION",
parse: parseStringToInt,
},
common.AdmiralEndpoint: "ADMIRAL_URL",
common.WithNotary: &parser{
env: "WITH_NOTARY",
parse: parseStringToBool,
},
}
)
type parser struct {
// the name of env
env string
// parse the value of env, e.g. parse string to int or
// parse string to bool
parse func(string) (interface{}, error)
}
func parseStringToInt(str string) (interface{}, error) {
return strconv.Atoi(str)
}
func parseStringToBool(str string) (interface{}, error) {
return strings.ToLower(str) == "true" ||
strings.ToLower(str) == "on", nil
}
// Init system configurations. Read from config store first,
// if null read from env
func Init() (err error) {
//init configuation store
if err = initCfgStore(); err != nil {
return err
}
//init key provider
initKeyProvider()
if os.Getenv("RESET") == "true" {
log.Info("RESET is set, resetting system configurations...")
return Reset()
}
cfg, err := GetSystemCfg()
if 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)
}
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")
if len(path) == 0 {
path = defaultKeyPath
}
log.Infof("key path: %s", path)
keyProvider = comcfg.NewFileKeyProvider(path)
}
// load the configurations from allEnvs, if all is false, it just loads
// the repeatLoadEnvs
func loadFromEnv(cfg map[string]interface{}, all bool) error {
envs := repeatLoadEnvs
if all {
envs = allEnvs
}
for k, v := range envs {
if str, ok := v.(string); ok {
cfg[k] = os.Getenv(str)
continue
}
if parser, ok := v.(*parser); ok {
i, err := parser.parse(os.Getenv(parser.env))
if err != nil {
return err
}
cfg[k] = i
continue
}
return fmt.Errorf("%v is not string or parse type", v)
}
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)
}