| |
| |
| |
| |
|
|
| import netaddr |
| import os |
| import random |
| import requests |
| from configlib import getConfig, OptionParser |
| from datetime import datetime, timedelta |
| from pymongo import MongoClient |
| from mozdef_util.utilities.logger import logger |
|
|
|
|
| def isIPv4(ip): |
| try: |
| |
| |
| |
| if '.' in ip and len(ip.split('.'))==4: |
| |
| netaddr.IPNetwork(ip.strip("'").strip('"')) |
| return True |
| else: |
| return False |
| except: |
| return False |
|
|
|
|
| def isIPv6(ip): |
| try: |
| return netaddr.valid_ipv6(ip) |
| except: |
| return False |
|
|
|
|
| def genMeteorID(): |
| return('%024x' % random.randrange(16**24)) |
|
|
|
|
| class message(object): |
| def __init__(self): |
| '''register our criteria for being passed a message |
| as a list of lower case strings to match with an rest endpoint |
| (i.e. blockip matches /blockip) |
| set the priority if you have a preference for order of plugins |
| 0 goes first, 100 is assumed/default if not sent |
| |
| Plugins will register in Meteor with attributes: |
| name: (as below) |
| description: (as below) |
| priority: (as below) |
| file: "plugins.filename" where filename.py is the plugin code. |
| |
| Plugin gets sent main rest options as: |
| self.restoptions |
| self.restoptions['configfile'] will be the .conf file |
| used by the restapi's index.py file. |
| |
| ''' |
|
|
| self.registration = ['blockip'] |
| self.priority = 10 |
| self.name = "IPBlockList" |
| self.description = "IP Block LIst" |
|
|
| |
| |
| self.configfile = './plugins/ipblocklist.conf' |
| self.options = None |
| if os.path.exists(self.configfile): |
| logger.debug('found conf file {0}\n'.format(self.configfile)) |
| self.initConfiguration() |
|
|
| def parse_network_whitelist(self, network_whitelist_location): |
| networks = [] |
| with open(network_whitelist_location, "r") as text_file: |
| for line in text_file: |
| line = line.strip().strip("'").strip('"') |
| if isIPv4(line) or isIPv6(line): |
| networks.append(line) |
| return networks |
|
|
| def initConfiguration(self): |
| myparser = OptionParser() |
| |
| (self.options, args) = myparser.parse_args([]) |
|
|
| |
| self.options.mongohost = getConfig( |
| 'mongohost', |
| 'localhost', |
| self.configfile) |
| self.options.mongoport = getConfig( |
| 'mongoport', |
| 3001, |
| self.configfile) |
|
|
| self.options.contact = getConfig('contact','',self.configfile) |
|
|
| |
| self.options.network_whitelist_file = getConfig('network_whitelist_file', '/dev/null', self.configfile) |
|
|
| |
| whitelist_networks = getConfig('whitelist_networks', '', self.configfile) |
| self.options.whitelist_networks = whitelist_networks.split(',') |
|
|
| |
| self.options.statuspage_api_key = getConfig( |
| 'statuspage_api_key', |
| '', |
| self.configfile) |
| self.options.statuspage_page_id = getConfig( |
| 'statuspage_page_id', |
| '', |
| self.configfile) |
| self.options.statuspage_url = 'https://api.statuspage.io/v1/pages/{0}/incidents.json'.format( |
| self.options.statuspage_page_id) |
| self.options.statuspage_component_id = getConfig( |
| 'statuspage_component_id', |
| '', |
| self.configfile) |
| self.options.statuspage_sub_component_id = getConfig( |
| 'statuspage_sub_component_id', |
| '', |
| self.configfile) |
|
|
| def blockIP(self, |
| ipaddress=None, |
| comment=None, |
| duration=None, |
| referenceID=None, |
| userID=None |
| ): |
| try: |
| |
| mongoclient = MongoClient(self.options.mongohost, self.options.mongoport) |
| ipblocklist = mongoclient.meteor['ipblocklist'] |
|
|
| |
| if (isIPv6(ipaddress) or isIPv4(ipaddress)) and (ipaddress not in netaddr.IPSet(['0.0.0.0'])): |
| ipcidr = netaddr.IPNetwork(ipaddress) |
|
|
| |
| ipblock = ipblocklist.find_one({'address': str(ipcidr)}) |
| |
| end_date = datetime.utcnow() + timedelta(hours=1) |
| if duration == '12hr': |
| end_date = datetime.utcnow() + timedelta(hours=12) |
| elif duration == '1d': |
| end_date = datetime.utcnow() + timedelta(days=1) |
| elif duration == '2d': |
| end_date = datetime.utcnow() + timedelta(days=2) |
| elif duration == '3d': |
| end_date = datetime.utcnow() + timedelta(days=3) |
| elif duration == '1w': |
| end_date = datetime.utcnow() + timedelta(days=7) |
| elif duration == '30d': |
| end_date = datetime.utcnow() + timedelta(days=30) |
|
|
| if ipblock is None: |
| |
| ipblock = dict() |
| ipblock['_id'] = genMeteorID() |
| |
| |
| ipblock['address'] = str(ipcidr) |
| ipblock['dateAdded'] = datetime.utcnow() |
| ipblock['dateExpiring'] = end_date |
| ipblock['comment'] = comment |
| ipblock['creator'] = userID |
| ipblock['reference'] = referenceID |
| ref = ipblocklist.insert(ipblock) |
| logger.debug('{0} written to db\n'.format(ref)) |
| logger.debug('%s: added to the ipblocklist table\n' % (ipaddress)) |
|
|
| |
| if len(self.options.statuspage_api_key) > 1: |
| try: |
| headers = {'Authorization': 'Oauth {0}'.format(self.options.statuspage_api_key)} |
| |
| |
| post_data = { |
| 'incident[name]': 'block IP {}'.format(str(ipcidr)), |
| 'incident[status]': 'resolved', |
| 'incident[impact_override]': 'none', |
| 'incident[body]': '{} initiated a block of IP {} until {}{}'.format( |
| userID, |
| str(ipcidr), |
| end_date.isoformat(), |
| self.options.contact), |
| 'incident[component_ids][]': self.options.statuspage_sub_component_id, |
| 'incident[components][{0}]'.format(self.options.statuspage_component_id): "operational" |
| } |
| response = requests.post(self.options.statuspage_url, |
| headers=headers, |
| data=post_data) |
| if response.ok: |
| logger.debug('%s: notification sent to statuspage.io\n' % (str(ipcidr))) |
| else: |
| logger.error('%s: statuspage.io notification failed %s\n' % (str(ipcidr), response.json())) |
| except Exception as e: |
| logger.error('Error while notifying statuspage.io for %s: %s\n' % (str(ipcidr), e)) |
| else: |
| logger.debug('%s: is already present in the ipblocklist table...updating\n' % (str(ipcidr))) |
| |
| ipblock['dateExpiring'] = end_date |
| ipblock['comment'] = comment |
| ipblocklist.replace_one({'_id': ipblock['_id']}, ipblock) |
| else: |
| logger.error('%s: is not a valid ip address\n' % (ipaddress)) |
| except Exception as e: |
| logger.exception('Error while blocking %s: %s\n' % (ipaddress, e)) |
|
|
| def onMessage(self, request, response): |
| ''' |
| request: http://bottlepy.org/docs/dev/api.html#the-request-object |
| response: http://bottlepy.org/docs/dev/api.html#the-response-object |
| |
| ''' |
| response.headers['X-PLUGIN'] = self.description |
|
|
| |
| self.options.ipwhitelist = self.parse_network_whitelist(self.options.network_whitelist_file) |
| for whitelist_value in self.options.whitelist_networks: |
| self.options.ipwhitelist.append(whitelist_value) |
|
|
| ipaddress = None |
| comment = None |
| duration = None |
| referenceID = None |
| userid = None |
| blockip = False |
|
|
| try: |
| |
| |
| for field in request.json: |
| |
| if self.name in field: |
| blockip = field[self.name] |
| if 'ipaddress' in field: |
| ipaddress = field['ipaddress'] |
| if 'duration' in field: |
| duration = field['duration'] |
| if 'comment' in field: |
| comment = field['comment'] |
| if 'referenceid' in field: |
| referenceID = field['referenceid'] |
| if 'userid' in field: |
| userid = field['userid'] |
|
|
| if blockip and ipaddress is not None: |
| |
| if isIPv4(ipaddress) or isIPv6(ipaddress): |
| ipcidr = netaddr.IPNetwork(ipaddress) |
| if not ipcidr.ip.is_loopback() \ |
| and not ipcidr.ip.is_private() \ |
| and not ipcidr.ip.is_reserved(): |
|
|
| whitelisted = False |
| for whitelist_range in self.options.ipwhitelist: |
| whitelist_network = netaddr.IPNetwork(whitelist_range) |
| if ipcidr in whitelist_network: |
| whitelisted = True |
| logger.debug('{0} is whitelisted as part of {1}\n'.format(ipcidr, whitelist_network)) |
|
|
| if not whitelisted: |
| self.blockIP(str(ipcidr), |
| comment, |
| duration, |
| referenceID, |
| userid) |
| logger.info('added {0} to blocklist\n'.format(ipaddress)) |
| else: |
| logger.info('not adding {0} to blocklist, it was found in whitelist\n'.format(ipaddress)) |
| except Exception as e: |
| logger.error('Error handling request.json %r \n' % (e)) |
|
|
| return (request, response) |
|
|