diff --git a/chaos_monkey/control.py b/chaos_monkey/control.py index b4f2769cd..fef37de1f 100644 --- a/chaos_monkey/control.py +++ b/chaos_monkey/control.py @@ -1,3 +1,4 @@ +import base64 import json import logging import platform @@ -111,6 +112,21 @@ class ControlClient(object): LOG.warn("Error connecting to control server %s: %s", WormConfiguration.current_server, exc) + @staticmethod + def send_log(log): + if not WormConfiguration.current_server: + return + try: + telemetry = {'monkey_guid': GUID, 'log': base64.b64encode(log)} + reply = requests.post("https://%s/api/log" % (WormConfiguration.current_server,), + data=json.dumps(telemetry), + headers={'content-type': 'application/json'}, + verify=False, + proxies=ControlClient.proxies) + except Exception as exc: + LOG.warn("Error connecting to control server %s: %s", + WormConfiguration.current_server, exc) + @staticmethod def load_control_config(): if not WormConfiguration.current_server: diff --git a/chaos_monkey/main.py b/chaos_monkey/main.py index c53232b2c..ef57492cc 100644 --- a/chaos_monkey/main.py +++ b/chaos_monkey/main.py @@ -12,6 +12,7 @@ from config import WormConfiguration, EXTERNAL_CONFIG_FILE from dropper import MonkeyDrops from model import MONKEY_ARG, DROPPER_ARG from monkey import ChaosMonkey +import utils if __name__ == "__main__": sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) @@ -78,12 +79,10 @@ def main(): try: if MONKEY_ARG == monkey_mode: - log_path = os.path.expandvars( - WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" else WormConfiguration.monkey_log_path_linux + log_path = utils.get_monkey_log_path() monkey_cls = ChaosMonkey elif DROPPER_ARG == monkey_mode: - log_path = os.path.expandvars( - WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" else WormConfiguration.dropper_log_path_linux + log_path = utils.get_dropper_log_path() monkey_cls = MonkeyDrops else: return True @@ -91,6 +90,8 @@ def main(): return True if WormConfiguration.use_file_logging: + if os.path.exists(log_path): + os.remove(log_path) LOG_CONFIG['handlers']['file']['filename'] = log_path LOG_CONFIG['root']['handlers'].append('file') else: diff --git a/chaos_monkey/monkey.py b/chaos_monkey/monkey.py index 79012dc39..79e8bf3ec 100644 --- a/chaos_monkey/monkey.py +++ b/chaos_monkey/monkey.py @@ -6,6 +6,7 @@ import sys import time import tunnel +import utils from config import WormConfiguration from control import ControlClient from model import DELAY_DELETE_CMD @@ -226,6 +227,8 @@ class ChaosMonkey(object): firewall.close() + self.send_log() + self._singleton.unlock() if WormConfiguration.self_delete_in_cleanup and -1 == sys.executable.find('python'): @@ -244,3 +247,13 @@ class ChaosMonkey(object): LOG.error("Exception in self delete: %s", exc) LOG.info("Monkey is shutting down") + + def send_log(self): + monkey_log_path = utils.get_monkey_log_path() + if os.path.exists(monkey_log_path): + with open(monkey_log_path, 'r') as f: + log = f.read() + else: + log = '' + + ControlClient.send_log(log) diff --git a/chaos_monkey/utils.py b/chaos_monkey/utils.py new file mode 100644 index 000000000..d95407341 --- /dev/null +++ b/chaos_monkey/utils.py @@ -0,0 +1,14 @@ +import os +import sys + +from config import WormConfiguration + + +def get_monkey_log_path(): + return os.path.expandvars(WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" \ + else WormConfiguration.monkey_log_path_linux + + +def get_dropper_log_path(): + return os.path.expandvars(WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" \ + else WormConfiguration.dropper_log_path_linux diff --git a/monkey_island/cc/app.py b/monkey_island/cc/app.py index 9c85f6230..2d8041eb0 100644 --- a/monkey_island/cc/app.py +++ b/monkey_island/cc/app.py @@ -1,22 +1,24 @@ from datetime import datetime + import bson -from bson.json_util import dumps -from flask import Flask, send_from_directory, redirect, make_response import flask_restful +from bson.json_util import dumps +from flask import Flask, send_from_directory, make_response from werkzeug.exceptions import NotFound from cc.database import mongo from cc.resources.client_run import ClientRun -from cc.resources.monkey import Monkey +from cc.resources.edge import Edge from cc.resources.local_run import LocalRun -from cc.resources.telemetry import Telemetry +from cc.resources.log import Log +from cc.resources.monkey import Monkey from cc.resources.monkey_configuration import MonkeyConfiguration from cc.resources.monkey_download import MonkeyDownload from cc.resources.netmap import NetMap -from cc.resources.edge import Edge from cc.resources.node import Node from cc.resources.report import Report from cc.resources.root import Root +from cc.resources.telemetry import Telemetry from cc.resources.telemetry_feed import TelemetryFeed from cc.services.config import ConfigService @@ -91,5 +93,6 @@ def init_app(mongo_url): api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/') api.add_resource(Report, '/api/report', '/api/report/') api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/') + api.add_resource(Log, '/api/log', '/api/log/') return app diff --git a/monkey_island/cc/resources/log.py b/monkey_island/cc/resources/log.py new file mode 100644 index 000000000..5a308b5dd --- /dev/null +++ b/monkey_island/cc/resources/log.py @@ -0,0 +1,29 @@ +import json + +import flask_restful +from bson import ObjectId +from flask import request + +from cc.database import mongo +from cc.services.log import LogService +from cc.services.node import NodeService + +__author__ = "itay.mizeretz" + + +class Log(flask_restful.Resource): + def get(self): + monkey_id = request.args.get('id') + exists_monkey_id = request.args.get('exists') + if monkey_id: + return LogService.get_log_by_monkey_id(ObjectId(monkey_id)) + else: + return LogService.log_exists(ObjectId(exists_monkey_id)) + + def post(self): + telemetry_json = json.loads(request.data) + + monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])['_id'] + log_id = LogService.add_log(monkey_id, telemetry_json['log']) + + return mongo.db.log.find_one_or_404({"_id": log_id}) diff --git a/monkey_island/cc/resources/root.py b/monkey_island/cc/resources/root.py index 25d7dfed7..f725163aa 100644 --- a/monkey_island/cc/resources/root.py +++ b/monkey_island/cc/resources/root.py @@ -33,7 +33,8 @@ class Root(flask_restful.Resource): @staticmethod def reset_db(): - [mongo.db[x].drop() for x in ['config', 'monkey', 'telemetry', 'node', 'edge', 'report']] + [mongo.db[x].drop() for x in + ['config', 'monkey', 'telemetry', 'node', 'edge', 'report', 'log', 'fs.chunks', 'fs.files']] ConfigService.init_config() return jsonify(status='OK') diff --git a/monkey_island/cc/services/log.py b/monkey_island/cc/services/log.py new file mode 100644 index 000000000..2783a1dfa --- /dev/null +++ b/monkey_island/cc/services/log.py @@ -0,0 +1,52 @@ +from datetime import datetime + +import gridfs + +import cc.services.node +from cc.database import mongo + +__author__ = "itay.mizeretz" + + +class LogService: + def __init__(self): + pass + + @staticmethod + def get_log_by_monkey_id(monkey_id): + log = mongo.db.log.find_one({'monkey_id': monkey_id}) + if log: + fs = gridfs.GridFS(mongo.db) + log_file = fs.get(log['file_id']) + monkey_label = cc.services.node.NodeService.get_monkey_label( + cc.services.node.NodeService.get_monkey_by_id(log['monkey_id'])) + return \ + { + 'monkey_label': monkey_label, + 'log': log_file.read(), + 'timestamp': log['timestamp'] + } + + @staticmethod + def remove_logs_by_monkey_id(monkey_id): + fs = gridfs.GridFS(mongo.db) + for log in mongo.db.log.find({'monkey_id': monkey_id}): + fs.delete(log['file_id']) + mongo.db.log.delete_many({'monkey_id': monkey_id}) + + @staticmethod + def add_log(monkey_id, log_data, timestamp=datetime.now()): + LogService.remove_logs_by_monkey_id(monkey_id) + fs = gridfs.GridFS(mongo.db) + file_id = fs.put(log_data) + return mongo.db.log.insert( + { + 'monkey_id': monkey_id, + 'file_id': file_id, + 'timestamp': timestamp + } + ) + + @staticmethod + def log_exists(monkey_id): + return mongo.db.log.find_one({'monkey_id': monkey_id}) is not None diff --git a/monkey_island/cc/services/node.py b/monkey_island/cc/services/node.py index 47cfba8d9..47cd9cd21 100644 --- a/monkey_island/cc/services/node.py +++ b/monkey_island/cc/services/node.py @@ -1,9 +1,12 @@ from datetime import datetime, timedelta + from bson import ObjectId +import cc.services.log from cc.database import mongo from cc.services.edge import EdgeService from cc.utils import local_ip_addresses + __author__ = "itay.mizeretz" @@ -54,6 +57,7 @@ class NodeService: else: new_node["services"] = [] + new_node['has_log'] = cc.services.log.LogService.log_exists(ObjectId(node_id)) return new_node @staticmethod @@ -241,7 +245,7 @@ class NodeService: @staticmethod def get_monkey_island_pseudo_net_node(): - return\ + return \ { "id": NodeService.get_monkey_island_pseudo_id(), "label": "MonkeyIsland",