Skip to content

Commit 3967bbc

Browse files
feat: CVE severity categorisation and scan result listing API enhancements (#5617)
* feat: add support for app and env sorting in scan list api and add medium, high and unknown severity support * fix: query fix for appName sort or envName sort * fix: sql script number change * fix: minor changes * fix: review fix * fix: remove dml on cve_store and handle it in code handling this versioning * fix: review comments * fix: update script numbers * fix: minor fix
1 parent 92aeea3 commit 3967bbc

11 files changed

+485
-367
lines changed

api/restHandler/ImageScanRestHandler.go

+12-11
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package restHandler
1919
import (
2020
"encoding/json"
2121
"fmt"
22+
securityBean "github.com/devtron-labs/devtron/pkg/security/bean"
2223
"net/http"
2324
"strconv"
2425

@@ -70,7 +71,7 @@ func (impl ImageScanRestHandlerImpl) ScanExecutionList(w http.ResponseWriter, r
7071
}
7172

7273
decoder := json.NewDecoder(r.Body)
73-
var request *security.ImageScanRequest
74+
var request *securityBean.ImageScanRequest
7475
err = decoder.Decode(&request)
7576
if err != nil {
7677
impl.logger.Errorw("request err, ScanExecutionList", "err", err, "payload", request)
@@ -82,8 +83,8 @@ func (impl ImageScanRestHandlerImpl) ScanExecutionList(w http.ResponseWriter, r
8283
if err != nil {
8384
impl.logger.Errorw("service err, ScanExecutionList", "err", err, "payload", request)
8485
if util.IsErrNoRows(err) {
85-
responseList := make([]*security.ImageScanHistoryResponse, 0)
86-
common.WriteJsonResp(w, nil, &security.ImageScanHistoryListingResponse{ImageScanHistoryResponse: responseList}, http.StatusOK)
86+
responseList := make([]*securityBean.ImageScanHistoryResponse, 0)
87+
common.WriteJsonResp(w, nil, &securityBean.ImageScanHistoryListingResponse{ImageScanHistoryResponse: responseList}, http.StatusOK)
8788
} else {
8889
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
8990
}
@@ -126,8 +127,8 @@ func (impl ImageScanRestHandlerImpl) ScanExecutionList(w http.ResponseWriter, r
126127
if err != nil {
127128
impl.logger.Errorw("service err, ScanExecutionList", "err", err, "payload", request)
128129
if util.IsErrNoRows(err) {
129-
responseList := make([]*security.ImageScanHistoryResponse, 0)
130-
common.WriteJsonResp(w, nil, &security.ImageScanHistoryListingResponse{ImageScanHistoryResponse: responseList}, http.StatusOK)
130+
responseList := make([]*securityBean.ImageScanHistoryResponse, 0)
131+
common.WriteJsonResp(w, nil, &securityBean.ImageScanHistoryListingResponse{ImageScanHistoryResponse: responseList}, http.StatusOK)
131132
} else {
132133
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
133134
}
@@ -177,7 +178,7 @@ func (impl ImageScanRestHandlerImpl) FetchExecutionDetail(w http.ResponseWriter,
177178
}
178179
}
179180
image := v.Get("image")
180-
request := &security.ImageScanRequest{
181+
request := &securityBean.ImageScanRequest{
181182
ImageScanDeployInfoId: imageScanDeployInfoId,
182183
Image: image,
183184
ArtifactId: artifactId,
@@ -189,7 +190,7 @@ func (impl ImageScanRestHandlerImpl) FetchExecutionDetail(w http.ResponseWriter,
189190
if err != nil {
190191
impl.logger.Errorw("service err, FetchExecutionDetail", "err", err, "payload", request)
191192
if util.IsErrNoRows(err) {
192-
common.WriteJsonResp(w, nil, &security.ImageScanExecutionDetail{}, http.StatusOK)
193+
common.WriteJsonResp(w, nil, &securityBean.ImageScanExecutionDetail{}, http.StatusOK)
193194
} else {
194195
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
195196
}
@@ -221,7 +222,7 @@ func (impl ImageScanRestHandlerImpl) FetchExecutionDetail(w http.ResponseWriter,
221222
}
222223
//RBAC
223224
} else {
224-
common.WriteJsonResp(w, err, &security.ImageScanExecutionDetail{}, http.StatusOK)
225+
common.WriteJsonResp(w, err, &securityBean.ImageScanExecutionDetail{}, http.StatusOK)
225226
}
226227

227228
common.WriteJsonResp(w, err, executionDetail, http.StatusOK)
@@ -230,7 +231,7 @@ func (impl ImageScanRestHandlerImpl) FetchExecutionDetail(w http.ResponseWriter,
230231
func (impl ImageScanRestHandlerImpl) FetchMinScanResultByAppIdAndEnvId(w http.ResponseWriter, r *http.Request) {
231232
v := r.URL.Query()
232233
var appId, envId int
233-
request := &security.ImageScanRequest{}
234+
request := &securityBean.ImageScanRequest{}
234235
appIds := v.Get("appId")
235236
if len(appIds) > 0 {
236237
appId, err := strconv.Atoi(appIds)
@@ -299,8 +300,8 @@ func (impl ImageScanRestHandlerImpl) VulnerabilityExposure(w http.ResponseWriter
299300
if err != nil {
300301
impl.logger.Errorw("service err, VulnerabilityExposure", "err", err, "payload", request)
301302
if util.IsErrNoRows(err) {
302-
responseList := make([]*security.ImageScanHistoryResponse, 0)
303-
common.WriteJsonResp(w, nil, &security.ImageScanHistoryListingResponse{ImageScanHistoryResponse: responseList}, http.StatusOK)
303+
responseList := make([]*securityBean.ImageScanHistoryResponse, 0)
304+
common.WriteJsonResp(w, nil, &securityBean.ImageScanHistoryListingResponse{ImageScanHistoryResponse: responseList}, http.StatusOK)
304305
} else {
305306
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
306307
}

api/restHandler/PolicyRestHandler.go

+10-10
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ import (
2020
"encoding/json"
2121
"errors"
2222
"fmt"
23+
securityBean "github.com/devtron-labs/devtron/internal/sql/repository/security/bean"
2324
"net/http"
2425
"strconv"
2526

2627
"github.com/devtron-labs/devtron/api/bean"
2728
"github.com/devtron-labs/devtron/api/restHandler/common"
28-
security2 "github.com/devtron-labs/devtron/internal/sql/repository/security"
2929
"github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin"
3030
user2 "github.com/devtron-labs/devtron/pkg/auth/user"
3131
"github.com/devtron-labs/devtron/pkg/cluster"
@@ -221,18 +221,18 @@ func (impl PolicyRestHandlerImpl) GetPolicy(w http.ResponseWriter, r *http.Reque
221221
req.Id = ids
222222
}
223223
var clusterId, environmentId, appId int
224-
var policyLevel security2.PolicyLevel
225-
if level == security2.Global.String() {
226-
policyLevel = security2.Global
227-
} else if level == security2.Cluster.String() {
224+
var policyLevel securityBean.PolicyLevel
225+
if level == securityBean.Global.String() {
226+
policyLevel = securityBean.Global
227+
} else if level == securityBean.Cluster.String() {
228228
clusterId = req.Id
229-
policyLevel = security2.Cluster
230-
} else if level == security2.Environment.String() {
229+
policyLevel = securityBean.Cluster
230+
} else if level == securityBean.Environment.String() {
231231
environmentId = req.Id
232-
policyLevel = security2.Environment
233-
} else if level == security2.Application.String() {
232+
policyLevel = securityBean.Environment
233+
} else if level == securityBean.Application.String() {
234234
appId = req.Id
235-
policyLevel = security2.Application
235+
policyLevel = securityBean.Application
236236
}
237237

238238
token := r.Header.Get("token")

internal/sql/repository/security/CvePolicyControle.go

+36-98
Original file line numberDiff line numberDiff line change
@@ -18,99 +18,37 @@ package security
1818

1919
import (
2020
"fmt"
21+
securityBean "github.com/devtron-labs/devtron/internal/sql/repository/security/bean"
2122
"github.com/devtron-labs/devtron/pkg/sql"
2223
"github.com/go-pg/pg"
2324
"github.com/go-pg/pg/orm"
2425
"time"
2526
)
2627

2728
type CvePolicy struct {
28-
tableName struct{} `sql:"cve_policy_control" pg:",discard_unknown_columns"`
29-
Id int `sql:"id,pk"`
30-
Global bool `sql:"global,notnull"`
31-
ClusterId int `sql:"cluster_id"`
32-
EnvironmentId int `sql:"env_id"`
33-
AppId int `sql:"app_id"`
34-
CVEStoreId string `sql:"cve_store_id"`
35-
Action PolicyAction `sql:"action, notnull"`
36-
Severity *Severity `sql:"severity, notnull "`
37-
Deleted bool `sql:"deleted, notnull"`
29+
tableName struct{} `sql:"cve_policy_control" pg:",discard_unknown_columns"`
30+
Id int `sql:"id,pk"`
31+
Global bool `sql:"global,notnull"`
32+
ClusterId int `sql:"cluster_id"`
33+
EnvironmentId int `sql:"env_id"`
34+
AppId int `sql:"app_id"`
35+
CVEStoreId string `sql:"cve_store_id"`
36+
Action securityBean.PolicyAction `sql:"action, notnull"`
37+
Severity *securityBean.Severity `sql:"severity, notnull "`
38+
Deleted bool `sql:"deleted, notnull"`
3839
sql.AuditLog
3940
CveStore *CveStore
4041
}
4142

42-
type PolicyAction int
43-
44-
const (
45-
Inherit PolicyAction = iota
46-
Allow
47-
Block
48-
Blockiffixed
49-
)
50-
51-
func (d PolicyAction) String() string {
52-
return [...]string{"inherit", "allow", "block", "blockiffixed"}[d]
53-
}
54-
55-
// ------------------
56-
type Severity int
57-
58-
const (
59-
Low Severity = iota
60-
Medium
61-
Critical
62-
High
63-
Safe
64-
)
65-
const (
66-
HIGH string = "high"
67-
CRITICAL string = "critical"
68-
SAFE string = "safe"
69-
LOW string = "low"
70-
MEDIUM string = "medium"
71-
MODERATE string = "moderate"
72-
)
73-
74-
// Handling for future use
75-
func (d Severity) ValuesOf(severity string) Severity {
76-
if severity == CRITICAL || severity == HIGH {
77-
return Critical
78-
} else if severity == MODERATE || severity == MEDIUM {
79-
return Medium
80-
} else if severity == LOW || severity == SAFE {
81-
return Low
82-
}
83-
return Low
84-
}
85-
86-
// Updating it for future use(not in use for standard severity)
87-
func (d Severity) String() string {
88-
return [...]string{"low", "moderate", "critical", "high", "safe"}[d]
89-
}
90-
91-
// ----------------
92-
type PolicyLevel int
93-
94-
const (
95-
Global PolicyLevel = iota
96-
Cluster
97-
Environment
98-
Application
99-
)
100-
101-
func (d PolicyLevel) String() string {
102-
return [...]string{"global", "cluster", "environment", "application"}[d]
103-
}
104-
105-
func (policy *CvePolicy) PolicyLevel() PolicyLevel {
43+
func (policy *CvePolicy) PolicyLevel() securityBean.PolicyLevel {
10644
if policy.ClusterId != 0 {
107-
return Cluster
45+
return securityBean.Cluster
10846
} else if policy.AppId != 0 {
109-
return Application
47+
return securityBean.Application
11048
} else if policy.EnvironmentId != 0 {
111-
return Environment
49+
return securityBean.Environment
11250
} else {
113-
return Global
51+
return securityBean.Global
11452
}
11553
}
11654

@@ -250,37 +188,37 @@ func (impl *CvePolicyRepositoryImpl) GetBlockedCVEList(cves []*CveStore, cluster
250188
return blockedCve, nil
251189
}
252190

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

255193
for _, cve := range cves {
256194
if policy, ok := cvePolicy[cve.Name]; ok {
257-
if policy.Action == Allow {
195+
if policy.Action == securityBean.Allow {
258196
continue
259-
} else if (policy.Action == Block) || (policy.Action == Blockiffixed && cve.FixedVersion != "") {
197+
} else if (policy.Action == securityBean.Block) || (policy.Action == securityBean.Blockiffixed && cve.FixedVersion != "") {
260198
blockedCVE = append(blockedCVE, cve)
261199
}
262200
} else {
263-
if severityPolicy[cve.Severity] != nil && severityPolicy[cve.Severity].Action == Allow {
201+
if severityPolicy[cve.GetSeverity()] != nil && severityPolicy[cve.GetSeverity()].Action == securityBean.Allow {
264202
continue
265-
} else if severityPolicy[cve.Severity] != nil && (severityPolicy[cve.Severity].Action == Block || (severityPolicy[cve.Severity].Action == Blockiffixed && cve.FixedVersion != "")) {
203+
} else if severityPolicy[cve.GetSeverity()] != nil && (severityPolicy[cve.GetSeverity()].Action == securityBean.Block || (severityPolicy[cve.GetSeverity()].Action == securityBean.Blockiffixed && cve.FixedVersion != "")) {
266204
blockedCVE = append(blockedCVE, cve)
267205
}
268206
}
269207
}
270208
return blockedCVE
271209
}
272210

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

275-
var policyLevel PolicyLevel
213+
var policyLevel securityBean.PolicyLevel
276214
if isAppstore && appId > 0 && envId > 0 && clusterId > 0 {
277-
policyLevel = Environment
215+
policyLevel = securityBean.Environment
278216
} else if appId > 0 && envId > 0 && clusterId > 0 {
279-
policyLevel = Application
217+
policyLevel = securityBean.Application
280218
} else if envId > 0 && clusterId > 0 {
281-
policyLevel = Environment
219+
policyLevel = securityBean.Environment
282220
} else if clusterId > 0 {
283-
policyLevel = Cluster
221+
policyLevel = securityBean.Cluster
284222
} else {
285223
//error in case of global or other policy
286224
return nil, nil, fmt.Errorf("policy not identified")
@@ -290,16 +228,16 @@ func (impl *CvePolicyRepositoryImpl) getApplicablePolicy(clusterId, envId, appId
290228
return cvePolicy, severityPolicy, err
291229
}
292230

293-
func (impl *CvePolicyRepositoryImpl) getPolicies(policyLevel PolicyLevel, clusterId, environmentId, appId int) (map[string]*CvePolicy, map[Severity]*CvePolicy, error) {
231+
func (impl *CvePolicyRepositoryImpl) getPolicies(policyLevel securityBean.PolicyLevel, clusterId, environmentId, appId int) (map[string]*CvePolicy, map[securityBean.Severity]*CvePolicy, error) {
294232
var policies []*CvePolicy
295233
var err error
296-
if policyLevel == Global {
234+
if policyLevel == securityBean.Global {
297235
policies, err = impl.GetGlobalPolicies()
298-
} else if policyLevel == Cluster {
236+
} else if policyLevel == securityBean.Cluster {
299237
policies, err = impl.GetClusterPolicies(clusterId)
300-
} else if policyLevel == Environment {
238+
} else if policyLevel == securityBean.Environment {
301239
policies, err = impl.GetEnvPolicies(clusterId, environmentId)
302-
} else if policyLevel == Application {
240+
} else if policyLevel == securityBean.Application {
303241
policies, err = impl.GetAppEnvPolicies(clusterId, environmentId, appId)
304242
} else {
305243
return nil, nil, fmt.Errorf("unsupported policy level: %s", policyLevel)
@@ -314,9 +252,9 @@ func (impl *CvePolicyRepositoryImpl) getPolicies(policyLevel PolicyLevel, cluste
314252
return cvePolicy, severityPolicy, nil
315253
}
316254

317-
func (impl *CvePolicyRepositoryImpl) getApplicablePolicies(policies []*CvePolicy) (map[string]*CvePolicy, map[Severity]*CvePolicy) {
255+
func (impl *CvePolicyRepositoryImpl) getApplicablePolicies(policies []*CvePolicy) (map[string]*CvePolicy, map[securityBean.Severity]*CvePolicy) {
318256
cvePolicy := make(map[string][]*CvePolicy)
319-
severityPolicy := make(map[Severity][]*CvePolicy)
257+
severityPolicy := make(map[securityBean.Severity][]*CvePolicy)
320258
for _, policy := range policies {
321259
if policy.CVEStoreId != "" {
322260
cvePolicy[policy.CveStore.Name] = append(cvePolicy[policy.CveStore.Name], policy)
@@ -347,8 +285,8 @@ func (impl *CvePolicyRepositoryImpl) getHighestPolicy(allPolicies map[string][]*
347285
return applicablePolicies
348286
}
349287

350-
func (impl *CvePolicyRepositoryImpl) getHighestPolicyS(allPolicies map[Severity][]*CvePolicy) map[Severity]*CvePolicy {
351-
applicablePolicies := make(map[Severity]*CvePolicy)
288+
func (impl *CvePolicyRepositoryImpl) getHighestPolicyS(allPolicies map[securityBean.Severity][]*CvePolicy) map[securityBean.Severity]*CvePolicy {
289+
applicablePolicies := make(map[securityBean.Severity]*CvePolicy)
352290
for key, policies := range allPolicies {
353291
var applicablePolicy *CvePolicy
354292
for _, policy := range policies {

internal/sql/repository/security/CveStoreRepository.go

+33-6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package security
1919
import (
2020
"fmt"
2121
"github.com/devtron-labs/devtron/internal/sql/repository/helper"
22+
securityBean "github.com/devtron-labs/devtron/internal/sql/repository/security/bean"
2223
"github.com/devtron-labs/devtron/pkg/sql"
2324
"github.com/go-pg/pg"
2425
"go.uber.org/zap"
@@ -27,15 +28,41 @@ import (
2728
)
2829

2930
type CveStore struct {
30-
tableName struct{} `sql:"cve_store" pg:",discard_unknown_columns"`
31-
Name string `sql:"name,pk"`
32-
Severity Severity `sql:"severity,notnull"`
33-
Package string `sql:"package,notnull"` // deprecated
34-
Version string `sql:"version,notnull"`
35-
FixedVersion string `sql:"fixed_version,notnull"`
31+
tableName struct{} `sql:"cve_store" pg:",discard_unknown_columns"`
32+
Name string `sql:"name,pk"`
33+
34+
// Deprecated: Severity, use StandardSeverity for all read purposes
35+
Severity securityBean.Severity `sql:"severity,notnull"`
36+
// Deprecated: Package
37+
Package string `sql:"package,notnull"` // deprecated, storing package data in image_scan_execution_result table
38+
// Deprecated: Version
39+
Version string `sql:"version,notnull"`
40+
// Deprecated: FixedVersion
41+
FixedVersion string `sql:"fixed_version,notnull"`
42+
43+
// StandardSeverity is the actual severity. use GetSeverity method to get severity of the vulnerability
44+
// earlier severity is maintained in Severity column by merging HIGH and CRITICAL severities.
45+
// later we introduced new column StandardSeverity to store raw severity, but didn't migrate the existing Severity data to StandardSeverity.
46+
// currently, we deprecated Severity.
47+
StandardSeverity *securityBean.Severity `sql:"standard_severity"`
3648
sql.AuditLog
3749
}
3850

51+
// GetSeverity returns the actual severity of the vulnerability.
52+
func (cve *CveStore) GetSeverity() securityBean.Severity {
53+
if cve.StandardSeverity == nil {
54+
// we need this as there was a time when StandardSeverity didn't exist.
55+
// and migration of Severity data to StandardSeverity is not done.
56+
return cve.Severity
57+
}
58+
return *cve.StandardSeverity
59+
}
60+
61+
func (cve *CveStore) SetStandardSeverity(severity securityBean.Severity) {
62+
cve.Severity = severity
63+
cve.StandardSeverity = &severity
64+
}
65+
3966
type VulnerabilityRequest struct {
4067
AppName string `json:"appName"`
4168
CveName string `json:"cveName"`

0 commit comments

Comments
 (0)