mirror of https://github.com/goharbor/harbor.git
354 lines
8.2 KiB
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)
|
|
}
|