[doctor/master] Track when malicious relays return to the network

commit efa0ed43194bbc833ad91233586c40198070a94f Author: Damian Johnson <atagar@torproject.org> Date: Sat Feb 20 11:36:42 2016 -0800 Track when malicious relays return to the network When BadExit relays are removed we want to continue to monitor for their return for a time after. This is a script requested by David on... https://trac.torproject.org/projects/tor/ticket/18246 Basic script's done but as the TODO comments show there's still some missing bits. --- data/tracked_relays.cfg | 22 +++++++ fingerprint_change_checker.py | 5 +- track_relays.py | 136 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+), 4 deletions(-) diff --git a/data/tracked_relays.cfg b/data/tracked_relays.cfg new file mode 100644 index 0000000..e25959e --- /dev/null +++ b/data/tracked_relays.cfg @@ -0,0 +1,22 @@ +# Relays we want notifications for when they reappear. This can be done by +# address (single addresses or ranges) or fingerprints. +# +# Anything in this file is public. All entries *MUST* have a description and an +# expiration date for when it can be removed. The description is particularly +# important so we can puzzle out why we were monitoring for this if it +# reappears. +# +# Few examples... +# +# PrivacyPT.description running sslstrip on 2011-01-05 +# PrivacyPT.expires 2016-01-15 +# PrivacyPT.address 84.90.72.186 +# +# trotsky.description sybil consisting of 383 exit relays on 2010-10-02 +# trotsky.expires 2016-02-01 +# trotsky.address 185.19.80.0/26 +# +# Unnamed001.description harvesting hidden services on 2012-08-12 +# Unnamed001.expires 2016-03-01 +# Unnamed001.fingerprint 05AF83344B3787D0DCCD47DC4A6A4668142A5F8C + diff --git a/fingerprint_change_checker.py b/fingerprint_change_checker.py index 493f51c..e13f3d0 100755 --- a/fingerprint_change_checker.py +++ b/fingerprint_change_checker.py @@ -89,10 +89,7 @@ def main(): body += "\n" - try: - util.send(EMAIL_SUBJECT, body = body, to = ['bad-relays@lists.torproject.org', 'atagar@torproject.org']) - except Exception as exc: - log.warn("Unable to send email: %s" % exc) + util.send(EMAIL_SUBJECT, body = body, to = ['bad-relays@lists.torproject.org', 'atagar@torproject.org']) # register that we've notified for these diff --git a/track_relays.py b/track_relays.py new file mode 100755 index 0000000..e770006 --- /dev/null +++ b/track_relays.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# Copyright 2016, Damian Johnson and The Tor Project +# See LICENSE for licensing information + +""" +Notifies if specific relays reappear in the network. +""" + +import datetime +import traceback + +import stem.descriptor.remote +import stem.util.conf + +import util + +log = util.get_logger('track_relays') + +EMAIL_SUBJECT = 'Relays Returned' + +EMAIL_BODY = """\ +The following previously BadExit relays have returned to the network... + +""" + + +class TrackedRelay(object): + """ + Represents a relay we're keeping an eye on. + + :var str identifier: brief identifier given to the entry + :var str description: description of why we're tracking it + :var datetime expires: when this entry expires + :var str address: address of the relay we're tracking + :var str fingerprint: fingerprint of the relay we're tracking + """ + + def __init__(self, identifier, config): + self.identifier = identifier + self.description = config.get('%s.description' % identifier, '') + + expires_str = config.get('%s.expires' % identifier, '') + + if not expires_str: + raise ValueError("Our config file is missing a '%s.expires' entry" % identifier) + + try: + self.expires = datetime.datetime.strptime(expires_str, '%Y-%m-%d') + except ValueError: + raise ValueError("'%s.expires' is malformed. We expect it to be in the form 'Year-Month-Day'" % identifier) + + self.address = config.get('%s.address' % identifier, None) + self.fingerprint = config.get('%s.fingerprint' % identifier, None) + + if not self.address and not self.fingerprint: + raise ValueError("We need either a '%s.address' or '%s.fingerprint' to track" % (identifier, identifier)) + + def __str__(self): + attr = [] + + if self.address: + attr.append('address: %s' % self.address) + + if self.fingerprint: + attr.append('fingerprint: %s' % self.fingerprint) + + return '%s (%s)' % (self.identifier, ', '.join(attr)) + + +def get_tracked_relays(): + """ + Provides the relays we're tracking. + + :returns: **list** of **TrackedRelay** we're tracking + + :raises: **ValueError** if our config file is malformed + """ + + config = stem.util.conf.get_config('tracked_relays') + config.load(util.get_path('data', 'tracked_relays.cfg')) + + # TODO: check for expired entries + + identifiers = set([key.split('.')[0] for key in config.keys()]) + return [TrackedRelay(identifier, config) for identifier in identifiers] + + +def main(): + # Map addresses and fingerprints to relays for constant time lookups. Address + # ranges are handled separately cuz... well, they're a pita. + + tracked_addresses = {} + tracked_address_ranges = {} + tracked_fingerprints = {} + + for relay in get_tracked_relays(): + if relay.address: + if '/' in relay.address: + tracked_address_ranges[relay.address] = relay + else: + tracked_addresses[relay.address] = relay + + if relay.fingerprint: + tracked_fingerprints[relay.fingerprint] = relay + + downloader = stem.descriptor.remote.DescriptorDownloader() + found_relays = {} # mapping of TrackedRelay => RouterStatusEntry + + for desc in downloader.get_consensus(): + if desc.address in tracked_addresses: + found_relays[tracked_addresses[desc.address]] = desc + elif desc.fingerprint in tracked_fingerprints: + found_relays[tracked_fingerprints[desc.fingerprint]] = desc + else: + pass # TODO: implement for tracked_address_ranges + + if found_relays: + log.debug("Sending a notification for %i relay entries..." % len(found_relays)) + body = EMAIL_BODY + + for tracked_relay, desc in found_relays.items(): + log.debug('* %s' % tracked_relay) + body += '* %s (%s)\n' % (tracked_relay.identifier, tracked_relay.description) + body += ' address: %s\n' % desc.address + body += ' fingerprint: %s\n\n' % desc.fingerprint + + util.send(EMAIL_SUBJECT, body = body, to = ['bad-relays@lists.torproject.org', 'atagar@torproject.org']) + + +if __name__ == '__main__': + try: + main() + except: + msg = "track_relays.py failed with:\n\n%s" % traceback.format_exc() + log.error(msg) + util.send("Script Error", body = msg, to = [util.ERROR_ADDRESS])
participants (1)
-
atagar@torproject.org