Collect vulnerabilities from arch linux - #33
Conversation
a2645c3 to
56a5a6c
Compare
pombredanne
left a comment
There was a problem hiding this comment.
Thank you!
Could you use the imperative style in your commit message? See https://github.com/nexB/aboutcode/wiki/Writing-good-commit-messages
Something such as Collect vulnerabilities from arch linux #20 (with trailing issue number)
Also what about adding a few unit tests (that do not depend on network being available and run offline)
Thank you again!
|
Thanks for the review. I'll add the suggested changes and update the PR soon. |
56a5a6c to
6e22217
Compare
pombredanne
left a comment
There was a problem hiding this comment.
Thanks!
See my comments inline.
| """ | ||
| for data in extract_data: | ||
| vulnerability = Vulnerability.objects.create( | ||
| summary=data.get('description', ''), |
There was a problem hiding this comment.
You use get() and therefore can create things with no values... it does not make sense IMHO. Also using None is fine. No need to put a default of ''
| ) | ||
| VulnerabilityReference.objects.create( | ||
| vulnerability=vulnerability, | ||
| reference_id=data.get('vulnerability_id', ''), |
There was a problem hiding this comment.
I am not sure we ever want to create a reference without a reference id/url/source/etc ... same issues as above wrt get()
| 'affected_version': item["affected"], | ||
| 'fixed_version': item["fixed"] | ||
| }) | ||
| return package_vulnerabilities |
There was a problem hiding this comment.
Why not yield instead? This is not stored anywhere is it?
There was a problem hiding this comment.
@pombredanne Well I am returning it just to maintain the consistency of the methods with other implmented scrapers like ubuntu and debian.
There was a problem hiding this comment.
@lohani2280 ok, there is no need for consistency... do what is right instead. And we can update the other data collectors later. But this is OK as is just now.
| package_vulnerabilities = [] | ||
|
|
||
| for item in arch_data: | ||
| if not item["name"] or not item["packages"][0] or not item["fixed"]: |
There was a problem hiding this comment.
what is item["packages"][0] ? it might be best to use a variable name for this
There was a problem hiding this comment.
Why do you skip vulnerabilities are do not have a fixed attribute
| if not item["name"] or not item["packages"][0] or not item["fixed"]: | ||
| continue | ||
|
|
||
| package_vulnerabilities.append({ |
There was a problem hiding this comment.
It would be best to use a proper object rather than a dict for this IMHO. Something like an attr thing (See scancode for usage)
| @@ -0,0 +1,56 @@ | |||
| [ | |||
| { | |||
| "name": "AVG-837", | |||
There was a problem hiding this comment.
If you were to explain this record in plain english, what would say? How many vulnerabilities references do we have in there? I think you are missing out all the CVE references and the fact these are arch linux packages
6e22217 to
5d6c681
Compare
|
@pombredanne I have updated the PR and have tried to address your last reviews. However, I have not updated the unittest. I just want you to review it once and if the changes are as per your expectations then I'll update the PR with unittest as well. |
pombredanne
left a comment
There was a problem hiding this comment.
Thank you! see my review comments.
| 'affected_version': item["affected"], | ||
| 'fixed_version': item["fixed"] | ||
| }) | ||
| return package_vulnerabilities |
There was a problem hiding this comment.
@lohani2280 ok, there is no need for consistency... do what is right instead. And we can update the other data collectors later. But this is OK as is just now.
|
|
||
| for item in arch_data: | ||
| vulnerabilities = item["issues"] | ||
| package_name = item["packages"][0] |
There was a problem hiding this comment.
You should check if there is not exactly one element in the packages array and raise an Exception if so... this way this becomes more robust if this changes in the future and we will be alerted to the change by the failure
Also can you use single quotes for texts unless this is :
- a docstring
- a string with single quotes
This is for the whole PR.
There was a problem hiding this comment.
@pombredanne I have followed your suggestions and raised an Exception if there is not exactly one element in the packages array. I found that there are already some entries having packages array with more than one element in it. For example:
{
"name": "AVG-458",
"packages": [
"libmupdf",
"mupdf",
"mupdf-tools",
"mupdf-gl"
],
"status": "Fixed",
"severity": "High",
"type": "arbitrary code execution",
"affected": "1.11-4",
"fixed": "1.11-5",
"ticket": null,
"issues": [
"CVE-2017-15587",
"CVE-2017-14687",
"CVE-2017-14686",
"CVE-2017-14685"
],
"advisories": [
"ASA-201711-2",
"ASA-201711-4",
"ASA-201711-1",
"ASA-201711-3"
]
}
So, should I raise an exception and skip these data entry from dumping into our database?. Please suggest your views on this.
There was a problem hiding this comment.
You should not raise an exception then... but instead handle all the data
| continue | ||
|
|
||
| for vulnerability in vulnerabilities: | ||
| if not affected_version: |
There was a problem hiding this comment.
Why do you skip things here? this is not clear to me. Especially affected_version is NOT part of the vulnerabilities
There was a problem hiding this comment.
@pombredanne This was unintended. However, I am skipping the fixed_version as I found many instances in the archlinux security tracker data where fixed_version is null and affected_vesion is not null. For example:
{
"name": "AVG-902",
"packages": [
"tcpreplay"
],
"status": "Unknown",
"severity": "High",
"type": "multiple issues",
"affected": "4.3.1-1",
"fixed": null,
"ticket": null,
"issues": [
"CVE-2019-8381",
"CVE-2019-8377",
"CVE-2019-8376"
],
"advisories": []
}
We would be collecting CVEs corresponding to affected_version so I thought of skipping the same CVEs corresponding to those null fixed_version.
5d6c681 to
6b4ddf1
Compare
pombredanne
left a comment
There was a problem hiding this comment.
So I am not sure I get your processing ....
Let's take a simple example with this snippet for a single vulnerability in the JSON feed:
{
"name": "AVG-708",
"packages": [
"wireshark-common",
"wireshark-gtk",
"wireshark-cli",
"wireshark-qt"
],
"status": "Fixed",
"severity": "Critical",
"type": "multiple issues",
"affected": "2.6.0-1",
"fixed": "2.6.1-1",
"ticket": null,
"issues": [
"CVE-2018-11362",
"CVE-2018-11361",
"CVE-2018-11360",
"CVE-2018-11359",
"CVE-2018-11358",
"CVE-2018-11357",
"CVE-2018-11356",
"CVE-2018-11355",
"CVE-2018-11354"
],
"advisories": [
"ASA-201805-25",
"ASA-201805-24",
"ASA-201805-22",
"ASA-201805-23"
]
}
Based on this can you tell me what should be the final state of database records that should exist once this has been processed?
This will help me understand your approach.
| def extract_vulnerabilities(arch_data): | ||
| """ | ||
| Return a sequence of mappings for each existing combination of | ||
| package and vulnerability from a mapping of arch linux vulnerabilities |
There was a problem hiding this comment.
arch_data is not a mapping? seems to be a sequence instead, right?
There was a problem hiding this comment.
Sorry, I forgot to update it. Will update in next sync.
|
|
||
|
|
||
| ARCHLINUX_TRACKER_URL = 'https://security.archlinux.org/json' | ||
|
|
There was a problem hiding this comment.
What about the advisories at https://security.archlinux.org/advisory/json ?
There was a problem hiding this comment.
@pombredanne I didn't see this link earlier. So, I was not including the advisories in my final scraped data. However, I plan to include it now.
There was a problem hiding this comment.
OK, that can be in a second step too. I posted #20 (comment) as a reminder
|
|
||
| for vulnerability in vulnerabilities: | ||
| if not fixed_version: | ||
| fixed_version = '' |
There was a problem hiding this comment.
Why would you care about an empty string vs. a None?
There was a problem hiding this comment.
Actually fixed_version attribute contains null value which on dumping in database throws error so I thought to convert the null into an empty string before dumping into the database.
| @@ -0,0 +1,56 @@ | |||
| [ | |||
| { | |||
| "name": "AVG-837", | |||
Summarizing all the loops(in the Step-1 Initially, we took a package Step-2 Then we appended all the CVEs one by one corresponding to Step-3 Then we appended all the CVEs one by one corresponding to Step-4 Repeated step-2 and step-3 with all the package in the Finally this entire list of dict is yield back to the caller. I am attatching the entire list which will be yielded for the above sample JSON data in this file for reference. |
|
@lohani2280 please do not link to Gdocs in tickets or PR. I did not open that link. Attach it to the ticket or paste it in the ticket. |
|
Note: I care only to see the end results at this stage... not the intermediate steps to get there. I think there are issues with your approach and rather than trying to unwind it, it is easier for me to comment on the final results for a single record. |
|
@pombredanne Following is the final set of created records for the snippet you provided as an example in above comment: |
|
OK, these are 72 records...but I am interested in what will end up in the models. There are four packages: and each comes with two versions: So that eight Package records each with one Package reference that point to actual ArchLinux packages Then we have nine CVEs and four advisories Somehow I would expect one single Vulnerability record for all these. And many (14) vulnerability references each with a proper id and URL. Then for each of the affected and fixed, there should be relationships established between packages and vulnerabilities. This is what you need to document and specify so I can review this. Once you have this, this can become your expected test for an end state and you can work to make your code pass this expected test. |
6b4ddf1 to
65ca798
Compare
|
@pombredanne Also, this is what will end up in the model for the sample record taken fom arch linux issue tracker:
|
5190572 to
4748733
Compare
pombredanne
left a comment
There was a problem hiding this comment.
Thanks! the code is clean (see my minor comments), and your explanation makes sense. But you removed all the tests? I would like to see the results of a test that uses the wireshark example and asserts the end results in the DB.
|
|
||
| def scrape_vulnerabilities(): | ||
| """ | ||
| Scrape arch linux' security tracker. |
There was a problem hiding this comment.
You should tell what this function returns instead.
Return ...
|
|
||
| def extract_vulnerabilities(arch_data): | ||
| """ | ||
| Return a series of mappings for each record of arch linux security tracker |
There was a problem hiding this comment.
series is not a very common name for an iterable. Either use Yield mappings ... or Return an iterable or ...
| package_vulnerabilities = [] | ||
| cves = item['issues'] | ||
| advisories = set(item['advisories']) | ||
| vulnerabilities = cves + list(advisories) |
There was a problem hiding this comment.
In mixing it all in a list, you are losing their origin and will have to parser these back afterwards to craft a proper URL for each.
Also what is the item['name'] below?
The end results is to get these as references, right? then you should compute the fields that are useful and needed for this now.
There was a problem hiding this comment.
@pombredanne No. For crafting a proper URL for each I don't need their origin as the URL doesn't depend on origin. Its available directly as https://security.archlinux.org/vulnerability_id
Example :
https://security.archlinux.org/CVE-2018-20592
https://security.archlinux.org/AVG-922
https://security.archlinux.org/ASA-201811-20
There was a problem hiding this comment.
For https://security.archlinux.org/CVE-2018-20592 would not there be also a URL such as https://nvd.nist.gov/vuln/detail/CVE-2018-20592 ? (which means that these are likely two references)
There was a problem hiding this comment.
Yes. Infact there are even more than 2 references available here https://security.archlinux.org/CVE-2018-20592 as you mentioned #33 (comment).
| 'description': item['type'], | ||
| 'status': item['status'], | ||
| 'severity': item['severity'], | ||
| 'version': [affected_version,fixed_version] |
There was a problem hiding this comment.
why use a positional list rather than two named items?
| for data in extract_data: | ||
| packages_name = data['packages_name'] | ||
| vulnerabilities = data['vulnerability_id'] | ||
| affected_version = data['version'][0] |
There was a problem hiding this comment.
Do not use this style of positional items. Use two items instead
| affected_version = item['affected'] | ||
| fixed_version = item['fixed'] | ||
| if not fixed_version: | ||
| fixed_version = 'None' |
There was a problem hiding this comment.
Never use a 'None' string. Use a plain None
There was a problem hiding this comment.
@pombredanne I tried but if I use plain None, I get following error while dumping into db:
sqlite3.IntegrityError: NOT NULL constraint failed: vulnerabilities_package.version
There was a problem hiding this comment.
you will need to show some actual error message so I can understand the issue. But imho this issue will likely vanish after the other refactorings
| if not fixed_version: | ||
| fixed_version = 'None' | ||
|
|
||
| package_vulnerabilities = { |
There was a problem hiding this comment.
What is this data structure bringing as value on top of the original data structure?
Either you craft something that adapts and transforms the data to the model for easy import and precomputes fields or may be it would be simpler to use the Arch JSON data directly to create models?
I reckon that you are using the same approach as was used before, but that approach may not be correct. This is ok to question and change
There was a problem hiding this comment.
@pombredanne I agree that package_vulnerability data structure doesn't bring any direct value on top of the original data structure. But IMO its not good on part of users to use the Arch JSON data directly to create models. I mean the users might want to view/observe the data before actually dumping it into the db. I'd rather suggest that instead of having 2 methods namely extract_vulnerabilities and scrape_vulnerabilities we could have only 1 of the method like extract_vulnerabilities. Also, we should leave archlinux_dump as it is. Please suggest your views on this.
There was a problem hiding this comment.
I mean the users might want to view/observe the data before actually dumping it into the db.
that's not the goal: we want to import fully automatically... the data will never ever be reviewed before it is imported
There was a problem hiding this comment.
so the only reason why you want to a tow step approach is for when the data is not structured in the first place. Here this is a JSON dump so there is not much of a transformation needed in a structure format. The key is converting the data to proper DB records in the simplest way. Does this make sense to you?
| VulnerabilityReference.objects.create( | ||
| vulnerability=vulnerability, | ||
| reference_id=vulnerability_id, | ||
| source='archlinux', |
There was a problem hiding this comment.
Not all is from archlinux... CVEs are not from there.
And we should have a URL for each https://github.com/nexB/vulnerablecode/blob/55a633db3c09d8b78d21bffea25a510e1444acdf/vulnerabilities/models.py#L48
| PackageReference.objects.create( | ||
| package=package_fixed, | ||
| repository='https://security.archlinux.org/package/'+ | ||
| '%s'% package_name |
There was a problem hiding this comment.
BTW, why
'https://security.archlinux.org/package/'+
'%s'% package_name
and not
'https://security.archlinux.org/package/%s' % package_name
both here and above?
|
Also I wonder how to get all the vulnerability references that are listed on this https://security.archlinux.org/CVE-2018-20592 ? |
|
Also please check the CI tests failures: https://travis-ci.org/nexB/vulnerablecode/builds/507496621?utm_source=github_status&utm_medium=notification |
0d662c3 to
8daf1df
Compare
I can crawl and extract all those vulnerability references from https://security.archlinux.org/CVE-2018-20592 using python. But then we need to modify our models to store multiple vulnerability references. |
a67e87e to
5acdd10
Compare
5acdd10 to
5989e5c
Compare
| # VulnerableCode is a free software code scanning tool from nexB Inc. and others. | ||
| # Visit https://github.com/nexB/vulnerablecode/ for support and download. | ||
|
|
||
|
|
There was a problem hiding this comment.
No need of this extra line.
| "ASA-201805-23" | ||
| ] | ||
| } | ||
| ] No newline at end of file |
There was a problem hiding this comment.
Keep an extra blank line in each file.
| from vulnerabilities.scraper import debian | ||
| from vulnerabilities.scraper import ubuntu | ||
|
|
||
|
|
There was a problem hiding this comment.
Two blank lines after import statements.
81ad043 to
8b49dfc
Compare
Signed-off-by: lohani2280 <lohani.ayush01@gmail.com>
Signed-off-by: lohani2280 <lohani.ayush01@gmail.com>
8b49dfc to
43a35ce
Compare
pombredanne
left a comment
There was a problem hiding this comment.
LGTM! there are a few things that could still be refined, but that quite mergeable now! thank you ++ for working through this
Signed-off-by: Jono Yang <jyang@nexb.com>
* Create copyright statement from holder information Signed-off-by: Jono Yang <jyang@nexb.com>
* This is used for the case where we are starting off a project and have not yet generated requirements files Signed-off-by: Jono Yang <jyang@nexb.com>
Signed-off-by: Jono Yang <jyang@nexb.com>
* Add --init option to configure.bat
* Update help text in configure and configure.bat
Signed-off-by: Jono Yang <jyang@nexb.com>
Signed-off-by: Jono Yang <jyang@nexb.com>
Signed-off-by: Jono Yang <jyang@nexb.com>
* Update README.rst Signed-off-by: Jono Yang <jyang@nexb.com>
* Update README.rst with instructions for post-initialization usage Signed-off-by: Jono Yang <jyang@nexb.com>
* Replace references to scancode-toolkit repo with links to the skeleton repo
* Remove --python option from configure.bat
Signed-off-by: Jono Yang <jyang@nexb.com>
Refers #20