mirror of https://github.com/openwrt/openwrt.git
179 lines
5.0 KiB
Python
Executable File
179 lines
5.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
#
|
|
# Parse the native package index files into a json file for use by
|
|
# downstream tools.
|
|
#
|
|
"""
|
|
|
|
import datetime
|
|
import email.parser
|
|
import json
|
|
import uuid
|
|
|
|
|
|
def parse_args():
|
|
from argparse import ArgumentParser
|
|
|
|
parser = ArgumentParser()
|
|
# fmt: off
|
|
|
|
parser.add_argument(dest="source",
|
|
help="File name for input, '-' for stdin")
|
|
parser.add_argument("-f", "--source-format", required=True,
|
|
choices=['apk', 'opkg'],
|
|
help=("Required source format of"
|
|
" input: 'apk' or 'opkg'"))
|
|
parser.add_argument("-m", "--manifest",
|
|
help=("File includes the packages to"
|
|
" be included in the output"))
|
|
|
|
# fmt: on
|
|
args = parser.parse_args()
|
|
return args
|
|
|
|
|
|
def get_apk_sbom(text: str, installed: set) -> list:
|
|
packages: dict = json.loads(text)
|
|
components: list = []
|
|
|
|
type_allowed: dict = {
|
|
"kernel": "operating-system",
|
|
"firmware": "firmware",
|
|
"libs": "library"
|
|
}
|
|
|
|
for package in packages["packages"]:
|
|
element: dict = {}
|
|
|
|
# required
|
|
if 'name' in package:
|
|
name: str = package['name']
|
|
element.update({"name": name})
|
|
if installed:
|
|
if name not in installed:
|
|
continue
|
|
|
|
if 'version' in package:
|
|
element.update({"version": package["version"]})
|
|
|
|
for tag in package.get("tags", []):
|
|
if tag.startswith("openwrt:cpe="):
|
|
cpe: str = tag.split("=")[-1]
|
|
element.update({"cpe": cpe})
|
|
|
|
# required
|
|
type_category: str = ''
|
|
|
|
for tag in package.get("tags", []):
|
|
if tag.startswith("openwrt:section="):
|
|
category: str = tag.split("=")[-1]
|
|
if type_allowed.get(category):
|
|
type_category = type_allowed.get(category)
|
|
if type_category:
|
|
element.update({"type": type_category})
|
|
else:
|
|
element.update({"type": "application"})
|
|
|
|
if 'license' in package:
|
|
licenses: list = []
|
|
for license in package["license"].split():
|
|
licenses.append({"license": {"name": license}})
|
|
element.update({"licenses": licenses})
|
|
|
|
components.append(element)
|
|
|
|
return components
|
|
|
|
|
|
def get_opkg_sbom(text: str, installed: set) -> list:
|
|
components: list = []
|
|
|
|
type_allowed: dict = {
|
|
"kernel": "operating-system",
|
|
"firmware": "firmware",
|
|
"libs": "library"
|
|
}
|
|
|
|
parser: email.parser.Parser = email.parser.Parser()
|
|
chunks: list[str] = text.strip().split("\n\n")
|
|
for chunk in chunks:
|
|
element: dict = {}
|
|
package: dict = parser.parsestr(chunk, headersonly=True)
|
|
|
|
# required
|
|
if 'Package' in package:
|
|
name: str = package['Package']
|
|
element.update({"name": name})
|
|
if installed:
|
|
if name not in installed:
|
|
continue
|
|
|
|
if 'Version' in package:
|
|
element.update({"version": package['Version']})
|
|
|
|
if 'CPE-ID' in package:
|
|
element.update({"cpe": package['CPE-ID']})
|
|
|
|
# required
|
|
if 'Section' in package:
|
|
type_category: str = ''
|
|
if type_allowed.get(package['Section']):
|
|
type_category = type_allowed.get(package['Section'])
|
|
if type_category:
|
|
element.update({"type": type_category})
|
|
else:
|
|
element.update({"type": "application"})
|
|
|
|
if 'license' in package:
|
|
licenses: list = []
|
|
for license in package["license"].split():
|
|
licenses.append({"license": {"name": license}})
|
|
element.update({"licenses": licenses})
|
|
|
|
if element:
|
|
components.append(element)
|
|
|
|
return components
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
|
|
args = parse_args()
|
|
|
|
input = sys.stdin if args.source == "-" else open(args.source, "r")
|
|
with input:
|
|
text: str = input.read()
|
|
|
|
# Read manifest file (installed packages)
|
|
packages: set = set()
|
|
if args.manifest:
|
|
with open(args.manifest, 'r') as file:
|
|
for line in file:
|
|
packages.add(line.split(' - ')[0].strip())
|
|
|
|
components: list = []
|
|
if args.source_format == "apk":
|
|
components = get_apk_sbom(text, packages)
|
|
elif args.source_format == "opkg":
|
|
components = get_opkg_sbom(text, packages)
|
|
else:
|
|
print("Source format unknown")
|
|
raise SystemExit
|
|
|
|
timestamp: str = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
cyclonedx: dict = {
|
|
"bomFormat": "CycloneDX",
|
|
"specVersion": "1.4",
|
|
"serialNumber": "urn:uuid:" + str(uuid.uuid4()),
|
|
"version": 1,
|
|
"metadata": {
|
|
"timestamp": timestamp,
|
|
},
|
|
"components": components,
|
|
}
|
|
|
|
print(json.dumps(cyclonedx, indent=2))
|