Skip to content

Commit 6688fcc

Browse files
BreadGenieBread Genie
andauthored
feat: Centos package list parser (#1203)
Fixes: #1195 Co-authored-by: Bread Genie <[email protected]>
1 parent c19e2ed commit 6688fcc

File tree

7 files changed

+80
-31
lines changed

7 files changed

+80
-31
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,13 @@ You can also use `-m` or `--merge` along with `-f --format` and `-o --output-fil
8383

8484
> Note: For backward compatibility, we still support `csv2cve` command for producing CVEs from csv but we recommend using new `--input-file` command instead.
8585
86-
`-L` or `--package-list` option runs a CVE scan on installed packages listed in a package list. It takes a python package list (requirements.txt) or a package list of packages of an Ubuntu system as an input for the scan. This option is much faster and detects more CVEs than the default method of scanning binaries.
86+
`-L` or `--package-list` option runs a CVE scan on installed packages listed in a package list. It takes a python package list (requirements.txt) or a package list of packages of an Ubuntu or CentOS system as an input for the scan. This option is much faster and detects more CVEs than the default method of scanning binaries.
8787

88-
> You can get a package list of all installed packages in an Ubuntu system by running `dpkg-query -W -f '${binary:Package}\n' > pkg-list` in the terminal and provide it as an input for a full package scan.
88+
You can get a package list of all installed packages in
89+
- an Ubuntu system by running `dpkg-query -W -f '${binary:Package}\n' > pkg-list`
90+
- a CentOS system by running `rpm -qa --queryformat '%{NAME}\n' > pkg-list`
91+
92+
in the terminal and provide it as an input by running `cve-bin-tool -L pkg-list` for a full package scan.
8993

9094
You can use `--config` option to provide configuration file for the tool. You can still override options specified in config file with command line arguments. See our sample config files in the
9195
[test/config](https://github.com/intel/cve-bin-tool/blob/main/test/config/)

cve_bin_tool/package_list_parser/__init__.py

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
# Copyright (C) 2021 Intel Corporation
22
# SPDX-License-Identifier: GPL-3.0-or-later
33

4-
import csv
54
import json
65
import re
76
from collections import defaultdict
87
from logging import Logger
98
from os.path import dirname, getsize, isfile, join
109
from subprocess import PIPE, run
11-
from sys import platform
10+
11+
import distro
1212

1313
from cve_bin_tool.error_handler import (
1414
EmptyTxtError,
@@ -23,6 +23,7 @@
2323

2424
ROOT_PATH = join(dirname(__file__), "..")
2525
PYPI_CSV = join(ROOT_PATH, "package_list_parser", "pypi_list.csv")
26+
SUPPORTED_DISTROS = ["ubuntu", "centos"]
2627

2728

2829
class PackageListParser:
@@ -42,15 +43,17 @@ def parse_list(self):
4243
self.check_file()
4344

4445
if not input_file.endswith("requirements.txt"):
45-
if platform != "linux":
46-
LOGGER.warning("Package list support only available on Linux!")
46+
if distro.id() not in SUPPORTED_DISTROS:
47+
LOGGER.warning(
48+
f"Package list support only available on {','.join(SUPPORTED_DISTROS)}!"
49+
)
4750
return {}
4851

4952
system_packages = []
50-
linux_distribution = run(["lsb_release", "-si"], stdout=PIPE)
5153

52-
if "Ubuntu" in linux_distribution.stdout.decode("utf-8"):
53-
LOGGER.info("Scanning ubuntu package list.")
54+
LOGGER.info(f"Scanning {distro.id().capitalize()} package list.")
55+
56+
if "ubuntu" in distro.id():
5457
installed_packages = run(
5558
[
5659
"dpkg-query",
@@ -59,6 +62,17 @@ def parse_list(self):
5962
],
6063
stdout=PIPE,
6164
)
65+
elif "centos" in distro.id():
66+
installed_packages = run(
67+
[
68+
"rpm",
69+
"--query",
70+
"--all",
71+
"--queryformat",
72+
'{"name": "%{NAME}", "version": "%{VERSION}"\\}, ',
73+
],
74+
stdout=PIPE,
75+
)
6276
installed_packages = json.loads(
6377
f"[{installed_packages.stdout.decode('utf-8')[0:-2]}]"
6478
)
@@ -144,15 +158,37 @@ def check_file(self):
144158
raise EmptyTxtError(input_file)
145159

146160
if not input_file.endswith("requirements.txt"):
147-
# Simulate installation on Ubuntu using apt-get to check if the file is valid
148-
output = run(
149-
[f"xargs", "-a", input_file, "apt-get", "install", "-s"],
150-
stderr=PIPE,
151-
stdout=PIPE,
152-
)
161+
if "ubuntu" in distro.id():
162+
# Simulate installation on Ubuntu using apt-get to check if the file is valid
163+
output = run(
164+
[f"xargs", "-a", input_file, "apt-get", "install", "-s"],
165+
stderr=PIPE,
166+
stdout=PIPE,
167+
)
153168

154-
if output.returncode != 0:
169+
if output.returncode != 0:
170+
with ErrorHandler(mode=error_mode):
171+
raise InvalidListError(
172+
f"Invalid Package list\n{output.stderr.decode('utf-8')}"
173+
)
174+
elif "centos" in distro.id():
175+
output = run(
176+
[f"xargs", "-a", input_file, "rpm", "-qi"],
177+
stderr=PIPE,
178+
stdout=PIPE,
179+
)
180+
181+
not_installed_packages = re.findall(
182+
r"package (.+) is not installed", output.stdout.decode("utf-8")
183+
)
184+
if not_installed_packages:
185+
with ErrorHandler(mode=error_mode):
186+
raise InvalidListError(
187+
f"The packages {','.join(not_installed_packages)} seems to be not installed.\nIt is either an invalid package or not installed.\nUse `sudo yum install $(cat package-list)` to install all packages"
188+
)
189+
190+
else:
191+
# TODO: Replace below error handling with a proper pip install dry run
192+
# See: https://github.com/pypa/pip/issues/53
155193
with ErrorHandler(mode=error_mode):
156-
raise InvalidListError(
157-
f"Invalid Package list\n{output.stderr.decode('utf-8')}"
158-
)
194+
raise InvalidListError("Invalid Python Package list")

doc/MANUAL.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ The output will look like following:
337337

338338
This option runs a CVE scan on installed packages listed in a package list. It takes a python package list (requirements.txt) or a package list of packages of an Ubuntu system as an input for the scan. This option is much faster and detects more CVEs than the default method of scanning binaries.
339339

340-
An example of the package list for Ubuntu systems:
340+
An example of the package list for Linux systems:
341341

342342
```
343343
bash
@@ -347,7 +347,10 @@ sed
347347
python3
348348
```
349349

350-
> Note: The packages in the package list should be installed in the system before the scan. Run `pip install -r requirements.txt` to install python packages and `sudo apt-get install $(package-list)` for packages in an Ubuntu system.
350+
> Note: The packages in the package list should be installed in the system before the scan. Run
351+
- `pip install -r requirements.txt` to install python packages
352+
- `sudo apt-get install $(cat package-list)` for packages in an Ubuntu system
353+
- `sudo yum install $(cat package-list)`for packages in a CentOS system
351354

352355
> Note: Don't use invalid package names in the package list, as it will throw errors.
353356
@@ -356,7 +359,11 @@ You can test it using our [test package list](https://github.com/intel/cve-bin-t
356359
```console
357360
cve-bin-tool -L test/txt/test_ubuntu_list.txt
358361
```
359-
> You could get a package list of all installed packages in an Ubuntu system by running `dpkg-query -W -f '${binary:Package}\n' > pkg-list` in the terminal and provide it as an input for a full installed packages scan.
362+
You can get a package list of all installed packages in
363+
- an Ubuntu system by running `dpkg-query -W -f '${binary:Package}\n' > pkg-list`
364+
- a CentOS system by running `rpm -qa --queryformat '%{NAME}\n' > pkg-list`
365+
366+
in the terminal and provide it as an input by running `cve-bin-tool -L pkg-list` for a full package scan.
360367

361368
### -C CONFIG, --config CONFIG
362369

requirements.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ jsonschema_not_in_db,jsonschema
1616
python_not_in_db,py
1717
srossross_not_in_db,rpmfile
1818
indygreg_not_in_db,zstandard
19+
nir0s_not_in_db,distro

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ pytest-asyncio
1515
rpmfile>=1.0.6
1616
zstandard; python_version >= "3.4"
1717
reportlab
18+
distro

test/test_package_list_parser.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
from os.path import dirname, join
77
from sys import platform
88

9+
import distro
910
import pytest
1011

1112
from cve_bin_tool.error_handler import ErrorMode
1213
from cve_bin_tool.package_list_parser import (
14+
SUPPORTED_DISTROS,
1315
EmptyTxtError,
1416
InvalidListError,
1517
PackageListParser,
@@ -21,12 +23,6 @@
2123
class TestPackageListParser:
2224
TXT_PATH = join(dirname(__file__), "txt")
2325

24-
DISTRO = (
25-
subprocess.run(["lsb_release", "-sd"], stdout=subprocess.PIPE)
26-
if platform == "linux"
27-
else ""
28-
)
29-
3026
REQ_PARSED_TRIAGE_DATA = {
3127
ProductInfo(vendor="httplib2_project*", product="httplib2", version="0.18.1"): {
3228
"default": {"remarks": Remarks.Unexplored, "comments": "", "severity": ""},
@@ -91,19 +87,23 @@ def test_valid_requirements(self, filepath, parsed_data):
9187
# Update the packages back to latest
9288
subprocess.run(["pip", "install", "httplib2", "requests", "html5lib", "-U"])
9389

90+
@pytest.mark.skipif(
91+
distro.id() not in SUPPORTED_DISTROS,
92+
reason=f"Test for {','.join(SUPPORTED_DISTROS)} systems",
93+
)
9494
@pytest.mark.parametrize(
9595
"filepath, exception",
96-
[(join(TXT_PATH, "test_broken_ubuntu_list.txt"), InvalidListError)],
96+
[(join(TXT_PATH, "test_broken_linux_list.txt"), InvalidListError)],
9797
)
98-
def test_invalid_ubuntu_list(self, filepath, exception):
98+
def test_invalid_linux_list(self, filepath, exception):
9999
package_list = PackageListParser(filepath, error_mode=ErrorMode.FullTrace)
100100
with pytest.raises(exception):
101101
package_list.parse_list()
102102

103103
@pytest.mark.skipif(
104104
"ACTIONS" not in environ
105105
or not platform == "linux"
106-
or "Ubuntu 20.04" not in DISTRO.stdout.decode(),
106+
or ("ubuntu" not in distro.id() and "20.04" not in distro.version()),
107107
reason="Running locally requires root permission",
108108
)
109109
@pytest.mark.parametrize(
File renamed without changes.

0 commit comments

Comments
 (0)