[Secure-testing-commits] r33498 - bin
Raphaël Hertzog
hertzog at moszumanska.debian.org
Fri Apr 10 19:33:01 UTC 2015
Author: hertzog
Date: 2015-04-10 19:33:00 +0000 (Fri, 10 Apr 2015)
New Revision: 33498
Added:
bin/lts-cve-triage.py
bin/tracker_data.py
Log:
Add new helper script bin/lts-cve-triage.py
It helps doing CVE triage by comparing status of issues with the
"next_lts" release (managed by the security team instead of the LTS team).
Added: bin/lts-cve-triage.py
===================================================================
--- bin/lts-cve-triage.py (rev 0)
+++ bin/lts-cve-triage.py 2015-04-10 19:33:00 UTC (rev 33498)
@@ -0,0 +1,85 @@
+#!/usr/bin/python
+
+# Copyright 2015 Raphael Hertzog <hertzog at debian.org>
+#
+# This file is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This file is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this file. If not, see <https://www.gnu.org/licenses/>.
+
+import collections
+
+from tracker_data import TrackerData, RELEASES
+
+tracker = TrackerData(update_cache=True)
+next_lts = RELEASES['next_lts']
+
+LIST_NAMES = (
+ ('triage_already_in_dsa_needed',
+ 'Issues to triage that are in dsa-needed'),
+ ('triage_likely_nodsa',
+ 'Issues to triage that are nodsa in {}'.format(next_lts)),
+ ('triage_other',
+ 'Other issues to triage (no special status)'),
+ ('triage_other_not_triaged_in_next_lts',
+ 'Other issues to triage (not yet triaged in {})'.format(next_lts)),
+ ('unexpected_nodsa',
+ 'Issues tagged no-dsa that are open in {}'.format(next_lts)),
+ ('possible_easy_fixes',
+ 'Issues that are already fixed in {}'.format(next_lts)),
+)
+
+lists = collections.defaultdict(lambda: collections.defaultdict(lambda: []))
+
+
+def add_to_list(key, pkg, issue):
+ assert key in [l[0] for l in LIST_NAMES]
+ lists[key][pkg].append(issue)
+
+
+for pkg in tracker.iterate_packages():
+ for issue in tracker.iterate_pkg_issues(pkg):
+ status_in_lts = issue.get_status('lts')
+ status_in_next_lts = issue.get_status('next_lts')
+
+ if status_in_lts.status in ('not-affected', 'resolved'):
+ continue
+
+ if status_in_lts.status == 'open':
+ if pkg not in tracker.dla_needed: # Issues not triaged yet
+ if status_in_next_lts.status == 'open':
+ if pkg in tracker.dsa_needed:
+ add_to_list('triage_already_in_dsa_needed', pkg, issue)
+ else:
+ add_to_list('triage_other_not_triaged_in_next_lts',
+ pkg, issue)
+ elif (status_in_next_lts.status == 'ignored' and
+ status_in_next_lts.reason == 'no-dsa'):
+ add_to_list('triage_likely_nodsa', pkg, issue)
+ else:
+ add_to_list('triage_other', pkg, issue)
+ if status_in_next_lts.status == 'resolved':
+ add_to_list('possible_easy_fixes', pkg, issue)
+
+ if (status_in_lts.status == 'ignored' and
+ status_in_lts.reason == 'no-dsa' and
+ status_in_next_lts.status == 'open'):
+ add_to_list('unexpected_nodsa', pkg, issue)
+
+for key, desc in LIST_NAMES:
+ if not len(lists[key]):
+ continue
+ print('{}:'.format(desc))
+ for pkg in sorted(lists[key].keys()):
+ cve_list = ' '.join(
+ [i.name for i in sorted(lists[key][pkg], key=lambda i: i.name)])
+ print('* {:20s} -> {}'.format(pkg, cve_list))
+ print('')
Property changes on: bin/lts-cve-triage.py
___________________________________________________________________
Added: svn:executable
+ *
Added: bin/tracker_data.py
===================================================================
--- bin/tracker_data.py (rev 0)
+++ bin/tracker_data.py 2015-04-10 19:33:00 UTC (rev 33498)
@@ -0,0 +1,188 @@
+# Copyright 2015 Raphael Hertzog <hertzog at debian.org>
+#
+# This file is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This file is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this file. If not, see <https://www.gnu.org/licenses/>.
+
+import json
+import os.path
+import re
+import subprocess
+
+import requests
+import six
+
+RELEASES = {
+ 'oldstable': 'squeeze',
+ 'stable': 'wheezy',
+ 'testing': 'jessie',
+ 'unstable': 'sid',
+ 'experimental': 'experimental',
+ # LTS specific aliases
+ 'lts': 'squeeze',
+ 'next_lts': 'wheezy',
+}
+
+
+def normalize_release(release):
+ if release in RELEASES:
+ return RELEASES[release]
+ elif release in RELEASES.values():
+ return release
+ else:
+ raise ValueError("Unknown release: {}".format(release))
+
+
+class TrackerData(object):
+ DATA_URL = "https://security-tracker.debian.org/tracker/data/json"
+ CACHED_DATA_PATH = "~/.cache/debian_security_tracker.json"
+ CACHED_REVISION_PATH = "~/.cache/debian_security_tracker.rev"
+ GET_REVISION_COMMAND = \
+ "LC_ALL=C svn info svn://anonscm.debian.org/secure-testing|"\
+ "awk '/^Revision:/ { print $2 }'"
+ DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data')
+
+ def __init__(self, update_cache=True):
+ self.cached_data_path = os.path.expanduser(self.CACHED_DATA_PATH)
+ self.cached_revision_path = os.path.expanduser(
+ self.CACHED_REVISION_PATH)
+ if update_cache:
+ self.update_cache()
+ self.load()
+
+ @property
+ def latest_revision(self):
+ """Return the current revision of the SVN repository"""
+ # Return cached value if available
+ if hasattr(self, '_latest_revision'):
+ return self._latest_revision
+ # Otherwise call out to svn to get the latest revision
+ output = subprocess.check_output(self.GET_REVISION_COMMAND,
+ shell=True)
+ self._latest_revision = int(output)
+ return self._latest_revision
+
+ def _cache_must_be_updated(self):
+ """Verify if the cache is out of date"""
+ if os.path.exists(self.cached_data_path) and os.path.exists(
+ self.cached_revision_path):
+ with open(self.cached_revision_path, 'r') as f:
+ try:
+ revision = int(f.readline())
+ except ValueError:
+ revision = None
+ if revision == self.latest_revision:
+ return False
+ return True
+
+ def update_cache(self):
+ """Update the cached data if it's out of date"""
+ if not self._cache_must_be_updated():
+ return
+
+ print("Updating {} from {} ...".format(self.CACHED_DATA_PATH,
+ self.DATA_URL))
+ response = requests.get(self.DATA_URL, allow_redirects=True)
+ if response.status_code == 200:
+ with open(self.cached_data_path, 'w') as cache_file:
+ cache_file.write(response.text)
+ with open(self.cached_revision_path, 'w') as rev_file:
+ rev_file.write('{}'.format(self.latest_revision))
+ else:
+ response.raise_for_status()
+
+ def load(self):
+ with open(self.cached_data_path, 'r') as f:
+ self.data = json.load(f)
+ self.load_dsa_dla_needed()
+
+ @classmethod
+ def parse_needed_file(self, inputfile):
+ PKG_RE = '^(\S+)(?:\s+\((.*)\)\s*)?$'
+ SEP_RE = '^--\s*$'
+ state = 'LOOK_FOR_SEP'
+ result = {}
+ package = ''
+ for line in inputfile:
+ if state == 'LOOK_FOR_SEP':
+ res = re.match(SEP_RE, line)
+ if not res:
+ if package:
+ result[package]['more'] += '\n' + line
+ continue
+ package = ''
+ state = 'LOOK_FOR_PKG'
+ elif state == 'LOOK_FOR_PKG':
+ res = re.match(PKG_RE, line)
+ if res:
+ package = res.group(1)
+ result[package] = {
+ 'taken_by': res.group(2),
+ 'more': '',
+ }
+ state = 'LOOK_FOR_SEP'
+ return result
+
+ def load_dsa_dla_needed(self):
+ with open(os.path.join(self.DATA_DIR, 'dsa-needed.txt'), 'r') as f:
+ self.dsa_needed = self.parse_needed_file(f)
+ with open(os.path.join(self.DATA_DIR, 'dla-needed.txt'), 'r') as f:
+ self.dla_needed = self.parse_needed_file(f)
+
+ def iterate_packages(self):
+ """Iterate over known packages"""
+ for pkg in self.data:
+ yield pkg
+
+ def iterate_pkg_issues(self, pkg):
+ for id, data in six.iteritems(self.data[pkg]):
+ data['package'] = pkg
+ yield Issue(id, data)
+
+class IssueStatus(object):
+
+ def __init__(self, status, reason=None):
+ self.status = status
+ self.reason = reason
+
+class Issue(object):
+ '''Status of a security issue'''
+
+ def __init__(self, name, data):
+ self.name = name
+ self.data = data
+
+ def get_status(self, release):
+ release = normalize_release(release)
+ data = self.data['releases'].get(release)
+ if data is None:
+ status = 'not-affected'
+ # XXX: ask for data to differentiate between "package not in
+ # release" and "package not-affected"
+ reason = 'unknown'
+ elif data['status'] == 'resolved':
+ status = 'resolved'
+ reason = 'fixed in {}'.format(
+ self.data['releases'][release]['fixed_version'])
+ elif 'nodsa' in data:
+ status = 'ignored'
+ reason = 'no-dsa'
+ elif data['urgency'] == 'unimportant':
+ status = 'ignored'
+ reason = 'unimportant'
+ elif data['urgency'] == 'end-of-life':
+ status = 'ignored'
+ reason = 'unsupported'
+ else:
+ status = 'open'
+ reason = 'nobody fixed it yet'
+ return IssueStatus(status, reason)
More information about the Secure-testing-commits
mailing list