diff --git a/src/common/config/encrypt/encrypt.go b/src/common/config/encrypt/encrypt.go new file mode 100644 index 000000000..e70c6a100 --- /dev/null +++ b/src/common/config/encrypt/encrypt.go @@ -0,0 +1,83 @@ +// Copyright Project Harbor Authors +// +// 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 ( + "os" + "sync" + + "github.com/goharbor/harbor/src/common/utils" + "github.com/goharbor/harbor/src/common/utils/log" +) + +var ( + defaultKeyPath = "/etc/core/key" +) + +// 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 KeyProvider + keyParams map[string]interface{} +} + +// NewAESEncryptor returns an instance of an AESEncryptor +func NewAESEncryptor(keyProvider KeyProvider) Encryptor { + return &AESEncryptor{ + keyProvider: keyProvider, + } +} + +var encryptInstance Encryptor +var encryptOnce sync.Once + +// Instance ... Get instance of encryptor +func Instance() Encryptor { + encryptOnce.Do(func() { + kp := os.Getenv("KEY_PATH") + if len(kp) == 0 { + kp = defaultKeyPath + } + log.Infof("the path of key used by key provider: %s", kp) + encryptInstance = NewAESEncryptor(NewFileKeyProvider(kp)) + + }) + return encryptInstance +} + +// 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/common/config/encrypt/encrypt_test.go b/src/common/config/encrypt/encrypt_test.go new file mode 100644 index 000000000..019f88d84 --- /dev/null +++ b/src/common/config/encrypt/encrypt_test.go @@ -0,0 +1,39 @@ +package encrypt + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "io/ioutil" + "os" + "testing" +) + +func TestMain(m *testing.M) { + secret := []byte("9TXCcHgNAAp1aSHh") + filename, err := ioutil.TempFile(os.TempDir(), "keyfile") + err = ioutil.WriteFile(filename.Name(), secret, 0644) + if err != nil { + fmt.Printf("failed to create temp key file\n") + } + + defer os.Remove(filename.Name()) + + os.Setenv("KEY_PATH", filename.Name()) + + ret := m.Run() + os.Exit(ret) +} + +func TestEncryptDecrypt(t *testing.T) { + password := "zhu888jie" + encrypted, err := Instance().Encrypt(password) + if err != nil { + t.Errorf("Failed to decrypt password, error %v", err) + } + decrypted, err := Instance().Decrypt(encrypted) + if err != nil { + t.Errorf("Failed to decrypt password, error %v", err) + } + assert.NotEqual(t, password, encrypted) + assert.Equal(t, password, decrypted) +} diff --git a/src/common/config/encrypt/keyprovider.go b/src/common/config/encrypt/keyprovider.go new file mode 100644 index 000000000..0f6c6ced7 --- /dev/null +++ b/src/common/config/encrypt/keyprovider.go @@ -0,0 +1,48 @@ +// Copyright Project Harbor Authors +// +// 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 ( + "io/ioutil" +) + +// KeyProvider provides the key used to encrypt and decrypt attrs +type KeyProvider interface { + // Get returns the key + // params can be used to pass parameters in different implements + Get(params map[string]interface{}) (string, error) +} + +// FileKeyProvider reads key from file +type FileKeyProvider struct { + path string +} + +// NewFileKeyProvider returns an instance of FileKeyProvider +// path: where the key should be read from +func NewFileKeyProvider(path string) KeyProvider { + return &FileKeyProvider{ + path: path, + } +} + +// Get returns the key read from file +func (f *FileKeyProvider) Get(params map[string]interface{}) (string, error) { + b, err := ioutil.ReadFile(f.path) + if err != nil { + return "", err + } + return string(b), nil +} diff --git a/src/common/config/encrypt/keyprovider_test.go b/src/common/config/encrypt/keyprovider_test.go new file mode 100644 index 000000000..8ac93962b --- /dev/null +++ b/src/common/config/encrypt/keyprovider_test.go @@ -0,0 +1,44 @@ +// Copyright Project Harbor Authors +// +// 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 ( + "io/ioutil" + "os" + "testing" +) + +func TestGetOfFileKeyProvider(t *testing.T) { + path := "/tmp/key" + key := "key_content" + + if err := ioutil.WriteFile(path, []byte(key), 0777); err != nil { + t.Errorf("failed to write to file %s: %v", path, err) + return + } + defer os.Remove(path) + + provider := NewFileKeyProvider(path) + k, err := provider.Get(nil) + if err != nil { + t.Errorf("failed to get key from the file provider: %v", err) + return + } + + if k != key { + t.Errorf("unexpected key: %s != %s", k, key) + return + } +} diff --git a/src/common/config/metadata/metadata.go b/src/common/config/metadata/metadata.go new file mode 100644 index 000000000..da72dd456 --- /dev/null +++ b/src/common/config/metadata/metadata.go @@ -0,0 +1,70 @@ +// Copyright Project Harbor Authors +// +// 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 metadata + +import ( + "sync" +) + +var metaDataOnce sync.Once +var metaDataInstance *CfgMetaData + +// Instance - Get Instance, make it singleton because there is only one copy of metadata in an env +func Instance() *CfgMetaData { + metaDataOnce.Do(func() { + metaDataInstance = newCfgMetaData() + metaDataInstance.init() + }) + return metaDataInstance +} + +func newCfgMetaData() *CfgMetaData { + return &CfgMetaData{metaMap: make(map[string]Item)} +} + +// CfgMetaData ... +type CfgMetaData struct { + metaMap map[string]Item +} + +// init ... +func (c *CfgMetaData) init() { + c.initFromArray(ConfigList) +} + +// initFromArray - Initial metadata from an array +func (c *CfgMetaData) initFromArray(items []Item) { + c.metaMap = make(map[string]Item) + for _, item := range items { + c.metaMap[item.Name] = item + } +} + +// GetByName - Get current metadata of current name, if not defined, return false in second params +func (c *CfgMetaData) GetByName(name string) (*Item, bool) { + if item, ok := c.metaMap[name]; ok { + return &item, true + } + return nil, false +} + +// GetAll - Get all metadata in current env +func (c *CfgMetaData) GetAll() []Item { + metaDataList := make([]Item, 0) + for _, value := range c.metaMap { + metaDataList = append(metaDataList, value) + } + return metaDataList +} diff --git a/src/common/config/metadata/metadata_test.go b/src/common/config/metadata/metadata_test.go new file mode 100644 index 000000000..e486adb49 --- /dev/null +++ b/src/common/config/metadata/metadata_test.go @@ -0,0 +1,51 @@ +// Copyright Project Harbor Authors +// +// 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 metadata + +import ( + "testing" +) + +func TestCfgMetaData_InitFromArray(t *testing.T) { + testArray := []Item{ + {Scope: SystemScope, Group: BasicGroup, EnvKey: "HARBOR_ADMIN_PASSWORD", DefaultValue: "", Name: "admin_initial_password", ItemType: &PasswordType{}, Editable: true}, + {Scope: SystemScope, Group: BasicGroup, EnvKey: "ADMIRAL_URL", DefaultValue: "NA", Name: "admiral_url", ItemType: &StringType{}, Editable: false}, + {Scope: UserScope, Group: BasicGroup, EnvKey: "AUTH_MODE", DefaultValue: "db_auth", Name: "auth_mode", ItemType: &StringType{}, Editable: false}, + {Scope: SystemScope, Group: BasicGroup, EnvKey: "CFG_EXPIRATION", DefaultValue: "5", Name: "cfg_expiration", ItemType: &StringType{}, Editable: false}, + {Scope: SystemScope, Group: BasicGroup, EnvKey: "CHART_REPOSITORY_URL", DefaultValue: "http://chartmuseum:9999", Name: "chart_repository_url", ItemType: &StringType{}, Editable: false}, + } + curInst := Instance() + curInst.initFromArray(testArray) + + if len(metaDataInstance.metaMap) != 5 { + t.Errorf("Can not initial metadata, size %v", len(metaDataInstance.metaMap)) + } + item, ok := curInst.GetByName("admin_initial_password") + if ok == false { + t.Errorf("Can not get admin_initial_password metadata") + } + if item.Name != "admin_initial_password" { + t.Errorf("Can not get admin_initial_password metadata") + } + +} + +func TestCfgMetaData_Init(t *testing.T) { + curInst := Instance() + curInst.init() + if len(metaDataInstance.metaMap) < 60 { + t.Errorf("Can not initial metadata, size %v", len(metaDataInstance.metaMap)) + } +} diff --git a/src/common/config/metadata/metadatalist.go b/src/common/config/metadata/metadatalist.go new file mode 100644 index 000000000..11480d56d --- /dev/null +++ b/src/common/config/metadata/metadatalist.go @@ -0,0 +1,134 @@ +// Copyright Project Harbor Authors +// +// 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 metadata + +import "github.com/goharbor/harbor/src/common" + +// Item - Configure item include default value, type, env name +type Item struct { + // The Scope of this configuration item: eg: SystemScope, UserScope + Scope string `json:"scope,omitempty"` + // email, ldapbasic, ldapgroup, uaa settings, used to retieve configure items by group + Group string `json:"group,omitempty"` + // environment key to retrieves this value when initialize, for example: POSTGRESQL_HOST, only used for system settings, for user settings no EnvKey + EnvKey string `json:"environment_key,omitempty"` + // The default string value for this key + DefaultValue string `json:"default_value,omitempty"` + // The key for current configure settings in database or rest api + Name string `json:"name,omitempty"` + // It can be &IntType{}, &StringType{}, &BoolType{}, &PasswordType{}, &MapType{} etc, any type interface implementation + ItemType Type + // Is this settign can be modified after configure + Editable bool `json:"editable,omitempty"` +} + +// Constant for configure item +const ( + // Scope + UserScope = "user" + SystemScope = "system" + // Group + LdapBasicGroup = "ldapbasic" + LdapGroupGroup = "ldapgroup" + EmailGroup = "email" + UAAGroup = "uaa" + DatabaseGroup = "database" + // Put all config items do not belong a existing group into basic + BasicGroup = "basic" + ClairGroup = "clair" +) + +var ( + + // ConfigList - All configure items used in harbor + // Steps to onboard a new setting + // 1. Add configure item in metadatalist.go + // 2. Get/Set config settings by CfgManager + // 3. CfgManager.Load()/CfgManager.Save() to load/save from configure storage. + ConfigList = []Item{ + {Name: "admin_initial_password", Scope: SystemScope, Group: BasicGroup, EnvKey: "HARBOR_ADMIN_PASSWORD", DefaultValue: "", ItemType: &PasswordType{}, Editable: true}, + {Name: "admiral_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "ADMIRAL_URL", DefaultValue: "NA", ItemType: &StringType{}, Editable: false}, + {Name: "auth_mode", Scope: UserScope, Group: BasicGroup, EnvKey: "AUTH_MODE", DefaultValue: "db_auth", ItemType: &StringType{}, Editable: false}, + {Name: "cfg_expiration", Scope: SystemScope, Group: BasicGroup, EnvKey: "CFG_EXPIRATION", DefaultValue: "5", ItemType: &IntType{}, Editable: false}, + {Name: "chart_repository_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "CHART_REPOSITORY_URL", DefaultValue: "http://chartmuseum:9999", ItemType: &StringType{}, Editable: false}, + + {Name: "clair_db", Scope: SystemScope, Group: ClairGroup, EnvKey: "CLAIR_DB", DefaultValue: "postgres", ItemType: &StringType{}, Editable: false}, + {Name: "clair_db_host", Scope: SystemScope, Group: ClairGroup, EnvKey: "CLAIR_DB_HOST", DefaultValue: "postgresql", ItemType: &StringType{}, Editable: false}, + {Name: "clair_db_password", Scope: SystemScope, Group: ClairGroup, EnvKey: "CLAIR_DB_PASSWORD", DefaultValue: "root123", ItemType: &PasswordType{}, Editable: false}, + {Name: "clair_db_port", Scope: SystemScope, Group: ClairGroup, EnvKey: "CLAIR_DB_PORT", DefaultValue: "5432", ItemType: &IntType{}, Editable: false}, + {Name: "clair_db_sslmode", Scope: SystemScope, Group: ClairGroup, EnvKey: "CLAIR_DB_SSLMODE", DefaultValue: "disable", ItemType: &StringType{}, Editable: false}, + {Name: "clair_db_username", Scope: SystemScope, Group: ClairGroup, EnvKey: "CLAIR_DB_USERNAME", DefaultValue: "postgres", ItemType: &StringType{}, Editable: false}, + {Name: "clair_url", Scope: SystemScope, Group: ClairGroup, EnvKey: "CLAIR_URL", DefaultValue: "http://clair:6060", ItemType: &StringType{}, Editable: false}, + + {Name: "core_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "CORE_URL", DefaultValue: "http://core:8080", ItemType: &StringType{}, Editable: false}, + {Name: "database_type", Scope: SystemScope, Group: BasicGroup, EnvKey: "DATABASE_TYPE", DefaultValue: "postgresql", ItemType: &StringType{}, Editable: false}, + + {Name: "email_from", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_FROM", DefaultValue: "admin ", ItemType: &StringType{}, Editable: false}, + {Name: "email_host", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_HOST", DefaultValue: "smtp.mydomain.com", ItemType: &StringType{}, Editable: false}, + {Name: "email_identity", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_IDENTITY", DefaultValue: "", ItemType: &StringType{}, Editable: false}, + {Name: "email_insecure", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_INSECURE", DefaultValue: "false", ItemType: &BoolType{}, Editable: false}, + {Name: "email_password", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_PWD", DefaultValue: "", ItemType: &PasswordType{}, Editable: false}, + {Name: "email_port", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_PORT", DefaultValue: "25", ItemType: &IntType{}, Editable: false}, + {Name: "email_ssl", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_SSL", DefaultValue: "false", ItemType: &BoolType{}, Editable: false}, + {Name: "email_username", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_USR", DefaultValue: "sample_admin@mydomain.com", ItemType: &StringType{}, Editable: false}, + + {Name: "ext_endpoint", Scope: SystemScope, Group: BasicGroup, EnvKey: "EXT_ENDPOINT", DefaultValue: "https://host01.com", ItemType: &StringType{}, Editable: false}, + {Name: "jobservice_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "JOBSERVICE_URL", DefaultValue: "http://jobservice:8080", ItemType: &StringType{}, Editable: false}, + + {Name: "ldap_base_dn", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_BASE_DN", DefaultValue: "", ItemType: &StringType{}, Editable: false}, + {Name: "ldap_filter", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_FILTER", DefaultValue: "", ItemType: &StringType{}, Editable: false}, + {Name: "ldap_group_base_dn", Scope: UserScope, Group: LdapGroupGroup, EnvKey: "LDAP_GROUP_BASE_DN", DefaultValue: "", ItemType: &StringType{}, Editable: false}, + {Name: "ldap_group_admin_dn", Scope: UserScope, Group: LdapGroupGroup, EnvKey: "LDAP_GROUP_ADMIN_DN", DefaultValue: "", ItemType: &StringType{}, Editable: false}, + {Name: "ldap_group_attribute_name", Scope: UserScope, Group: LdapGroupGroup, EnvKey: "LDAP_GROUP_GID", DefaultValue: "", ItemType: &StringType{}, Editable: false}, + {Name: "ldap_group_search_filter", Scope: UserScope, Group: LdapGroupGroup, EnvKey: "LDAP_GROUP_FILTER", DefaultValue: "", ItemType: &StringType{}, Editable: false}, + {Name: "ldap_group_search_scope", Scope: UserScope, Group: LdapGroupGroup, EnvKey: "LDAP_GROUP_SCOPE", DefaultValue: "2", ItemType: &IntType{}, Editable: false}, + {Name: "ldap_scope", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_SCOPE", DefaultValue: "2", ItemType: &IntType{}, Editable: true}, + {Name: "ldap_search_dn", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_SEARCH_DN", DefaultValue: "", ItemType: &StringType{}, Editable: false}, + {Name: "ldap_search_password", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_SEARCH_PWD", DefaultValue: "", ItemType: &PasswordType{}, Editable: false}, + {Name: "ldap_timeout", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_TIMEOUT", DefaultValue: "5", ItemType: &IntType{}, Editable: false}, + {Name: "ldap_uid", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_UID", DefaultValue: "cn", ItemType: &StringType{}, Editable: true}, + {Name: "ldap_url", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_URL", DefaultValue: "", ItemType: &StringType{}, Editable: true}, + {Name: "ldap_verify_cert", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_VERIFY_CERT", DefaultValue: "true", ItemType: &BoolType{}, Editable: false}, + + {Name: "max_job_workers", Scope: SystemScope, Group: BasicGroup, EnvKey: "MAX_JOB_WORKERS", DefaultValue: "10", ItemType: &IntType{}, Editable: false}, + {Name: "notary_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "NOTARY_URL", DefaultValue: "http://notary-server:4443", ItemType: &StringType{}, Editable: false}, + + {Name: "postgresql_database", Scope: SystemScope, Group: DatabaseGroup, EnvKey: "POSTGRESQL_DATABASE", DefaultValue: "registry", ItemType: &StringType{}, Editable: false}, + {Name: "postgresql_host", Scope: SystemScope, Group: DatabaseGroup, EnvKey: "POSTGRESQL_HOST", DefaultValue: "postgresql", ItemType: &StringType{}, Editable: false}, + {Name: "postgresql_password", Scope: SystemScope, Group: DatabaseGroup, EnvKey: "POSTGRESQL_PASSWORD", DefaultValue: "root123", ItemType: &PasswordType{}, Editable: false}, + {Name: "postgresql_port", Scope: SystemScope, Group: DatabaseGroup, EnvKey: "POSTGRESQL_PORT", DefaultValue: "5432", ItemType: &IntType{}, Editable: false}, + {Name: "postgresql_sslmode", Scope: SystemScope, Group: DatabaseGroup, EnvKey: "POSTGRESQL_SSLMODE", DefaultValue: "disable", ItemType: &StringType{}, Editable: false}, + {Name: "postgresql_username", Scope: SystemScope, Group: DatabaseGroup, EnvKey: "POSTGRESQL_USERNAME", DefaultValue: "postgres", ItemType: &StringType{}, Editable: false}, + + {Name: "project_creation_restriction", Scope: UserScope, Group: BasicGroup, EnvKey: "PROJECT_CREATION_RESTRICTION", DefaultValue: common.ProCrtRestrEveryone, ItemType: &StringType{}, Editable: false}, + {Name: "read_only", Scope: UserScope, Group: BasicGroup, EnvKey: "READ_ONLY", DefaultValue: "false", ItemType: &BoolType{}, Editable: false}, + + {Name: "registry_storage_provider_name", Scope: SystemScope, Group: BasicGroup, EnvKey: "REGISTRY_STORAGE_PROVIDER_NAME", DefaultValue: "filesystem", ItemType: &StringType{}, Editable: false}, + {Name: "registry_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "REGISTRY_URL", DefaultValue: "http://registry:5000", ItemType: &StringType{}, Editable: false}, + {Name: "registry_controller_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "REGISTRY_CONTROLLER_URL", DefaultValue: "http://registryctl:8080", ItemType: &StringType{}, Editable: false}, + {Name: "self_registration", Scope: UserScope, Group: BasicGroup, EnvKey: "SELF_REGISTRATION", DefaultValue: "true", ItemType: &BoolType{}, Editable: false}, + {Name: "token_expiration", Scope: UserScope, Group: BasicGroup, EnvKey: "TOKEN_EXPIRATION", DefaultValue: "30", ItemType: &IntType{}, Editable: false}, + {Name: "token_service_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "TOKEN_SERVICE_URL", DefaultValue: "", ItemType: &StringType{}, Editable: false}, + + {Name: "uaa_client_id", Scope: UserScope, Group: UAAGroup, EnvKey: "UAA_CLIENTID", DefaultValue: "", ItemType: &StringType{}, Editable: false}, + {Name: "uaa_client_secret", Scope: UserScope, Group: UAAGroup, EnvKey: "UAA_CLIENTSECRET", DefaultValue: "", ItemType: &StringType{}, Editable: false}, + {Name: "uaa_endpoint", Scope: UserScope, Group: UAAGroup, EnvKey: "UAA_ENDPOINT", DefaultValue: "", ItemType: &StringType{}, Editable: false}, + {Name: "uaa_verify_cert", Scope: UserScope, Group: UAAGroup, EnvKey: "UAA_VERIFY_CERT", DefaultValue: "false", ItemType: &BoolType{}, Editable: false}, + + {Name: "with_chartmuseum", Scope: SystemScope, Group: BasicGroup, EnvKey: "WITH_CHARTMUSEUM", DefaultValue: "false", ItemType: &BoolType{}, Editable: true}, + {Name: "with_clair", Scope: SystemScope, Group: BasicGroup, EnvKey: "WITH_CLAIR", DefaultValue: "true", ItemType: &BoolType{}, Editable: true}, + {Name: "with_notary", Scope: SystemScope, Group: BasicGroup, EnvKey: "WITH_NOTARY", DefaultValue: "false", ItemType: &BoolType{}, Editable: true}, + } +) diff --git a/src/common/config/metadata/type.go b/src/common/config/metadata/type.go new file mode 100644 index 000000000..726e420b3 --- /dev/null +++ b/src/common/config/metadata/type.go @@ -0,0 +1,122 @@ +// Copyright Project Harbor Authors +// +// 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 metadata + +import ( + "encoding/json" + "strconv" +) + +// Type - Use this interface to define and encapsulate the behavior of validation and transformation +type Type interface { + // validate the configure value + validate(str string) error + // get the real type of current value, if it is int, return int, if it is string return string etc. + get(str string) (interface{}, error) +} + +// StringType ... +type StringType struct { +} + +func (t *StringType) validate(str string) error { + return nil +} + +func (t *StringType) get(str string) (interface{}, error) { + return str, nil +} + +// IntType .. +type IntType struct { +} + +func (t *IntType) validate(str string) error { + _, err := strconv.Atoi(str) + return err +} + +// GetInt ... +func (t *IntType) get(str string) (interface{}, error) { + return strconv.Atoi(str) +} + +// LdapScopeType - The LDAP scope is a int type, but its is limit to 0, 1, 2 +type LdapScopeType struct { + IntType +} + +// Validate - Verify the range is limited +func (t *LdapScopeType) validate(str string) error { + if str == "0" || str == "1" || str == "2" { + return nil + } + return ErrInvalidData +} + +// Int64Type ... +type Int64Type struct { +} + +func (t *Int64Type) validate(str string) error { + _, err := strconv.ParseInt(str, 10, 64) + return err +} + +// GetInt64 ... +func (t *Int64Type) get(str string) (interface{}, error) { + return strconv.ParseInt(str, 10, 64) +} + +// BoolType ... +type BoolType struct { +} + +func (t *BoolType) validate(str string) error { + _, err := strconv.ParseBool(str) + return err +} + +func (t *BoolType) get(str string) (interface{}, error) { + return strconv.ParseBool(str) +} + +// PasswordType ... +type PasswordType struct { +} + +func (t *PasswordType) validate(str string) error { + return nil +} + +func (t *PasswordType) get(str string) (interface{}, error) { + return str, nil +} + +// MapType ... +type MapType struct { +} + +func (t *MapType) validate(str string) error { + result := map[string]interface{}{} + err := json.Unmarshal([]byte(str), &result) + return err +} + +func (t *MapType) get(str string) (interface{}, error) { + result := map[string]string{} + err := json.Unmarshal([]byte(str), &result) + return result, err +} diff --git a/src/common/config/metadata/type_test.go b/src/common/config/metadata/type_test.go new file mode 100644 index 000000000..ca1504025 --- /dev/null +++ b/src/common/config/metadata/type_test.go @@ -0,0 +1,98 @@ +// Copyright Project Harbor Authors +// +// 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 metadata + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestIntType_validate(t *testing.T) { + test := &IntType{} + assert.NotNil(t, test.validate("sample")) + assert.Nil(t, test.validate("1000")) + +} + +func TestIntType_get(t *testing.T) { + test := &IntType{} + result, _ := test.get("1000") + assert.IsType(t, result, 1000) +} + +func TestStringType_get(t *testing.T) { + test := &StringType{} + result, _ := test.get("1000") + assert.IsType(t, result, "sample") +} + +func TestStringType_validate(t *testing.T) { + test := &StringType{} + assert.Nil(t, test.validate("sample")) +} + +func TestLdapScopeType_validate(t *testing.T) { + test := &LdapScopeType{} + assert.NotNil(t, test.validate("3")) + assert.Nil(t, test.validate("2")) +} + +func TestInt64Type_validate(t *testing.T) { + test := &Int64Type{} + assert.NotNil(t, test.validate("sample")) + assert.Nil(t, test.validate("1000")) +} + +func TestInt64Type_get(t *testing.T) { + test := &Int64Type{} + result, _ := test.get("32") + assert.Equal(t, result, int64(32)) +} + +func TestBoolType_validate(t *testing.T) { + test := &BoolType{} + assert.NotNil(t, test.validate("sample")) + assert.Nil(t, test.validate("True")) +} + +func TestBoolType_get(t *testing.T) { + test := &BoolType{} + result, _ := test.get("true") + assert.Equal(t, result, true) + result, _ = test.get("false") + assert.Equal(t, result, false) +} + +func TestPasswordType_validate(t *testing.T) { + test := &PasswordType{} + assert.Nil(t, test.validate("zhu88jie")) +} + +func TestPasswordType_get(t *testing.T) { + test := &PasswordType{} + assert.Nil(t, test.validate("zhu88jie")) +} + +func TestMapType_validate(t *testing.T) { + test := &MapType{} + assert.Nil(t, test.validate(`{"sample":"abc", "another":"welcome"}`)) + assert.NotNil(t, test.validate(`{"sample":"abc", "another":"welcome"`)) +} + +func TestMapType_get(t *testing.T) { + test := &MapType{} + result, _ := test.get(`{"sample":"abc", "another":"welcome"}`) + assert.Equal(t, result, map[string]string{"sample": "abc", "another": "welcome"}) +} diff --git a/src/common/config/metadata/value.go b/src/common/config/metadata/value.go new file mode 100644 index 000000000..44e7ee79b --- /dev/null +++ b/src/common/config/metadata/value.go @@ -0,0 +1,159 @@ +// Copyright Project Harbor Authors +// +// 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 metadata + +import ( + "errors" + + "github.com/goharbor/harbor/src/common/utils/log" +) + +var ( + // ErrNotDefined ... + ErrNotDefined = errors.New("configure item is not defined in metadata") + // ErrTypeNotMatch ... + ErrTypeNotMatch = errors.New("the required value doesn't matched with metadata defined") + // ErrInvalidData ... + ErrInvalidData = errors.New("the data provided is invalid") + // ErrValueNotSet ... + ErrValueNotSet = errors.New("the configure value is not set") +) + +// ConfigureValue - struct to hold a actual value, also include the name of config metadata. +type ConfigureValue struct { + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` +} + +// NewConfigureValue ... +func NewConfigureValue(name, value string) *ConfigureValue { + result := &ConfigureValue{} + err := result.Set(name, value) + if err != nil { + log.Errorf("Failed to set name:%v, value:%v, error %v", name, value, err) + result.Name = name // Keep name to trace error + } + return result +} + +// GetString - Get the string value of current configure +func (c *ConfigureValue) GetString() string { + // Any type has the string value + if _, ok := Instance().GetByName(c.Name); ok { + return c.Value + } + return "" +} + +// GetName ... +func (c *ConfigureValue) GetName() string { + return c.Name +} + +// GetInt - return the int value of current value +func (c *ConfigureValue) GetInt() int { + if item, ok := Instance().GetByName(c.Name); ok { + val, err := item.ItemType.get(c.Value) + if err != nil { + log.Errorf("GetInt failed, error: %+v", err) + return 0 + } + if intValue, suc := val.(int); suc { + return intValue + } + } + log.Errorf("The current value's metadata is not defined, %+v", c) + return 0 +} + +// GetInt64 - return the int64 value of current value +func (c *ConfigureValue) GetInt64() int64 { + if item, ok := Instance().GetByName(c.Name); ok { + val, err := item.ItemType.get(c.Value) + if err != nil { + log.Errorf("GetInt64 failed, error: %+v", err) + return 0 + } + if int64Value, suc := val.(int64); suc { + return int64Value + } + } + log.Errorf("The current value's metadata is not defined, %+v", c) + return 0 +} + +// GetBool - return the bool value of current setting +func (c *ConfigureValue) GetBool() bool { + if item, ok := Instance().GetByName(c.Name); ok { + val, err := item.ItemType.get(c.Value) + if err != nil { + log.Errorf("GetBool failed, error: %+v", err) + return false + } + if boolValue, suc := val.(bool); suc { + return boolValue + } + } + log.Errorf("The current value's metadata is not defined, %+v", c) + return false +} + +// GetStringToStringMap - return the string to string map of current value +func (c *ConfigureValue) GetStringToStringMap() map[string]string { + result := map[string]string{} + if item, ok := Instance().GetByName(c.Name); ok { + val, err := item.ItemType.get(c.Value) + if err != nil { + log.Errorf("The GetBool failed, error: %+v", err) + return result + } + if mapValue, suc := val.(map[string]string); suc { + return mapValue + } + } + log.Errorf("GetStringToStringMap failed, current value's metadata is not defined, %+v", c) + return result +} + +// Validate - to validate configure items, if passed, return nil, else return error +func (c *ConfigureValue) Validate() error { + if item, ok := Instance().GetByName(c.Name); ok { + return item.ItemType.validate(c.Value) + } + return ErrNotDefined +} + +// GetPassword ... +func (c *ConfigureValue) GetPassword() string { + if _, ok := Instance().GetByName(c.Name); ok { + return c.Value + } + log.Errorf("GetPassword failed, metadata not defined: %v", c.Name) + return "" +} + +// Set - set this configure item to configure store +func (c *ConfigureValue) Set(name, value string) error { + if item, ok := Instance().GetByName(name); ok { + err := item.ItemType.validate(value) + if err == nil { + c.Name = name + c.Value = value + return nil + } + return err + } + return ErrNotDefined +} diff --git a/src/common/config/metadata/value_test.go b/src/common/config/metadata/value_test.go new file mode 100644 index 000000000..a201a36ad --- /dev/null +++ b/src/common/config/metadata/value_test.go @@ -0,0 +1,51 @@ +// Copyright Project Harbor Authors +// +// 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 metadata + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +var testingMetaDataArray = []Item{ + {Name: "ldap_search_scope", ItemType: &LdapScopeType{}, Scope: "system", Group: "ldapbasic"}, + {Name: "ldap_search_dn", ItemType: &StringType{}, Scope: "user", Group: "ldapbasic"}, + {Name: "ulimit", ItemType: &Int64Type{}, Scope: "user", Group: "ldapbasic"}, + {Name: "ldap_verify_cert", ItemType: &BoolType{}, Scope: "user", Group: "ldapbasic"}, + {Name: "sample_map_setting", ItemType: &MapType{}, Scope: "user", Group: "ldapbasic"}, +} + +func TestConfigureValue_GetBool(t *testing.T) { + assert.Equal(t, NewConfigureValue("ldap_verify_cert", "true").GetBool(), true) + assert.Equal(t, NewConfigureValue("unknown", "false").GetBool(), false) +} + +func TestConfigureValue_GetString(t *testing.T) { + assert.Equal(t, NewConfigureValue("ldap_url", "ldaps://ldap.vmware.com").GetString(), "ldaps://ldap.vmware.com") +} + +func TestConfigureValue_GetStringToStringMap(t *testing.T) { + Instance().initFromArray(testingMetaDataArray) + assert.Equal(t, NewConfigureValue("sample_map_setting", `{"sample":"abc"}`).GetStringToStringMap(), map[string]string{"sample": "abc"}) + Instance().init() +} +func TestConfigureValue_GetInt(t *testing.T) { + assert.Equal(t, NewConfigureValue("ldap_timeout", "5").GetInt(), 5) +} + +func TestConfigureValue_GetInt64(t *testing.T) { + Instance().initFromArray(testingMetaDataArray) + assert.Equal(t, NewConfigureValue("ulimit", "99999").GetInt64(), int64(99999)) +}