Skip to content

Commit 502ebf2

Browse files
committed
Add VulnerabilitySummary and VulnerabilityListing APIs to ImageScan module
**Details**: - Introduced new APIs for fetching vulnerability summary and listing with advanced filtering. - Updated `ImageScanRestHandler` with handlers for the new endpoints. - Enhanced `ImageScanService` to support vulnerability data aggregation, filtering, and pagination. - Added `ScanStatus` filter for advanced scan state queries. - Updated repositories and models for new data structures and query support.
1 parent 4b94d56 commit 502ebf2

File tree

6 files changed

+946
-21
lines changed

6 files changed

+946
-21
lines changed

api/restHandler/ImageScanRestHandler.go

Lines changed: 233 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,16 @@ package restHandler
1919
import (
2020
"encoding/json"
2121
"fmt"
22+
"net/http"
23+
"strconv"
24+
2225
"github.com/devtron-labs/devtron/pkg/cluster/environment"
2326
"github.com/devtron-labs/devtron/pkg/policyGovernance/security/imageScanning"
2427
securityBean "github.com/devtron-labs/devtron/pkg/policyGovernance/security/imageScanning/bean"
2528
security2 "github.com/devtron-labs/devtron/pkg/policyGovernance/security/imageScanning/repository"
29+
"github.com/devtron-labs/devtron/pkg/policyGovernance/security/imageScanning/repository/bean"
2630
"github.com/devtron-labs/devtron/util/sliceUtil"
27-
"net/http"
28-
"strconv"
31+
"go.opentelemetry.io/otel"
2932

3033
"github.com/devtron-labs/devtron/api/restHandler/common"
3134
"github.com/devtron-labs/devtron/internal/util"
@@ -46,6 +49,8 @@ type ImageScanRestHandler interface {
4649
FetchExecutionDetail(w http.ResponseWriter, r *http.Request)
4750
FetchMinScanResultByAppIdAndEnvId(w http.ResponseWriter, r *http.Request)
4851
VulnerabilityExposure(w http.ResponseWriter, r *http.Request)
52+
VulnerabilitySummary(w http.ResponseWriter, r *http.Request)
53+
VulnerabilityListing(w http.ResponseWriter, r *http.Request)
4954
}
5055

5156
type ImageScanRestHandlerImpl struct {
@@ -402,3 +407,229 @@ func (impl ImageScanRestHandlerImpl) VulnerabilityExposure(w http.ResponseWriter
402407
results.VulnerabilityExposure = vulnerabilityExposure
403408
common.WriteJsonResp(w, err, results, http.StatusOK)
404409
}
410+
411+
func (impl ImageScanRestHandlerImpl) VulnerabilitySummary(w http.ResponseWriter, r *http.Request) {
412+
ctx, span := otel.Tracer("imageScanRestHandler").Start(r.Context(), "VulnerabilitySummary")
413+
defer span.End()
414+
415+
userId, err := impl.userService.GetLoggedInUser(r)
416+
if userId == 0 || err != nil {
417+
common.HandleUnauthorized(w, r)
418+
return
419+
}
420+
421+
// Parse request body with filters
422+
decoder := json.NewDecoder(r.Body)
423+
var summaryRequest *securityBean.VulnerabilitySummaryRequest
424+
err = decoder.Decode(&summaryRequest)
425+
if err != nil {
426+
impl.logger.Errorw("request err, VulnerabilitySummary", "err", err, "payload", r.Body)
427+
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
428+
return
429+
}
430+
431+
// Create ImageScanRequest with filters for fetching deploy info
432+
request := &securityBean.ImageScanRequest{
433+
ImageScanFilter: bean.ImageScanFilter{
434+
EnvironmentIds: summaryRequest.EnvironmentIds,
435+
ClusterIds: summaryRequest.ClusterIds,
436+
},
437+
}
438+
439+
deployInfoList, err := impl.imageScanService.FetchAllDeployInfo(request)
440+
if err != nil {
441+
impl.logger.Errorw("service err, VulnerabilitySummary", "err", err)
442+
if util.IsErrNoRows(err) {
443+
emptySummary := &securityBean.VulnerabilitySummary{
444+
TotalVulnerabilities: 0,
445+
SeverityCount: &securityBean.SeverityCount{
446+
Critical: 0,
447+
High: 0,
448+
Medium: 0,
449+
Low: 0,
450+
Unknown: 0,
451+
},
452+
FixableVulnerabilities: 0,
453+
NotFixableVulnerabilities: 0,
454+
}
455+
common.WriteJsonResp(w, nil, emptySummary, http.StatusOK)
456+
} else {
457+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
458+
}
459+
return
460+
}
461+
462+
filteredDeployInfoList, err := impl.imageScanService.FilterDeployInfoByScannedArtifactsDeployedInEnv(deployInfoList)
463+
if err != nil {
464+
impl.logger.Errorw("request err, FilterDeployInfoListForScannedArtifacts", "err", err)
465+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
466+
return
467+
}
468+
469+
_, rbacSpan := otel.Tracer("imageScanRestHandler").Start(ctx, "RBACProcessing")
470+
token := r.Header.Get("token")
471+
isSuperAdmin := false
472+
if ok := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*"); ok {
473+
isSuperAdmin = true
474+
}
475+
var ids []int
476+
if isSuperAdmin {
477+
ids = sliceUtil.NewSliceFromFuncExec(filteredDeployInfoList, func(item *security2.ImageScanDeployInfo) int {
478+
return item.Id
479+
})
480+
} else {
481+
ids, err = impl.getAuthorisedImageScanDeployInfoIds(token, filteredDeployInfoList)
482+
if err != nil {
483+
impl.logger.Errorw("error in getting authorised image scan deploy info ids", "err", err)
484+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
485+
return
486+
}
487+
}
488+
rbacSpan.End()
489+
490+
if len(ids) == 0 {
491+
emptySummary := &securityBean.VulnerabilitySummary{
492+
TotalVulnerabilities: 0,
493+
SeverityCount: &securityBean.SeverityCount{
494+
Critical: 0,
495+
High: 0,
496+
Medium: 0,
497+
Low: 0,
498+
Unknown: 0,
499+
},
500+
FixableVulnerabilities: 0,
501+
NotFixableVulnerabilities: 0,
502+
}
503+
common.WriteJsonResp(w, nil, emptySummary, http.StatusOK)
504+
return
505+
}
506+
507+
summary, err := impl.imageScanService.FetchVulnerabilitySummary(ctx, summaryRequest, ids)
508+
if err != nil {
509+
impl.logger.Errorw("service err, VulnerabilitySummary", "err", err)
510+
if util.IsErrNoRows(err) {
511+
emptySummary := &securityBean.VulnerabilitySummary{
512+
TotalVulnerabilities: 0,
513+
SeverityCount: &securityBean.SeverityCount{
514+
Critical: 0,
515+
High: 0,
516+
Medium: 0,
517+
Low: 0,
518+
Unknown: 0,
519+
},
520+
FixableVulnerabilities: 0,
521+
NotFixableVulnerabilities: 0,
522+
}
523+
common.WriteJsonResp(w, nil, emptySummary, http.StatusOK)
524+
} else {
525+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
526+
}
527+
return
528+
}
529+
common.WriteJsonResp(w, err, summary, http.StatusOK)
530+
}
531+
532+
func (impl ImageScanRestHandlerImpl) VulnerabilityListing(w http.ResponseWriter, r *http.Request) {
533+
ctx, span := otel.Tracer("imageScanRestHandler").Start(r.Context(), "VulnerabilityListing")
534+
defer span.End()
535+
536+
userId, err := impl.userService.GetLoggedInUser(r)
537+
if userId == 0 || err != nil {
538+
common.HandleUnauthorized(w, r)
539+
return
540+
}
541+
542+
// Parse request body
543+
decoder := json.NewDecoder(r.Body)
544+
var request *securityBean.VulnerabilityListingRequest
545+
err = decoder.Decode(&request)
546+
if err != nil {
547+
impl.logger.Errorw("request err, VulnerabilityListing", "err", err, "payload", r.Body)
548+
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
549+
return
550+
}
551+
552+
// Fetch all deploy info to apply RBAC
553+
deployInfoRequest := &securityBean.ImageScanRequest{
554+
ImageScanFilter: bean.ImageScanFilter{
555+
EnvironmentIds: request.EnvironmentIds,
556+
ClusterIds: request.ClusterIds,
557+
},
558+
}
559+
560+
deployInfoList, err := impl.imageScanService.FetchAllDeployInfo(deployInfoRequest)
561+
if err != nil {
562+
impl.logger.Errorw("service err, VulnerabilityListing", "err", err)
563+
if util.IsErrNoRows(err) {
564+
emptyResponse := &securityBean.VulnerabilityListingResponse{
565+
Offset: request.Offset,
566+
Size: request.Size,
567+
Total: 0,
568+
Vulnerabilities: []*securityBean.VulnerabilityDetail{},
569+
}
570+
common.WriteJsonResp(w, nil, emptyResponse, http.StatusOK)
571+
} else {
572+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
573+
}
574+
return
575+
}
576+
577+
filteredDeployInfoList, err := impl.imageScanService.FilterDeployInfoByScannedArtifactsDeployedInEnv(deployInfoList)
578+
if err != nil {
579+
impl.logger.Errorw("request err, FilterDeployInfoListForScannedArtifacts", "err", err)
580+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
581+
return
582+
}
583+
584+
// Apply RBAC
585+
_, rbacSpan := otel.Tracer("imageScanRestHandler").Start(ctx, "RBACProcessing")
586+
token := r.Header.Get("token")
587+
isSuperAdmin := false
588+
if ok := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*"); ok {
589+
isSuperAdmin = true
590+
}
591+
var ids []int
592+
if isSuperAdmin {
593+
ids = sliceUtil.NewSliceFromFuncExec(filteredDeployInfoList, func(item *security2.ImageScanDeployInfo) int {
594+
return item.Id
595+
})
596+
} else {
597+
ids, err = impl.getAuthorisedImageScanDeployInfoIds(token, filteredDeployInfoList)
598+
if err != nil {
599+
impl.logger.Errorw("error in getting authorised image scan deploy info ids", "err", err)
600+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
601+
return
602+
}
603+
}
604+
rbacSpan.End()
605+
606+
if len(ids) == 0 {
607+
emptyResponse := &securityBean.VulnerabilityListingResponse{
608+
Offset: request.Offset,
609+
Size: request.Size,
610+
Total: 0,
611+
Vulnerabilities: []*securityBean.VulnerabilityDetail{},
612+
}
613+
common.WriteJsonResp(w, nil, emptyResponse, http.StatusOK)
614+
return
615+
}
616+
617+
// Fetch vulnerability listing
618+
listing, err := impl.imageScanService.FetchVulnerabilityListing(ctx, request, ids)
619+
if err != nil {
620+
impl.logger.Errorw("service err, VulnerabilityListing", "err", err)
621+
if util.IsErrNoRows(err) {
622+
emptyResponse := &securityBean.VulnerabilityListingResponse{
623+
Offset: request.Offset,
624+
Size: request.Size,
625+
Total: 0,
626+
Vulnerabilities: []*securityBean.VulnerabilityDetail{},
627+
}
628+
common.WriteJsonResp(w, nil, emptyResponse, http.StatusOK)
629+
} else {
630+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
631+
}
632+
return
633+
}
634+
common.WriteJsonResp(w, err, listing, http.StatusOK)
635+
}

api/router/ImageScanRouter.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ func NewImageScanRouterImpl(imageScanRestHandler restHandler.ImageScanRestHandle
3434

3535
func (impl ImageScanRouterImpl) InitImageScanRouter(configRouter *mux.Router) {
3636
configRouter.Path("/list").HandlerFunc(impl.imageScanRestHandler.ScanExecutionList).Methods("POST")
37+
configRouter.Path("/summary").HandlerFunc(impl.imageScanRestHandler.VulnerabilitySummary).Methods("POST")
38+
configRouter.Path("/vulnerabilities").HandlerFunc(impl.imageScanRestHandler.VulnerabilityListing).Methods("POST")
3739

3840
//image=image:abc&envId=3&appId=100&artifactId=100&executionId=100
3941
configRouter.Path("/executionDetail").HandlerFunc(impl.imageScanRestHandler.FetchExecutionDetail).Methods("GET")

pkg/cluster/environment/EnvironmentService.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ type EnvironmentService interface {
7070
GetCombinedEnvironmentListForDropDownByClusterIds(token string, clusterIds []int, auth func(token string, object string) bool) ([]*bean2.ClusterEnvDto, error)
7171
HandleErrorInClusterConnections(clusters []*bean4.ClusterBean, respMap *sync.Map, clusterExistInDb bool)
7272
GetDetailsById(envId int) (*repository.Environment, error)
73+
FindNamesByIds(envIds []int) (map[int]string, error)
7374
}
7475

7576
type EnvironmentServiceImpl struct {
@@ -752,3 +753,7 @@ func (impl EnvironmentServiceImpl) GetDetailsById(envId int) (*repository.Enviro
752753
}
753754
return envDetails, nil
754755
}
756+
757+
func (impl EnvironmentServiceImpl) FindNamesByIds(envIds []int) (map[int]string, error) {
758+
return impl.environmentRepository.FindNamesByIds(envIds)
759+
}

0 commit comments

Comments
 (0)