ossaudit-ng 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ossaudit_ng-1.0.0/.coveragerc +7 -0
- ossaudit_ng-1.0.0/.isort.cfg +3 -0
- ossaudit_ng-1.0.0/.pylintrc +65 -0
- ossaudit_ng-1.0.0/.style.yapf +8 -0
- ossaudit_ng-1.0.0/LICENSE +26 -0
- ossaudit_ng-1.0.0/MANIFEST.in +4 -0
- ossaudit_ng-1.0.0/Makefile +52 -0
- ossaudit_ng-1.0.0/PKG-INFO +50 -0
- ossaudit_ng-1.0.0/README.md +118 -0
- ossaudit_ng-1.0.0/mypy.ini +21 -0
- ossaudit_ng-1.0.0/ossaudit/__init__.py +68 -0
- ossaudit_ng-1.0.0/ossaudit/__main__.py +14 -0
- ossaudit_ng-1.0.0/ossaudit/audit.py +142 -0
- ossaudit_ng-1.0.0/ossaudit/cache.py +58 -0
- ossaudit_ng-1.0.0/ossaudit/cli.py +176 -0
- ossaudit_ng-1.0.0/ossaudit/const.py +17 -0
- ossaudit_ng-1.0.0/ossaudit/option.py +74 -0
- ossaudit_ng-1.0.0/ossaudit/packages.py +157 -0
- ossaudit_ng-1.0.0/ossaudit_ng.egg-info/PKG-INFO +50 -0
- ossaudit_ng-1.0.0/ossaudit_ng.egg-info/SOURCES.txt +52 -0
- ossaudit_ng-1.0.0/ossaudit_ng.egg-info/dependency_links.txt +1 -0
- ossaudit_ng-1.0.0/ossaudit_ng.egg-info/entry_points.txt +2 -0
- ossaudit_ng-1.0.0/ossaudit_ng.egg-info/requires.txt +5 -0
- ossaudit_ng-1.0.0/ossaudit_ng.egg-info/top_level.txt +1 -0
- ossaudit_ng-1.0.0/requirements/README.md +20 -0
- ossaudit_ng-1.0.0/requirements/requirements-dev-1.txt +1 -0
- ossaudit_ng-1.0.0/requirements/requirements-dev-2.txt +1 -0
- ossaudit_ng-1.0.0/requirements/requirements-dev-3.txt +15 -0
- ossaudit_ng-1.0.0/requirements/requirements.txt +10 -0
- ossaudit_ng-1.0.0/setup.cfg +7 -0
- ossaudit_ng-1.0.0/setup.py +74 -0
- ossaudit_ng-1.0.0/tests/__init__.py +3 -0
- ossaudit_ng-1.0.0/tests/__pycache__/__init__.cpython-313.pyc +0 -0
- ossaudit_ng-1.0.0/tests/__pycache__/helpers.cpython-313.pyc +0 -0
- ossaudit_ng-1.0.0/tests/__pycache__/test_audit.cpython-313-pytest-9.0.3.pyc +0 -0
- ossaudit_ng-1.0.0/tests/__pycache__/test_audit.cpython-313.pyc +0 -0
- ossaudit_ng-1.0.0/tests/__pycache__/test_cache.cpython-313-pytest-9.0.3.pyc +0 -0
- ossaudit_ng-1.0.0/tests/__pycache__/test_cache.cpython-313.pyc +0 -0
- ossaudit_ng-1.0.0/tests/__pycache__/test_cli.cpython-313-pytest-9.0.3.pyc +0 -0
- ossaudit_ng-1.0.0/tests/__pycache__/test_cli.cpython-313.pyc +0 -0
- ossaudit_ng-1.0.0/tests/__pycache__/test_option.cpython-313-pytest-9.0.3.pyc +0 -0
- ossaudit_ng-1.0.0/tests/__pycache__/test_option.cpython-313.pyc +0 -0
- ossaudit_ng-1.0.0/tests/__pycache__/test_packages.cpython-313-pytest-9.0.3.pyc +0 -0
- ossaudit_ng-1.0.0/tests/__pycache__/test_packages.cpython-313.pyc +0 -0
- ossaudit_ng-1.0.0/tests/__pycache__/test_scan.cpython-313-pytest-9.0.3.pyc +0 -0
- ossaudit_ng-1.0.0/tests/data/vulns01.json +49 -0
- ossaudit_ng-1.0.0/tests/helpers.py +30 -0
- ossaudit_ng-1.0.0/tests/test_audit.py +249 -0
- ossaudit_ng-1.0.0/tests/test_cache.py +170 -0
- ossaudit_ng-1.0.0/tests/test_cli.py +201 -0
- ossaudit_ng-1.0.0/tests/test_option.py +186 -0
- ossaudit_ng-1.0.0/tests/test_packages.py +216 -0
- ossaudit_ng-1.0.0/tests/test_scan.py +89 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
[MASTER]
|
|
2
|
+
persistent=no
|
|
3
|
+
|
|
4
|
+
ignore=.git
|
|
5
|
+
|
|
6
|
+
load-plugins=
|
|
7
|
+
pylint.extensions.bad_builtin,
|
|
8
|
+
pylint.extensions.check_elif,
|
|
9
|
+
pylint.extensions.docparams,
|
|
10
|
+
pylint.extensions.docstyle,
|
|
11
|
+
pylint.extensions.mccabe,
|
|
12
|
+
pylint.extensions.overlapping_exceptions,
|
|
13
|
+
pylint.extensions.redefined_variable_type,
|
|
14
|
+
pylint_quotes,
|
|
15
|
+
|
|
16
|
+
accept-no-param-doc=no
|
|
17
|
+
accept-no-raise-doc=no
|
|
18
|
+
accept-no-yields-doc=no
|
|
19
|
+
|
|
20
|
+
extension-pkg-whitelist=
|
|
21
|
+
unsafe-load-any-extension=no
|
|
22
|
+
|
|
23
|
+
[MESSAGES CONTROL]
|
|
24
|
+
confidence=
|
|
25
|
+
enable=all
|
|
26
|
+
disable=
|
|
27
|
+
docstring-first-line-empty,
|
|
28
|
+
invalid-name,
|
|
29
|
+
locally-disabled,
|
|
30
|
+
locally-enabled,
|
|
31
|
+
missing-docstring,
|
|
32
|
+
missing-param-doc,
|
|
33
|
+
missing-raises-doc,
|
|
34
|
+
missing-return-type-doc,
|
|
35
|
+
missing-type-doc,
|
|
36
|
+
missing-yield-doc,
|
|
37
|
+
missing-yield-type-doc,
|
|
38
|
+
suppressed-message,
|
|
39
|
+
too-few-public-methods,
|
|
40
|
+
too-many-ancestors,
|
|
41
|
+
too-many-arguments,
|
|
42
|
+
use-symbolic-message-instead,
|
|
43
|
+
|
|
44
|
+
[VARIABLES]
|
|
45
|
+
init-import=yes
|
|
46
|
+
|
|
47
|
+
[BASIC]
|
|
48
|
+
good-names=f,i,j,k,m,db,ex,_
|
|
49
|
+
argument-naming-style=snake_case
|
|
50
|
+
attr-naming-style=snake_case
|
|
51
|
+
class-naming-style=PascalCase
|
|
52
|
+
const-naming-style=UPPER_CASE
|
|
53
|
+
function-naming-style=snake_case
|
|
54
|
+
method-naming-style=snake_case
|
|
55
|
+
module-naming-style=snake_case
|
|
56
|
+
variable-naming-style=snake_case
|
|
57
|
+
|
|
58
|
+
[FORMAT]
|
|
59
|
+
expected-line-ending-format=LF
|
|
60
|
+
max-line-length=79
|
|
61
|
+
|
|
62
|
+
[PYLINT-QUOTES]
|
|
63
|
+
string-quote=double
|
|
64
|
+
triple-quote=double
|
|
65
|
+
docstring-quote=double
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Copyright (c) 2019, Hans Jerry Illikainen <hji@dyntopia.com>
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
|
5
|
+
modification, are permitted provided that the following conditions
|
|
6
|
+
are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright
|
|
9
|
+
notice, this list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
12
|
+
notice, this list of conditions and the following disclaimer in
|
|
13
|
+
the documentation and/or other materials provided with the
|
|
14
|
+
distribution.
|
|
15
|
+
|
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
17
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
18
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
19
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
20
|
+
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
21
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
22
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
23
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
24
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
25
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
26
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
.PHONY: all dist install install-dev test qa
|
|
2
|
+
|
|
3
|
+
all:
|
|
4
|
+
|
|
5
|
+
install:
|
|
6
|
+
pip3 install -r requirements/requirements.txt --no-binary :all:
|
|
7
|
+
./setup.py install
|
|
8
|
+
|
|
9
|
+
install-dev:
|
|
10
|
+
pip3 install -r requirements/requirements.txt --no-binary :all:
|
|
11
|
+
pip3 install -r requirements/requirements-dev-1.txt --no-binary :all:
|
|
12
|
+
pip3 install -r requirements/requirements-dev-2.txt --no-binary :all:
|
|
13
|
+
pip3 install -r requirements/requirements-dev-3.txt --no-binary :all:
|
|
14
|
+
./setup.py develop
|
|
15
|
+
|
|
16
|
+
test:
|
|
17
|
+
python3 -m unittest -q
|
|
18
|
+
|
|
19
|
+
qa:
|
|
20
|
+
coverage run -m unittest -q
|
|
21
|
+
coverage report -m
|
|
22
|
+
isort --check-only --diff --recursive .
|
|
23
|
+
mypy .
|
|
24
|
+
pycodestyle .
|
|
25
|
+
pyflakes .
|
|
26
|
+
pylint --output-format parseable setup.py ossaudit tests
|
|
27
|
+
yapf --diff --recursive .
|
|
28
|
+
ossaudit \
|
|
29
|
+
--file requirements/requirements.txt \
|
|
30
|
+
--file requirements/requirements-dev-1.txt \
|
|
31
|
+
--file requirements/requirements-dev-2.txt \
|
|
32
|
+
--file requirements/requirements-dev-3.txt
|
|
33
|
+
|
|
34
|
+
dist:
|
|
35
|
+
rm -rf dist tmp
|
|
36
|
+
|
|
37
|
+
tag="$$(git tag --sort=-creatordate |head -n 1)" && \
|
|
38
|
+
git verify-tag "$$tag" && \
|
|
39
|
+
git clone --shared --branch "$$tag" . tmp
|
|
40
|
+
|
|
41
|
+
cd tmp && \
|
|
42
|
+
./setup.py sdist && \
|
|
43
|
+
gpg --detach-sign --armor "$$(ls dist/*.tar.gz)"
|
|
44
|
+
|
|
45
|
+
mv tmp/dist .
|
|
46
|
+
rm -rf tmp
|
|
47
|
+
gpg --verify dist/*.asc
|
|
48
|
+
|
|
49
|
+
upload:
|
|
50
|
+
git push --tags
|
|
51
|
+
tag="$$(git tag --sort=-creatordate |head -n 1 |cut -dv -f2)" && \
|
|
52
|
+
twine upload dist/*$$tag.tar.gz
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ossaudit-ng
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Audit python packages for known vulnerabilities using Sonatype OSS Index v3 API
|
|
5
|
+
Home-page: https://github.com/stuartz/ossaudit
|
|
6
|
+
Author: Stuart Zurcher
|
|
7
|
+
Author-email: stuartz.ccrx@gmail.com
|
|
8
|
+
Maintainer: Stuart Zurcher
|
|
9
|
+
Maintainer-email: stuartz.ccrx@gmail.com
|
|
10
|
+
License: BSD-2-Clause
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Information Technology
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved
|
|
16
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Topic :: Security
|
|
26
|
+
Classifier: Topic :: Software Development
|
|
27
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
28
|
+
Classifier: Topic :: Software Development :: Testing
|
|
29
|
+
Classifier: Topic :: Utilities
|
|
30
|
+
Requires-Python: >=3.5
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Requires-Dist: appdirs
|
|
33
|
+
Requires-Dist: click
|
|
34
|
+
Requires-Dist: dparse
|
|
35
|
+
Requires-Dist: requests
|
|
36
|
+
Requires-Dist: texttable
|
|
37
|
+
Dynamic: author
|
|
38
|
+
Dynamic: author-email
|
|
39
|
+
Dynamic: classifier
|
|
40
|
+
Dynamic: description
|
|
41
|
+
Dynamic: home-page
|
|
42
|
+
Dynamic: license
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
Dynamic: maintainer
|
|
45
|
+
Dynamic: maintainer-email
|
|
46
|
+
Dynamic: requires-dist
|
|
47
|
+
Dynamic: requires-python
|
|
48
|
+
Dynamic: summary
|
|
49
|
+
|
|
50
|
+
Fork of ossaudit by Hans Jerry Illikainen (https://github.com/illikainen/ossaudit). Includes some portions of PRs from sseide. See https://github.com/stuartz/ossaudit
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
|
|
2
|
+
# ossaudit
|
|
3
|
+
A fork of https://github.com/illikainen/ossaudit.git
|
|
4
|
+
This fork includes JSON output, config file support, Bearer token auth, and HTTP/HTTPS proxy support.
|
|
5
|
+
|
|
6
|
+
## About
|
|
7
|
+
|
|
8
|
+
`ossaudit` uses [Sonatype OSS Index][1] to audit Python packages for
|
|
9
|
+
known vulnerabilities.
|
|
10
|
+
|
|
11
|
+
It can check installed packages and/or packages specified in dependency
|
|
12
|
+
files. The following formats are supported with [dparse][2]:
|
|
13
|
+
|
|
14
|
+
- PIP requirement files
|
|
15
|
+
- Pipfile
|
|
16
|
+
- Pipfile.lock
|
|
17
|
+
- tox.ini
|
|
18
|
+
- conda.yml
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
### Normal
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
./setup.py develop
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
$ ossaudit --help
|
|
35
|
+
Usage: ossaudit [OPTIONS]
|
|
36
|
+
|
|
37
|
+
Options:
|
|
38
|
+
-c, --config TEXT Configuration file.
|
|
39
|
+
-i, --installed Audit installed packages.
|
|
40
|
+
-f, --file FILENAME Audit packages in file (can be specified multiple
|
|
41
|
+
times).
|
|
42
|
+
--token TEXT Token for authentication.
|
|
43
|
+
--column TEXT Column to show (can be specified multiple times or passed a comma separated list).
|
|
44
|
+
[default: name, version, title] additional optons -- id, cve, cvss_score, description
|
|
45
|
+
--ignore-id TEXT Ignore a vulnerability by Sonatype ID or CVE (can be
|
|
46
|
+
specified multiple times).
|
|
47
|
+
--ignore-cache Temporarily ignore existing cache.
|
|
48
|
+
--reset-cache Remove existing cache.
|
|
49
|
+
--json Output vulnerabilities as json list
|
|
50
|
+
--json-full Output all dependencies found and their vulnerabilities
|
|
51
|
+
as json list (columns given are ignored)
|
|
52
|
+
--http-proxy TEXT HTTP proxy URL.
|
|
53
|
+
--https-proxy TEXT HTTPS proxy URL.
|
|
54
|
+
--help Show this message and exit.
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## As import
|
|
58
|
+
```python
|
|
59
|
+
from ossaudit import scan
|
|
60
|
+
list_of_vulnerabilites = scan(installed=True) # Pass options as args
|
|
61
|
+
for v in list_of_vulnerabilites:
|
|
62
|
+
# v is type <class 'ossaudit.audit.Vulnerability'>
|
|
63
|
+
print(v.name, v.version, v.title, v.cvss_score)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Configuration
|
|
67
|
+
|
|
68
|
+
[Appdirs][3] is used to determine storage paths. This means that the
|
|
69
|
+
location of the configuration file is platform-specific:
|
|
70
|
+
|
|
71
|
+
- `*nix`: `~/.config/ossaudit/config.ini`
|
|
72
|
+
- `macOS`: `~/Library/Preferences/ossaudit/config.ini`
|
|
73
|
+
- `Windows`: `C:\Users\<username>\AppData\Local\ossaudit\ossaudit\config.ini`
|
|
74
|
+
|
|
75
|
+
It can be overridden with the `--config` command-line argument and with
|
|
76
|
+
the `OSSAUDIT_CONFIG` environment variable.
|
|
77
|
+
|
|
78
|
+
Example configuration:
|
|
79
|
+
|
|
80
|
+
```ini
|
|
81
|
+
[ossaudit]
|
|
82
|
+
# Optional: OSS Index API token (Bearer token auth).
|
|
83
|
+
# A free account and token can be created at https://ossindex.sonatype.org/
|
|
84
|
+
#token = string
|
|
85
|
+
|
|
86
|
+
# Optional: comma-separated list of columns to show.
|
|
87
|
+
# Default: name, version, title
|
|
88
|
+
# Supported: id, name, version, cve, cvss_score, title, description
|
|
89
|
+
#columns = name,version,title,cvss_score
|
|
90
|
+
|
|
91
|
+
# Optional: comma-separated list of vulnerability IDs (Sonatype ID or CVE) to ignore.
|
|
92
|
+
#ignore_ids = x,y,z
|
|
93
|
+
|
|
94
|
+
# Optional: Ignore cache
|
|
95
|
+
#ignore_cache = True
|
|
96
|
+
|
|
97
|
+
# Optional: Reset the cache
|
|
98
|
+
#reset_cache = True
|
|
99
|
+
|
|
100
|
+
# Optional: Output format to json
|
|
101
|
+
#json = True
|
|
102
|
+
|
|
103
|
+
# Optional: Full output of OSS Index results to json
|
|
104
|
+
#json_full = True
|
|
105
|
+
|
|
106
|
+
# Optional: HTTP/HTTPS proxy (can also be set via HTTP_PROXY/HTTPS_PROXY env vars)
|
|
107
|
+
#http_proxy = http://proxy.example.com:8080
|
|
108
|
+
#https_proxy = http://proxy.example.com:8080
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Authentication is **not** required. However, requests are rate limited
|
|
112
|
+
and authenticated requests are less restricted. A free account and API
|
|
113
|
+
token can be created on [OSS Index][1].
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
[1]: https://ossindex.sonatype.org/
|
|
117
|
+
[2]: https://github.com/pyupio/dparse
|
|
118
|
+
[3]: https://github.com/ActiveState/appdirs
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[mypy]
|
|
2
|
+
check_untyped_defs = true
|
|
3
|
+
|
|
4
|
+
disallow_any_decorated = true
|
|
5
|
+
disallow_incomplete_defs = true
|
|
6
|
+
disallow_subclassing_any = true
|
|
7
|
+
disallow_untyped_calls = true
|
|
8
|
+
disallow_untyped_decorators = true
|
|
9
|
+
disallow_untyped_defs = true
|
|
10
|
+
|
|
11
|
+
ignore_missing_imports = true
|
|
12
|
+
|
|
13
|
+
no_implicit_optional = true
|
|
14
|
+
|
|
15
|
+
strict_optional = true
|
|
16
|
+
|
|
17
|
+
warn_no_return = true
|
|
18
|
+
warn_redundant_casts = true
|
|
19
|
+
warn_return_any = true
|
|
20
|
+
warn_unused_configs = true
|
|
21
|
+
warn_unused_ignores = true
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Copyright (c) 2019, Hans Jerry Illikainen <hji@dyntopia.com>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: BSD-2-Clause
|
|
4
|
+
|
|
5
|
+
__version__ = "1.0.0"
|
|
6
|
+
__project__ = "ossaudit-ng"
|
|
7
|
+
|
|
8
|
+
from typing import Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
from .audit import AuditError, Vulnerability, components, flatten_vuln_list
|
|
11
|
+
from . import packages as _packages
|
|
12
|
+
|
|
13
|
+
__all__ = ["AuditError", "Vulnerability", "scan"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def scan(
|
|
17
|
+
installed: bool = False,
|
|
18
|
+
files: Optional[List[str]] = None,
|
|
19
|
+
token: Optional[str] = None,
|
|
20
|
+
ignore_ids: Optional[List[str]] = None,
|
|
21
|
+
ignore_cache: bool = False,
|
|
22
|
+
proxies: Optional[Dict[str, str]] = None,
|
|
23
|
+
) -> List[Vulnerability]:
|
|
24
|
+
"""Audit packages and return a list of vulnerabilities.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
installed:
|
|
29
|
+
Audit currently installed packages.
|
|
30
|
+
files:
|
|
31
|
+
Paths to requirements files to audit.
|
|
32
|
+
token:
|
|
33
|
+
Sonatype OSS Index API token.
|
|
34
|
+
ignore_ids:
|
|
35
|
+
Sonatype IDs or CVE IDs to exclude from results.
|
|
36
|
+
ignore_cache:
|
|
37
|
+
Skip the local cache and always query the API.
|
|
38
|
+
proxies:
|
|
39
|
+
Proxy URLs keyed by scheme, e.g. ``{"https": "http://proxy:8080"}``.
|
|
40
|
+
|
|
41
|
+
Returns
|
|
42
|
+
-------
|
|
43
|
+
List[Vulnerability]
|
|
44
|
+
Every vulnerability found, minus any in *ignore_ids*.
|
|
45
|
+
|
|
46
|
+
Raises
|
|
47
|
+
------
|
|
48
|
+
AuditError
|
|
49
|
+
On API authentication, rate-limit, or unexpected status errors.
|
|
50
|
+
"""
|
|
51
|
+
pkgs = [] # type: list
|
|
52
|
+
if installed:
|
|
53
|
+
pkgs += _packages.get_installed()
|
|
54
|
+
if files:
|
|
55
|
+
handles = [open(f) for f in files]
|
|
56
|
+
try:
|
|
57
|
+
pkgs += _packages.get_from_files(handles)
|
|
58
|
+
finally:
|
|
59
|
+
for h in handles:
|
|
60
|
+
h.close()
|
|
61
|
+
|
|
62
|
+
all_coordinates = components(pkgs, token, proxies, ignore_cache)
|
|
63
|
+
vulns = flatten_vuln_list(all_coordinates)
|
|
64
|
+
|
|
65
|
+
if ignore_ids:
|
|
66
|
+
vulns = [v for v in vulns if v.id not in ignore_ids and v.cve not in ignore_ids]
|
|
67
|
+
|
|
68
|
+
return vulns
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Copyright (c) 2019, Hans Jerry Illikainen <hji@dyntopia.com>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: BSD-2-Clause
|
|
4
|
+
|
|
5
|
+
from . import __project__, cli
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main() -> None:
|
|
9
|
+
prefix = __project__.upper()
|
|
10
|
+
cli.cli(auto_envvar_prefix=prefix) # pylint: disable=E1120,E1123
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
main()
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Copyright (c) 2019, Hans Jerry Illikainen <hji@dyntopia.com>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: BSD-2-Clause
|
|
4
|
+
|
|
5
|
+
from collections import namedtuple
|
|
6
|
+
from typing import Dict, Generator, List, Optional
|
|
7
|
+
from urllib.parse import urljoin
|
|
8
|
+
|
|
9
|
+
import requests
|
|
10
|
+
|
|
11
|
+
from . import cache, const, packages
|
|
12
|
+
|
|
13
|
+
Vulnerability = namedtuple(
|
|
14
|
+
"Vulnerability", [
|
|
15
|
+
"name",
|
|
16
|
+
"version",
|
|
17
|
+
"id",
|
|
18
|
+
"cve",
|
|
19
|
+
"cvss_score",
|
|
20
|
+
"title",
|
|
21
|
+
"description",
|
|
22
|
+
]
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AuditError(Exception):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def components(
|
|
31
|
+
pkgs: List[packages.Package],
|
|
32
|
+
token: Optional[str] = None,
|
|
33
|
+
proxies: Optional[Dict[str, str]] = None,
|
|
34
|
+
ignore_cache: bool = False,
|
|
35
|
+
) -> List[Vulnerability]:
|
|
36
|
+
old = list(_from_cache(pkgs, ignore_cache))
|
|
37
|
+
new = list(_from_api([
|
|
38
|
+
p for p in pkgs
|
|
39
|
+
if not any(p.coordinate == o.coordinate for o, _ in old)
|
|
40
|
+
], ignore_cache, token, proxies)) # yapf: disable
|
|
41
|
+
return (new + old)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def flatten_vuln_list(vulns):
|
|
45
|
+
return [
|
|
46
|
+
_transform(p, vuln)
|
|
47
|
+
for p, c in vulns # package, coordinate from ossindex
|
|
48
|
+
for vuln in c.get("vulnerabilities", [])
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _from_api(
|
|
54
|
+
pkgs: List[packages.Package],
|
|
55
|
+
ignore_cache: bool,
|
|
56
|
+
token: Optional[str] = None,
|
|
57
|
+
proxies: Optional[Dict[str, str]] = None
|
|
58
|
+
) -> Generator:
|
|
59
|
+
url = urljoin(const.API, const.COMPONENT_REPORT)
|
|
60
|
+
headers = {"Authorization": f"Bearer {token}"} if token else {}
|
|
61
|
+
|
|
62
|
+
for i in range(0, len(pkgs), const.MAX_PACKAGES):
|
|
63
|
+
coordinates = [p.coordinate for p in pkgs[i:i + const.MAX_PACKAGES]]
|
|
64
|
+
res = requests.post(url, headers=headers, json={"coordinates": coordinates},
|
|
65
|
+
proxies=proxies, timeout=const.REQ_TIMEOUT)
|
|
66
|
+
|
|
67
|
+
if res.status_code == 200:
|
|
68
|
+
for entry in res.json():
|
|
69
|
+
pkg = next((
|
|
70
|
+
p for p in pkgs
|
|
71
|
+
if p.coordinate == entry.get("coordinates")
|
|
72
|
+
), packages.Package("unknown", "0"))
|
|
73
|
+
if not ignore_cache:
|
|
74
|
+
cache.save(entry)
|
|
75
|
+
yield (pkg, entry)
|
|
76
|
+
elif res.status_code == 401:
|
|
77
|
+
raise AuditError("invalid credentials")
|
|
78
|
+
elif res.status_code == 429:
|
|
79
|
+
raise AuditError("too many requests")
|
|
80
|
+
else:
|
|
81
|
+
raise AuditError("unknown status code {}".format(res.status_code))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _from_cache(pkgs: List[packages.Package], ignore_cache: bool) -> Generator:
|
|
85
|
+
if not ignore_cache:
|
|
86
|
+
for pkg in pkgs:
|
|
87
|
+
entry = cache.get(pkg.coordinate)
|
|
88
|
+
if entry:
|
|
89
|
+
yield (pkg, entry)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _transform(pkg: packages.Package, vuln: Dict) -> Vulnerability:
|
|
93
|
+
return Vulnerability(
|
|
94
|
+
name=pkg.name,
|
|
95
|
+
version=pkg.version,
|
|
96
|
+
id=vuln.get("id", ""),
|
|
97
|
+
cve=vuln.get("cve", ""),
|
|
98
|
+
cvss_score=vuln.get("cvssScore", ""),
|
|
99
|
+
title=vuln.get("title", ""),
|
|
100
|
+
description=vuln.get("description", ""),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def create_report(vulns: List):
|
|
105
|
+
"""Create a list of dict objects as returned by the SonarType OSSIndex scanner. The list contains on
|
|
106
|
+
dict per python package found and queried against the database. The packages "vulnerabilities" field
|
|
107
|
+
is an list with all vulnerabilites for this package. The vulnerability list can be empty.
|
|
108
|
+
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
vulns : List with a tuple of the package object and the SonarType response for this package
|
|
112
|
+
|
|
113
|
+
Returns
|
|
114
|
+
-------
|
|
115
|
+
list - a list of scan coordinates (packages) and a list with their vulnerabilities (if any)
|
|
116
|
+
"""
|
|
117
|
+
out_list = []
|
|
118
|
+
for p, c in vulns:
|
|
119
|
+
del c["time"]
|
|
120
|
+
out_list.append(c)
|
|
121
|
+
return out_list
|
|
122
|
+
|
|
123
|
+
def create_vuln_list(vulns: List, columns: List):
|
|
124
|
+
"""Create a list of dicts from the vulnerability tuples containing only the column names requests
|
|
125
|
+
|
|
126
|
+
Parameters
|
|
127
|
+
----------
|
|
128
|
+
vulns : List list of all vulnerabilities found
|
|
129
|
+
columns : List list of all columns(fields) needed from the vulnerability for output
|
|
130
|
+
|
|
131
|
+
Returns
|
|
132
|
+
-------
|
|
133
|
+
list - a list of vulnerability dictionaries containing the columns given only
|
|
134
|
+
"""
|
|
135
|
+
out_list = []
|
|
136
|
+
for v in vulns:
|
|
137
|
+
v_dict = v._asdict()
|
|
138
|
+
v_out = {}
|
|
139
|
+
for c in columns:
|
|
140
|
+
v_out[c] = v_dict[c] if c in v_dict else ""
|
|
141
|
+
out_list.append(v_out)
|
|
142
|
+
return out_list
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Copyright (c) 2019, Hans Jerry Illikainen <hji@dyntopia.com>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: BSD-2-Clause
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import time
|
|
7
|
+
from typing import Dict, Optional
|
|
8
|
+
|
|
9
|
+
from . import const
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get(coordinate: str) -> Optional[Dict]:
|
|
13
|
+
if const.CACHE.exists():
|
|
14
|
+
with const.CACHE.open() as f:
|
|
15
|
+
try:
|
|
16
|
+
entry = next(
|
|
17
|
+
(
|
|
18
|
+
e for e in json.load(f)
|
|
19
|
+
if e["coordinates"] == coordinate
|
|
20
|
+
),
|
|
21
|
+
{},
|
|
22
|
+
)
|
|
23
|
+
if _is_valid(entry):
|
|
24
|
+
return entry
|
|
25
|
+
except json.JSONDecodeError:
|
|
26
|
+
pass
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def save(entry: Dict) -> None:
|
|
31
|
+
entries = [] # type: list
|
|
32
|
+
|
|
33
|
+
if const.CACHE.exists():
|
|
34
|
+
with const.CACHE.open() as f:
|
|
35
|
+
try:
|
|
36
|
+
entries = [
|
|
37
|
+
e for e in json.load(f) if _is_valid(e)
|
|
38
|
+
and e.get("coordinates") != entry.get("coordinates")
|
|
39
|
+
]
|
|
40
|
+
except json.JSONDecodeError:
|
|
41
|
+
pass
|
|
42
|
+
else:
|
|
43
|
+
const.CACHE.parent.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
|
|
45
|
+
with const.CACHE.open("w") as f:
|
|
46
|
+
entry["time"] = time.time()
|
|
47
|
+
json.dump(entries + [entry], f)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def reset() -> None:
|
|
51
|
+
if const.CACHE.exists():
|
|
52
|
+
const.CACHE.unlink()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _is_valid(entry: Dict) -> bool:
|
|
56
|
+
then = entry.get("time", float("inf"))
|
|
57
|
+
now = time.time()
|
|
58
|
+
return not then > now + const.CACHE_TIME
|