From 1e03a6b7d241a9eaa3f9950613b37d8c100602d1 Mon Sep 17 00:00:00 2001 From: Alex Legler Date: Wed, 1 Jun 2016 19:56:44 +0200 Subject: Add initial CVETool CLI utility --- bin/cvetool | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100755 bin/cvetool diff --git a/bin/cvetool b/bin/cvetool new file mode 100755 index 0000000..8e388e0 --- /dev/null +++ b/bin/cvetool @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +# Copyright 2016 Alex Legler +# Distributed under the terms of the GNU General Public License v3 + +import json +import re +import string +import sys +import os +import httplib2 +from base64 import b64encode + +URI_BASE = 'https://glsamaker.gentoo.org' + +class CVETool: + """ Interface to GLSAMaker's CVETool """ + + def __init__(self, auth, command, args): + self.auth = auth + + if command == 'info': + self.info(self.cleanup_cve(sys.argv[2])) + elif command == 'assign': + if len(args) < 2: + print('Usage: assign [...]') + print('Assigns a set of CVEs to a bug') + sys.exit(1) + + self.assign(args[0], [self.cleanup_cve(cve) for cve in args[1:]]) + elif command == 'nfu': + if len(args) != 1: + print('Usage: nfu ') + print('Marks a CVE as not-for-us') + sys.exit(1) + + self.nfu(self.cleanup_cve(args[0])) + elif command == 'pw': + if len(sys.argv) != 4: + print('Usage: pw ') + print('Generates a base64-encoded credential for storing') + sys.exit(1) + + self.pw(sys.argv[2], sys.argv[3]) + else: + self.usage(sys.argv[0]) + sys.exit(1) + + def info(self, cve): + data = self.json_request('/cve/info/' + cve + '.json') + + print(' CVE ID: ' + data['cve_id']) + print(' Summary: ' + data['summary']) + print(' Published: ' + data['published_at']) + print('-' * 80) + print(' State: ' + data['state']) + print(' Bugs: ' + ' , '.join(['https://bugs.gentoo.org/' + str(bug) for bug in data['bugs']])) + + def assign(self, bug, cves): + cve_ids = [self.get_internal_cve_id(cve) for cve in cves] + response = self.request('/cve/assign/?bug=' + str(bug) + '&cves=' + ','.join([str(c) for c in cve_ids])) + + if (response == 'ok'): + print('Assigned bug {} to {}'.format(str(bug), ', '.join(cves))) + else: + print('Assigning likely failed: ' + response) + sys.exit(1) + + def nfu(self, cve): + cve_id = self.get_internal_cve_id(cve) + response = self.request('/cve/nfu/?cves=' + str(cve_id) + '&reason=') + + if (response == 'ok'): + print('Marked {} as NFU'.format(cve)) + else: + print('Assigning likely failed: ' + response) + sys.exit(1) + + + def usage(self, programname): + """ Print usage information """ + print('Usage: {} [args]'.format(programname)) + print('CLI for CVETool.') + + def pw(self, user, password): + print(b64encode(bytes(user + ':' + password, 'utf-8')).decode('ascii')) + + def get_internal_cve_id(self, cve): + """ Resolves a CVE id to the internal databse ID """ + return self.json_request('/cve/info/' + cve + '.json')['id'] + + def json_request(self, uri, method='GET'): + return json.loads(self.request(uri, method)) + + def cleanup_cve(self, str): + regex = re.compile('^(CVE-)?\d{4}-\d{4,}$') + if not regex.match(str): + raise ValueError('Cannot parse CVE: ' + str) + + if not str.startswith('CVE-'): + return 'CVE-' + str + else: + return str + + def request(self, uri, method='GET'): + client = httplib2.Http('.cache') + full_uri = URI_BASE + uri + response, content = client.request(full_uri, method, headers = { 'Authorization': 'Basic ' + self.auth }) + + status = response['status'] + if (status[0] != '2' and status != '304'): + raise RuntimeError(full_uri + ': ' + status) + + return content.decode('utf-8') + +def main(): + if not 'CVETOOL_AUTH' in os.environ and not sys.argv[1] == 'pw': + print('CVETOOL_AUTH environment variable missing. Generate its contents with the pw subcommand.') + sys.exit(1) + + auth = None + if 'CVETOOL_AUTH' in os.environ: + auth = os.environ['CVETOOL_AUTH'] + + CVETool(auth, sys.argv[1], sys.argv[2:]) + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + print('\n ! Exiting.') -- cgit v1.2.3-65-gdbad