diff --git a/vulnerabilities/admin.py b/vulnerabilities/admin.py index 2b790aabd..85f37cf8c 100644 --- a/vulnerabilities/admin.py +++ b/vulnerabilities/admin.py @@ -24,10 +24,9 @@ from django.contrib import admin from vulnerabilities.models import ( - ImpactedPackage, + Vulnerability_Package_Relation, Importer, Package, - ResolvedPackage, Vulnerability, VulnerabilityReference, ) @@ -48,16 +47,11 @@ class PackageAdmin(admin.ModelAdmin): pass -@admin.register(ImpactedPackage) +@admin.register(Vulnerability_Package_Relation) class ImpactedPackageAdmin(admin.ModelAdmin): pass -@admin.register(ResolvedPackage) -class ResolvedPackageAdmin(admin.ModelAdmin): - pass - - @admin.register(Importer) class ImporterAdmin(admin.ModelAdmin): pass diff --git a/vulnerabilities/models.py b/vulnerabilities/models.py index 5fde0c21b..fadde3795 100644 --- a/vulnerabilities/models.py +++ b/vulnerabilities/models.py @@ -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): """ 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') \ No newline at end of file diff --git a/vulnerablecode/settings.py b/vulnerablecode/settings.py index 220925f52..6eeba95bc 100644 --- a/vulnerablecode/settings.py +++ b/vulnerablecode/settings.py @@ -47,6 +47,7 @@ 'vulnerabilities', 'rest_framework', 'django_filters', + 'django_extensions' ] MIDDLEWARE = [