Skip to content

Commit 8b9123e

Browse files
committed
Add Improver Documentation
Signed-off-by: Hritik Vijay <hritikxx8@gmail.com>
1 parent 4390b6a commit 8b9123e

8 files changed

Lines changed: 325 additions & 14 deletions

docs/source/index.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,16 @@ Welcome to VulnerableCode! In this documentation you’ll find information on:
2222
:caption: Tutorial
2323

2424
tutorial_add_new_importer
25+
tutorial_add_new_improver
2526

2627

2728
.. toctree::
2829
:maxdepth: 2
2930
:caption: Reference Documentation
3031

31-
reference_importer_concepts
32-
reference_improver_concepts
32+
reference_importer_overview
33+
reference_improver_overview
34+
reference_framework_overview
3335
command-line-interface
3436

3537
.. toctree::

docs/source/reference_importer_concepts.rst renamed to docs/source/reference_importer_overview.rst

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
.. _importer-concepts:
1+
.. _importer-overview:
22

3-
Importer Concepts
3+
Importer Overview
44
==================
55

66
Importers are responsible to scrape vulnerability data from various data sources without creating
@@ -17,7 +17,8 @@ processed and inserted into the ``Advisory`` model.
1717

1818
While implementing an importer, it is important to make sure that the importer does not alter the
1919
upstream data at all. Its only job is to convert the data from a data source into structured - yet
20-
non relational - data.
20+
non relational - data. The importers must **not** be smart or performing trickeries
21+
under the hood.
2122
This ensures that we always have a *true* copy of an advisory without any speculations or
2223
improvements.
2324

@@ -28,7 +29,7 @@ library whose development goes hand in hand with VulnerableCode.
2829

2930
The data imported by importers is not useful by itself, it must be processed into a relational
3031
model. The version ranges are required to be dissolved into concrete ranges. These are achieved by
31-
``Improvers``. For more, see: :ref:`improver-concepts`
32+
``Improvers``. For more, see: :ref:`improver-overview`
3233

3334
As of now, the following importers have been implemented in VulnerableCode
3435

docs/source/reference_improver_concepts.rst

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
.. _improver-overview:
2+
3+
Improver Overview
4+
===================
5+
6+
Improvers improve upon already imported data. They are responsible for creating a relational
7+
model for vulnerabilites and packages.
8+
9+
An Improver is supposed to contain data points about a vulnerability and the relevant discrete
10+
affected and fixed packages (in the form of `PackageURLs
11+
<https://github.com/package-url/packageurl-python>`_).
12+
There is no notion of version ranges here, all package versions must be explicitly specified.
13+
As this concrete relationship might not always be absolutely correct, improvers supply with a
14+
confidence score and only the record with the highest confidence against a vulnerability and package
15+
relationship is stored in the database.
16+
17+
There are two categories of improvers:
18+
19+
- **Generic**: Improve upon some imported data irrespective of any importer. These improvers are
20+
defined in :file:`vulnerabilites/improvers/`
21+
- **Importer Specific**: Improve upon data imported by a specific importer. These are defined in the
22+
corresponding importer file itself.
23+
24+
Both types of improvers internally work in a similar fashion. They indicate which ``Advisory`` they
25+
are interested in and when supplied with those Advisories, they return Inferences.
26+
An ``Inference`` is more explicit than an ``Advisory`` and is able to answer the questions like, "Is
27+
package A vulnerable to Vulnerability B ?". Of course, there is some confidence attached with the
28+
answer which could also be ``MAX_CONFIDENCE`` in certain cases.
29+
30+
The possibilities with improvers is endless, they are not restricted to take one approach. Features
31+
like *Time Travel* and *finding fix commits* could be Implemented as well.
32+
33+
You can find more in-code documentation about improvers in :file:`vulnerabilites/improver.py` and
34+
the framework responsible for invoking these improvers in :file:`vulnerabilites/improve_runner.py`

docs/source/tutorial_add_new_importer.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
.. _tutorial_add_a_new_importer:
2+
13
Add a new importer
24
====================
35

46
This tutorial contains all the things one should know to quickly
57
implement an importer.
68
A lot of internal sausage about importers could be found inside the
79
:file:`vulnerabilites/importer.py` file.
8-
Make sure to go through :ref:`importer-concepts` before you begin writing one.
10+
Make sure to go through :ref:`importer-overview` before you begin writing one.
911

1012
TL;DR
1113
-------
@@ -18,6 +20,8 @@ TL;DR
1820
#. Add the newly created importer to the importers registry at
1921
``vulnerabilites/importers/__init__.py``
2022

23+
.. _tutorial_add_a_new_importer_prerequisites:
24+
2125
Prerequisites
2226
--------------
2327

@@ -216,6 +220,9 @@ version management from `univers <https://github.com/nexB/univers>`_
216220
| Use ``make valid`` to format your new code using black and isort automatically.
217221
| Use ``make check`` to check for formatting errrors.
218222
223+
Register the Importer
224+
^^^^^^^^^^^^^^^^^^^^^^
225+
219226
Finally, register your importer in the importer registry at
220227
:file:`vulnerabilites/importers/__init__.py`
221228

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
.. _tutorial_add_a_new_improver:
2+
3+
Add a new improver
4+
====================
5+
6+
This tutorial contains all the things one should know to quickly
7+
implement an improver.
8+
A lot of internal sausage about improvers could be found inside the
9+
:file:`vulnerabilites/improver.py` file.
10+
Make sure to go through :ref:`improver-overview` before you begin writing one.
11+
12+
TL;DR
13+
-------
14+
15+
#. Locate the importer that this improver will be improving data of at
16+
:file:`vulnerabilities/importers/{importer_name.py}` file.
17+
#. Create a new improver subclass inheriting from the ``Improver`` superclass defined in
18+
``vulnerabilites.improver``. It is conventional to end an improver name with *Improver*.
19+
#. Implement the ``interesting_advisories`` property to return a QuerySet of imported data
20+
(``Advisory``) you are interested in.
21+
#. Implement the ``get_inferences`` method to return an iterable of ``Inference`` objects for the
22+
given ``AdvisoryData``
23+
#. Add the newly created improver to the improvers registry at
24+
``vulnerabilites/improvers/__init__.py``
25+
26+
Prerequisites
27+
--------------
28+
29+
Before writing an improver, it is important to familiarize yourself with the following concepts.
30+
31+
Importer
32+
^^^^^^^^^^
33+
34+
Importers are responsible for scraping vulnerability data from various data sources without creating
35+
a complete relational model between vulnerabilites, their fixes and store them in a structured
36+
fashion. These data are stored in the ``Advisory`` model and can be converted to an equivalent
37+
``AdvioryData`` for various use cases.
38+
See :ref:`importer-overview` for a brief overview on importers.
39+
40+
Importer Prerequisites
41+
^^^^^^^^^^^^^^^^^^^^^^^
42+
43+
Improvers consume data produced by importers, thus it is important to familiarize yourself with
44+
:ref:`Importer Prerequisites <tutorial_add_a_new_importer_prerequisites>`
45+
46+
Inference
47+
^^^^^^^^^^^
48+
49+
Inferences express the contract between the improvers and the improve runner framework.
50+
An inference is supposed to contain data points about a vulnerability without any uncertainties,
51+
which means, one inference will target one vulnerability with the specific relevant affected and
52+
fixed packages (in the form of `PackageURLs <https://github.com/package-url/packageurl-python>`_)
53+
There is no notion of version ranges here, all package versions must be explicitly specified.
54+
55+
Because this concrete relationship is hardly available anywhere on the upstream, we have to *infer*
56+
these values, thus the name.
57+
As infering something is not always perfect, an Inference also comes with a confidence score.
58+
59+
Improver
60+
^^^^^^^^^
61+
62+
All the Improvers must inherit from ``Improver`` superclass and implement the
63+
``interesting_advisories`` property and the ``get_inferences`` method.
64+
65+
Writing an improver
66+
---------------------
67+
68+
Locate the Source File
69+
^^^^^^^^^^^^^^^^^^^^^^^^
70+
71+
If the improver will be working on data imported by an specific importer, it will sit in the same
72+
file at :file:`vulnerabilites/importers/{importer-name.py}`.
73+
Otherwise, if it is a generic improver, create a new file
74+
:file:`vulnerabilites/improvers/{improver-name.py}`
75+
76+
Explore Package Managers (Optional)
77+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
78+
79+
If your Improver depends on the discrete versions of a package, the package managers' VersionAPI
80+
located at :file:`vulnerabilites/package_managers.py` could come in handy. You'll need to
81+
instantiate the relevant ``VersionAPI`` in the improver's constructor and use them later in the
82+
implemented methods. See an already implemented improver (NginxBasicImprover) for an example usage.
83+
84+
Implement the ``interesting_advisories`` Property
85+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
86+
87+
This property is supposed to return a QuerySet of ``Advisory`` on which the ``Improver`` is
88+
interested to work on.
89+
90+
For example, if the improver is interested to work on Advisories imported by ``ExampleImporter``,
91+
the property can be implemented as
92+
93+
.. code-block:: python
94+
95+
class ExampleBasicImprover(Improver):
96+
97+
@property
98+
def interesting_advisories(self) -> QuerySet:
99+
return Advisory.objects.filter(created_by=ExampleImporter.qualified_name)
100+
101+
Implement the ``get_inferences`` Method
102+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
103+
104+
The framework calls ``get_inferences`` method for every ``AdvisoryData`` that is obtained from
105+
the ``Advisory`` QuerySet returned by the ``interesting_advisories`` property.
106+
107+
It is expected to return an iterable of ``Inference`` objects for the given ``AdvisoryData``. To
108+
avoid storing a lot of Inferences in memory, it is nicer to yield from this method.
109+
110+
A very simple Improver that processes all Advisories to create the minimal relationships that can be
111+
obtained by existing data can be found at :file:`vulnerabilites/improvers/default.py` It is an
112+
example of a generic improver, for more sophisticated and targetted one, you can look at an already
113+
implemented improver (for eg, in :file:`vulnerabilites/importers/nginx.py`).
114+
115+
Improvers are not limited to improving discrete versions, they may also improve ``aliases``.
116+
One such example, improving the importer written in the :ref:`importer tutorial
117+
<tutorial_add_a_new_importer>`, is shown below.
118+
119+
.. code-block:: python
120+
121+
from datetime import datetime
122+
from datetime import timezone
123+
from typing import Iterable
124+
125+
import requests
126+
from django.db.models.query import QuerySet
127+
from packageurl import PackageURL
128+
from univers.version_range import NginxVersionRange
129+
from univers.versions import SemverVersion
130+
131+
from vulnerabilities.importer import AdvisoryData
132+
from vulnerabilities.improver import MAX_CONFIDENCE
133+
from vulnerabilities.improver import Improver
134+
from vulnerabilities.improver import Inference
135+
from vulnerabilities.models import Advisory
136+
from vulnerabilities.severity_systems import SCORING_SYSTEMS
137+
138+
139+
class ExampleImporter(Importer):
140+
...
141+
142+
143+
class ExampleAliasImprover(Improver):
144+
@property
145+
def interesting_advisories(self) -> QuerySet:
146+
return Advisory.objects.filter(created_by=ExampleImporter.qualified_name)
147+
148+
def get_inferences(self, advisory_data) -> Iterable[Inference]:
149+
for alias in advisory_data.aliases:
150+
new_aliases = fetch_additional_aliases(alias)
151+
aliases = new_aliases + [alias]
152+
yield Inference(aliases=aliases, confidence=MAX_CONFIDENCE)
153+
154+
155+
def fetch_additional_aliases(alias):
156+
alias_map = {
157+
"CVE-2021-23017": ["PYSEC-1337", "CERTIN-1337"],
158+
"CVE-2021-1234": ["ANONSEC-1337", "CERTDES-1337"],
159+
}
160+
return alias_map.get(alias)
161+
162+
163+
.. note::
164+
165+
| Use ``make valid`` to format your new code using black and isort automatically.
166+
| Use ``make check`` to check for formatting errrors.
167+
168+
Register the Improver
169+
^^^^^^^^^^^^^^^^^^^^^^
170+
171+
Finally, register your improver in the improver registry at
172+
:file:`vulnerabilites/improvers/__init__.py`
173+
174+
.. code-block:: python
175+
:emphasize-lines: 7
176+
177+
from vulnerabilities import importers
178+
from vulnerabilities.improvers import default
179+
180+
IMPROVERS_REGISTRY = [
181+
default.DefaultImprover,
182+
importers.nginx.NginxBasicImprover,
183+
importers.example.ExampleAliasImprover,
184+
]
185+
186+
IMPROVERS_REGISTRY = {x.qualified_name: x for x in IMPROVERS_REGISTRY}
187+
188+
Congratulations! You've written your first improver.
189+
190+
Run Your First Improver
191+
^^^^^^^^^^^^^^^^^^^^^^^^^^
192+
193+
If everything went fine, you'll see your improver in the list of available improvers
194+
195+
.. code-block:: console
196+
:emphasize-lines: 6
197+
198+
$ ./manage.py improve --list
199+
200+
Vulnerability data can be processed by these available improvers:
201+
vulnerabilities.improvers.default.DefaultImprover
202+
vulnerabilities.importers.nginx.NginxBasicImprover
203+
vulnerabilities.importers.example.ExampleAliasImprover
204+
205+
Before running the improver, make sure you have imported the data. An improver cannot improve if
206+
there is nothing imported.
207+
208+
.. code-block:: console
209+
210+
$ ./manage.py import vulnerabilities.importers.example.ExampleImporter
211+
212+
Importing data using vulnerabilities.importers.example.ExampleImporter
213+
Successfully imported data using vulnerabilities.importers.example.ExampleImporter
214+
215+
Now, run the improver
216+
217+
.. code-block:: console
218+
219+
$ ./manage.py improve vulnerabilities.importers.example.ExampleAliasImprover
220+
221+
Improving data using vulnerabilities.importers.example.ExampleAliasImprover
222+
Successfully improved data using vulnerabilities.importers.example.ExampleAliasImprover
223+
224+
See :ref:`command_line_interface` for command line usage instructions.
225+
226+
Enable Debug Logging (Optional)
227+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
228+
229+
For more visibility, turn on debug logs in :file:`vulnerablecode/settings.py`.
230+
231+
.. code-block:: python
232+
233+
DEBUG = True
234+
LOGGING = {
235+
'version': 1,
236+
'disable_existing_loggers': False,
237+
'handlers': {
238+
'console': {
239+
'class': 'logging.StreamHandler',
240+
},
241+
},
242+
'root': {
243+
'handlers': ['console'],
244+
'level': 'DEBUG',
245+
},
246+
}
247+
248+
Invoke the improve command now and you'll see (in a fresh database, after importing)
249+
250+
.. code-block:: console
251+
252+
$ ./manage.py improve vulnerabilities.importers.example.ExampleAliasImprover
253+
254+
Improving data using vulnerabilities.importers.example.ExampleAliasImprover
255+
Running improver: vulnerabilities.importers.example.ExampleAliasImprover
256+
Improving advisory id: 1
257+
New alias for <Vulnerability: VULCOID-23dd9060-3bc0-4454-bfbd-d16c08a966a6>: PYSEC-1337
258+
New alias for <Vulnerability: VULCOID-23dd9060-3bc0-4454-bfbd-d16c08a966a6>: CVE-2021-23017
259+
New alias for <Vulnerability: VULCOID-23dd9060-3bc0-4454-bfbd-d16c08a966a6>: CERTIN-1337
260+
Improving advisory id: 2
261+
New alias for <Vulnerability: VULCOID-fae4e06e-4815-45fe-ae95-8d2356ffb5b9>: CERTDES-1337
262+
New alias for <Vulnerability: VULCOID-fae4e06e-4815-45fe-ae95-8d2356ffb5b9>: ANONSEC-1337
263+
New alias for <Vulnerability: VULCOID-fae4e06e-4815-45fe-ae95-8d2356ffb5b9>: CVE-2021-1234
264+
Finished improving using vulnerabilities.importers.example.ExampleAliasImprover.
265+
Successfully improved data using vulnerabilities.importers.example.ExampleAliasImprover
266+
267+
.. note::
268+
269+
Even though CVE-2021-23017 and CVE-2021-1234 are not supplied by this improver yet it shows them
270+
because we left out running the ``DefaultImprover`` in the example. The ``DefaultImprover``
271+
inserts minimal data found via the importers in the database (Here, the above two CVEs). Run
272+
importer, DefaultImprover and then your improver in this sequence to avoid this anomaly.

0 commit comments

Comments
 (0)