#!/usr/bin/python3 import os, sys, re, argparse import bugzilla from bugzilla.utils import check_being_logged_in, make_url, get_bugzilla_api # modify-bugs script - is based on python-bugzilla (our in-tree patched copy) and requests libraries # for now this script should be kept Python 3.6 compatible (SLE15-SP7) CVE_PATTERN = re.compile(r"CVE-[0-9]{4}-[0-9]{4,}") def handle_bugs(bug_string): if not bug_string: return None ret = [] for b in bug_string.split(','): try: if b.startswith('bsc#'): b = b[4:] bz_number = int(b) if bz_number < 1: raise Exception() ret.append(bz_number) except: print(f'{b} is not a valid bz number', file=sys.stderr) if not ret: sys.exit(1) return ret def handle_cves(cve_string): if not cve_string: return None ret = [] for c in cve_string.split(','): m = re.match(CVE_PATTERN, c) if not m: print(f'{c} is not a valid CVE number', file=sys.stderr) continue ret.append(c) if not ret: sys.exit(1) return ret # this scripts supports both comma separated values for -V, -B and --cc options and multiple such options # this is where everything is collected into one array class BugActions(argparse.Action): bug_list = [] cve_list = [] cc_list = [] def __call__(self, parser, namespace, values, option_string = None): if option_string in ["-V", "--cves"]: cves = handle_cves(values) if cves: self.cve_list.extend(cves) elif option_string in ["-B", "--bugs"]: bugs = handle_bugs(values) if bugs: self.bug_list.extend(bugs) elif option_string == '--cc': if values: self.cc_list.extend(values.split(',')) def parse_args(): parser = argparse.ArgumentParser("modify-bugs") parser.add_argument("-f", "--file", help="path to a file containing the text of comments", default=None, type=str) parser.add_argument("-c", "--comment", help="the text of comments", default=None, type=str) parser.add_argument("-p", "--public", help="shall the comments be public; by default they are private", action="store_true", default=False) parser.add_argument("-V", "--cves", help="comma separated list of cves; can be use multiple times; can be used with --bugs", action=BugActions) parser.add_argument("-B", "--bugs", help="comma separated list of bugs; can be use multiple times; can be used with --cves", action=BugActions) resolved_group = parser.add_mutually_exclusive_group() resolved_group.add_argument("--resolved-cve", help="reassign the bug back to the security team", action="store_true", default=False) resolved_group.add_argument("--resolved-fixed", help="make bug RESOLVED FIXED", action="store_true", default=False) resolved_group.add_argument("--resolved-invalid", help="make bug RESOLVED INVALID", action="store_true", default=False) resolved_group.add_argument("--resolved-wontfix", help="make bug RESOLVED WONTFIX", action="store_true", default=False) resolved_group.add_argument("--resolved-worksforme", help="make bug RESOLVED WORKSFORME", action="store_true", default=False) resolved_group.add_argument("--resolved-feature", help="make bug RESOLVED FEATURE", action="store_true", default=False) resolved_group.add_argument("--in-progress", help="swith bug to IN_PROGRESS status", action="store_true", default=False) arch_group = parser.add_mutually_exclusive_group() arch_group.add_argument("--arch-s390x", help="set arch to s390x", action="store_true", default=False) arch_group.add_argument("--arch-ppc64le", help="set arch to ppc64le", action="store_true", default=False) arch_group.add_argument("--arch-x86_64", help="set arch to x86_64", action="store_true", default=False) arch_group.add_argument("--arch-aarch64", help="set arch to aarch64", action="store_true", default=False) arch_group.add_argument("--arch-all", help="set arch to aarch64", action="store_true", default=False) parser.add_argument("--cc", help="comma separated list of emails; can be use multiple times", action=BugActions) parser.add_argument("-a", "--assignee", help="email as assignees for the bugs", default=None, type=str) parser.add_argument("-v", "--verbose", help="be a bit more verbose", action="store_true", default=False) parser.add_argument("--rest", help="Use REST API instead of XMLRPC API (experimental, for debugging purposes)", action="store_true", default=False) parser.add_argument("-n", "--dry-run", help="Dry run: print the arguments which would be passed to Bugzilla without actually doing anything", action="store_true", default=False) parser.add_argument("--debug", help="Enable Bugzilla rpc debugging", action="store_true", default=False) return parser.parse_args() if __name__ == "__main__": args = parse_args() if args.debug: import logging logging.basicConfig(level=logging.DEBUG) if not BugActions.bug_list and not BugActions.cve_list: print("You must provide either a bug ID or a CVE number", file=sys.stderr) sys.exit(1) comment = None if args.comment: comment = args.comment elif args.resolved_cve: comment = 'All branches handled' if args.file and not comment: if not os.path.isfile(args.file): print(f"'{args.file}' must exists and must be a regular file", file=sys.stderr) sys.exit(1) try: comment = open(args.file, 'r').read() except: print(f"failed to read '{args.file}'", file=sys.stderr) sys.exit(1) assignee = None if args.resolved_cve: assignee = 'kernel-security-sentinel@lists.suse.com' elif args.assignee: assignee = args.assignee try: bzapi = get_bugzilla_api(args.rest) check_being_logged_in(bzapi) except Exception as e: print(f"Failed to connect to bugzilla...: {e}", file=sys.stderr) sys.exit(1) bug_ids = { b for b in BugActions.bug_list } if BugActions.bug_list else set() if BugActions.cve_list: kvargs = { 'status': ['NEW', 'IN_PROGRESS', 'CONFIRMED', 'REOPENED'], 'product': 'SUSE Security Incidents', 'component': 'Incidents', 'alias': BugActions.cve_list, 'include_fields': ["id"] } query = bzapi.build_query(**kvargs) cve_bugs = bzapi.query(query) bug_ids |= { b.id for b in cve_bugs } if not bug_ids: print(f"There's no bug to process...", file=sys.stderr) sys.exit(1) bargs= {} if comment: bargs['comment'] = comment + '\n' if not args.public: bargs['comment_private'] = True if assignee: bargs['assigned_to'] = assignee if BugActions.cc_list: bargs['cc_add'] = BugActions.cc_list if args.resolved_cve: pass elif args.resolved_fixed: bargs['status'] = 'RESOLVED' bargs['resolution'] = 'FIXED' elif args.resolved_invalid: bargs['status'] = 'RESOLVED' bargs['resolution'] = 'INVALID' elif args.resolved_wontfix: bargs['status'] = 'RESOLVED' bargs['resolution'] = 'WONTFIX' elif args.resolved_worksforme: bargs['status'] = 'RESOLVED' bargs['resolution'] = 'WORKSFORME' elif args.resolved_feature: bargs['status'] = 'RESOLVED' bargs['resolution'] = 'FEATURE' elif args.in_progress: bargs['status'] = 'IN_PROGRESS' if args.arch_s390x: bargs['platform'] = 'S/390-64' elif args.arch_ppc64le: bargs['platform'] = 'PowerPC-64' elif args.arch_x86_64: bargs['platform'] = 'x86-64' elif args.arch_aarch64: bargs['platform'] = 'aarch64' elif args.arch_all: bargs['platform'] = 'All' if bargs: if args.dry_run: print(f"{bargs}") sys.exit(0) vals = bzapi.build_update(**bargs) try: bzapi.update_bugs([ b for b in bug_ids ], vals) if args.verbose: for b in bug_ids: print(make_url(b)) except Exception as e: print(f"Failed to update bugs... {bug_ids}: {e}", file=sys.stderr) sys.exit(1) else: print("Nothing to do!", file=sys.stderr)