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

Support schema version 2 of the Trivy JSON format. #9798

Merged
Show file tree
Hide file tree
Changes from all 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
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
fniessink marked this conversation as resolved.
Show resolved Hide resolved
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
Loading