Skip to content

Commit

Permalink
Cap number of scan vulnerabilities reported (#728)
Browse files Browse the repository at this point in the history

Signed-off-by: munishchouhan <[email protected]>
Signed-off-by: Paolo Di Tommaso <[email protected]>
Co-authored-by: Paolo Di Tommaso <[email protected]>
  • Loading branch information
munishchouhan and pditommaso authored Nov 1, 2024
1 parent adb7500 commit 2f0d8f9
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ class ScanConfig {
@Value('${wave.scan.environment}')
List<String> environment

@Value('${wave.scan.vulnerability.limit:100}')
Integer vulnerabilityLimit

String getScanImage() {
return scanImage
}
Expand Down Expand Up @@ -133,6 +136,6 @@ class ScanConfig {

@PostConstruct
private void init() {
log.info("Scan config: docker image name: ${scanImage}; cache directory: ${cacheDirectory}; timeout=${timeout}; cpus: ${requestsCpu}; mem: ${requestsMemory}; severity: $severity; retry-attempts: $retryAttempts; env=${environment}")
log.info("Scan config: docker image name: ${scanImage}; cache directory: ${cacheDirectory}; timeout=${timeout}; cpus: ${requestsCpu}; mem: ${requestsMemory}; severity: $severity; vulnerability-limit: $vulnerabilityLimit; retry-attempts: $retryAttempts; env=${environment}")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -269,12 +269,14 @@ class ContainerScanServiceImpl implements ContainerScanService, JobHandler<ScanE
ScanEntry result
if( state.succeeded() ) {
try {
result = entry.success(TrivyResultProcessor.process(job.workDir.resolve(Trivy.OUTPUT_FILE_NAME)))
final scanFile = job.workDir.resolve(Trivy.OUTPUT_FILE_NAME)
final vulnerabilities = TrivyResultProcessor.parse(scanFile, config.vulnerabilityLimit)
result = entry.success(vulnerabilities)
log.info("Container scan succeeded - id=${entry.scanId}; exit=${state.exitCode}; stdout=${state.stdout}")
}
catch (NoSuchFileException e) {
result = entry.failure(0, "No such file: ${e.message}")
log.warn("Container scan failed - id=${entry.scanId}; exit=${state.exitCode}; stdout=${state.stdout}; exception: NoSuchFile=${e.message}")
log.warn("Container scan failed - id=${entry.scanId}; NoSuchFile=${e.message}")
}
}
else{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import org.jetbrains.annotations.NotNull
@CompileStatic
class ScanVulnerability implements Comparable<ScanVulnerability> {

static final Map<String,Integer> ORDER = Map.of(
static final private Map<String,Integer> ORDER = Map.of(
'LOW', 0,
'MEDIUM', 1,
'HIGH', 2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ package io.seqera.wave.service.scan
import java.nio.file.Path

import groovy.json.JsonSlurper
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import io.seqera.wave.exception.ScanRuntimeException
/**
Expand All @@ -30,16 +32,23 @@ import io.seqera.wave.exception.ScanRuntimeException
* @author Paolo Di Tommaso <[email protected]>
*/
@Slf4j
@CompileStatic
class TrivyResultProcessor {

static List<ScanVulnerability> process(Path reportFile) {
process(reportFile.getText())
static List<ScanVulnerability> parse(Path scanFile) {
return parse(scanFile.getText())
}

static List<ScanVulnerability> process(String trivyResult) {
static List<ScanVulnerability> parse(Path scanFile, int maxEntries) {
final result = parse(scanFile)
return filter(result, maxEntries)
}

@CompileDynamic
static List<ScanVulnerability> parse(String scanJson) {
final slurper = new JsonSlurper()
try{
final jsonMap = slurper.parseText(trivyResult) as Map
final jsonMap = slurper.parseText(scanJson) as Map
return jsonMap.Results.collect { result ->
result.Vulnerabilities.collect { vulnerability ->
new ScanVulnerability(
Expand All @@ -57,4 +66,8 @@ class TrivyResultProcessor {
throw new ScanRuntimeException("Failed to parse the trivy result", e)
}
}

static protected List<ScanVulnerability> filter(List<ScanVulnerability> vulnerabilities, int limit){
vulnerabilities.toSorted((v,w) -> w.compareTo(v)).take(limit)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ class ContainerScanServiceImplTest extends Specification {
and:
def KEY = 'scan-20'
def jobService = Mock(JobService)
def service = new ContainerScanServiceImpl(scanStore: scanStore, persistenceService: persistenceService, jobService: jobService)
def service = new ContainerScanServiceImpl(scanStore: scanStore, persistenceService: persistenceService, jobService: jobService, config: new ScanConfig(vulnerabilityLimit: 100))
def job = JobSpec.scan(KEY, 'ubuntu:latest', Instant.now(), Duration.ofMinutes(1), workDir)
def scan = ScanEntry.of(scanId: KEY, buildId: 'build-20', containerImage: 'ubuntu:latest', startTime: Instant.now())

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class TrivyResultProcessorTest extends Specification {
"""

when:
def result = TrivyResultProcessor.process(trivyDockerResulJson)
def result = TrivyResultProcessor.parse(trivyDockerResulJson)

then:
def vulnerability = result[0]
Expand All @@ -105,9 +105,94 @@ class TrivyResultProcessorTest extends Specification {

}

def "should return a sorted map of vulnerabilities"() {
given:
def trivyDockerResulJson = """
{ "Results": [
{
"Target": "sample-application",
"Class": "os-pkgs",
"Type": "linux",
"Vulnerabilities": [
{
"VulnerabilityID": "CVE-2023-0001",
"PkgID": "[email protected]",
"PkgName": "example-lib",
"InstalledVersion": "1.0.0",
"FixedVersion": "1.0.1",
"Severity": "LOW",
"Description": "A minor vulnerability with low impact.",
"PrimaryURL": "https://example.com/CVE-2023-0001"
},
{
"VulnerabilityID": "CVE-2023-0002",
"PkgID": "[email protected]",
"PkgName": "example-lib",
"InstalledVersion": "1.2.3",
"FixedVersion": "1.2.4",
"Severity": "MEDIUM",
"Description": "A vulnerability that allows unauthorized access.",
"PrimaryURL": "https://example.com/CVE-2023-0002"
},
{
"VulnerabilityID": "CVE-2023-0003",
"PkgID": "[email protected]",
"PkgName": "example-lib",
"InstalledVersion": "2.3.4",
"FixedVersion": "2.3.5",
"Severity": "HIGH",
"Description": "A vulnerability that could lead to remote code execution.",
"PrimaryURL": "https://example.com/CVE-2023-0003"
},
{
"VulnerabilityID": "CVE-2023-0004",
"PkgID": "[email protected]",
"PkgName": "example-lib",
"InstalledVersion": "3.0.0",
"FixedVersion": "3.0.1",
"Severity": "HIGH",
"Description": "A random test vulnerability with unspecified impact.",
"PrimaryURL": "https://example.com/CVE-2023-0004"
},
{
"VulnerabilityID": "CVE-2023-0005",
"PkgID": "[email protected]",
"PkgName": "example-lib",
"InstalledVersion": "3.1.0",
"FixedVersion": "3.1.1",
"Severity": "CRITICAL",
"Description": "Another random test vulnerability for testing purposes.",
"PrimaryURL": "https://example.com/CVE-2023-0005"
}
]
}
]
}""".stripIndent()

when:
def result = TrivyResultProcessor.parse(trivyDockerResulJson)
result = TrivyResultProcessor.filter(result, 4)

then:
result.size() == 4
result[0].severity == "CRITICAL"
result[0].id == "CVE-2023-0005"
result[1].severity == "HIGH"
result[1].id == "CVE-2023-0003"
result[2].severity == "HIGH"
result[2].id == "CVE-2023-0004"
result[3].severity == "MEDIUM"
result[3].id == "CVE-2023-0002"
}

def 'should not fail with empty list' () {
expect:
TrivyResultProcessor.filter([], 10) == []
}

def "process should throw exception if json is not correct"() {
when:
TrivyResultProcessor.process("invalid json")
TrivyResultProcessor.parse("invalid json")
then:
thrown ScanRuntimeException
}
Expand Down

0 comments on commit 2f0d8f9

Please sign in to comment.