Commit 3c731ce7 authored by Jérome Perrin's avatar Jérome Perrin

new --audit option for sbom output

query OSV API for vulnerabilities and (just) print a summary
parent 7b09c882
Pipeline #35254 failed with stage
in 0 seconds
...@@ -37,6 +37,8 @@ import json ...@@ -37,6 +37,8 @@ import json
import sys, configparser, re, codecs import sys, configparser, re, codecs
import uuid import uuid
import requests
# PkgInfo represents information about a package # PkgInfo represents information about a package
PkgInfo = namedtuple('PkgInfo', [ PkgInfo = namedtuple('PkgInfo', [
...@@ -546,7 +548,7 @@ def fmt_bom(bom): # -> str ...@@ -546,7 +548,7 @@ def fmt_bom(bom): # -> str
return ''.join(outv) return ''.join(outv)
def fmt_bom_cyclonedx_json(bom, software_path): def fmt_bom_cyclonedx_json(bom, software_path, audit=False):
# possible future extensions: # possible future extensions:
# - describe patches applied to components (using components[*].pedigree.patches ) # - describe patches applied to components (using components[*].pedigree.patches )
...@@ -595,40 +597,54 @@ def fmt_bom_cyclonedx_json(bom, software_path): ...@@ -595,40 +597,54 @@ def fmt_bom_cyclonedx_json(bom, software_path):
} }
} }
components = bom_json["components"] = [] components = bom_json["components"] = []
for _, pkginfo in sorted(bom.items()): with requests.Session() as session:
cpe = None for _, pkginfo in sorted(bom.items()):
externalReferences = [] cpe = None
if pkginfo.url: externalReferences = []
externalReferences.append( if pkginfo.url:
{ externalReferences.append(
'url': pkginfo.url, {
'type': ( 'url': pkginfo.url,
'vcs' 'type': (
if pkginfo.kind == 'git' 'vcs'
else 'distribution' if pkginfo.kind == 'git'
), else 'distribution'
} ),
) }
purl_type = 'generic' )
if pkginfo.kind == 'egg': purl_type = 'generic'
purl_type = 'pypi' if pkginfo.kind == 'egg':
elif pkginfo.kind == 'gem': purl_type = 'pypi'
purl_type = 'gem' elif pkginfo.kind == 'gem':
else: purl_type = 'gem'
cpe = f'cpe:2.3:*:*:{pkginfo.name}:{pkginfo.version}:*:*:*:*:*:*:*' else:
purl = f'pkg:{purl_type}/{pkginfo.name}@{pkginfo.version}' cpe = f'cpe:2.3:*:*:{pkginfo.name}:{pkginfo.version}:*:*:*:*:*:*:*'
component = { purl = f'pkg:{purl_type}/{pkginfo.name}@{pkginfo.version}'
'name': pkginfo.name, component = {
'purl': purl, 'name': pkginfo.name,
'type': 'library', 'purl': purl,
'version': pkginfo.version, 'type': 'library',
} 'version': pkginfo.version,
if cpe: }
component['cpe'] = cpe if cpe:
if externalReferences: component['cpe'] = cpe
component['externalReferences'] = externalReferences if externalReferences:
components.append(component) component['externalReferences'] = externalReferences
components.append(component)
if audit:
resp = session.post(
'https://api.osv.dev/v1/query',
json={"package": {"purl": purl}})
for vuln in resp.json().get('vulns', []):
reference = sorted(
vuln['references'] + [{'url': 'N/A'}],
key=lambda r: not r.get('type') == 'ADVISORY')[0]['url']
print("WARNING: {} {} {} {}".format(
pkginfo.name,
reference,
vuln['id'],
vuln.get('summary', vuln.get('details', '')),
))
return bom_json return bom_json
...@@ -638,6 +654,7 @@ def main(): ...@@ -638,6 +654,7 @@ def main():
description=__doc__ description=__doc__
) )
parser.add_argument('-f', '--format', choices=['text', 'cyclonedx-json'], default='text') parser.add_argument('-f', '--format', choices=['text', 'cyclonedx-json'], default='text')
parser.add_argument('--audit', action="store_true", help='Query OSV database for known vulnerabilities')
parser.add_argument('-o', '--output', parser.add_argument('-o', '--output',
type=argparse.FileType('w', encoding='UTF-8'), type=argparse.FileType('w', encoding='UTF-8'),
default=sys.stdout) default=sys.stdout)
...@@ -659,7 +676,7 @@ def main(): ...@@ -659,7 +676,7 @@ def main():
print(fmt_bom(bom), file=args.output) print(fmt_bom(bom), file=args.output)
else: else:
assert args.format == 'cyclonedx-json' assert args.format == 'cyclonedx-json'
json.dump(fmt_bom_cyclonedx_json(bom, args.software_path), args.output, indent=True) json.dump(fmt_bom_cyclonedx_json(bom, args.software_path, args.audit), args.output, indent=True)
if __name__ == '__main__': if __name__ == '__main__':
......
...@@ -13,6 +13,7 @@ setup( ...@@ -13,6 +13,7 @@ setup(
keywords = 'Nexedi software build BOM', keywords = 'Nexedi software build BOM',
packages = find_packages(), packages = find_packages(),
requires = [ 'requests', ],
extras_require = { extras_require = {
'test': ['pytest'], 'test': ['pytest'],
}, },
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment