configuration api can handle scan all policy

This commit is contained in:
Tan Jiang 2017-06-27 21:20:00 +08:00
parent 159d549dbd
commit af0e8c85bb
7 changed files with 163 additions and 122 deletions

View File

@ -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"
)

View File

@ -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,
},
}

View File

@ -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,

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)
}
}