-
-
Notifications
You must be signed in to change notification settings - Fork 328
[EXPERIMENT] New models #216
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,15 +33,69 @@ | |
|
|
||
| from vulnerabilities.data_source import DataSource | ||
|
|
||
| class Importer(models.Model): | ||
| """ | ||
| Metadata and pointer to the implementation for a source of vulnerability data (aka security | ||
| advisories) | ||
| """ | ||
| name = models.CharField(max_length=100, unique=True, help_text='Name of the importer') | ||
|
|
||
| license = models.CharField( | ||
| max_length=100, | ||
| blank=True, | ||
| help_text='License of the vulnerability data', | ||
| ) | ||
|
|
||
| last_run = models.DateTimeField(null=True, help_text='UTC Timestamp of the last run') | ||
|
|
||
| data_source = models.CharField( | ||
| max_length=100, | ||
| help_text='Name of the data source implementation importable from vulnerabilities.importers' | ||
| ) | ||
| data_source_cfg = pgfields.JSONField( | ||
| null=False, | ||
| default=dict, | ||
| help_text='Implementation-specific configuration for the data source', | ||
| ) | ||
|
|
||
| def make_data_source(self, batch_size: int, cutoff_date: datetime = None) -> DataSource: | ||
| """ | ||
| Return a configured and ready to use instance of this importers data source implementation. | ||
|
|
||
| batch_size - max. number of records to return on each iteration | ||
| cutoff_date - optional timestamp of the oldest data to include in the import | ||
| """ | ||
| importers_module = importlib.import_module('vulnerabilities.importers') | ||
| klass = getattr(importers_module, self.data_source) | ||
|
|
||
| ds = klass( | ||
| batch_size, | ||
| last_run_date=self.last_run, | ||
| cutoff_date=cutoff_date, | ||
| config=self.data_source_cfg, | ||
| ) | ||
|
|
||
| return ds | ||
|
|
||
| def __str__(self): | ||
| return self.name | ||
|
|
||
|
|
||
| class Vulnerability(models.Model): | ||
| """ | ||
| A software vulnerability with minimal information. Identifiers other than CVE ID are stored as | ||
| VulnerabilityReference. | ||
| """ | ||
| cve_id = models.CharField(max_length=50, help_text='CVE ID', unique=True, null=True) | ||
| summary = models.TextField(help_text='Summary of the vulnerability', blank=True) | ||
| cvss = models.FloatField(max_length=100, help_text='CVSS Score', null=True) | ||
| vuln_id = models.CharField(max_length=50, help_text='eg CVE ID, RUST SEC ID', unique=True, null=True) | ||
| reference_ids = pgfields.JSONField() | ||
|
|
||
| # Whatever goes into vuln_id is a vulnerability identifier | ||
| # which is undivisible i.e atomic vulnerability id. All CVEs fit into this. | ||
|
|
||
| # reference_ids are usually but not limited to `advisory` ids like USN-4399-1 | ||
| # https://usn.ubuntu.com/4399-1/. | ||
| # Contents of reference_ids are a name/id given to collection of | ||
| # other small vulnerbilties. For example USN-4399-1 refers to CVE-2020-8618, CVE-2020-8619 | ||
|
|
||
| def __str__(self): | ||
| return self.cve_id or self.summary | ||
|
|
@@ -56,26 +110,25 @@ class VulnerabilityReference(models.Model): | |
| package manager. | ||
| """ | ||
| vulnerability = models.ForeignKey( | ||
| Vulnerability, on_delete=models.CASCADE) | ||
| source = models.CharField( | ||
| max_length=50, help_text='Source(s) name eg:NVD', blank=True) | ||
| reference_id = models.CharField( | ||
| max_length=50, help_text='Reference ID, eg:DSA-4465-1', blank=True) | ||
| url = models.URLField( | ||
| max_length=1024, help_text='URL of Vulnerability data', blank=True) | ||
| Vulnerability, on_delete=models.CASCADE) | ||
| source = models.ForeignKey( | ||
| Importer, on_delete=models.CASCADE) | ||
| urls = pgfields.JSONField() | ||
| summary = models.TextField() | ||
|
|
||
| class Meta: | ||
| unique_together = ('vulnerability', 'source', 'reference_id', 'url') | ||
|
|
||
| def __str__(self): | ||
| return f'{self.source} {self.reference_id} {self.url}' | ||
| unique_together = ('vulnerability', 'source') | ||
|
|
||
| class VulnerabilityScore(models.Model): | ||
| vulnerability_reference = models.ForeignKey(VulnerabilityReference, on_delete=models.CASCADE) | ||
| type = models.CharField(max_length=50, help_text='Vulnerability score type', blank=True) | ||
| score = models.CharField(max_length=50) | ||
|
|
||
| class Package(PackageURLMixin): | ||
| """ | ||
| A software package with links to relevant vulnerabilities. | ||
| """ | ||
| vulnerabilities = models.ManyToManyField(to='Vulnerability', through='ImpactedPackage') | ||
| vulnerabilities = models.ManyToManyField(to='Vulnerability', through='Vulnerability_Package_Relation') | ||
|
|
||
| class Meta: | ||
| unique_together = ('name', 'namespace', 'type', 'version', 'qualifiers', 'subpath') | ||
|
|
@@ -111,68 +164,17 @@ def __str__(self): | |
| return self.package_url | ||
|
|
||
|
|
||
| class ImpactedPackage(models.Model): | ||
| class Vulnerability_Package_Relation(models.Model): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume this name is a placeholder, right? How about
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah that would be better.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @sbs2001 I am not convinced by the name too (and we should not use snake case for Class or model names).
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. About the name thing, it's just a placeholder, Btw
Good question. There are some issues with having 2 tables, Impacted and Resolved . Issue 1 : This is pure garbage, nothing can be interpreted from these entries. With a single table + flag, I can use a Issue 2 : Check https://github.com/nexB/vulnerablecode/blob/58d0376e7319d06387662cb393f3c39d9893088d/vulnerabilities/import_runner.py#L121 , I am not sure I understand the exact issue but it's something along the lines that updating vulnerability status of a already existing package is not possible. @haikoschol can you explain this, with a snippet? Having a single table, changes delete to an update(of the flag), which bypasses this issue.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Never mind @haikoschol already left a brief explanation of this issue in the comments at https://github.com/nexB/vulnerablecode/blob/58d0376e7319d06387662cb393f3c39d9893088d/vulnerabilities/tests/test_import_runner.py#L201 |
||
| """ | ||
| Relates a vulnerability to package(s) impacted by it. | ||
| """ | ||
| # { | ||
| vulnerability = models.ForeignKey(Vulnerability, on_delete=models.CASCADE) | ||
| package = models.ForeignKey(Package, on_delete=models.CASCADE) | ||
| is_vulnerable = models.BooleanField() | ||
| # } till this point we have a consensus in this model | ||
|
|
||
| class Meta: | ||
| unique_together = ('vulnerability', 'package') | ||
|
|
||
| version_range = models.CharField(max_length=50) | ||
|
|
||
| class ResolvedPackage(models.Model): | ||
| """ | ||
| Relates a vulnerability to package(s) that contain a fix or resolution of this vulnerability. | ||
| """ | ||
| vulnerability = models.ForeignKey(Vulnerability, on_delete=models.CASCADE) | ||
| package = models.ForeignKey(Package, on_delete=models.CASCADE) | ||
|
|
||
|
|
||
| class Importer(models.Model): | ||
| """ | ||
| Metadata and pointer to the implementation for a source of vulnerability data (aka security | ||
| advisories) | ||
| """ | ||
| name = models.CharField(max_length=100, unique=True, help_text='Name of the importer') | ||
|
|
||
| license = models.CharField( | ||
| max_length=100, | ||
| blank=True, | ||
| help_text='License of the vulnerability data', | ||
| ) | ||
|
|
||
| last_run = models.DateTimeField(null=True, help_text='UTC Timestamp of the last run') | ||
|
|
||
| data_source = models.CharField( | ||
| max_length=100, | ||
| help_text='Name of the data source implementation importable from vulnerabilities.importers' | ||
| ) | ||
| data_source_cfg = pgfields.JSONField( | ||
| null=False, | ||
| default=dict, | ||
| help_text='Implementation-specific configuration for the data source', | ||
| ) | ||
|
|
||
| def make_data_source(self, batch_size: int, cutoff_date: datetime = None) -> DataSource: | ||
| """ | ||
| Return a configured and ready to use instance of this importers data source implementation. | ||
|
|
||
| batch_size - max. number of records to return on each iteration | ||
| cutoff_date - optional timestamp of the oldest data to include in the import | ||
| """ | ||
| importers_module = importlib.import_module('vulnerabilities.importers') | ||
| klass = getattr(importers_module, self.data_source) | ||
|
|
||
| ds = klass( | ||
| batch_size, | ||
| last_run_date=self.last_run, | ||
| cutoff_date=cutoff_date, | ||
| config=self.data_source_cfg, | ||
| ) | ||
|
|
||
| return ds | ||
|
|
||
| def __str__(self): | ||
| return self.name | ||
| class Meta: | ||
| unique_together = ('vulnerability', 'package') | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this field really be nullable? What do we do with a vulnerability that has no
vuln_idand noreference_ids? If your response is that there is always at least one reference ID, then why not store that invuln_id? Or in other words; I haven't quite understood the difference betweenvuln_idandreference_idshere.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I expected that, hence I had added comments in the code to define what is vuln_id and what is reference_id, here is a repaste:
As far as the advisories I've looked at, yes there is some sort of id present, but I'm not 100% confident whether this will stay true.
Yes. If it is a atomic vulnerability id, then it probably didn't belonged in the reference_id in the first place. RUST-SEC ids are stored in reference_id no matter whether they have CVE or not. If they don't have a CVE, they become atomic, because no other id will denote that specific vulnerability.
I also had this idea, which I didn't mentioned here, but a
vuln_id's value should also be present along with(if present) other reference_ids in thereference_idscolumn. The idea beingvuln_idis also it's own reference_id.As @pombredanne mentioned, we have to give them our ids, but that's gonna introduce a whole lot of other complexities(how to make id's consistent ?).
My other point is , should we really worry about vulnerabilities without any id's . As far as I have inspected these advisories, only FriendsOfPHP were missing these , which was solved, because GH provide their ids for FriendsOfPHP advisories.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I should have mentioned that I read the comment and still didn't get it (undivisible/atomic and "small vulnerabilities" confused me). But now I think I understand. Some advisories cover multiple vulnerabilities and if those have CVE IDs, they will all be mentioned.
The problem with storing IDs other than CVE in
vuln_idis that we require them to be unique across all publishers of advisories. That might be the case coincidentally, but I don't think there are any efforts to ensure that. But, in practice it will probably work and if not that problem can be solved when it occurs.I think that was just referring to the automatically added primary key column.
I don't think we should worry about vulnerabilities without IDs. But the only reason I can think of for making this column nullable is to be able to store vulnerabilities without IDs. Hence my question. :)
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sbs2001 re:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sbs2001 I thin we could do better with a simpler model.
vuln_id, if we want and need this, it would then becomes our own id that we assign automatically IMHO. I am not sure we need an id though I can some benefit for users.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pombredanne re
I have explained it in this ticket itself, can you take a look at Problem 1 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pombredanne
Probably yes.
Sure, I have done that in a comment below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pombredanne
I don't understand this, can you elaborate this further?