Skip to content

Commit

Permalink
Allow for measuring the source up-to-dateness of Trivy JSON reports.
Browse files Browse the repository at this point in the history
Closes #10608.
  • Loading branch information
fniessink committed Jan 14, 2025
1 parent 563829f commit 4edddea
Show file tree
Hide file tree
Showing 11 changed files with 182 additions and 116 deletions.
26 changes: 13 additions & 13 deletions components/collector/.vulture_ignore_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,18 +148,19 @@
SonarQubeUncoveredLines # unused class (src/source_collectors/sonarqube/uncovered_lines.py:6)
TrelloIssues # unused class (src/source_collectors/trello/issues.py:13)
TrelloSourceUpToDateness # unused class (src/source_collectors/trello/source_up_to_dateness.py:12)
VulnerabilityID # unused variable (src/source_collectors/trivy/security_warnings.py:19)
Title # unused variable (src/source_collectors/trivy/security_warnings.py:20)
Description # unused variable (src/source_collectors/trivy/security_warnings.py:21)
PkgName # unused variable (src/source_collectors/trivy/security_warnings.py:23)
InstalledVersion # unused variable (src/source_collectors/trivy/security_warnings.py:24)
FixedVersion # unused variable (src/source_collectors/trivy/security_warnings.py:25)
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)
SchemaVersion # unused variable (src/source_collectors/trivy/security_warnings.py:49)
Results # unused variable (src/source_collectors/trivy/security_warnings.py:50)
TrivyJSONSecurityWarnings # unused class (src/source_collectors/trivy/security_warnings.py:56)
VulnerabilityID # unused variable (src/source_collectors/trivy/base.py:15)
Title # unused variable (src/source_collectors/trivy/base.py:16)
Description # unused variable (src/source_collectors/trivy/base.py:17)
PkgName # unused variable (src/source_collectors/trivy/base.py:19)
InstalledVersion # unused variable (src/source_collectors/trivy/base.py:20)
FixedVersion # unused variable (src/source_collectors/trivy/base.py:21)
References # unused variable (src/source_collectors/trivy/base.py:22)
Target # unused variable (src/source_collectors/trivy/base.py:28)
Vulnerabilities # unused variable (src/source_collectors/trivy/base.py:29)
SchemaVersion # unused variable (src/source_collectors/trivy/base.py:45)
Results # unused variable (src/source_collectors/trivy/base.py:46)
TrivyJSONSecurityWarnings # unused class (src/source_collectors/trivy/security_warnings.py:12)
TrivyJSONSourceUpToDateness # unused class (src/source_collectors/trivy/source_up_to_dateness.py:10)
VisualStudioTRXSourceUpToDateness # unused class (src/source_collectors/visual_studio_trx/source_up_to_dateness.py:13)
totalCount # unused variable (tests/source_collectors/github/test_merge_requests.py:16)
baseRefName # unused variable (tests/source_collectors/github/test_merge_requests.py:24)
Expand All @@ -173,4 +174,3 @@
pageInfo # unused variable (tests/source_collectors/github/test_merge_requests.py:45)
totalCount # unused variable (tests/source_collectors/github/test_merge_requests.py:46)
pullRequests # unused variable (tests/source_collectors/github/test_merge_requests.py:53)
RobotFrameworkSourceVersion # unused class (tests/source_collectors/robot_framework/test_source_version.py:6)
1 change: 1 addition & 0 deletions components/collector/src/source_collectors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@
from .trello.issues import TrelloIssues
from .trello.source_up_to_dateness import TrelloSourceUpToDateness
from .trivy.security_warnings import TrivyJSONSecurityWarnings
from .trivy.source_up_to_dateness import TrivyJSONSourceUpToDateness
from .visual_studio_trx.source_up_to_dateness import VisualStudioTRXSourceUpToDateness
from .visual_studio_trx.test_cases import VisualStudioTRXTestCases
from .visual_studio_trx.tests import VisualStudioTRXTests
49 changes: 49 additions & 0 deletions components/collector/src/source_collectors/trivy/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""Base classes for Trivy JSON collectors."""

from typing import TypedDict

# The types below are based on https://aquasecurity.github.io/trivy/v0.45/docs/configuration/reporting/#json.
# That documentation says: "VulnerabilityID, PkgName, InstalledVersion, and Severity in Vulnerabilities are always
# filled with values, but other fields might be empty." This unfortunately does not tell us whether empty means
# an empty string or null. It's also unclear whether keys may be missing. For now we assume all keys are always
# present and missing values are empty strings.


class TrivyJSONVulnerability(TypedDict):
"""Trivy JSON for one vulnerability."""

VulnerabilityID: str
Title: str
Description: str
Severity: str
PkgName: str
InstalledVersion: str
FixedVersion: str
References: list[str]


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


# 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
Original file line number Diff line number Diff line change
@@ -1,56 +1,12 @@
"""Trivy JSON collector."""

from typing import TypedDict, cast
from typing import cast

from base_collectors import JSONFileSourceCollector, SecurityWarningsSourceCollector
from collector_utilities.type import JSON
from model import Entities, Entity

# The types below are based on https://aquasecurity.github.io/trivy/v0.45/docs/configuration/reporting/#json.
# That documentation says: "VulnerabilityID, PkgName, InstalledVersion, and Severity in Vulnerabilities are always
# filled with values, but other fields might be empty." This unfortunately does not tell us whether empty means
# an empty string or null. It's also unclear whether keys may be missing. For now we assume all keys are always
# present and missing values are empty strings.


class TrivyJSONVulnerability(TypedDict):
"""Trivy JSON for one vulnerability."""

VulnerabilityID: str
Title: str
Description: str
Severity: str
PkgName: str
InstalledVersion: str
FixedVersion: str
References: list[str]


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


# 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
from .base import TrivyJSON


class TrivyJSONSecurityWarnings(SecurityWarningsSourceCollector, JSONFileSourceCollector):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Trivy JSON collector."""

from datetime import datetime

from base_collectors import JSONFileSourceCollector, TimePassedCollector
from collector_utilities.date_time import parse_datetime
from collector_utilities.exceptions import CollectorError
from collector_utilities.type import Response


class TrivyJSONSourceUpToDateness(JSONFileSourceCollector, TimePassedCollector):
"""Trivy JSON collector for source up-to-dateness."""

async def _parse_source_response_date_time(self, response: Response) -> datetime:
"""Override to parse the date of the most recent analysis."""
json = await response.json()
try:
created_at = json["CreatedAt"]
except TypeError as error:
message = "Measuring source up-to-dateness is not supported with Trivy JSON schema version 1"
raise CollectorError(message) from error
return parse_datetime(created_at)
61 changes: 61 additions & 0 deletions components/collector/tests/source_collectors/trivy/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""Base class for Trivy JSON collector unit tests."""

from tests.source_collectors.source_collector_test_case import SourceCollectorTestCase


class TrivyJSONTestCase(SourceCollectorTestCase):
"""Base class for Trivy JSON Unit tests."""

SOURCE_TYPE = "trivy_json"
SCHEMA_VERSIONS = (1, 2)

def vulnerabilities_json(self, schema_version: int = 2):
"""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": "",
"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",
],
},
{
"VulnerabilityID": "CVE-2024-5432",
"PkgName": "python",
"InstalledVersion": "3.13.1",
"Title": "Vulnerability without fixed version",
"Description": "This vulnerability has no fixed version field.",
"Severity": "LOW",
"References": ["https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-5432"],
},
],
},
]
if schema_version == 1:
return results
return {"SchemaVersion": 2, "CreatedAt": "2024-12-26T21:58:15.943876+05:30", "Results": results}
Original file line number Diff line number Diff line change
@@ -1,65 +1,12 @@
"""Unit tests for the Trivy JSON security warnings collector."""

from tests.source_collectors.source_collector_test_case import SourceCollectorTestCase
from .base import TrivyJSONTestCase


class TrivyJSONSecurityWarningsTest(SourceCollectorTestCase):
class TrivyJSONSecurityWarningsTest(TrivyJSONTestCase):
"""Unit tests for the security warning metric."""

SOURCE_TYPE = "trivy_json"
METRIC_TYPE = "security_warnings"
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": "",
"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",
],
},
{
"VulnerabilityID": "CVE-2024-5432",
"PkgName": "python",
"InstalledVersion": "3.13.1",
"Title": "Vulnerability without fixed version",
"Description": "This vulnerability has no fixed version field.",
"Severity": "LOW",
"References": ["https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-5432"],
},
],
},
]
if schema_version == 1:
return results
return {"SchemaVersion": 2, "Results": results}

def expected_entities(self):
"""Return the expected entities."""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Unit tests for the Trivy JSON source up-to-dateness collector."""

from collector_utilities.date_time import days_ago, parse_datetime

from .base import TrivyJSONTestCase


class TrivyJSONSourceUpToDatenessTest(TrivyJSONTestCase):
"""Unit tests for the source up-to-dateness metric."""

METRIC_TYPE = "source_up_to_dateness"

async def test_source_up_to_dateness_schema_version_1(self):
"""Test the source up-to-dateness."""
response = await self.collect(get_request_json_return_value=self.vulnerabilities_json(1))
expected_error = "Measuring source up-to-dateness is not supported with Trivy JSON schema version 1"
self.assert_measurement(response, parse_error=expected_error)

async def test_source_up_to_dateness_schema_version_2(self):
"""Test the source up-to-dateness."""
response = await self.collect(get_request_json_return_value=self.vulnerabilities_json())
expected_value = str(days_ago(parse_datetime("2024-12-26T21:58:15.943876+05:30")))
self.assert_measurement(response, value=expected_value)
1 change: 1 addition & 0 deletions components/shared_code/src/shared_data_model/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,7 @@
"sonarqube",
"testng",
"trello",
"trivy_json",
"visual_studio_trx",
],
tags=[Tag.CI],
Expand Down
9 changes: 7 additions & 2 deletions components/shared_code/src/shared_data_model/sources/trivy.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from shared_data_model.meta.source import Source
from shared_data_model.parameters import FixAvailability, Severities, access_parameters

ALL_TRIVY_JSON_METRICS = ["security_warnings", "source_up_to_dateness"]

TRIVY_JSON = Source(
name="Trivy JSON",
description="A Trivy vulnerability report in JSON format.",
Expand All @@ -16,10 +18,13 @@
placeholder="all levels",
help="If provided, only count security warnings with the selected levels.",
values=["unknown", "low", "medium", "high", "critical"],
metrics=["security_warnings"],
),
"fix_availability": FixAvailability(),
**access_parameters(["security_warnings"], source_type="Trivy vulnerability report", source_type_format="JSON"),
**access_parameters(
ALL_TRIVY_JSON_METRICS,
source_type="Trivy vulnerability report",
source_type_format="JSON",
),
},
entities={
"security_warnings": Entity(
Expand Down
1 change: 1 addition & 0 deletions docs/src/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ If your currently installed *Quality-time* version is not the latest version, pl
### Added

- When measuring missing metrics, make the subject type and the metric type of the missing metrics link to the reference documentation. Closes [#10528](https://github.com/ICTU/quality-time/issues/10528).
- Allow for measuring the source up-to-dateness of Trivy JSON reports. Closes [#10608](https://github.com/ICTU/quality-time/issues/10608).
- Support version 4.1 of the OWASP Dependency-Check DTD (OWASP Dependency-Check version 12.0.0). Closes [#10645](https://github.com/ICTU/quality-time/issues/10645).

### Changed
Expand Down

0 comments on commit 4edddea

Please sign in to comment.