diff --git a/src/common/const.go b/src/common/const.go index ca3bf1a26..e2bd9c60d 100644 --- a/src/common/const.go +++ b/src/common/const.go @@ -20,9 +20,9 @@ const ( LDAPAuth = "ldap_auth" ProCrtRestrEveryone = "everyone" ProCrtRestrAdmOnly = "adminonly" - LDAPScopeBase = "1" - LDAPScopeOnelevel = "2" - LDAPScopeSubtree = "3" + LDAPScopeBase = 1 + LDAPScopeOnelevel = 2 + LDAPScopeSubtree = 3 RoleProjectAdmin = 1 RoleDeveloper = 2 @@ -65,4 +65,5 @@ const ( AdmiralEndpoint = "admiral_url" WithNotary = "with_notary" WithClair = "with_clair" + ScanAllPolicy = "scan_all_policy" ) diff --git a/src/common/models/scan_job.go b/src/common/models/scan_job.go index f821a6dc6..51fffab28 100644 --- a/src/common/models/scan_job.go +++ b/src/common/models/scan_job.go @@ -96,3 +96,28 @@ type VulnerabilityItem struct { Description string `json:"description"` Fixed string `json:"fixedVersion,omitempty"` } + +// ScanAllPolicy is represent the json request and object for scan all policy, the parm is het +type ScanAllPolicy struct { + Type string `json:"type"` + Parm map[string]interface{} `json:"parameter, omitempty"` +} + +const ( + // ScanAllNone "none" for not doing any scan all + ScanAllNone = "none" + // ScanAllDaily for doing scan all daily + ScanAllDaily = "daily" + // ScanAllOnRefresh for doing scan all when the Clair DB is refreshed. + ScanAllOnRefresh = "on_refresh" + // ScanAllDailyTime the key for parm of daily scan all policy. + ScanAllDailyTime = "daily_time" +) + +//DefaultScanAllPolicy ... +var DefaultScanAllPolicy = ScanAllPolicy{ + Type: ScanAllDaily, + Parm: map[string]interface{}{ + ScanAllDailyTime: 0, + }, +} diff --git a/src/ui/api/config.go b/src/ui/api/config.go index 42bac0e54..c868f71dc 100644 --- a/src/ui/api/config.go +++ b/src/ui/api/config.go @@ -17,10 +17,11 @@ package api import ( "fmt" "net/http" - "strconv" + "reflect" "github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common/dao" + "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/ui/config" ) @@ -64,6 +65,33 @@ var ( common.AdminInitialPassword, } + stringKeys = []string{ + common.ExtEndpoint, + common.AUTHMode, + common.DatabaseType, + common.MySQLHost, + common.MySQLUsername, + common.MySQLPassword, + common.MySQLDatabase, + common.SQLiteFile, + common.LDAPURL, + common.LDAPSearchDN, + common.LDAPSearchPwd, + common.LDAPBaseDN, + common.LDAPUID, + common.LDAPFilter, + common.TokenServiceURL, + common.RegistryURL, + common.EmailHost, + common.EmailUsername, + common.EmailPassword, + common.EmailFrom, + common.EmailIdentity, + common.ProjectCreationRestriction, + common.JobLogDir, + common.AdminInitialPassword, + } + numKeys = []string{ common.EmailPort, common.LDAPScope, @@ -131,10 +159,10 @@ func (c *ConfigAPI) Get() { // Put updates configurations func (c *ConfigAPI) Put() { - m := map[string]string{} + m := map[string]interface{}{} c.DecodeJSONReq(&m) - cfg := map[string]string{} + cfg := map[string]interface{}{} for _, k := range validKeys { if v, ok := m[k]; ok { cfg[k] = v @@ -152,35 +180,7 @@ func (c *ConfigAPI) Put() { c.CustomAbort(http.StatusBadRequest, err.Error()) } - if value, ok := cfg[common.AUTHMode]; ok { - mode, err := config.AuthMode() - if err != nil { - log.Errorf("failed to get auth mode: %v", err) - c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) - } - - if mode != value { - flag, err := authModeCanBeModified() - if err != nil { - log.Errorf("failed to determine whether auth mode can be modified: %v", err) - c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) - } - - if !flag { - c.CustomAbort(http.StatusBadRequest, - fmt.Sprintf("%s can not be modified as new users have been inserted into database", - common.AUTHMode)) - } - } - } - - result, err := convertForPut(cfg) - if err != nil { - log.Errorf("failed to convert configurations: %v", err) - c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) - } - - if err := config.Upload(result); err != nil { + if err := config.Upload(cfg); err != nil { log.Errorf("failed to upload configurations: %v", err) c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) } @@ -199,18 +199,53 @@ func (c *ConfigAPI) Reset() { } } -func validateCfg(c map[string]string) (bool, error) { - isSysErr := false +func validateCfg(c map[string]interface{}) (bool, error) { + strMap := map[string]string{} + for _, k := range stringKeys { + if _, ok := c[k]; !ok { + continue + } + if _, ok := c[k].(string); !ok { + return false, fmt.Errorf("Invalid value type, expected string, key: %s, value: %v, type: %v", k, c[k], reflect.TypeOf(c[k])) + } + strMap[k] = c[k].(string) + } + numMap := map[string]int{} + for _, k := range numKeys { + if _, ok := c[k]; !ok { + continue + } + if _, ok := c[k].(float64); !ok { + return false, fmt.Errorf("Invalid value type, expected float64, key: %s, value: %v, type: %v", k, c[k], reflect.TypeOf(c[k])) + } + numMap[k] = int(c[k].(float64)) + } + boolMap := map[string]bool{} + for _, k := range boolKeys { + if _, ok := c[k]; !ok { + continue + } + if _, ok := c[k].(bool); !ok { + return false, fmt.Errorf("Invalid value type, expected bool, key: %s, value: %v, type: %v", k, c[k], reflect.TypeOf(c[k])) + } + boolMap[k] = c[k].(bool) + } mode, err := config.AuthMode() if err != nil { - isSysErr = true - return isSysErr, err + return true, err } - if value, ok := c[common.AUTHMode]; ok { + if value, ok := strMap[common.AUTHMode]; ok { if value != common.DBAuth && value != common.LDAPAuth { - return isSysErr, fmt.Errorf("invalid %s, shoud be %s or %s", common.AUTHMode, common.DBAuth, common.LDAPAuth) + return false, fmt.Errorf("invalid %s, shoud be %s or %s", common.AUTHMode, common.DBAuth, common.LDAPAuth) + } + flag, err := authModeCanBeModified() + if err != nil { + return true, err + } + if mode != value && !flag { + return false, fmt.Errorf("%s can not be modified as new users have been inserted into database", common.AUTHMode) } mode = value } @@ -218,123 +253,70 @@ func validateCfg(c map[string]string) (bool, error) { if mode == common.LDAPAuth { ldap, err := config.LDAP() if err != nil { - isSysErr = true - return isSysErr, err + return true, err } if len(ldap.URL) == 0 { - if _, ok := c[common.LDAPURL]; !ok { - return isSysErr, fmt.Errorf("%s is missing", common.LDAPURL) + if _, ok := strMap[common.LDAPURL]; !ok { + return false, fmt.Errorf("%s is missing", common.LDAPURL) } } if len(ldap.BaseDN) == 0 { - if _, ok := c[common.LDAPBaseDN]; !ok { - return isSysErr, fmt.Errorf("%s is missing", common.LDAPBaseDN) + if _, ok := strMap[common.LDAPBaseDN]; !ok { + return false, fmt.Errorf("%s is missing", common.LDAPBaseDN) } } if len(ldap.UID) == 0 { - if _, ok := c[common.LDAPUID]; !ok { - return isSysErr, fmt.Errorf("%s is missing", common.LDAPUID) + if _, ok := strMap[common.LDAPUID]; !ok { + return false, fmt.Errorf("%s is missing", common.LDAPUID) } } if ldap.Scope == 0 { - if _, ok := c[common.LDAPScope]; !ok { - return isSysErr, fmt.Errorf("%s is missing", common.LDAPScope) + if _, ok := numMap[common.LDAPScope]; !ok { + return false, fmt.Errorf("%s is missing", common.LDAPScope) } } } - if ldapURL, ok := c[common.LDAPURL]; ok && len(ldapURL) == 0 { - return isSysErr, fmt.Errorf("%s is empty", common.LDAPURL) + if ldapURL, ok := strMap[common.LDAPURL]; ok && len(ldapURL) == 0 { + return false, fmt.Errorf("%s is empty", common.LDAPURL) } - if baseDN, ok := c[common.LDAPBaseDN]; ok && len(baseDN) == 0 { - return isSysErr, fmt.Errorf("%s is empty", common.LDAPBaseDN) + if baseDN, ok := strMap[common.LDAPBaseDN]; ok && len(baseDN) == 0 { + return false, fmt.Errorf("%s is empty", common.LDAPBaseDN) } - if uID, ok := c[common.LDAPUID]; ok && len(uID) == 0 { - return isSysErr, fmt.Errorf("%s is empty", common.LDAPUID) + if uID, ok := strMap[common.LDAPUID]; ok && len(uID) == 0 { + return false, fmt.Errorf("%s is empty", common.LDAPUID) } - if scope, ok := c[common.LDAPScope]; ok && + if scope, ok := numMap[common.LDAPScope]; ok && scope != common.LDAPScopeBase && scope != common.LDAPScopeOnelevel && scope != common.LDAPScopeSubtree { - return isSysErr, fmt.Errorf("invalid %s, should be %s, %s or %s", + return false, fmt.Errorf("invalid %s, should be %s, %s or %s", common.LDAPScope, common.LDAPScopeBase, common.LDAPScopeOnelevel, common.LDAPScopeSubtree) } - - for _, k := range boolKeys { - v, ok := c[k] - if !ok { - continue + for k, n := range numMap { + if n < 0 { + return false, fmt.Errorf("invalid %s: %d", k, n) } - - if v != "0" && v != "1" { - return isSysErr, fmt.Errorf("%s should be %s or %s", - k, "0", "1") - } - } - - for _, k := range numKeys { - v, ok := c[k] - if !ok { - continue - } - - n, err := strconv.Atoi(v) - if err != nil || n < 0 { - return isSysErr, fmt.Errorf("invalid %s: %s", k, v) - } - if (k == common.EmailPort || k == common.MySQLPort) && n > 65535 { - return isSysErr, fmt.Errorf("invalid %s: %s", k, v) + return false, fmt.Errorf("invalid %s: %d", k, n) } } - if crt, ok := c[common.ProjectCreationRestriction]; ok && + if crt, ok := strMap[common.ProjectCreationRestriction]; ok && crt != common.ProCrtRestrEveryone && crt != common.ProCrtRestrAdmOnly { - return isSysErr, fmt.Errorf("invalid %s, should be %s or %s", + return false, fmt.Errorf("invalid %s, should be %s or %s", common.ProjectCreationRestriction, common.ProCrtRestrAdmOnly, common.ProCrtRestrEveryone) } - - return isSysErr, nil -} - -//convert map[string]string to map[string]interface{} -func convertForPut(m map[string]string) (map[string]interface{}, error) { - cfg := map[string]interface{}{} - - for k, v := range m { - cfg[k] = v - } - - for _, k := range numKeys { - if _, ok := cfg[k]; !ok { - continue - } - - v, err := strconv.Atoi(cfg[k].(string)) - if err != nil { - return nil, err - } - cfg[k] = v - } - - for _, k := range boolKeys { - if _, ok := cfg[k]; !ok { - continue - } - - cfg[k] = cfg[k] == "1" - } - - return cfg, nil + return false, nil } // delete sensitive attrs and add editable field to every attr @@ -347,6 +329,9 @@ func convertForGet(cfg map[string]interface{}) (map[string]*value, error) { } } + if _, ok := cfg[common.ScanAllPolicy]; !ok { + cfg[common.ScanAllPolicy] = models.DefaultScanAllPolicy + } for k, v := range cfg { result[k] = &value{ Value: v, diff --git a/src/ui/api/config_test.go b/src/ui/api/config_test.go index 1b83759ec..eeffdade8 100644 --- a/src/ui/api/config_test.go +++ b/src/ui/api/config_test.go @@ -60,8 +60,8 @@ func TestPutConfig(t *testing.T) { assert := assert.New(t) apiTest := newHarborAPI() - cfg := map[string]string{ - common.VerifyRemoteCert: "0", + cfg := map[string]interface{}{ + common.VerifyRemoteCert: false, } code, err := apiTest.PutConfig(*admin, cfg) diff --git a/src/ui/api/harborapi_test.go b/src/ui/api/harborapi_test.go index 3b580d9cc..d9f1d234a 100644 --- a/src/ui/api/harborapi_test.go +++ b/src/ui/api/harborapi_test.go @@ -1005,7 +1005,7 @@ func (a testapi) GetConfig(authInfo usrInfo) (int, map[string]*value, error) { return code, cfg, err } -func (a testapi) PutConfig(authInfo usrInfo, cfg map[string]string) (int, error) { +func (a testapi) PutConfig(authInfo usrInfo, cfg map[string]interface{}) (int, error) { _sling := sling.New().Base(a.basePath).Put("/api/configurations").BodyJSON(cfg) code, _, err := request(_sling, jsonAcceptHeader, authInfo) diff --git a/src/ui/config/config.go b/src/ui/config/config.go index 0b139eb48..520fbaec0 100644 --- a/src/ui/config/config.go +++ b/src/ui/config/config.go @@ -15,6 +15,7 @@ package config import ( + "encoding/json" "fmt" "os" "strings" @@ -339,7 +340,7 @@ func ClairEndpoint() string { func AdmiralEndpoint() string { cfg, err := mg.Get() if err != nil { - log.Errorf("Failed to get configuration, will return empty string as admiral's endpoint") + log.Errorf("Failed to get configuration, will return empty string as admiral's endpoint, error: %v", err) return "" } @@ -349,6 +350,30 @@ func AdmiralEndpoint() string { return cfg[common.AdmiralEndpoint].(string) } +// ScanAllPolicy returns the policy which controls the scan all. +func ScanAllPolicy() models.ScanAllPolicy { + var res models.ScanAllPolicy + cfg, err := mg.Get() + if err != nil { + log.Errorf("Failed to get configuration, will return default scan all policy, error: %v", err) + return models.DefaultScanAllPolicy + } + v, ok := cfg[common.ScanAllPolicy] + if !ok { + return models.DefaultScanAllPolicy + } + b, err := json.Marshal(v) + if err != nil { + log.Errorf("Failed to Marshal the value in configuration for Scan All policy, error: %v, returning the default policy", err) + return models.DefaultScanAllPolicy + } + if err := json.Unmarshal(b, &res); err != nil { + log.Errorf("Failed to unmarshal the value in configuration for Scan All policy, error: %v, returning the default policy", err) + return models.DefaultScanAllPolicy + } + return res +} + // WithAdmiral returns a bool to indicate if Harbor's deployed with admiral. func WithAdmiral() bool { return len(AdmiralEndpoint()) > 0 diff --git a/src/ui/config/config_test.go b/src/ui/config/config_test.go index c7655a75e..c496b06ad 100644 --- a/src/ui/config/config_test.go +++ b/src/ui/config/config_test.go @@ -155,4 +155,9 @@ func TestConfig(t *testing.T) { if mode != "db_auth" { t.Errorf("unexpected mode: %s != %s", mode, "db_auth") } + + if s := ScanAllPolicy(); s.Type != "daily" { + t.Errorf("unexpected scan all policy %v", s) + } + }