Skip to content
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

Centos package list parser #1203

Merged
merged 9 commits into from
Jun 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,13 @@ You can also use `-m` or `--merge` along with `-f --format` and `-o --output-fil

> Note: For backward compatibility, we still support `csv2cve` command for producing CVEs from csv but we recommend using new `--input-file` command instead.

`-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.
`-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.

> 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.
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`
- a CentOS system by running `rpm -qa --queryformat '%{NAME}\n' > pkg-list`

in the terminal and provide it as an input by running `cve-bin-tool -L pkg-list` for a full package scan.

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
[test/config](https://github.com/intel/cve-bin-tool/blob/main/test/config/)
Expand Down
70 changes: 53 additions & 17 deletions cve_bin_tool/package_list_parser/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Copyright (C) 2021 Intel Corporation
# SPDX-License-Identifier: GPL-3.0-or-later

import csv
import json
import re
from collections import defaultdict
from logging import Logger
from os.path import dirname, getsize, isfile, join
from subprocess import PIPE, run
from sys import platform

import distro

from cve_bin_tool.error_handler import (
EmptyTxtError,
Expand All @@ -23,6 +23,7 @@

ROOT_PATH = join(dirname(__file__), "..")
PYPI_CSV = join(ROOT_PATH, "package_list_parser", "pypi_list.csv")
SUPPORTED_DISTROS = ["ubuntu", "centos"]


class PackageListParser:
Expand All @@ -42,15 +43,17 @@ def parse_list(self):
self.check_file()

if not input_file.endswith("requirements.txt"):
if platform != "linux":
LOGGER.warning("Package list support only available on Linux!")
if distro.id() not in SUPPORTED_DISTROS:
LOGGER.warning(
f"Package list support only available on {','.join(SUPPORTED_DISTROS)}!"
)
return {}

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

if "Ubuntu" in linux_distribution.stdout.decode("utf-8"):
LOGGER.info("Scanning ubuntu package list.")
LOGGER.info(f"Scanning {distro.id().capitalize()} package list.")

if "ubuntu" in distro.id():
installed_packages = run(
[
"dpkg-query",
Expand All @@ -59,6 +62,17 @@ def parse_list(self):
],
stdout=PIPE,
)
elif "centos" in distro.id():
installed_packages = run(
[
"rpm",
"--query",
"--all",
"--queryformat",
'{"name": "%{NAME}", "version": "%{VERSION}"\\}, ',
],
stdout=PIPE,
)
installed_packages = json.loads(
f"[{installed_packages.stdout.decode('utf-8')[0:-2]}]"
)
Expand Down Expand Up @@ -144,15 +158,37 @@ def check_file(self):
raise EmptyTxtError(input_file)

if not input_file.endswith("requirements.txt"):
# Simulate installation on Ubuntu using apt-get to check if the file is valid
output = run(
[f"xargs", "-a", input_file, "apt-get", "install", "-s"],
stderr=PIPE,
stdout=PIPE,
)
if "ubuntu" in distro.id():
# Simulate installation on Ubuntu using apt-get to check if the file is valid
output = run(
[f"xargs", "-a", input_file, "apt-get", "install", "-s"],
stderr=PIPE,
stdout=PIPE,
)

if output.returncode != 0:
if output.returncode != 0:
with ErrorHandler(mode=error_mode):
raise InvalidListError(
f"Invalid Package list\n{output.stderr.decode('utf-8')}"
)
elif "centos" in distro.id():
output = run(
[f"xargs", "-a", input_file, "rpm", "-qi"],
stderr=PIPE,
stdout=PIPE,
)

not_installed_packages = re.findall(
r"package (.+) is not installed", output.stdout.decode("utf-8")
)
if not_installed_packages:
with ErrorHandler(mode=error_mode):
raise InvalidListError(
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"
)

else:
# TODO: Replace below error handling with a proper pip install dry run
# See: https://github.com/pypa/pip/issues/53
with ErrorHandler(mode=error_mode):
raise InvalidListError(
f"Invalid Package list\n{output.stderr.decode('utf-8')}"
)
raise InvalidListError("Invalid Python Package list")
13 changes: 10 additions & 3 deletions doc/MANUAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ The output will look like following:

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.

An example of the package list for Ubuntu systems:
An example of the package list for Linux systems:

```
bash
Expand All @@ -347,7 +347,10 @@ sed
python3
```

> 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.
> 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
- `sudo apt-get install $(cat package-list)` for packages in an Ubuntu system
- `sudo yum install $(cat package-list)`for packages in a CentOS system

> Note: Don't use invalid package names in the package list, as it will throw errors.

Expand All @@ -356,7 +359,11 @@ You can test it using our [test package list](https://github.com/intel/cve-bin-t
```console
cve-bin-tool -L test/txt/test_ubuntu_list.txt
```
> 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.
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`
- a CentOS system by running `rpm -qa --queryformat '%{NAME}\n' > pkg-list`

in the terminal and provide it as an input by running `cve-bin-tool -L pkg-list` for a full package scan.

### -C CONFIG, --config CONFIG

Expand Down
1 change: 1 addition & 0 deletions requirements.csv
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ jsonschema_not_in_db,jsonschema
python_not_in_db,py
srossross_not_in_db,rpmfile
indygreg_not_in_db,zstandard
nir0s_not_in_db,distro
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ pytest-asyncio
rpmfile>=1.0.6
zstandard; python_version >= "3.4"
reportlab
distro
18 changes: 9 additions & 9 deletions test/test_package_list_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
from os.path import dirname, join
from sys import platform

import distro
import pytest

from cve_bin_tool.error_handler import ErrorMode
from cve_bin_tool.package_list_parser import (
SUPPORTED_DISTROS,
EmptyTxtError,
InvalidListError,
PackageListParser,
Expand All @@ -21,12 +23,6 @@
class TestPackageListParser:
TXT_PATH = join(dirname(__file__), "txt")

DISTRO = (
subprocess.run(["lsb_release", "-sd"], stdout=subprocess.PIPE)
if platform == "linux"
else ""
)

REQ_PARSED_TRIAGE_DATA = {
ProductInfo(vendor="httplib2_project*", product="httplib2", version="0.18.1"): {
"default": {"remarks": Remarks.Unexplored, "comments": "", "severity": ""},
Expand Down Expand Up @@ -91,19 +87,23 @@ def test_valid_requirements(self, filepath, parsed_data):
# Update the packages back to latest
subprocess.run(["pip", "install", "httplib2", "requests", "html5lib", "-U"])

@pytest.mark.skipif(
distro.id() not in SUPPORTED_DISTROS,
reason=f"Test for {','.join(SUPPORTED_DISTROS)} systems",
)
@pytest.mark.parametrize(
"filepath, exception",
[(join(TXT_PATH, "test_broken_ubuntu_list.txt"), InvalidListError)],
[(join(TXT_PATH, "test_broken_linux_list.txt"), InvalidListError)],
)
def test_invalid_ubuntu_list(self, filepath, exception):
def test_invalid_linux_list(self, filepath, exception):
package_list = PackageListParser(filepath, error_mode=ErrorMode.FullTrace)
with pytest.raises(exception):
package_list.parse_list()

@pytest.mark.skipif(
"ACTIONS" not in environ
or not platform == "linux"
or "Ubuntu 20.04" not in DISTRO.stdout.decode(),
or ("ubuntu" not in distro.id() and "20.04" not in distro.version()),
reason="Running locally requires root permission",
)
@pytest.mark.parametrize(
Expand Down