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

Issue-1466: Enhance the pdf report generation #1804

Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
64 changes: 47 additions & 17 deletions deepfence_worker/tasks/reports/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ type ScanData[T any] struct {
}

type NodeWiseData[T any] struct {
SeverityCount map[string]map[string]int32
ScanData map[string]ScanData[T]
SeverityCount map[string]map[string]int32
ScanData map[string]ScanData[T]
OverallSeverityCounts map[string]int32
}

func searchScansFilter(params sdkUtils.ReportParams) rptSearch.SearchScanReq {
Expand Down Expand Up @@ -118,7 +119,17 @@ func scanResultFilter(levelKey string, levelValues []string, masked []bool) repo

return filter
}
func CalculateOverallSeverityCounts(severityCountsList ...map[string]int32) map[string]int32 {
overallSeverityCounts := make(map[string]int32)
ramanan-ravi marked this conversation as resolved.
Show resolved Hide resolved

for _, severityCounts := range severityCountsList {
for severity, count := range severityCounts {
overallSeverityCounts[severity] += count
}
}

return overallSeverityCounts
}
func getVulnerabilityData(ctx context.Context, params sdkUtils.ReportParams) (*Info[model.Vulnerability], error) {

if params.Filters.MostExploitableReport {
Expand Down Expand Up @@ -151,11 +162,14 @@ func getVulnerabilityData(ctx context.Context, params sdkUtils.ReportParams) (*I
params.Filters.SeverityOrCheckType, params.Filters.AdvancedReportFilters.Masked)

nodeWiseData := NodeWiseData[model.Vulnerability]{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was there before that PR but this could be simplified a lot across model.Secret, model.Vulnerability and so on

SeverityCount: make(map[string]map[string]int32),
ScanData: make(map[string]ScanData[model.Vulnerability]),
SeverityCount: make(map[string]map[string]int32),
ScanData: make(map[string]ScanData[model.Vulnerability]),
OverallSeverityCounts: make(map[string]int32),
}

overallSeverityCounts := CalculateOverallSeverityCounts()
for _, s := range scans {
overallSeverityCounts = CalculateOverallSeverityCounts(overallSeverityCounts, s.SeverityCounts)
result, common, err := rptScans.GetScanResults[model.Vulnerability](
ctx, sdkUtils.NEO4JVulnerabilityScan, s.ScanID, severityFilter, model.FetchWindow{})
if err != nil {
Expand All @@ -171,6 +185,7 @@ func getVulnerabilityData(ctx context.Context, params sdkUtils.ReportParams) (*I
ScanResults: result,
}
}
nodeWiseData.OverallSeverityCounts = overallSeverityCounts

data := Info[model.Vulnerability]{
ScanType: VULNERABILITY,
Expand Down Expand Up @@ -200,13 +215,15 @@ func getMostExploitableVulnData(ctx context.Context, params sdkUtils.ReportParam
start time.Time = time.Now()
)
nodeWiseData := NodeWiseData[model.Vulnerability]{
SeverityCount: make(map[string]map[string]int32),
ScanData: make(map[string]ScanData[model.Vulnerability]),
SeverityCount: make(map[string]map[string]int32),
ScanData: make(map[string]ScanData[model.Vulnerability]),
OverallSeverityCounts: make(map[string]int32),
}
nodeKey := "most_exploitable_vulnerabilities"
nodeWiseData.SeverityCount[nodeKey] = make(map[string]int32)
nodeWiseData.ScanData[nodeKey] = ScanData[model.Vulnerability]{ScanResults: entries}
sevMap := nodeWiseData.SeverityCount[nodeKey]
nodeWiseData.OverallSeverityCounts = CalculateOverallSeverityCounts(nodeWiseData.OverallSeverityCounts, nodeWiseData.SeverityCount[nodeKey])
for _, entry := range entries {
count, present := sevMap[entry.CveSeverity]
if !present {
Expand Down Expand Up @@ -258,11 +275,13 @@ func getSecretData(ctx context.Context, params sdkUtils.ReportParams) (*Info[mod
params.Filters.SeverityOrCheckType, params.Filters.AdvancedReportFilters.Masked)

nodeWiseData := NodeWiseData[model.Secret]{
SeverityCount: make(map[string]map[string]int32),
ScanData: make(map[string]ScanData[model.Secret]),
SeverityCount: make(map[string]map[string]int32),
ScanData: make(map[string]ScanData[model.Secret]),
OverallSeverityCounts: make(map[string]int32),
}

overallSeverityCounts := CalculateOverallSeverityCounts()
for _, s := range scans {
overallSeverityCounts = CalculateOverallSeverityCounts(overallSeverityCounts, s.SeverityCounts)
ramanan-ravi marked this conversation as resolved.
Show resolved Hide resolved
result, common, err := rptScans.GetScanResults[model.Secret](
ctx, sdkUtils.NEO4JSecretScan, s.ScanID, severityFilter, model.FetchWindow{})
if err != nil {
Expand All @@ -278,6 +297,7 @@ func getSecretData(ctx context.Context, params sdkUtils.ReportParams) (*Info[mod
ScanResults: result,
}
}
nodeWiseData.OverallSeverityCounts = overallSeverityCounts

data := Info[model.Secret]{
ScanType: SECRET,
Expand Down Expand Up @@ -319,11 +339,13 @@ func getMalwareData(ctx context.Context, params sdkUtils.ReportParams) (*Info[mo
params.Filters.SeverityOrCheckType, params.Filters.AdvancedReportFilters.Masked)

nodeWiseData := NodeWiseData[model.Malware]{
SeverityCount: make(map[string]map[string]int32),
ScanData: make(map[string]ScanData[model.Malware]),
SeverityCount: make(map[string]map[string]int32),
ScanData: make(map[string]ScanData[model.Malware]),
OverallSeverityCounts: make(map[string]int32),
}

overallSeverityCounts := CalculateOverallSeverityCounts()
for _, s := range scans {
overallSeverityCounts = CalculateOverallSeverityCounts(overallSeverityCounts, s.SeverityCounts)
result, common, err := rptScans.GetScanResults[model.Malware](
ctx, sdkUtils.NEO4JMalwareScan, s.ScanID, severityFilter, model.FetchWindow{})
if err != nil {
Expand All @@ -339,6 +361,7 @@ func getMalwareData(ctx context.Context, params sdkUtils.ReportParams) (*Info[mo
ScanResults: result,
}
}
nodeWiseData.OverallSeverityCounts = overallSeverityCounts

data := Info[model.Malware]{
ScanType: MALWARE,
Expand Down Expand Up @@ -380,11 +403,13 @@ func getComplianceData(ctx context.Context, params sdkUtils.ReportParams) (*Info
params.Filters.SeverityOrCheckType, params.Filters.AdvancedReportFilters.Masked)

nodeWiseData := NodeWiseData[model.Compliance]{
SeverityCount: make(map[string]map[string]int32),
ScanData: make(map[string]ScanData[model.Compliance]),
SeverityCount: make(map[string]map[string]int32),
ScanData: make(map[string]ScanData[model.Compliance]),
OverallSeverityCounts: make(map[string]int32),
}

overallSeverityCounts := CalculateOverallSeverityCounts()
for _, s := range scans {
overallSeverityCounts = CalculateOverallSeverityCounts(overallSeverityCounts, s.SeverityCounts)
result, common, err := rptScans.GetScanResults[model.Compliance](
ctx, sdkUtils.NEO4JComplianceScan, s.ScanID, severityFilter, model.FetchWindow{})
if err != nil {
Expand All @@ -400,6 +425,7 @@ func getComplianceData(ctx context.Context, params sdkUtils.ReportParams) (*Info
ScanResults: result,
}
}
nodeWiseData.OverallSeverityCounts = overallSeverityCounts

data := Info[model.Compliance]{
ScanType: COMPLIANCE,
Expand Down Expand Up @@ -442,11 +468,14 @@ func getCloudComplianceData(ctx context.Context, params sdkUtils.ReportParams) (
params.Filters.SeverityOrCheckType, params.Filters.AdvancedReportFilters.Masked)

nodeWiseData := NodeWiseData[model.CloudCompliance]{
SeverityCount: make(map[string]map[string]int32),
ScanData: make(map[string]ScanData[model.CloudCompliance]),
SeverityCount: make(map[string]map[string]int32),
ScanData: make(map[string]ScanData[model.CloudCompliance]),
OverallSeverityCounts: make(map[string]int32),
}
overallSeverityCounts := CalculateOverallSeverityCounts()

for _, s := range scans {
overallSeverityCounts = CalculateOverallSeverityCounts(overallSeverityCounts, s.SeverityCounts)
result, common, err := rptScans.GetScanResults[model.CloudCompliance](
ctx, sdkUtils.NEO4JCloudComplianceScan, s.ScanID, severityFilter, model.FetchWindow{})
if err != nil {
Expand All @@ -462,6 +491,7 @@ func getCloudComplianceData(ctx context.Context, params sdkUtils.ReportParams) (
ScanResults: result,
}
}
nodeWiseData.OverallSeverityCounts = overallSeverityCounts

data := Info[model.CloudCompliance]{
ScanType: CLOUD_COMPLIANCE,
Expand Down
2 changes: 2 additions & 0 deletions deepfence_worker/tasks/reports/templates/base.gohtml
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@
}

</style>
<script src="https://go-echarts.github.io/go-echarts-assets/assets/echarts.min.js"></script>
</head>

<body>
Expand All @@ -265,6 +266,7 @@
{{ $scan_types := list "vulnerability" "secret" "malware" }}
{{ if mustHas .ScanType $scan_types }}
{{ template "summary-table" . }}
{{ template "piechart" . }}
{{ end }}

{{ if eq .ScanType "compliance" }}
Expand Down
75 changes: 75 additions & 0 deletions deepfence_worker/tasks/reports/templates/piechart.gohtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{{ define "piechart" }}
<h3>Overall Summary:</h3>

{{ if .NodeWiseData.OverallSeverityCounts }}
<style>
.container {
display: flex;
justify-content: center;
align-items: center;
}

.item {
margin: auto;
}
</style>

<div class="container">
<div class="item" id="severityChart" style="width:900px;height:500px;"></div>
</div>
<div class="page-break"></div>

<script type="text/javascript">
"use strict";
var severityPieChart = echarts.init(document.getElementById('severityChart'), "white");
var severityChart_option = {
"animation": true,
"color": ["#f56682", "#f57600", "#ff9c32", "#e5c354", "#61717d", "#3ba272", "#fc8452", "#9a60b4", "#ea7ccc"],
"legend": {
"show": true,
"type": ""
},
"series": [{
"name": "Severity",
"type": "pie",
"smooth": false,
"connectNulls": false,
"showSymbol": false,
"waveAnimation": false,
"renderLabelForZeroData": false,
"selectedMode": false,
"animation": false,
"data": [{
"name": "Critical ({{ .NodeWiseData.OverallSeverityCounts.critical }})",
"value": {{ .NodeWiseData.OverallSeverityCounts.critical }}
},{
"name": "High ({{ .NodeWiseData.OverallSeverityCounts.high }})",
"value": {{ .NodeWiseData.OverallSeverityCounts.high }}
}, {
"name": "Medium ({{ .NodeWiseData.OverallSeverityCounts.medium }})",
"value": {{ .NodeWiseData.OverallSeverityCounts.medium }}
}, {
"name": "Low ({{ .NodeWiseData.OverallSeverityCounts.low }})",
"value": {{ .NodeWiseData.OverallSeverityCounts.low }}
}, {
"name": "Unknown ({{ .NodeWiseData.OverallSeverityCounts.unknown }})",
"value": {{ .NodeWiseData.OverallSeverityCounts.unknown }}
}],
"label": {
"show": true,
"position": "top",
"formatter": "{b} {d}%"
}
}],
"title": {
"text": "Unique Severity"
},
"tooltip": {
"show": false
}
};

severityPieChart.setOption(severityChart_option);
</script>
{{ end }}
{{ end }}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
{{ range $i, $v := $value.ScanResults }}
<tr>
<td style="width: 40px">{{ add1 $i }}</td>
<td style="width: 150px">{{ $v.Cve_id }}</td>
<td style="width: 300px">{{ $v.Cve_caused_by_package }}</td>
<td style="width: 65px">{{ $v.Cve_severity }}</td>
<td>{{ trunc 80 $v.Cve_description }}</td>
<td style="width: 35px; text-align: center;"><a style="text-decoration: none;" href="{{ $v.Cve_link }}"
<td style="width: 150px">{{ $v.CveID }}</td>
<td style="width: 300px">{{ $v.CveCausedByPackage }}</td>
<td style="width: 65px">{{ $v.CveSeverity }}</td>
<td>{{ trunc 80 $v.CveDescription }}</td>
<td style="width: 35px; text-align: center;"><a style="text-decoration: none;" href="{{ $v.CveLink }}"
target="_blank" rel="noopener noreferrer"><img height='15px'
src="data:image/png;base64,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" /></a>
</td>
Expand Down
Loading