Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: CVE severity categorisation and scan result listing API enhancements #5617

Merged
merged 18 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions api/restHandler/ImageScanRestHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package restHandler
import (
"encoding/json"
"fmt"
securityBean "github.com/devtron-labs/devtron/pkg/security/bean"
"net/http"
"strconv"

Expand Down Expand Up @@ -70,7 +71,7 @@ func (impl ImageScanRestHandlerImpl) ScanExecutionList(w http.ResponseWriter, r
}

decoder := json.NewDecoder(r.Body)
var request *security.ImageScanRequest
var request *securityBean.ImageScanRequest
err = decoder.Decode(&request)
if err != nil {
impl.logger.Errorw("request err, ScanExecutionList", "err", err, "payload", request)
Expand All @@ -82,8 +83,8 @@ func (impl ImageScanRestHandlerImpl) ScanExecutionList(w http.ResponseWriter, r
if err != nil {
impl.logger.Errorw("service err, ScanExecutionList", "err", err, "payload", request)
if util.IsErrNoRows(err) {
responseList := make([]*security.ImageScanHistoryResponse, 0)
common.WriteJsonResp(w, nil, &security.ImageScanHistoryListingResponse{ImageScanHistoryResponse: responseList}, http.StatusOK)
responseList := make([]*securityBean.ImageScanHistoryResponse, 0)
common.WriteJsonResp(w, nil, &securityBean.ImageScanHistoryListingResponse{ImageScanHistoryResponse: responseList}, http.StatusOK)
} else {
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
}
Expand Down Expand Up @@ -126,8 +127,8 @@ func (impl ImageScanRestHandlerImpl) ScanExecutionList(w http.ResponseWriter, r
if err != nil {
impl.logger.Errorw("service err, ScanExecutionList", "err", err, "payload", request)
if util.IsErrNoRows(err) {
responseList := make([]*security.ImageScanHistoryResponse, 0)
common.WriteJsonResp(w, nil, &security.ImageScanHistoryListingResponse{ImageScanHistoryResponse: responseList}, http.StatusOK)
responseList := make([]*securityBean.ImageScanHistoryResponse, 0)
common.WriteJsonResp(w, nil, &securityBean.ImageScanHistoryListingResponse{ImageScanHistoryResponse: responseList}, http.StatusOK)
} else {
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
}
Expand Down Expand Up @@ -177,7 +178,7 @@ func (impl ImageScanRestHandlerImpl) FetchExecutionDetail(w http.ResponseWriter,
}
}
image := v.Get("image")
request := &security.ImageScanRequest{
request := &securityBean.ImageScanRequest{
ImageScanDeployInfoId: imageScanDeployInfoId,
Image: image,
ArtifactId: artifactId,
Expand All @@ -189,7 +190,7 @@ func (impl ImageScanRestHandlerImpl) FetchExecutionDetail(w http.ResponseWriter,
if err != nil {
impl.logger.Errorw("service err, FetchExecutionDetail", "err", err, "payload", request)
if util.IsErrNoRows(err) {
common.WriteJsonResp(w, nil, &security.ImageScanExecutionDetail{}, http.StatusOK)
common.WriteJsonResp(w, nil, &securityBean.ImageScanExecutionDetail{}, http.StatusOK)
} else {
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
}
Expand Down Expand Up @@ -221,7 +222,7 @@ func (impl ImageScanRestHandlerImpl) FetchExecutionDetail(w http.ResponseWriter,
}
//RBAC
} else {
common.WriteJsonResp(w, err, &security.ImageScanExecutionDetail{}, http.StatusOK)
common.WriteJsonResp(w, err, &securityBean.ImageScanExecutionDetail{}, http.StatusOK)
}

common.WriteJsonResp(w, err, executionDetail, http.StatusOK)
Expand All @@ -230,7 +231,7 @@ func (impl ImageScanRestHandlerImpl) FetchExecutionDetail(w http.ResponseWriter,
func (impl ImageScanRestHandlerImpl) FetchMinScanResultByAppIdAndEnvId(w http.ResponseWriter, r *http.Request) {
v := r.URL.Query()
var appId, envId int
request := &security.ImageScanRequest{}
request := &securityBean.ImageScanRequest{}
appIds := v.Get("appId")
if len(appIds) > 0 {
appId, err := strconv.Atoi(appIds)
Expand Down Expand Up @@ -299,8 +300,8 @@ func (impl ImageScanRestHandlerImpl) VulnerabilityExposure(w http.ResponseWriter
if err != nil {
impl.logger.Errorw("service err, VulnerabilityExposure", "err", err, "payload", request)
if util.IsErrNoRows(err) {
responseList := make([]*security.ImageScanHistoryResponse, 0)
common.WriteJsonResp(w, nil, &security.ImageScanHistoryListingResponse{ImageScanHistoryResponse: responseList}, http.StatusOK)
responseList := make([]*securityBean.ImageScanHistoryResponse, 0)
common.WriteJsonResp(w, nil, &securityBean.ImageScanHistoryListingResponse{ImageScanHistoryResponse: responseList}, http.StatusOK)
} else {
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
}
Expand Down
20 changes: 10 additions & 10 deletions api/restHandler/PolicyRestHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ import (
"encoding/json"
"errors"
"fmt"
securityBean "github.com/devtron-labs/devtron/internal/sql/repository/security/bean"
"net/http"
"strconv"

"github.com/devtron-labs/devtron/api/bean"
"github.com/devtron-labs/devtron/api/restHandler/common"
security2 "github.com/devtron-labs/devtron/internal/sql/repository/security"
"github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin"
user2 "github.com/devtron-labs/devtron/pkg/auth/user"
"github.com/devtron-labs/devtron/pkg/cluster"
Expand Down Expand Up @@ -221,18 +221,18 @@ func (impl PolicyRestHandlerImpl) GetPolicy(w http.ResponseWriter, r *http.Reque
req.Id = ids
}
var clusterId, environmentId, appId int
var policyLevel security2.PolicyLevel
if level == security2.Global.String() {
policyLevel = security2.Global
} else if level == security2.Cluster.String() {
var policyLevel securityBean.PolicyLevel
if level == securityBean.Global.String() {
policyLevel = securityBean.Global
} else if level == securityBean.Cluster.String() {
clusterId = req.Id
policyLevel = security2.Cluster
} else if level == security2.Environment.String() {
policyLevel = securityBean.Cluster
} else if level == securityBean.Environment.String() {
environmentId = req.Id
policyLevel = security2.Environment
} else if level == security2.Application.String() {
policyLevel = securityBean.Environment
} else if level == securityBean.Application.String() {
appId = req.Id
policyLevel = security2.Application
policyLevel = securityBean.Application
}

token := r.Header.Get("token")
Expand Down
134 changes: 36 additions & 98 deletions internal/sql/repository/security/CvePolicyControle.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,99 +18,37 @@ package security

import (
"fmt"
securityBean "github.com/devtron-labs/devtron/internal/sql/repository/security/bean"
"github.com/devtron-labs/devtron/pkg/sql"
"github.com/go-pg/pg"
"github.com/go-pg/pg/orm"
"time"
)

type CvePolicy struct {
tableName struct{} `sql:"cve_policy_control" pg:",discard_unknown_columns"`
Id int `sql:"id,pk"`
Global bool `sql:"global,notnull"`
ClusterId int `sql:"cluster_id"`
EnvironmentId int `sql:"env_id"`
AppId int `sql:"app_id"`
CVEStoreId string `sql:"cve_store_id"`
Action PolicyAction `sql:"action, notnull"`
Severity *Severity `sql:"severity, notnull "`
Deleted bool `sql:"deleted, notnull"`
tableName struct{} `sql:"cve_policy_control" pg:",discard_unknown_columns"`
Id int `sql:"id,pk"`
Global bool `sql:"global,notnull"`
ClusterId int `sql:"cluster_id"`
EnvironmentId int `sql:"env_id"`
AppId int `sql:"app_id"`
CVEStoreId string `sql:"cve_store_id"`
Action securityBean.PolicyAction `sql:"action, notnull"`
Severity *securityBean.Severity `sql:"severity, notnull "`
Deleted bool `sql:"deleted, notnull"`
sql.AuditLog
CveStore *CveStore
}

type PolicyAction int

const (
Inherit PolicyAction = iota
Allow
Block
Blockiffixed
)

func (d PolicyAction) String() string {
return [...]string{"inherit", "allow", "block", "blockiffixed"}[d]
}

// ------------------
type Severity int

const (
Low Severity = iota
Medium
Critical
High
Safe
)
const (
HIGH string = "high"
CRITICAL string = "critical"
SAFE string = "safe"
LOW string = "low"
MEDIUM string = "medium"
MODERATE string = "moderate"
)

// Handling for future use
func (d Severity) ValuesOf(severity string) Severity {
if severity == CRITICAL || severity == HIGH {
return Critical
} else if severity == MODERATE || severity == MEDIUM {
return Medium
} else if severity == LOW || severity == SAFE {
return Low
}
return Low
}

// Updating it for future use(not in use for standard severity)
func (d Severity) String() string {
return [...]string{"low", "moderate", "critical", "high", "safe"}[d]
}

// ----------------
type PolicyLevel int

const (
Global PolicyLevel = iota
Cluster
Environment
Application
)

func (d PolicyLevel) String() string {
return [...]string{"global", "cluster", "environment", "application"}[d]
}

func (policy *CvePolicy) PolicyLevel() PolicyLevel {
func (policy *CvePolicy) PolicyLevel() securityBean.PolicyLevel {
if policy.ClusterId != 0 {
return Cluster
return securityBean.Cluster
} else if policy.AppId != 0 {
return Application
return securityBean.Application
} else if policy.EnvironmentId != 0 {
return Environment
return securityBean.Environment
} else {
return Global
return securityBean.Global
}
}

Expand Down Expand Up @@ -250,37 +188,37 @@ func (impl *CvePolicyRepositoryImpl) GetBlockedCVEList(cves []*CveStore, cluster
return blockedCve, nil
}

func EnforceCvePolicy(cves []*CveStore, cvePolicy map[string]*CvePolicy, severityPolicy map[Severity]*CvePolicy) (blockedCVE []*CveStore) {
func EnforceCvePolicy(cves []*CveStore, cvePolicy map[string]*CvePolicy, severityPolicy map[securityBean.Severity]*CvePolicy) (blockedCVE []*CveStore) {

for _, cve := range cves {
if policy, ok := cvePolicy[cve.Name]; ok {
if policy.Action == Allow {
if policy.Action == securityBean.Allow {
continue
} else if (policy.Action == Block) || (policy.Action == Blockiffixed && cve.FixedVersion != "") {
} else if (policy.Action == securityBean.Block) || (policy.Action == securityBean.Blockiffixed && cve.FixedVersion != "") {
blockedCVE = append(blockedCVE, cve)
}
} else {
if severityPolicy[cve.Severity] != nil && severityPolicy[cve.Severity].Action == Allow {
if severityPolicy[cve.StandardSeverity] != nil && severityPolicy[cve.StandardSeverity].Action == securityBean.Allow {
continue
} else if severityPolicy[cve.Severity] != nil && (severityPolicy[cve.Severity].Action == Block || (severityPolicy[cve.Severity].Action == Blockiffixed && cve.FixedVersion != "")) {
} else if severityPolicy[cve.StandardSeverity] != nil && (severityPolicy[cve.StandardSeverity].Action == securityBean.Block || (severityPolicy[cve.StandardSeverity].Action == securityBean.Blockiffixed && cve.FixedVersion != "")) {
blockedCVE = append(blockedCVE, cve)
}
}
}
return blockedCVE
}

func (impl *CvePolicyRepositoryImpl) getApplicablePolicy(clusterId, envId, appId int, isAppstore bool) (map[string]*CvePolicy, map[Severity]*CvePolicy, error) {
func (impl *CvePolicyRepositoryImpl) getApplicablePolicy(clusterId, envId, appId int, isAppstore bool) (map[string]*CvePolicy, map[securityBean.Severity]*CvePolicy, error) {

var policyLevel PolicyLevel
var policyLevel securityBean.PolicyLevel
if isAppstore && appId > 0 && envId > 0 && clusterId > 0 {
policyLevel = Environment
policyLevel = securityBean.Environment
} else if appId > 0 && envId > 0 && clusterId > 0 {
policyLevel = Application
policyLevel = securityBean.Application
} else if envId > 0 && clusterId > 0 {
policyLevel = Environment
policyLevel = securityBean.Environment
} else if clusterId > 0 {
policyLevel = Cluster
policyLevel = securityBean.Cluster
} else {
//error in case of global or other policy
return nil, nil, fmt.Errorf("policy not identified")
Expand All @@ -290,16 +228,16 @@ func (impl *CvePolicyRepositoryImpl) getApplicablePolicy(clusterId, envId, appId
return cvePolicy, severityPolicy, err
}

func (impl *CvePolicyRepositoryImpl) getPolicies(policyLevel PolicyLevel, clusterId, environmentId, appId int) (map[string]*CvePolicy, map[Severity]*CvePolicy, error) {
func (impl *CvePolicyRepositoryImpl) getPolicies(policyLevel securityBean.PolicyLevel, clusterId, environmentId, appId int) (map[string]*CvePolicy, map[securityBean.Severity]*CvePolicy, error) {
var policies []*CvePolicy
var err error
if policyLevel == Global {
if policyLevel == securityBean.Global {
policies, err = impl.GetGlobalPolicies()
} else if policyLevel == Cluster {
} else if policyLevel == securityBean.Cluster {
policies, err = impl.GetClusterPolicies(clusterId)
} else if policyLevel == Environment {
} else if policyLevel == securityBean.Environment {
policies, err = impl.GetEnvPolicies(clusterId, environmentId)
} else if policyLevel == Application {
} else if policyLevel == securityBean.Application {
policies, err = impl.GetAppEnvPolicies(clusterId, environmentId, appId)
} else {
return nil, nil, fmt.Errorf("unsupported policy level: %s", policyLevel)
Expand All @@ -314,9 +252,9 @@ func (impl *CvePolicyRepositoryImpl) getPolicies(policyLevel PolicyLevel, cluste
return cvePolicy, severityPolicy, nil
}

func (impl *CvePolicyRepositoryImpl) getApplicablePolicies(policies []*CvePolicy) (map[string]*CvePolicy, map[Severity]*CvePolicy) {
func (impl *CvePolicyRepositoryImpl) getApplicablePolicies(policies []*CvePolicy) (map[string]*CvePolicy, map[securityBean.Severity]*CvePolicy) {
cvePolicy := make(map[string][]*CvePolicy)
severityPolicy := make(map[Severity][]*CvePolicy)
severityPolicy := make(map[securityBean.Severity][]*CvePolicy)
for _, policy := range policies {
if policy.CVEStoreId != "" {
cvePolicy[policy.CveStore.Name] = append(cvePolicy[policy.CveStore.Name], policy)
Expand Down Expand Up @@ -347,8 +285,8 @@ func (impl *CvePolicyRepositoryImpl) getHighestPolicy(allPolicies map[string][]*
return applicablePolicies
}

func (impl *CvePolicyRepositoryImpl) getHighestPolicyS(allPolicies map[Severity][]*CvePolicy) map[Severity]*CvePolicy {
applicablePolicies := make(map[Severity]*CvePolicy)
func (impl *CvePolicyRepositoryImpl) getHighestPolicyS(allPolicies map[securityBean.Severity][]*CvePolicy) map[securityBean.Severity]*CvePolicy {
applicablePolicies := make(map[securityBean.Severity]*CvePolicy)
for key, policies := range allPolicies {
var applicablePolicy *CvePolicy
for _, policy := range policies {
Expand Down
14 changes: 8 additions & 6 deletions internal/sql/repository/security/CveStoreRepository.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package security
import (
"fmt"
"github.com/devtron-labs/devtron/internal/sql/repository/helper"
securityBean "github.com/devtron-labs/devtron/internal/sql/repository/security/bean"
"github.com/devtron-labs/devtron/pkg/sql"
"github.com/go-pg/pg"
"go.uber.org/zap"
Expand All @@ -27,12 +28,13 @@ import (
)

type CveStore struct {
tableName struct{} `sql:"cve_store" pg:",discard_unknown_columns"`
Name string `sql:"name,pk"`
Severity Severity `sql:"severity,notnull"`
Package string `sql:"package,notnull"` // deprecated
Version string `sql:"version,notnull"`
FixedVersion string `sql:"fixed_version,notnull"`
tableName struct{} `sql:"cve_store" pg:",discard_unknown_columns"`
Name string `sql:"name,pk"`
//Severity securityBean.Severity `sql:"severity,notnull"`
Package string `sql:"package,notnull"` // deprecated
Version string `sql:"version,notnull"`
FixedVersion string `sql:"fixed_version,notnull"`
StandardSeverity securityBean.Severity `sql:"standard_severity,notnull"`
sql.AuditLog
}

Expand Down
Loading
Loading