def _try_parse_related_vulnerabilities( vulns: List[Dict], ) -> List[NVDReference]: """ Best effort attempt at parsing other vulnerabilities from grype response. Ignores any errors raised and chugs along """ nvd_objects = [] if isinstance(vulns, list) and vulns: for vuln_dict in vulns: try: nvd_objects.append( NVDReference( vulnerability_id=vuln_dict.get("id"), # description=vuln_dict.get("description"), description=None, severity=vuln_dict.get("severity"), link=vuln_dict.get("dataSource"), cvss=VulnerabilityMapper._try_parse_cvss( vuln_dict.get("cvss", [])), )) except (AttributeError, ValueError): log.debug( "Ignoring error parsing related vulnerability dict %s", vuln_dict, ) return nvd_objects
def test_from(self): match = VulnerabilityMatch( artifact=Artifact( name="blah", location="/usr/local/java/blah", pkg_type="java", version="1.2.3maven", ), vulnerability=Vulnerability( feed="vulnerabilities", feed_group="whatever:hello", vulnerability_id="meh", ), nvd=[NVDReference(vulnerability_id="CVE-abc")], ) rank_strategy = FeedGroupRank() ranked_match = RankedVulnerabilityMatch.from_match(match, FeedGroupRank()) assert ranked_match assert ranked_match.vuln_id == match.vulnerability.vulnerability_id assert ranked_match.vuln_namespace == match.vulnerability.feed_group assert ranked_match.pkg_name == match.artifact.name assert ranked_match.pkg_type == match.artifact.pkg_type assert ranked_match.pkg_version == match.artifact.version assert ranked_match.pkg_path == match.artifact.location assert ranked_match.rank == rank_strategy.__default__
def test_get_nvd_data_from_nvd_references_multiple_cvss(self): test_input = NVDReference( vulnerability_id="CVE-x", cvss=[ CVSS( version="2.0", base_score=1.1, exploitability_score=1.2, impact_score=1.3, ), CVSS( version="3.0", base_score=2.1, exploitability_score=2.2, impact_score=2.3, ), CVSS( version="3.1", base_score=3.1, exploitability_score=3.2, impact_score=3.3, ), ], ) expected_output = { "id": test_input.vulnerability_id, "cvss_v2": { "base_score": 1.1, "exploitability_score": 1.2, "impact_score": 1.3, }, "cvss_v3": { "base_score": 3.1, "exploitability_score": 3.2, "impact_score": 3.3, }, } actual_output = api_utils.get_nvd_data_from_nvd_references([test_input]) assert len(actual_output) == 1 assert actual_output[0] == expected_output
def test_execute_absolute_duplicates(self, count): a = VulnerabilityMatch( artifact=Artifact( name="blah", location="/usr/local/java/blah", pkg_type="java", version="1.2.3maven", ), vulnerability=Vulnerability( feed="vulnerabilities", feed_group="whatever:hello", vulnerability_id="meh", ), nvd=[NVDReference(vulnerability_id="CVE-2019-12904")], ) input_matches = [a for x in range(count)] results = ImageVulnerabilitiesDeduplicator(FeedGroupRank()).execute( input_matches ) assert len(results) == 1
class TestMakeVulnerabilityReport: @pytest.mark.parametrize( "test_input, expected", [ pytest.param(CVSS(version="2"), "cvss_v2", id="v2"), pytest.param(CVSS(version="2.0"), "cvss_v2", id="v2.0"), pytest.param(CVSS(version="2.1"), "cvss_v2", id="v2.1"), pytest.param(CVSS(version="2.x"), "cvss_v2", id="v2.x"), pytest.param(CVSS(version="3"), "cvss_v3", id="v3"), pytest.param(CVSS(version="3.0"), "cvss_v3", id="v3.0"), pytest.param(CVSS(version="3.3"), "cvss_v3", id="v3.3"), pytest.param(CVSS(version="3.y"), "cvss_v3", id="v3.y"), ], ) def test_to_cvss_score_valid(self, test_input, expected): test_input.base_score = 5.7 test_input.exploitability_score = 6.8 test_input.impact_score = 4.3 expected_output = { expected: { "base_score": test_input.base_score, "exploitability_score": test_input.exploitability_score, "impact_score": test_input.impact_score, } } assert api_utils.to_cvss_score(test_input) == expected_output @pytest.mark.parametrize( "test_input", [ pytest.param(CVSS(version="1"), id="v1"), pytest.param(CVSS(version="4"), id="v1"), pytest.param(CVSS(version="foo"), id="vfoo"), pytest.param(CVSS(version=""), id="blank"), pytest.param(CVSS(), id="none"), ], ) def test_to_cvss_score_invalid_version(self, test_input): assert api_utils.to_cvss_score(test_input) is None @pytest.mark.parametrize( "test_input", [ pytest.param( [NVDReference(vulnerability_id="CVE-x", cvss=[])], id="empty-list" ), pytest.param( [NVDReference(vulnerability_id="CVE-x", cvss=None)], id="none" ), pytest.param( [ NVDReference(vulnerability_id="CVE-x", cvss=[]), NVDReference(vulnerability_id="CVE-x", cvss=None), ], id="combo", ), ], ) def test_get_nvd_data_from_nvd_references_no_cvss(self, test_input): actual_output = api_utils.get_nvd_data_from_nvd_references(test_input) assert len(test_input) == len(actual_output) for input_item, output_item in zip(test_input, actual_output): assert output_item == { "id": input_item.vulnerability_id, "cvss_v2": { "base_score": -1.0, "exploitability_score": -1.0, "impact_score": -1.0, }, "cvss_v3": { "base_score": -1.0, "exploitability_score": -1.0, "impact_score": -1.0, }, } def test_get_nvd_data_from_nvd_references_multiple_cvss(self): test_input = NVDReference( vulnerability_id="CVE-x", cvss=[ CVSS( version="2.0", base_score=1.1, exploitability_score=1.2, impact_score=1.3, ), CVSS( version="3.0", base_score=2.1, exploitability_score=2.2, impact_score=2.3, ), CVSS( version="3.1", base_score=3.1, exploitability_score=3.2, impact_score=3.3, ), ], ) expected_output = { "id": test_input.vulnerability_id, "cvss_v2": { "base_score": 1.1, "exploitability_score": 1.2, "impact_score": 1.3, }, "cvss_v3": { "base_score": 3.1, "exploitability_score": 3.2, "impact_score": 3.3, }, } actual_output = api_utils.get_nvd_data_from_nvd_references([test_input]) assert len(actual_output) == 1 assert actual_output[0] == expected_output @pytest.mark.parametrize( "test_input", [ pytest.param(Vulnerability(cvss=[]), id="empty-list"), pytest.param(Vulnerability(cvss=None), id="none"), ], ) def test_get_vendor_data_from_vulnerability_no_cvss(self, test_input): assert api_utils.get_vendor_data_from_vulnerability(test_input) == [] @pytest.mark.parametrize( "test_input, expected_output", [ pytest.param( Vulnerability( vulnerability_id="CVE-x", cvss=[ CVSS( version="2.3", base_score=1.1, exploitability_score=1.2, impact_score=1.3, ) ], ), [ { "id": "CVE-x", "cvss_v2": { "base_score": 1.1, "exploitability_score": 1.2, "impact_score": 1.3, }, "cvss_v3": { "base_score": -1.0, "exploitability_score": -1.0, "impact_score": -1.0, }, } ], id="single_cvss", ), pytest.param( Vulnerability( vulnerability_id="CVE-x", cvss=[ CVSS( version="2.3", base_score=1.1, exploitability_score=1.2, impact_score=1.3, ), CVSS( version="3.1", base_score=2.1, exploitability_score=2.2, impact_score=2.3, ), ], ), [ { "id": "CVE-x", "cvss_v2": { "base_score": 1.1, "exploitability_score": 1.2, "impact_score": 1.3, }, "cvss_v3": { "base_score": -1.0, "exploitability_score": -1.0, "impact_score": -1.0, }, }, { "id": "CVE-x", "cvss_v2": { "base_score": -1.0, "exploitability_score": -1.0, "impact_score": -1.0, }, "cvss_v3": { "base_score": 2.1, "exploitability_score": 2.2, "impact_score": 2.3, }, }, ], id="multiple_cvss", ), ], ) def test_get_vendor_data_from_vulnerability(self, test_input, expected_output): actual_output = api_utils.get_vendor_data_from_vulnerability(test_input) assert len(actual_output) == len(expected_output) for actual_item, expected_item in zip(actual_output, expected_output): assert actual_item == expected_item @pytest.mark.parametrize( "test_input", [ pytest.param(Vulnerability(cvss=[]), id="empty-list"), pytest.param(Vulnerability(cvss=None), id="none"), ], ) def test_get_nvd_data_from_vulnerability_no_cvss(self, test_input): assert api_utils.get_nvd_data_from_vulnerability(test_input) == [] @pytest.mark.parametrize( "test_input, expected_output", [ pytest.param( Vulnerability( vulnerability_id="CVE-x", cvss=[ CVSS( version="2.3", base_score=1.1, exploitability_score=1.2, impact_score=1.3, ) ], ), [ { "id": "CVE-x", "cvss_v2": { "base_score": 1.1, "exploitability_score": 1.2, "impact_score": 1.3, }, "cvss_v3": { "base_score": -1.0, "exploitability_score": -1.0, "impact_score": -1.0, }, } ], id="single_cvss", ), pytest.param( Vulnerability( vulnerability_id="CVE-x", cvss=[ CVSS( version="2.3", base_score=1.1, exploitability_score=1.2, impact_score=1.3, ), CVSS( version="3.1", base_score=2.1, exploitability_score=2.2, impact_score=2.3, ), ], ), [ { "id": "CVE-x", "cvss_v2": { "base_score": 1.1, "exploitability_score": 1.2, "impact_score": 1.3, }, "cvss_v3": { "base_score": 2.1, "exploitability_score": 2.2, "impact_score": 2.3, }, }, ], id="multiple_cvss", ), ], ) def test_get_nvd_data_from_vulnerability(self, test_input, expected_output): actual_output = api_utils.get_nvd_data_from_vulnerability(test_input) assert len(actual_output) == len(expected_output) for actual_item, expected_item in zip(actual_output, expected_output): assert actual_item == expected_item @pytest.mark.parametrize( "report_type, package_type, expected", [ pytest.param("all", "apkg", True, id="all-apkg"), pytest.param("all", "gem", True, id="all-gem"), pytest.param("os", "python", False, id="os-python"), pytest.param("non-os", "dpkg", False, id="nonos-dpkg"), pytest.param("foo", "bar", True, id="foo-bar"), pytest.param("os", "bar", True, id="os-bar"), ], ) def test_is_type_match(self, report_type, package_type, expected): assert api_utils.is_type_match(report_type, package_type) == expected
class TestImageVulnerabilitiesDeduplicator: @pytest.mark.parametrize( "test_input, expected_index", [ pytest.param( [ VulnerabilityMatch( vulnerability=Vulnerability( feed="vulnerabilities", feed_group="nvdv2:cves", vulnerability_id="CVE-2019-12904", ), nvd=[NVDReference(vulnerability_id="CVE-2019-12904")], ), VulnerabilityMatch( vulnerability=Vulnerability( feed="vulnerabilities", feed_group="ubuntu:20.04", vulnerability_id="CVE-2019-12904", ), nvd=[NVDReference(vulnerability_id="CVE-2019-12904")], ), ], 1, id="different-namespaces", ), pytest.param( [ VulnerabilityMatch( vulnerability=Vulnerability( feed="vulnerabilities", feed_group="nvdv2:cves", vulnerability_id="CVE-2019-12904", ), nvd=[NVDReference(vulnerability_id="CVE-2019-12904")], ), VulnerabilityMatch( vulnerability=Vulnerability( feed="vulnerabilities", feed_group="github:java", vulnerability_id="GHSA-foobar", ), nvd=[NVDReference(vulnerability_id="CVE-2019-12904")], ), ], 1, id="different-identifiers", ), pytest.param( [ VulnerabilityMatch( vulnerability=Vulnerability( feed="vulnerabilities", feed_group="github:java", vulnerability_id="GHSA-foobar", ), nvd=[NVDReference(vulnerability_id="CVE-2019-12904")], ), VulnerabilityMatch( vulnerability=Vulnerability( feed="vulnerabilities", feed_group="ubuntu:20.04", vulnerability_id="CVE-2019-12904", ), nvd=[NVDReference(vulnerability_id="CVE-2019-12904")], ), ], 1, id="non-nvd-namespaces", ), pytest.param( [ VulnerabilityMatch( vulnerability=Vulnerability( feed="vulnerabilities", feed_group="nvdv2:cves", vulnerability_id="CVE-2019-12904", ), nvd=[NVDReference(vulnerability_id="CVE-2019-12904")], ), VulnerabilityMatch( vulnerability=Vulnerability( feed="vulnerabilities", feed_group="ubuntu:20.04", vulnerability_id="CVE-2019-12904", ), nvd=[], ), ], 1, id="no-nvd-refs", ), pytest.param( [ VulnerabilityMatch( vulnerability=Vulnerability( feed="vulnerabilities", feed_group="nvdv2:cves", vulnerability_id="CVE-2019-12904", ), nvd=[NVDReference(vulnerability_id="CVE-2019-12345")], ), VulnerabilityMatch( vulnerability=Vulnerability( feed="vulnerabilities", feed_group="nvdv2:cves", vulnerability_id="CVE-2019-12904", ), nvd=[NVDReference(vulnerability_id="CVE-2019-12904")], ), VulnerabilityMatch( vulnerability=Vulnerability( feed="vulnerabilities", feed_group="github:java", vulnerability_id="GHSA-foobar", ), nvd=[ NVDReference(vulnerability_id="CVE-2019-12904"), NVDReference(vulnerability_id="CVE-2019-12345"), ], ), ], 2, id="multiple-nvd-refs", ), ], ) def test_execute(self, test_input, expected_index): artifact = Artifact( name="blah", location="/usr/local/java/blah", pkg_type="java", version="1.2.3maven", ) for item in test_input: item.artifact = artifact results = ImageVulnerabilitiesDeduplicator(FeedGroupRank()).execute(test_input) assert len(results) == 1 actual = results[0].vulnerability expected = test_input[expected_index] assert actual.vulnerability_id == expected.vulnerability.vulnerability_id assert actual.feed_group == expected.vulnerability.feed_group @pytest.mark.parametrize("count", [1, 2, 3, 4, 5]) def test_execute_absolute_duplicates(self, count): a = VulnerabilityMatch( artifact=Artifact( name="blah", location="/usr/local/java/blah", pkg_type="java", version="1.2.3maven", ), vulnerability=Vulnerability( feed="vulnerabilities", feed_group="whatever:hello", vulnerability_id="meh", ), nvd=[NVDReference(vulnerability_id="CVE-2019-12904")], ) input_matches = [a for x in range(count)] results = ImageVulnerabilitiesDeduplicator(FeedGroupRank()).execute( input_matches ) assert len(results) == 1 @pytest.mark.parametrize( "test_input", [pytest.param([], id="empty-list"), pytest.param(None, id="none")], ) def test_execute_invalid_input(self, test_input): assert ( ImageVulnerabilitiesDeduplicator(FeedGroupRank()).execute(test_input) == list() )
class TestVulnerabilityIdentity: @pytest.mark.parametrize( "test_input", [ pytest.param( [NVDReference(vulnerability_id="CVE-abc")], id="single-nvd", ), pytest.param( [ NVDReference(vulnerability_id="CVE-abc"), NVDReference(vulnerability_id="CVE-def"), NVDReference(vulnerability_id="CVE-ghi"), ], id="multiple-nvd", ), ], ) def test_from_with_nvd(self, test_input): match = VulnerabilityMatch( artifact=Artifact( name="blah", location="/usr/local/java/blah", pkg_type="java", version="1.2.3maven", ), vulnerability=Vulnerability( feed="vulnerabilities", feed_group="whatever:hello", vulnerability_id="meh", ), ) match.nvd = test_input identity_objects = VulnerabilityIdentity.from_match(match) assert identity_objects assert isinstance(identity_objects, list) and len(identity_objects) == len( test_input ) for identity_object, input_nvd in zip(identity_objects, test_input): assert identity_object.vuln_id == input_nvd.vulnerability_id assert identity_object.pkg_name == match.artifact.name assert identity_object.pkg_type == match.artifact.pkg_type assert identity_object.pkg_version == match.artifact.version assert identity_object.pkg_path == match.artifact.location def test_from_without_nvd(self): match = VulnerabilityMatch( artifact=Artifact( name="blah", location="/usr/local/java/blah", pkg_type="java", version="1.2.3maven", ), vulnerability=Vulnerability( feed="vulnerabilities", feed_group="whatever:hello", vulnerability_id="meh", ), nvd=[], ) identity_objects = VulnerabilityIdentity.from_match(match) assert identity_objects assert isinstance(identity_objects, list) and len(identity_objects) == 1 identity_object = identity_objects[0] assert identity_object.vuln_id == match.vulnerability.vulnerability_id assert identity_object.pkg_name == match.artifact.name assert identity_object.pkg_type == match.artifact.pkg_type assert identity_object.pkg_version == match.artifact.version assert identity_object.pkg_path == match.artifact.location @pytest.mark.parametrize( "lhs, rhs, expected", [ pytest.param( VulnerabilityMatch( vulnerability=Vulnerability( feed="trusty", feed_group="trusty:chameleon", vulnerability_id="meh", ), nvd=[NVDReference(vulnerability_id="CVE-abc")], ), VulnerabilityMatch( Vulnerability( feed="hedgehog", feed_group="hedgy:thorny", vulnerability_id="foo", ), nvd=[NVDReference(vulnerability_id="CVE-abc")], ), True, id="equal-different-namespaces", ), pytest.param( VulnerabilityMatch( vulnerability=Vulnerability( feed="trusty", feed_group="trusty:chameleon", vulnerability_id="meh", ), nvd=[ NVDReference(vulnerability_id="CVE-abc"), NVDReference(vulnerability_id="CVE-def"), NVDReference(vulnerability_id="CVE-ghi"), ], ), VulnerabilityMatch( vulnerability=Vulnerability( feed="hedgehog", feed_group="hedgy:thorny", vulnerability_id="foo", ), nvd=[ NVDReference(vulnerability_id="CVE-abc"), NVDReference(vulnerability_id="CVE-def"), NVDReference(vulnerability_id="CVE-ghi"), ], ), True, id="equal-multiple-cvss", ), pytest.param( VulnerabilityMatch( vulnerability=Vulnerability( feed="trusty", feed_group="trusty:chameleon", vulnerability_id="meh", ), nvd=[NVDReference(vulnerability_id="CVE-abc")], ), VulnerabilityMatch( vulnerability=Vulnerability( feed="hedgehog", feed_group="hedgy:thorny", vulnerability_id="foo", ), nvd=[NVDReference(vulnerability_id="CVE-def")], ), False, id="not-equal", ), ], ) def test_equality_constant_artifact(self, lhs, rhs, expected): artifact = Artifact( name="blah", location="/usr/local/java/blah", pkg_type="java", version="1.2.3maven", ) lhs.artifact = artifact rhs.artifact = artifact assert ( VulnerabilityIdentity.from_match(lhs) == VulnerabilityIdentity.from_match(rhs) ) == expected @pytest.mark.parametrize("count", [1, 2, 3, 4, 5]) def test_hash(self, count): record = VulnerabilityIdentity( vuln_id="meh", pkg_name="blah", pkg_version="1.2.3maven", pkg_type="java", pkg_path="blah", ) test_input = [record for x in range(count)] result = set(test_input) assert result and len(result) == 1
class TestRankedVulnerabilityMatch: def test_from(self): match = VulnerabilityMatch( artifact=Artifact( name="blah", location="/usr/local/java/blah", pkg_type="java", version="1.2.3maven", ), vulnerability=Vulnerability( feed="vulnerabilities", feed_group="whatever:hello", vulnerability_id="meh", ), nvd=[NVDReference(vulnerability_id="CVE-abc")], ) rank_strategy = FeedGroupRank() ranked_match = RankedVulnerabilityMatch.from_match(match, FeedGroupRank()) assert ranked_match assert ranked_match.vuln_id == match.vulnerability.vulnerability_id assert ranked_match.vuln_namespace == match.vulnerability.feed_group assert ranked_match.pkg_name == match.artifact.name assert ranked_match.pkg_type == match.artifact.pkg_type assert ranked_match.pkg_version == match.artifact.version assert ranked_match.pkg_path == match.artifact.location assert ranked_match.rank == rank_strategy.__default__ @pytest.mark.parametrize( "lhs, rhs, expected", [ pytest.param( VulnerabilityMatch( vulnerability=Vulnerability( feed="trusty", feed_group="trusty:chameleon", vulnerability_id="meh", ), nvd=[NVDReference(vulnerability_id="CVE-abc")], ), VulnerabilityMatch( vulnerability=Vulnerability( feed="hedgehog", feed_group="hedgy:thorny", vulnerability_id="foo", ), nvd=[NVDReference(vulnerability_id="CVE-abc")], ), False, id="not-equal-different-ids", ), pytest.param( VulnerabilityMatch( vulnerability=Vulnerability( feed="trusty", feed_group="trusty:chameleon", vulnerability_id="meh", ), nvd=[ NVDReference(vulnerability_id="CVE-abc"), NVDReference(vulnerability_id="CVE-def"), NVDReference(vulnerability_id="CVE-ghi"), ], ), VulnerabilityMatch( vulnerability=Vulnerability( feed="trusty", feed_group="trusty:chameleon", vulnerability_id="meh", ), nvd=[NVDReference(vulnerability_id="CVE-abc")], ), True, id="equal-different-cvss", ), pytest.param( VulnerabilityMatch( vulnerability=Vulnerability( feed="trusty", feed_group="trusty:chameleon", vulnerability_id="meh", ), nvd=[NVDReference(vulnerability_id="CVE-abc")], ), VulnerabilityMatch( vulnerability=Vulnerability( feed="trusty", feed_group="trusty:python", vulnerability_id="meh", ), nvd=[NVDReference(vulnerability_id="CVE-abc")], ), False, id="not-equal-different-namespaces", ), ], ) def test_equality_constant_artifact(self, lhs, rhs, expected): artifact = Artifact( name="blah", location="/usr/local/java/blah", pkg_type="java", version="1.2.3maven", ) lhs.artifact = artifact rhs.artifact = artifact assert ( RankedVulnerabilityMatch.from_match(lhs, FeedGroupRank()) == RankedVulnerabilityMatch.from_match(rhs, FeedGroupRank()) ) == expected @pytest.mark.parametrize("count", [1, 2, 3, 4, 5]) def test_hash_empty_match(self, count): record = RankedVulnerabilityMatch( vuln_id="meh", vuln_namespace="trusty:chameleon", pkg_name="blah", pkg_version="1.2.3maven", pkg_type="java", pkg_path="blah", rank=100, match_obj=VulnerabilityMatch(), ) test_input = [record for x in range(count)] result = set(test_input) assert result and len(result) == 1 @pytest.mark.parametrize( "test_input", [ pytest.param( [ VulnerabilityMatch( artifact=Artifact( name="blah", location="/usr/local/java/blah", pkg_type="java", version="1.2.3maven", ), vulnerability=Vulnerability( feed="twisty", feed_group="twisty:python", vulnerability_id="meh", ), nvd=[NVDReference(vulnerability_id="CVE-abc")], ), VulnerabilityMatch( artifact=Artifact( name="foo", location="/usr/local/java/foo", pkg_type="unknown", version="1.2.3", ), vulnerability=Vulnerability( feed="tricky", feed_group="tricky:chameleon", vulnerability_id="meh", ), nvd=[NVDReference(vulnerability_id="CVE-def")], ), ], id="different-matches", ), pytest.param( [ VulnerabilityMatch( artifact=Artifact( name="blah", location="/usr/local/java/blah", pkg_type="java", version="1.2.3maven", ), vulnerability=Vulnerability( feed="twisty", feed_group="twisty:python", vulnerability_id="meh", ), nvd=[NVDReference(vulnerability_id="CVE-abc")], ), ] * 3, id="same-matches", ), ], ) def test_hash(self, test_input): vuln_rank_objects = [ RankedVulnerabilityMatch( vuln_id="meh", vuln_namespace="trusty:chameleon", pkg_name="blah", pkg_version="1.2.3maven", pkg_type="java", pkg_path="/usr/local/blah", rank=100, match_obj=item, ) for item in test_input ] result = set(vuln_rank_objects) assert result and len(result) == 1 result = list(result)[0] assert result.vuln_id == "meh" assert result.vuln_namespace == "trusty:chameleon" assert result.pkg_name == "blah" assert result.pkg_type == "java" assert result.pkg_path == "/usr/local/blah" assert result.rank == 100