Skip to content

Commit

Permalink
Support schema version 2 of the Trivy JSON format.
Browse files Browse the repository at this point in the history
Closes #9711.
  • Loading branch information
fniessink committed Sep 19, 2024
1 parent 3d4c618 commit f9af1c3
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 75 deletions.
5 changes: 3 additions & 2 deletions components/collector/.vulture_ignore_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@
PipDependencies # unused class (src/source_collectors/pip/dependencies.py:10)
PyupioSafetySecurityWarnings # unused class (src/source_collectors/pyupio_safety/security_warnings.py:12)
QualityTimeMetrics # unused class (src/source_collectors/quality_time/metrics.py:18)
QualityTimeMissingMetrics # unused class (src/source_collectors/quality_time/missing_metrics.py:11)
QualityTimeSourceUpToDateness # unused class (src/source_collectors/quality_time/source_up_to_dateness.py:12)
QualityTimeSourceVersion # unused class (src/source_collectors/quality_time/source_version.py:9)
RobotFrameworkSourceUpToDateness # unused class (src/source_collectors/robot_framework/source_up_to_dateness.py:13)
Expand Down Expand Up @@ -153,7 +152,9 @@
References # unused variable (src/source_collectors/trivy/security_warnings.py:26)
Target # unused variable (src/source_collectors/trivy/security_warnings.py:32)
Vulnerabilities # unused variable (src/source_collectors/trivy/security_warnings.py:33)
TrivyJSONSecurityWarnings # unused class (src/source_collectors/trivy/security_warnings.py:39)
SchemaVersion # unused variable (src/source_collectors/trivy/security_warnings.py:42)
Results # unused variable (src/source_collectors/trivy/security_warnings.py:43)
TrivyJSONSecurityWarnings # unused class (src/source_collectors/trivy/security_warnings.py:49)
totalCount # unused variable (tests/source_collectors/github/test_merge_requests.py:16)
baseRefName # unused variable (tests/source_collectors/github/test_merge_requests.py:24)
createdAt # unused variable (tests/source_collectors/github/test_merge_requests.py:27)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,31 @@ class TrivyJSONVulnerability(TypedDict):
References: list[str]


class TrivyJSONDependencyRepository(TypedDict):
class TrivyJSONResult(TypedDict):
"""Trivy JSON for one dependency repository."""

Target: str
Vulnerabilities: list[TrivyJSONVulnerability] | None # The examples in the Trivy docs show this key can be null


TrivyJSON = list[TrivyJSONDependencyRepository]
# Trivy JSON reports come in two different forms, following schema version 1 or schema version 2.
# Schema version 1 is not explicitly documented as a schema. The Trivy docs only give an example report.
# See https://aquasecurity.github.io/trivy/v0.55/docs/configuration/reporting/#json.
# Schema version 2 is not explicitly documented as a schema either. The only thing available seems to be a GitHub
# discussion: https://github.com/aquasecurity/trivy/discussions/1050.
# Issue to improve the documentation: https://github.com/aquasecurity/trivy/discussions/7552

TriviJSONSchemaVersion1 = list[TrivyJSONResult]


class TrivyJSONSchemaVersion2(TypedDict):
"""Trivy JSON conform schema version 2."""

SchemaVersion: int
Results: list[TrivyJSONResult]


TrivyJSON = TriviJSONSchemaVersion1 | TrivyJSONSchemaVersion2


class TrivyJSONSecurityWarnings(SecurityWarningsSourceCollector, JSONFileSourceCollector):
Expand All @@ -46,9 +63,11 @@ class TrivyJSONSecurityWarnings(SecurityWarningsSourceCollector, JSONFileSourceC
def _parse_json(self, json: JSON, filename: str) -> Entities:
"""Override to parse the vulnerabilities from the Trivy JSON."""
entities = Entities()
for dependency_repository in cast(TrivyJSON, json):
target = dependency_repository["Target"]
for vulnerability in dependency_repository.get("Vulnerabilities") or []:
trivy_json = cast(TrivyJSON, json)
results = trivy_json["Results"] if isinstance(trivy_json, dict) else trivy_json
for result in results:
target = result["Target"]
for vulnerability in result.get("Vulnerabilities") or []:
vulnerability_id = vulnerability["VulnerabilityID"]
package_name = vulnerability["PkgName"]
entities.append(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
"""Unit tests for the Trivy JSON security warnings collector."""

from typing import ClassVar

from source_collectors.trivy.security_warnings import TrivyJSON

from tests.source_collectors.source_collector_test_case import SourceCollectorTestCase


Expand All @@ -12,74 +8,88 @@ class TrivyJSONSecurityWarningsTest(SourceCollectorTestCase):

SOURCE_TYPE = "trivy_json"
METRIC_TYPE = "security_warnings"
VULNERABILITIES_JSON: ClassVar[TrivyJSON] = [
{
"Target": "php-app/composer.lock",
"Vulnerabilities": None,
},
{
"Target": "trivy-ci-test (alpine 3.7.1)",
"Vulnerabilities": [
{
"VulnerabilityID": "CVE-2018-16840",
"PkgName": "curl",
"InstalledVersion": "7.61.0-r0",
"FixedVersion": "7.61.1-r1",
"Title": 'curl: Use-after-free when closing "easy" handle in Curl_close()',
"Description": "A heap use-after-free flaw was found in curl versions from 7.59.0 through ...",
"Severity": "HIGH",
"References": [
"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-16840",
],
},
{
"VulnerabilityID": "CVE-2019-3822",
"PkgName": "curl",
"InstalledVersion": "7.61.1-r0",
"FixedVersion": "7.61.2-r2",
"Title": "curl: NTLMv2 type-3 header stack buffer overflow",
"Description": "libcurl versions from 7.36.0 to before 7.64.0 are vulnerable to ...",
"Severity": "MEDIUM",
"References": [
"https://curl.haxx.se/docs/CVE-2019-3822.html",
"https://lists.apache.org/thread.html",
],
},
],
},
]
EXPECTED_ENTITIES: ClassVar[list[dict[str, str]]] = [
{
"key": "CVE-2018-16840@curl@trivy-ci-test (alpine 3_7_1)",
"vulnerability_id": "CVE-2018-16840",
"title": 'curl: Use-after-free when closing "easy" handle in Curl_close()',
"description": "A heap use-after-free flaw was found in curl versions from 7.59.0 through ...",
"level": "HIGH",
"package_name": "curl",
"installed_version": "7.61.0-r0",
"fixed_version": "7.61.1-r1",
"url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-16840",
},
{
"key": "CVE-2019-3822@curl@trivy-ci-test (alpine 3_7_1)",
"vulnerability_id": "CVE-2019-3822",
"title": "curl: NTLMv2 type-3 header stack buffer overflow",
"description": "libcurl versions from 7.36.0 to before 7.64.0 are vulnerable to ...",
"level": "MEDIUM",
"package_name": "curl",
"installed_version": "7.61.1-r0",
"fixed_version": "7.61.2-r2",
"url": "https://curl.haxx.se/docs/CVE-2019-3822.html",
},
]
SCHEMA_VERSIONS = (1, 2)

def vulnerabilities_json(self, schema_version: int = 1):
"""Return the Trivy Vulnerabilities JSON."""
results = [
{
"Target": "php-app/composer.lock",
"Vulnerabilities": None,
},
{
"Target": "trivy-ci-test (alpine 3.7.1)",
"Vulnerabilities": [
{
"VulnerabilityID": "CVE-2018-16840",
"PkgName": "curl",
"InstalledVersion": "7.61.0-r0",
"FixedVersion": "7.61.1-r1",
"Title": 'curl: Use-after-free when closing "easy" handle in Curl_close()',
"Description": "A heap use-after-free flaw was found in curl versions from 7.59.0 through ...",
"Severity": "HIGH",
"References": [
"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-16840",
],
},
{
"VulnerabilityID": "CVE-2019-3822",
"PkgName": "curl",
"InstalledVersion": "7.61.1-r0",
"FixedVersion": "7.61.2-r2",
"Title": "curl: NTLMv2 type-3 header stack buffer overflow",
"Description": "libcurl versions from 7.36.0 to before 7.64.0 are vulnerable to ...",
"Severity": "MEDIUM",
"References": [
"https://curl.haxx.se/docs/CVE-2019-3822.html",
"https://lists.apache.org/thread.html",
],
},
],
},
]
if schema_version == 1:
return results
return {"SchemaVersion": 2, "Results": results}

def expected_entities(self):
"""Return the expected entities."""
return [
{
"key": "CVE-2018-16840@curl@trivy-ci-test (alpine 3_7_1)",
"vulnerability_id": "CVE-2018-16840",
"title": 'curl: Use-after-free when closing "easy" handle in Curl_close()',
"description": "A heap use-after-free flaw was found in curl versions from 7.59.0 through ...",
"level": "HIGH",
"package_name": "curl",
"installed_version": "7.61.0-r0",
"fixed_version": "7.61.1-r1",
"url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-16840",
},
{
"key": "CVE-2019-3822@curl@trivy-ci-test (alpine 3_7_1)",
"vulnerability_id": "CVE-2019-3822",
"title": "curl: NTLMv2 type-3 header stack buffer overflow",
"description": "libcurl versions from 7.36.0 to before 7.64.0 are vulnerable to ...",
"level": "MEDIUM",
"package_name": "curl",
"installed_version": "7.61.1-r0",
"fixed_version": "7.61.2-r2",
"url": "https://curl.haxx.se/docs/CVE-2019-3822.html",
},
]

async def test_warnings(self):
"""Test the number of security warnings."""
response = await self.collect(get_request_json_return_value=self.VULNERABILITIES_JSON)
self.assert_measurement(response, value="2", entities=self.EXPECTED_ENTITIES)
for schema_version in self.SCHEMA_VERSIONS:
with self.subTest(schema_version=schema_version):
response = await self.collect(get_request_json_return_value=self.vulnerabilities_json(schema_version))
self.assert_measurement(response, value="2", entities=self.expected_entities())

async def test_warning_levels(self):
"""Test the number of security warnings when specifying a level."""
self.set_source_parameter("levels", ["high", "critical"])
response = await self.collect(get_request_json_return_value=self.VULNERABILITIES_JSON)
self.assert_measurement(response, value="1", entities=[self.EXPECTED_ENTITIES[0]])
for schema_version in self.SCHEMA_VERSIONS:
with self.subTest(schema_version=schema_version):
response = await self.collect(get_request_json_return_value=self.vulnerabilities_json(schema_version))
self.assert_measurement(response, value="1", entities=[self.expected_entities()[0]])
1 change: 1 addition & 0 deletions docs/src/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ If your currently installed *Quality-time* version is not v5.15.0, please first
- Allow for configuring Jenkins as source for the metric 'CI-pipeline duration' (GitLab CI was already supported, Azure DevOps will follow later). Partially implements [#6423](https://github.com/ICTU/quality-time/issues/6423).
- Show the number of ignored measurement entities (entities marked as "False positive, "Won't fix" or "Will be fixed") in the measurement value popup. Closes [#7626](https://github.com/ICTU/quality-time/issues/7626).
- Add GitHub as possible source for the 'merge requests' metric. Patch contributed by Tobias Termeczky (the/experts). Closes [#9323](https://github.com/ICTU/quality-time/issues/9323).
- Support schema version 2 of the Trivy JSON format. Closes [#9711](https://github.com/ICTU/quality-time/issues/9711).

### Changed

Expand Down

0 comments on commit f9af1c3

Please sign in to comment.