Skip to content

Commit e2b60c9

Browse files
authored
Add dates and changelog for packages and vulnerabilities (#1310)
* Add chaneglog Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * Add importer name to advisory importers Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * Fix views Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * clean-up models.py Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * Add importer name and importing authority for istio Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * Fix models Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * Make code more readable Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * Cleanup models Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * Add tests for changelogs Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * Adjust package changelog according to new requirements Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * Remove importing_authority Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * Add vulnerability changelog Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * Address review comments Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * Make changes according to recent PR merging Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * Format package_details.html Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * Add tests for checking unicity of importer names Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * Add tooltip for package and vulnerability views Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * Typo fixes Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * Fix failing tests Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * Address review comments Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * Fix typos Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * Address review comments Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * Fix pypa importer changelog url Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> * Refactor ubuntu usn Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com> --------- Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com>
1 parent d21d2c1 commit e2b60c9

118 files changed

Lines changed: 4942 additions & 2290 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

vulnerabilities/import_runner.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,20 @@
1818

1919
from vulnerabilities.importer import AdvisoryData
2020
from vulnerabilities.importer import Importer
21+
from vulnerabilities.importers import IMPORTERS_REGISTRY
2122
from vulnerabilities.improver import Inference
2223
from vulnerabilities.improvers.default import DefaultImporter
2324
from vulnerabilities.models import Advisory
2425
from vulnerabilities.models import Alias
2526
from vulnerabilities.models import Package
2627
from vulnerabilities.models import PackageRelatedVulnerability
2728
from vulnerabilities.models import Vulnerability
29+
from vulnerabilities.models import VulnerabilityChangeLog
2830
from vulnerabilities.models import VulnerabilityReference
2931
from vulnerabilities.models import VulnerabilityRelatedReference
3032
from vulnerabilities.models import VulnerabilitySeverity
3133
from vulnerabilities.models import Weakness
34+
from vulnerabilities.utils import get_importer_name
3235

3336
logger = logging.getLogger(__name__)
3437

@@ -102,6 +105,7 @@ def process_advisories(
102105
"created_by": importer_name,
103106
"date_collected": datetime.datetime.now(tz=datetime.timezone.utc),
104107
},
108+
url=data.url,
105109
)
106110
if not obj.date_imported:
107111
advisories.append(obj)
@@ -151,6 +155,7 @@ def process_inferences(inferences: List[Inference], advisory: Advisory, improver
151155
vulnerability_id=inference.vulnerability_id,
152156
aliases=inference.aliases,
153157
summary=inference.summary,
158+
advisory=advisory,
154159
)
155160

156161
if not vulnerability:
@@ -193,24 +198,24 @@ def process_inferences(inferences: List[Inference], advisory: Advisory, improver
193198
)
194199

195200
for affected_purl in inference.affected_purls or []:
196-
vulnerable_package = Package.objects.get_or_create_from_purl(purl=affected_purl)
201+
vulnerable_package, _ = Package.objects.get_or_create_from_purl(purl=affected_purl)
197202
PackageRelatedVulnerability(
198203
vulnerability=vulnerability,
199204
package=vulnerable_package,
200205
created_by=improver_name,
201206
confidence=inference.confidence,
202207
fix=False,
203-
).update_or_create()
208+
).update_or_create(advisory=advisory)
204209

205210
if inference.fixed_purl:
206-
fixed_package = Package.objects.get_or_create_from_purl(purl=inference.fixed_purl)
211+
fixed_package, _ = Package.objects.get_or_create_from_purl(purl=inference.fixed_purl)
207212
PackageRelatedVulnerability(
208213
vulnerability=vulnerability,
209214
package=fixed_package,
210215
created_by=improver_name,
211216
confidence=inference.confidence,
212217
fix=True,
213-
).update_or_create()
218+
).update_or_create(advisory=advisory)
214219

215220
if inference.weaknesses and vulnerability:
216221
for cwe_id in inference.weaknesses:
@@ -246,7 +251,7 @@ def create_valid_vulnerability_reference(url, reference_id=None):
246251

247252

248253
def get_or_create_vulnerability_and_aliases(
249-
aliases: List[str], vulnerability_id=None, summary=None
254+
aliases: List[str], vulnerability_id=None, summary=None, advisory=None
250255
):
251256
"""
252257
Get or create vulnerabilitiy and aliases such that all existing and new
@@ -297,6 +302,12 @@ def get_or_create_vulnerability_and_aliases(
297302
vulnerability = create_vulnerability_and_add_aliases(
298303
aliases=new_alias_names, summary=summary
299304
)
305+
importer_name = get_importer_name(advisory)
306+
VulnerabilityChangeLog.log_import(
307+
importer=importer_name,
308+
source_url=advisory.url,
309+
vulnerability=vulnerability,
310+
)
300311
except Exception as e:
301312
logger.error(
302313
f"Cannot create vulnerability with summary {summary!r} and {new_alias_names!r} {e!r}.\n{traceback_format_exc()}."

vulnerabilities/importer.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ class AdvisoryData:
248248
references: List[Reference] = dataclasses.field(default_factory=list)
249249
date_published: Optional[datetime.datetime] = None
250250
weaknesses: List[int] = dataclasses.field(default_factory=list)
251+
url: Optional[str] = None
251252

252253
def __post_init__(self):
253254
if self.date_published and not self.date_published.tzinfo:
@@ -271,6 +272,7 @@ def to_dict(self):
271272
"references": [ref.to_dict() for ref in self.references],
272273
"date_published": self.date_published.isoformat() if self.date_published else None,
273274
"weaknesses": self.weaknesses,
275+
"url": self.url if self.url else "",
274276
}
275277

276278
@classmethod
@@ -287,6 +289,7 @@ def from_dict(cls, advisory_data):
287289
if date_published
288290
else None,
289291
"weaknesses": advisory_data["weaknesses"],
292+
"url": advisory_data.get("url") or None,
290293
}
291294
return cls(**transformed)
292295

@@ -313,6 +316,8 @@ class Importer:
313316
license_url = ""
314317
notice = ""
315318
vcs_response: VCSResponse = None
319+
# It needs to be unique and immutable
320+
importer_name = ""
316321

317322
def __init__(self):
318323
if not self.spdx_license_expression:
@@ -358,6 +363,9 @@ class OvalImporter(Importer):
358363
`OvalDataSource` class. Subclasses must implement the methods `_fetch` and `set_api`.
359364
"""
360365

366+
data_url: str = ""
367+
importer_name = "Oval Importer"
368+
361369
@staticmethod
362370
def create_purl(pkg_name: str, pkg_data: Mapping) -> PackageURL:
363371
"""
@@ -472,4 +480,5 @@ def get_data_from_xml_doc(
472480
affected_packages=affected_packages,
473481
references=sorted(references),
474482
date_published=date_published,
483+
url=self.data_url,
475484
)

vulnerabilities/importers/alpine_linux.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
class AlpineImporter(Importer):
3636
spdx_license_expression = "CC-BY-SA-4.0"
3737
license_url = "https://secdb.alpinelinux.org/license.txt"
38+
importer_name = "Alpine Linux Importer"
3839

3940
def advisory_data(self) -> Iterable[AdvisoryData]:
4041
page_response_content = fetch_response(BASE_URL).content
@@ -50,7 +51,7 @@ def advisory_data(self) -> Iterable[AdvisoryData]:
5051
if not record["packages"]:
5152
LOGGER.error(f'"packages" not found in {link!r}')
5253
continue
53-
yield from process_record(record)
54+
yield from process_record(record=record, url=link)
5455

5556

5657
def fetch_advisory_directory_links(page_response_content: str) -> List[str]:
@@ -98,12 +99,12 @@ def check_for_attributes(record) -> bool:
9899
return True
99100

100101

101-
def process_record(record: dict) -> Iterable[AdvisoryData]:
102+
def process_record(record: dict, url: str) -> Iterable[AdvisoryData]:
102103
"""
103104
Return a list of AdvisoryData objects by processing data
104105
present in that `record`
105106
"""
106-
if not record["packages"]:
107+
if not record.get("packages"):
107108
LOGGER.error(f'"packages" not found in this record {record!r}')
108109
return []
109110

@@ -114,10 +115,11 @@ def process_record(record: dict) -> Iterable[AdvisoryData]:
114115
if not check_for_attributes(record):
115116
continue
116117
yield from load_advisories(
117-
package["pkg"],
118-
record["distroversion"],
119-
record["reponame"],
120-
record["archs"],
118+
pkg_infos=package["pkg"],
119+
distroversion=record["distroversion"],
120+
reponame=record["reponame"],
121+
archs=record["archs"],
122+
url=url,
121123
)
122124

123125

@@ -126,6 +128,7 @@ def load_advisories(
126128
distroversion: str,
127129
reponame: str,
128130
archs: List[str],
131+
url: str,
129132
) -> Iterable[AdvisoryData]:
130133
"""
131134
Yield AdvisoryData by mapping data from `pkg_infos`
@@ -211,4 +214,5 @@ def load_advisories(
211214
references=references,
212215
affected_packages=affected_packages,
213216
aliases=aliases,
217+
url=url,
214218
)

vulnerabilities/importers/apache_httpd.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class ApacheHTTPDImporter(Importer):
3333
base_url = "https://httpd.apache.org/security/json/"
3434
spdx_license_expression = "Apache-2.0"
3535
license_url = "https://www.apache.org/licenses/LICENSE-2.0"
36+
importer_name = "Apache HTTPD Importer"
3637

3738
def advisory_data(self):
3839
links = fetch_links(self.base_url)
@@ -106,6 +107,7 @@ def to_advisory(self, data):
106107
summary=description or "",
107108
affected_packages=affected_packages,
108109
references=[reference],
110+
url=reference.url,
109111
)
110112

111113
def to_version_ranges(self, versions_data, fixed_versions):

vulnerabilities/importers/apache_kafka.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ class ApacheKafkaImporter(Importer):
9595
ASF_PAGE_URL = "https://kafka.apache.org/cve-list"
9696
spdx_license_expression = "Apache-2.0"
9797
license_url = "https://www.apache.org/licenses/"
98+
importer_name = "Apache Kafka Importer"
9899

99100
@staticmethod
100101
def fetch_advisory_page(self):
@@ -191,6 +192,7 @@ def to_advisory(self, advisory_page):
191192
affected_packages=affected_packages,
192193
references=references,
193194
date_published=date_published,
195+
url=f"{self.ASF_PAGE_URL}#{cve_id}",
194196
)
195197
)
196198

vulnerabilities/importers/apache_tomcat.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,15 @@ class ApacheTomcatImporter(Importer):
118118

119119
spdx_license_expression = "Apache-2.0"
120120
license_url = "https://www.apache.org/licenses/LICENSE-2.0"
121+
importer_name = "Apache Tomcat Importer"
121122

122123
def fetch_advisory_pages(self):
123124
"""
124125
Yield the content of each HTML page containing version-related security data.
125126
"""
126127
links = self.fetch_advisory_links("https://tomcat.apache.org/security")
127128
for page_url in links:
128-
yield requests.get(page_url).content
129+
yield page_url, requests.get(page_url).content
129130

130131
def fetch_advisory_links(self, url):
131132
"""
@@ -147,12 +148,12 @@ def advisory_data(self):
147148
"""
148149
advisories = []
149150

150-
for advisory_page in self.fetch_advisory_pages():
151-
advisories.extend(self.extract_advisories_from_page(advisory_page))
151+
for url, advisory_page in self.fetch_advisory_pages():
152+
advisories.extend(self.extract_advisories_from_page(url, advisory_page))
152153

153154
return advisories
154155

155-
def extract_advisories_from_page(self, apache_tomcat_advisory_html):
156+
def extract_advisories_from_page(self, url, apache_tomcat_advisory_html):
156157
"""
157158
Yield AdvisoryData objects extracted from the HTML text ``apache_tomcat_advisory_html``.
158159
"""
@@ -162,7 +163,7 @@ def extract_advisories_from_page(self, apache_tomcat_advisory_html):
162163
)
163164

164165
for advisory_group in fixed_version_advisory_groups:
165-
yield from generate_advisory_data_objects(advisory_group)
166+
yield from generate_advisory_data_objects(url, advisory_group)
166167

167168

168169
@dataclasses.dataclass(order=True)
@@ -265,7 +266,7 @@ def extract_tomcat_advisory_data_from_page(apache_tomcat_advisory_html):
265266
)
266267

267268

268-
def generate_advisory_data_objects(tomcat_advisory_data_object):
269+
def generate_advisory_data_objects(url, tomcat_advisory_data_object):
269270
fixed_versions = tomcat_advisory_data_object.fixed_versions
270271
severity_scores = ("Low:", "Moderate:", "Important:", "High:", "Critical:")
271272

@@ -361,6 +362,7 @@ def generate_advisory_data_objects(tomcat_advisory_data_object):
361362
summary="",
362363
affected_packages=affected_packages,
363364
references=references,
365+
url=url,
364366
)
365367

366368

vulnerabilities/importers/archlinux.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class ArchlinuxImporter(Importer):
2828
url = "https://security.archlinux.org/json"
2929
spdx_license_expression = "MIT"
3030
license_url = "https://github.com/archlinux/arch-security-tracker/blob/master/LICENSE"
31+
importer_name = "Arch Linux Importer"
3132

3233
def fetch(self) -> Iterable[Mapping]:
3334
response = fetch_response(self.url)
@@ -91,6 +92,7 @@ def parse_advisory(self, record) -> List[AdvisoryData]:
9192
summary=summary,
9293
affected_packages=affected_packages,
9394
references=references,
95+
url=f"https://security.archlinux.org/{record['name']}",
9496
)
9597
)
9698

vulnerabilities/importers/debian.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class DebianImporter(Importer):
7777
"""
7878

7979
api_url = "https://security-tracker.debian.org/tracker/data/json"
80+
importer_name = "Debian Importer"
8081

8182
def get_response(self):
8283
response = requests.get(self.api_url)
@@ -154,4 +155,5 @@ def parse(self, pkg_name: str, records: Mapping[str, Any]) -> Iterable[AdvisoryD
154155
summary=record.get("description", ""),
155156
affected_packages=affected_packages,
156157
references=references,
158+
url=self.api_url,
157159
)

vulnerabilities/importers/debian_oval.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class DebianOvalImporter(OvalImporter):
5454
Cheers,
5555
Moritz
5656
"""
57+
importer_name = "Debian Oval Importer"
5758

5859
def __init__(self, *args, **kwargs):
5960
super().__init__(*args, **kwargs)
@@ -66,6 +67,7 @@ def _fetch(self):
6667
releases = ["wheezy", "stretch", "jessie", "buster", "bullseye"]
6768
for release in releases:
6869
file_url = f"https://www.debian.org/security/oval/oval-definitions-{release}.xml.bz2"
70+
self.data_url = file_url
6971
resp = requests.get(file_url).content
7072
extracted = bz2.decompress(resp)
7173
yield (

vulnerabilities/importers/elixir_security.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
# See https://github.com/nexB/vulnerablecode for support or download.
77
# See https://aboutcode.org for more information about nexB OSS projects.
88
#
9+
import urllib.parse as urlparse
910
from pathlib import Path
1011
from typing import Set
1112

13+
from dateutil import parser as dateparser
1214
from packageurl import PackageURL
1315
from univers.version_constraint import VersionConstraint
1416
from univers.version_range import HexVersionRange
@@ -26,21 +28,26 @@ class ElixirSecurityImporter(Importer):
2628
repo_url = "git+https://github.com/dependabot/elixir-security-advisories"
2729
license_url = "https://github.com/dependabot/elixir-security-advisories/blob/master/LICENSE.txt"
2830
spdx_license_expression = "CC0-1.0"
31+
importer_name = "Elixir Security Importer"
2932

3033
def advisory_data(self) -> Set[AdvisoryData]:
3134
try:
32-
self.clone(repo_url=self.repo_url)
33-
path = Path(self.vcs_response.dest_dir)
34-
vuln = path / "packages"
35+
self.clone(self.repo_url)
36+
base_path = Path(self.vcs_response.dest_dir)
37+
vuln = base_path / "packages"
3538
for file in vuln.glob("**/*.yml"):
36-
yield from self.process_file(file)
39+
yield from self.process_file(file, base_path)
3740
finally:
3841
if self.vcs_response:
3942
self.vcs_response.delete()
4043

41-
def process_file(self, path):
42-
path = str(path)
43-
yaml_file = load_yaml(path)
44+
def process_file(self, file, base_path):
45+
relative_path = str(file.relative_to(base_path)).strip("/")
46+
advisory_url = (
47+
f"https://github.com/dependabot/elixir-security-advisories/blob/master/{relative_path}"
48+
)
49+
file = str(file)
50+
yaml_file = load_yaml(file)
4451
cve_id = ""
4552
summary = yaml_file.get("description") or ""
4653
pkg_name = yaml_file.get("package") or ""
@@ -94,9 +101,15 @@ def process_file(self, path):
94101
)
95102
)
96103

104+
date_published = None
105+
if yaml_file.get("disclosure_date"):
106+
date_published = dateparser.parse(yaml_file.get("disclosure_date"))
107+
97108
yield AdvisoryData(
98109
aliases=[cve_id],
99110
summary=summary,
100111
references=references,
101112
affected_packages=affected_packages,
113+
url=advisory_url,
114+
date_published=date_published,
102115
)

0 commit comments

Comments
 (0)