Skip to content

Commit 4ebaa48

Browse files
authored
Merge pull request #600 from Hritik14/migration/importer_yielder
Dump importer_yielder in favor of IMPORTER_REGISTRY and drop Etags
2 parents 14850ea + 45cdaf5 commit 4ebaa48

79 files changed

Lines changed: 664 additions & 1407 deletions

Some content is hidden

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

vulnerabilities/admin.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525

2626
from vulnerabilities.models import (
2727
PackageRelatedVulnerability,
28-
Importer,
2928
Package,
3029
Vulnerability,
3130
VulnerabilityReference,
@@ -55,11 +54,6 @@ class PackageRelatedVulnerabilityAdmin(admin.ModelAdmin):
5554
search_fields = ["vulnerability__vulnerability_id", "package__name"]
5655

5756

58-
@admin.register(Importer)
59-
class ImporterAdmin(admin.ModelAdmin):
60-
pass
61-
62-
6357
@admin.register(VulnerabilitySeverity)
6458
class VulnerabilitySeverityAdmin(admin.ModelAdmin):
6559
pass

vulnerabilities/fixtures/github.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696
"name": "github",
9797
"license": "",
9898
"last_run": "2021-03-06T09:09:01.523Z",
99-
"data_source": "GitHubAPIDataSource",
99+
"data_source": "GitHubAPIImporter",
100100
"data_source_cfg": {
101101
"endpoint": "https://api.github.com/graphql",
102102
"ecosystems": [
@@ -115,4 +115,4 @@
115115
"reference": 136
116116
}
117117
}
118-
]
118+
]

vulnerabilities/fixtures/openssl.json

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -40538,7 +40538,7 @@
4053840538
"name": "rust",
4053940539
"license": "cc0-1.0",
4054040540
"last_run": "2020-09-14T12:47:17.451Z",
40541-
"data_source": "RustDataSource",
40541+
"data_source": "RustImporter",
4054240542
"data_source_cfg": {
4054340543
"branch": null,
4054440544
"repository_url": "https://github.com/RustSec/advisory-db",
@@ -40555,7 +40555,7 @@
4055540555
"name": "alpine",
4055640556
"license": "",
4055740557
"last_run": null,
40558-
"data_source": "AlpineDataSource",
40558+
"data_source": "AlpineImporter",
4055940559
"data_source_cfg": {
4056040560
"branch": null,
4056140561
"repository_url": "https://gitlab.alpinelinux.org/alpine/infra/alpine-secdb"
@@ -40569,7 +40569,7 @@
4056940569
"name": "archlinux",
4057040570
"license": "mit",
4057140571
"last_run": null,
40572-
"data_source": "ArchlinuxDataSource",
40572+
"data_source": "ArchlinuxImporter",
4057340573
"data_source_cfg": {
4057440574
"archlinux_tracker_url": "https://security.archlinux.org/json"
4057540575
}
@@ -40582,7 +40582,7 @@
4058240582
"name": "debian",
4058340583
"license": "mit",
4058440584
"last_run": null,
40585-
"data_source": "DebianDataSource",
40585+
"data_source": "DebianImporter",
4058640586
"data_source_cfg": {
4058740587
"debian_tracker_url": "https://security-tracker.debian.org/tracker/data/json"
4058840588
}
@@ -40595,7 +40595,7 @@
4059540595
"name": "safetydb",
4059640596
"license": "cc-by-nc-4.0",
4059740597
"last_run": null,
40598-
"data_source": "SafetyDbDataSource",
40598+
"data_source": "SafetyDbImporter",
4059940599
"data_source_cfg": {
4060040600
"url": "https://raw.githubusercontent.com/pyupio/safety-db/master/data/insecure_full.json",
4060140601
"etags": {}
@@ -40609,7 +40609,7 @@
4060940609
"name": "npm",
4061040610
"license": "mit",
4061140611
"last_run": null,
40612-
"data_source": "NpmDataSource",
40612+
"data_source": "NpmImporter",
4061340613
"data_source_cfg": {
4061440614
"repository_url": "https://github.com/nodejs/security-wg.git"
4061540615
}
@@ -40622,7 +40622,7 @@
4062240622
"name": "ruby",
4062340623
"license": "",
4062440624
"last_run": null,
40625-
"data_source": "RubyDataSource",
40625+
"data_source": "RubyImporter",
4062640626
"data_source_cfg": {
4062740627
"repository_url": "https://github.com/rubysec/ruby-advisory-db.git"
4062840628
}
@@ -40635,7 +40635,7 @@
4063540635
"name": "ubuntu",
4063640636
"license": "gpl-2.0",
4063740637
"last_run": null,
40638-
"data_source": "UbuntuDataSource",
40638+
"data_source": "UbuntuImporter",
4063940639
"data_source_cfg": {
4064040640
"etags": {},
4064140641
"releases": [
@@ -40655,7 +40655,7 @@
4065540655
"name": "retiredotnet",
4065640656
"license": "mit",
4065740657
"last_run": null,
40658-
"data_source": "RetireDotnetDataSource",
40658+
"data_source": "RetireDotnetImporter",
4065940659
"data_source_cfg": {
4066040660
"repository_url": "https://github.com/RetireNet/Packages.git"
4066140661
}
@@ -40668,7 +40668,7 @@
4066840668
"name": "suse_backports",
4066940669
"license": "",
4067040670
"last_run": null,
40671-
"data_source": "SUSEBackportsDataSource",
40671+
"data_source": "SUSEBackportsImporter",
4067240672
"data_source_cfg": {
4067340673
"url": "http://ftp.suse.com/pub/projects/security/yaml/",
4067440674
"etags": {}
@@ -40682,7 +40682,7 @@
4068240682
"name": "debian_oval",
4068340683
"license": "",
4068440684
"last_run": null,
40685-
"data_source": "DebianOvalDataSource",
40685+
"data_source": "DebianOvalImporter",
4068640686
"data_source_cfg": {
4068740687
"etags": {},
4068840688
"releases": [
@@ -40701,7 +40701,7 @@
4070140701
"name": "redhat",
4070240702
"license": "cc-by-4.0",
4070340703
"last_run": null,
40704-
"data_source": "RedhatDataSource",
40704+
"data_source": "RedhatImporter",
4070540705
"data_source_cfg": {}
4070640706
}
4070740707
},
@@ -40712,7 +40712,7 @@
4071240712
"name": "gentoo",
4071340713
"license": "",
4071440714
"last_run": null,
40715-
"data_source": "GentooDataSource",
40715+
"data_source": "GentooImporter",
4071640716
"data_source_cfg": {
4071740717
"repository_url": "https://anongit.gentoo.org/git/data/glsa.git"
4071840718
}
@@ -40725,7 +40725,7 @@
4072540725
"name": "openssl",
4072640726
"license": "",
4072740727
"last_run": "2020-09-14T12:52:58.762Z",
40728-
"data_source": "OpenSSLDataSource",
40728+
"data_source": "OpenSSLImporter",
4072940729
"data_source_cfg": {
4073040730
"etags": {
4073140731
"https://www.openssl.org/news/vulnerabilities.xml": "\"513bd-5aee0a1c716f0-gzip\""
@@ -40740,7 +40740,7 @@
4074040740
"name": "ubuntu_usn",
4074140741
"license": "gpl-2.0",
4074240742
"last_run": null,
40743-
"data_source": "UbuntuUSNDataSource",
40743+
"data_source": "UbuntuUSNImporter",
4074440744
"data_source_cfg": {
4074540745
"etags": {},
4074640746
"db_url": "https://usn.ubuntu.com/usn-db/database-all.json.bz2"
@@ -40754,7 +40754,7 @@
4075440754
"name": "github",
4075540755
"license": "",
4075640756
"last_run": null,
40757-
"data_source": "GitHubAPIDataSource",
40757+
"data_source": "GitHubAPIImporter",
4075840758
"data_source_cfg": {
4075940759
"endpoint": "https://api.github.com/graphql",
4076040760
"ecosystems": [
@@ -40765,4 +40765,4 @@
4076540765
}
4076640766
}
4076740767
}
40768-
]
40768+
]

vulnerabilities/helpers.py

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from typing import List
2828
from typing import Optional
2929
from typing import Tuple
30+
from unittest.mock import MagicMock
3031

3132
import requests
3233
import saneyaml
@@ -67,32 +68,8 @@ def fetch_yaml(url):
6768
return saneyaml.load(response.content)
6869

6970

70-
# FIXME: this is NOT how etags work .
71-
# We should instead send the proper HTTP header
72-
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match
73-
# and integrate this finely in the processing as this typically needs to use
74-
# streaming=True requests, and proper handling of the HTTP return code
75-
# In all cases this ends up being a single request, not a HEADD followed
76-
# by another real request
77-
def create_etag(data_src, url, etag_key):
78-
"""
79-
Etags are like hashes of web responses. For a data source `data_src`,
80-
we maintain (url, etag) mappings in the DB. `create_etag` creates
81-
(`url`, etag) pair. If a (`url`, etag) already exists then the code
82-
skips processing the response further to avoid duplicate work.
83-
84-
`etag_key` is the name of header which contains the etag for the url.
85-
"""
86-
etag = requests.head(url).headers.get(etag_key)
87-
if not etag:
88-
return True
89-
90-
elif url in data_src.config.etags:
91-
if data_src.config.etags[url] == etag:
92-
return False
93-
94-
data_src.config.etags[url] = etag
95-
return True
71+
# FIXME: Remove this entirely after complete importer-improver migration
72+
create_etag = MagicMock()
9673

9774

9875
def contains_alpha(string):
@@ -190,3 +167,12 @@ def split_markdown_front_matter(text: str) -> Tuple[str, str]:
190167
return frontmatter, markdown
191168

192169
return "", text
170+
171+
172+
# TODO: Replace this with combination of @classmethod and @property after upgrading to python 3.9
173+
class classproperty(object):
174+
def __init__(self, fget):
175+
self.fget = fget
176+
177+
def __get__(self, owner_self, owner_cls):
178+
return self.fget(owner_cls)

vulnerabilities/import_runner.py

Lines changed: 19 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -25,79 +25,50 @@
2525
import datetime
2626
import json
2727
import logging
28-
from typing import Set
28+
from typing import List
2929
from typing import Iterable
3030

3131

3232
from vulnerabilities import models
3333
from vulnerabilities.models import Advisory
34-
from vulnerabilities.data_source import AdvisoryData
34+
from vulnerabilities.importer import AdvisoryData
35+
from vulnerabilities.importer import Importer
3536

3637
logger = logging.getLogger(__name__)
3738

3839

3940
class ImportRunner:
4041
"""
4142
The ImportRunner is responsible for inserting and updating data about vulnerabilities and
42-
affected/unaffected/fixed packages in the database. The two main goals for the implementation
43-
are correctness and efficiency.
43+
affected/unaffected/fixed packages in the database. The main goal for the implementation
44+
is correctness
4445
4546
Correctness:
4647
- There must be no duplicates in the database (should be enforced by the schema).
4748
- No valid data from the data source must be skipped or truncated.
48-
49-
Efficiency:
50-
- Bulk inserts should be used whenever possible.
51-
- Checking whether a record already exists should be kept to a minimum
52-
(the data source should know this instead).
53-
- All update and select operations must use indexed columns.
5449
"""
5550

56-
def __init__(self, importer: models.Importer):
51+
def __init__(self, importer: Importer):
5752
self.importer = importer
5853

59-
def run(self, cutoff_date: datetime.datetime = None) -> None:
54+
def run(self) -> None:
6055
"""
6156
Create a data source for the given importer and store the data retrieved in the database.
62-
63-
cutoff_date - optional timestamp of the oldest data to include in the import
64-
65-
NB: Data sources provide two kinds of records; vulnerabilities and packages. Vulnerabilities
66-
are potentially shared across many packages, from the same data source and from different
67-
data sources. For example, a vulnerability in the Linux kernel is mentioned by advisories
68-
from all Linux distributions that package this kernel version.
6957
"""
70-
logger.info(f"Starting import for {self.importer.name}.")
71-
data_source = self.importer.make_data_source(cutoff_date=cutoff_date)
72-
with data_source:
73-
advisory_data = data_source.advisory_data()
74-
importer_name = data_source.qualified_name()
75-
process_advisories(advisory_datas=advisory_data, importer_name=importer_name)
76-
self.importer.last_run = datetime.datetime.now(tz=datetime.timezone.utc)
77-
self.importer.data_source_cfg = dataclasses.asdict(data_source.config)
78-
self.importer.save()
79-
80-
logger.info(f"Finished import for {self.importer.name}.")
81-
58+
importer_name = self.importer.qualified_name
59+
importer_class = self.importer
60+
logger.info(f"Starting import for {importer_name}")
61+
advisory_datas = importer_class().advisory_data()
62+
count = process_advisories(advisory_datas=advisory_datas, importer_name=importer_name)
63+
logger.info(f"Finished import for {importer_name}. Imported {count} advisories.")
8264

83-
def vuln_ref_exists(vulnerability, url, reference_id):
84-
return models.VulnerabilityReference.objects.filter(
85-
vulnerability=vulnerability, reference_id=reference_id, url=url
86-
).exists()
8765

88-
89-
def get_vuln_pkg_refs(vulnerability, package):
90-
return models.PackageRelatedVulnerability.objects.filter(
91-
vulnerability=vulnerability,
92-
package=package,
93-
)
94-
95-
96-
def process_advisories(advisory_datas: Iterable[AdvisoryData], importer_name: str) -> None:
66+
def process_advisories(advisory_datas: Iterable[AdvisoryData], importer_name: str) -> List:
9767
"""
9868
Insert advisories into the database
69+
Return the number of inserted advisories.
9970
"""
100-
71+
count = 0
10172
for data in advisory_datas:
10273
obj, created = Advisory.objects.get_or_create(
10374
aliases=data.aliases,
@@ -114,5 +85,8 @@ def process_advisories(advisory_datas: Iterable[AdvisoryData], importer_name: st
11485
logger.info(
11586
f"[*] New Advisory with aliases: {obj.aliases!r}, created_by: {obj.created_by}"
11687
)
88+
count += 1
11789
else:
11890
logger.debug(f"Advisory with aliases: {obj.aliases!r} already exists. Skipped.")
91+
92+
return count

0 commit comments

Comments
 (0)