diff --git a/chaos_monkey/monkey.py b/chaos_monkey/monkey.py index be7184ad3..482211b81 100644 --- a/chaos_monkey/monkey.py +++ b/chaos_monkey/monkey.py @@ -162,6 +162,9 @@ class ChaosMonkey(object): break else: LOG.info("Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__) + ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__, + 'exploiter': exploiter.__class__.__name__}) + except Exception, exc: LOG.error("Exception while attacking %s using %s: %s", machine, exploiter.__class__.__name__, exc) @@ -169,7 +172,7 @@ class ChaosMonkey(object): if successful_exploiter: self._exploited_machines.add(machine) - ControlClient.send_telemetry('exploit', {'machine': machine.__dict__, + ControlClient.send_telemetry('exploit', {'result': True, 'machine': machine.__dict__, 'exploiter': successful_exploiter.__class__.__name__}) LOG.info("Successfully propagated to %s using %s", diff --git a/monkey_business/readme.txt b/monkey_business/readme.txt index 6f456df9e..b00d16469 100644 --- a/monkey_business/readme.txt +++ b/monkey_business/readme.txt @@ -1,4 +1,45 @@ -dependencies: -sudo pip install pyVmomi -sudo pip install celery -sudo pip install -U celery[mongodb] \ No newline at end of file +How to install Monkey Business server: + +---------------- On Linux ----------------: +1. Create the following directories: + sudo mkdir /var/monkey_business + sudo chmod 777 /var/monkey_business + mkdir -p /var/monkey_business/bin/mongodb + mkdir -p /var/monkey_business/db + mkdir -p /var/monkey_business/cc + +2. Install the following packages: + sudo pip install flask + sudo pip install Flask-Pymongo + sudo pip install Flask-Restful + sudo pip install python-dateutil + sudo pip install pyVmomi + sudo pip install celery + sudo pip install -U celery[mongodb] + +4. Download MongoDB and extract it to /var/monkey_business/bin/mongodb + for debian64 - https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-debian71-3.0.7.tgz + for ubuntu64 14.10 - https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1410-clang-3.0.7.tgz + find more at - https://www.mongodb.org/downloads#production + untar.gz with: tar -zxvf filename.tar.gz -C /var/monkey_business/bin/mongodb + (make sure the content of the mongo folder is in this directory, meaning this path exists: + /var/monkey_business/bin/mongodb/bin) + +5. install OpenSSL + sudo apt-get install openssl + +6. Generate SSL Certificate, Run create_certificate.sh (located under /linux) + +7. Copy monkey business server to /var/monkey_business: + cp -r [monkey_island_source]/cc /var/monkey_business/ + + +How to run: +1. run run.sh + * This performs: + DB startup: + /var/monkey_business/bin/mongodb/bin/mongod --dbpath db --fork --logpath db.log + Jobs worker startup: + nohup celery -A tasks_manager worker --loglevel=info + Main Web Server startup: + nohup python main.py \ No newline at end of file diff --git a/monkey_island/cc/admin/ui/index.html b/monkey_island/cc/admin/ui/index.html index d1c79e1d5..c2a8eb2a1 100644 --- a/monkey_island/cc/admin/ui/index.html +++ b/monkey_island/cc/admin/ui/index.html @@ -83,8 +83,9 @@ </div> <div id="info" class="panel-body panel-collapse collapse in"> <div> - Num of Monkeys: <label id="infoNumOfMonkeys">0</label> (<label id="infoNumOfParents">0</label> by exploiting)<br/> - Num of Hosts Detected: <label id="infoNumOfHosts">0</label><br/> + Num of Monkeys: <label id="infoNumOfMonkeys">0</label> (<label id="infoNumOfParents">0</label> exploiting were done)<br/> + Monkyes Alive: <label id="infoNumOfAlive">0</label><br/> + Num of Hosts Not Exploited: <label id="infoNumOfHosts">0</label><br/> Num of Tunnels Used: <label id="infoNumOfTunnels">0</label><br/> </div> <div> @@ -132,7 +133,7 @@ </div> <hr> <div id="selectionInfo"> - <label>No selection</label> + <label>Monkey not selected</label> </div> <hr> <div class="panel panel-default"> @@ -188,6 +189,24 @@ </div> <!-- /.Config section --> + <!-- Config section --> + <div class="col-lg-3 col-md-6 col-sm-6"> + <div class="panel panel-default"> + <div class="panel-heading"> + <a href="#reset" data-toggle="collapse">Reset Database</a> + </div> + <div id="reset" style="overflow: visible" class="panel-body panel-collapse collapse" aria-expanded="true"> + <span class="input-group-btn"> + <button id="btnResetDB" class="btn btn-default" type="button" + onclick="resetDB()" style="margin-top:-4px"> + Reset Database + </button> + </span> + </div> + </div> + </div> + <!-- /.Config section --> + </div> </body> diff --git a/monkey_island/cc/admin/ui/js/monkeys-admin.js b/monkey_island/cc/admin/ui/js/monkeys-admin.js index b98edc16a..c58b4f54e 100644 --- a/monkey_island/cc/admin/ui/js/monkeys-admin.js +++ b/monkey_island/cc/admin/ui/js/monkeys-admin.js @@ -62,12 +62,13 @@ function initAdmin() { nodes = []; edges = []; + createNodes(); createEdges(); createTunnels(); createScanned(); var data = { - nodes: createNodes(), + nodes: nodes, edges: edges }; @@ -196,8 +197,19 @@ function updateMonkeys() { else { monkeys.push(new_monkeys[i]); - nodes.push(createMonkeyNode(new_monkeys[i])); - updateCounters(); + var exiting_scan = undefined; + for (var j=0; j<new_monkeys[i].ip_addresses.length; j++) { + exiting_scan = getScannedByIP(new_monkeys[i].ip_addresses[j]); + if (exiting_scan != undefined) { + break; + } + } + if (exiting_scan == undefined) { + nodes.push(createMonkeyNode(new_monkeys[i])); + } + else { + convertScanNodeToMonkey(exiting_scan, new_monkeys[i]); + } } } @@ -208,6 +220,7 @@ function updateMonkeys() { refreshDrawing(); } createScanned(); + updateCounters(); }); } @@ -238,9 +251,16 @@ function createMonkeyNode(monkey) { } img = ICONS_DIR + img + ICONS_EXT; - if (monkey.parent == monkey.guid) { + if (monkey.parent == null) { font = { color: 'red' }; } + else { + for (var i=0; i<monkey.parent.length; i++) { + if (monkey.parent[i][1] == null) { + font = { color: 'red' }; + } + } + } return { 'id': monkey.id, @@ -283,15 +303,52 @@ function createMachineNode(machine) { }; } +function convertScanNodeToMonkey(scanned, monkey) { + var monNode = createMonkeyNode(monkey); + nodes.push(monNode); + + // move edges to new node + for (var i = 0; i < edges.length; i++) { + if (edges[i].to == scanned.id) { + edges[i].to = monNode.id; + } + if (edges[i].from == scanned.id) { + edges[i].from = monNode.id; + } + } + for (var i=0; i<scannedMachines.length; i++) { + if (scannedMachines[i].id == scanned.id) { + scannedMachines.splice(i, 1); + break; + } + } + for (var i=0; i<nodes.length; i++) { + if (nodes[i].id == scanned.id) { + nodes.splice(i, 1); + break; + } + } +} + function createEdges() { for (var i = 0; i < monkeys.length; i++) { var monkey = monkeys[i]; - if(monkey.parent != monkey.guid) { - var parent = getMonkeyByGuid(monkey.parent); - if(parent && !edgeExists([parent.id, monkey.id, EDGE_TYPE_PARENT])) { - edges.push({from: parent.id, to: monkey.id, arrows:'middle', type: EDGE_TYPE_PARENT, color: EDGE_COLOR_PARENT}); - numOfParentLinks++; + if (monkey.parent == null) { continue; }; + + for (var j=0; j<monkey.parent.length; j++) { + if(monkey.parent[j][0] != monkey.guid) { + var parent = getMonkeyByGuid(monkey.parent[j][0]); + var exploit = monkey.parent[j][1]; + + if(parent && !edgeExists([parent.id, monkey.id, EDGE_TYPE_PARENT])) { + var title = "<center><b>" + exploit + "</b></center>From: " + parent.hostname + "<br/>To: " + monkey.hostname; + edges.push({from: parent.id, to: monkey.id, arrows:'middle', type: EDGE_TYPE_PARENT, title: title, /*label: exploit, font: {color: 'red', size: 10, align: 'top'},*/ color: EDGE_COLOR_PARENT}); + if (removeEdge([parent.id, monkey.id, EDGE_TYPE_SCAN])) { + numOfScanLinks--; + } + numOfParentLinks++; + } } } } @@ -327,24 +384,20 @@ function createScanned() { var scan = scans[i]; var monkey = getMonkeyByGuid(scan.monkey_guid); - //Check if we already exploited this machine from another PoV, if so no point in scanning. - if (null != getMonkeyByIP(scan.data.machine.ip_addr)) { - //if so, make sure we don't already have such a node - nodes = nodes.filter(function (node) { - return (node.id != ip_addr); - }); - continue; - } + // And check if we've already added this scanned machine + var machineNode = getMonkeyByIP(scan.data.machine.ip_addr); - //And check if we've already added this scanned machine - var machineNode = getScannedByIP(scan.data.machine.ip_addr) if (null == machineNode) { - machineNode = createMachineNode(scan.data.machine); - scannedMachines.push(machineNode); - nodes.push(machineNode); + machineNode = getScannedByIP(scan.data.machine.ip_addr); + + if (null == machineNode) { + machineNode = createMachineNode(scan.data.machine); + scannedMachines.push(machineNode); + nodes.push(machineNode); + } } - if(!edgeExists([monkey.id, machineNode.id, EDGE_TYPE_SCAN])) { + if(!edgeExists([monkey.id, machineNode.id, EDGE_TYPE_SCAN]) && !edgeExists([monkey.id, machineNode.id, EDGE_TYPE_PARENT])) { edges.push({from: monkey.id, to: machineNode.id, arrows:'middle', type: EDGE_TYPE_SCAN, color: EDGE_COLOR_SCAN}); numOfScanLinks++; } @@ -372,10 +425,34 @@ function buildMonkeyDescription(monkey) { } html += "<label>Last Seen:</label> " + monkey.keepalive + "</br>" + - "<label>IP Address:</label></br>"; + "<label>IP Address:</label><br/>"; + html += "<ul>"; for (var i = 0; i < monkey.ip_addresses.length; i++) { - html += monkey.ip_addresses[i] + "</br>" + html += "<li>" + monkey.ip_addresses[i]; + } + html += "</ul>"; + + + if (monkey.parent != null) { + html += "<label>Exploited by:</label><br/>" + html += "<ul>"; + for (var i = 0; i < monkey.parent.length; i++) { + html += "<li>"; + if (monkey.parent[i][0] == monkey.guid) { + html += "Manual Run<br/>"; + } + else { + parent = getMonkeyByGuid(monkey.parent[i][0]); + if (!parent) { html += "Unknown Source"; continue; } + + html += parent.hostname + " ("; + if (monkey.parent[i][1] == null) {html += "Unknown"} + else {html += monkey.parent[i][1];} + html += ")"; + } + } + html += "</ul>"; } return html; @@ -386,6 +463,11 @@ function updateCounters() { $('#infoNumOfHosts').html(scannedMachines.length); $('#infoNumOfParents').html(numOfParentLinks); $('#infoNumOfTunnels').html(numOfTunnelLinks); + var numOfAlive = monkeys.length; + for (var i=0;i<monkeys.length;i++) { + if (monkeys[i].dead) {numOfAlive--;} + } + $('#infoNumOfAlive').html(numOfAlive); } @@ -451,22 +533,22 @@ function onDoubleClick(properties) { */ function onSelect(properties) { - if (properties.nodes.length > 0) { + if ((properties.nodes.length > 0) && getMonkey(properties.nodes[0])){ onNodeSelect(properties.nodes); } else { - var content = "<b>No selection</b>" + var content = "<b>Monkey not selected</b>" $("#selectionInfo").html(content); $('#monkey-config').hide() $('#btnConfigLoad, #btnConfigUpdate').hide(); $('#monkey-enabled').hide(); telemTable.clear(); telemTable.draw(); - } - if (properties.edges.length > 0) { - onEdgeSelect(properties.edges); + if (properties.edges.length > 0) { + onEdgeSelect(properties.edges); + } } } @@ -483,33 +565,32 @@ function onNodeSelect(nodeId) { if (monkey) { htmlContent = buildMonkeyDescription(monkey); $("#monkeySearch").val(monkey.hostname); + $("#selectionInfo").html(htmlContent); + $('#monkey-config').show() + $('#btnConfigLoad, #btnConfigUpdate').show(); + + loadMonkeyConfig(); + + if (monkey.config.alive) { + $("[name='chboxMonkeyEnabled']").bootstrapSwitch('state', true, true); + } + else { + $("[name='chboxMonkeyEnabled']").bootstrapSwitch('state', false, true); + } + $('#monkey-enabled').show(); + + $.getJSON('/api/telemetry?monkey_guid=' + monkey.guid, function(json) { + telemTable.clear(); + var telemetries = json.objects; + + for (var i = 0; i < telemetries.length; i++) { + telemTable.row.add([telemetries[i].timestamp, telemetries[i].telem_type, JSON.stringify(telemetries[i].data)]); + } + + telemTable.draw(); + }); } - $("#selectionInfo").html(htmlContent); - $('#monkey-config').show() - $('#btnConfigLoad, #btnConfigUpdate').show(); - - loadMonkeyConfig(); - - if (monkey.config.alive) { - $("[name='chboxMonkeyEnabled']").bootstrapSwitch('state', true); - } - else { - $("[name='chboxMonkeyEnabled']").bootstrapSwitch('state', false); - } - $('#monkey-enabled').show(); - - $.getJSON('/api/telemetry?monkey_guid=' + monkey.guid, function(json) { - telemTable.clear(); - var telemetries = json.objects; - - for (var i = 0; i < telemetries.length; i++) { - telemTable.row.add([telemetries[i].timestamp, telemetries[i].telem_type, JSON.stringify(telemetries[i].data)]); - } - - telemTable.draw(); - }); - network.selectNodes([nodeId]); } @@ -518,7 +599,32 @@ function onNodeSelect(nodeId) { */ function onEdgeSelect(edge) { var edge = getEdge(edge); + var monkey = getMonkey(edge.from); + if (!monkey) {return;}; + var target = undefined; + if (edge.type == 'scan') { + target = getScannedByIP(edge.to) + } + else { + target = getMonkey(edge.to) + } + + $.getJSON(jsonFileTelemetry + '?monkey_guid=' + monkey.guid, function(json) { + telemTable.clear(); + var telemetries = json.objects; + + for (var i = 0; i < telemetries.length; i++) { + var telem = telemetries[i] + if (telem.telem_type == 'scan' || telem.telem_type == 'exploit') { + if (((edge.type == 'scan') && (telem.data.machine.ip_addr == target.id)) || + ((edge.type == 'parent') && (0 <= $.inArray(telem.data.machine.ip_addr, target.ip_addresses)))) { + telemTable.row.add([telemetries[i].timestamp, telemetries[i].telem_type, JSON.stringify(telemetries[i].data)]); + } + } + } + telemTable.draw(); + }); } function toggleMonkeyEnabled(event, state) { @@ -694,8 +800,17 @@ function resetDB() { url : '/api?action=reset', type : 'GET', success : function(response, textStatus, jqXhr) { - console.log("DB was successfully reset!"); - location.reload(); + console.log(response); + if (response.status != 'OK') { + BootstrapDialog.show({ + title: "Reset DB", + message: "The following error occured: " + response.reason + }); + } + else { + console.log("DB was successfully reset!"); + location.reload(); + } }, error : function(jqXHR, textStatus, errorThrown) { // log the error to the console @@ -728,24 +843,24 @@ function getMonkeyByGuid(guid) { if (monkeys[i].guid == guid) { return monkeys[i]; } - } + } + return null; } function getMonkeyByIP(ip) { - for (var i = 0; i < monkeys.length; i++) { + for (var i = 0; i < monkeys.length; i++) { var monkey = monkeys[i]; - for (var j = 0; j< monkey.ip_addresses; j++) { - if (monkeys[i].ip == ip) { - return monkeys[i]; + for (var j = 0; j< monkey.ip_addresses.length; j++) { + if (monkey.ip_addresses[j] == ip) { + return monkey; } } } return null; } -function getScannedByIP(ip) -{ - for (var i = 0; i < scannedMachines.length; i++) { +function getScannedByIP(ip) { + for (var i = 0; i < scannedMachines.length; i++) { var machine = scannedMachines[i]; if (machine.id == ip) { return machine @@ -801,13 +916,23 @@ function edgeExists(link) { var to = edges[i].to; var type = edges[i].type; if (from == link[0] && to == link[1] && type == link[2]) { - return true; + return edges[i]; } } } - - +function removeEdge(link) { + for (var i = 0; i < edges.length; i++) { + var from = edges[i].from; + var to = edges[i].to; + var type = edges[i].type; + if (from == link[0] && to == link[1] && type == link[2]) { + edges.splice(i, 1); + return true; + } + } + return false; +} /** * Clears the value in the local storage diff --git a/monkey_island/cc/main.py b/monkey_island/cc/main.py index 97799c303..ea4c55b35 100644 --- a/monkey_island/cc/main.py +++ b/monkey_island/cc/main.py @@ -5,7 +5,7 @@ from flask.ext.pymongo import PyMongo from flask import make_response import bson.json_util import json -from datetime import datetime +from datetime import datetime, timedelta import dateutil.parser MONKEY_DOWNLOADS = [ @@ -50,6 +50,7 @@ mongo = PyMongo(app) class Monkey(restful.Resource): def get(self, guid=None, **kw): + update_dead_monkeys() # refresh monkeys status if not guid: guid = request.args.get('guid') timestamp = request.args.get('timestamp') @@ -59,7 +60,7 @@ class Monkey(restful.Resource): else: result = {'timestamp': datetime.now().isoformat()} find_filter = {} - if None != timestamp: + if timestamp is not None: find_filter['modifytime'] = {'$gt': dateutil.parser.parse(timestamp)} result['objects'] = [x for x in mongo.db.monkey.find(find_filter)] return result @@ -67,7 +68,7 @@ class Monkey(restful.Resource): def patch(self, guid): monkey_json = json.loads(request.data) update = {"$set": {'modifytime': datetime.now()}} - + if monkey_json.has_key('keepalive'): update['$set']['keepalive'] = dateutil.parser.parse(monkey_json['keepalive']) else: @@ -76,7 +77,7 @@ class Monkey(restful.Resource): update['$set']['config'] = monkey_json['config'] if monkey_json.has_key('tunnel'): update['$set']['tunnel'] = monkey_json['tunnel'] - + return mongo.db.monkey.update({"guid": guid}, update, upsert=False) def post(self, **kw): @@ -88,7 +89,7 @@ class Monkey(restful.Resource): monkey_json['modifytime'] = datetime.now() - # if new monkey, change config according to "new monkeys" config. + # if new monkey telem, change config according to "new monkeys" config. db_monkey = mongo.db.monkey.find_one({"guid": monkey_json["guid"]}) if not db_monkey: new_config = mongo.db.config.find_one({'name': 'newconfig'}) or {} @@ -99,18 +100,31 @@ class Monkey(restful.Resource): if db_config.has_key('current_server'): del db_config['current_server'] monkey_json.get('config', {}).update(db_config) - - if not monkey_json.has_key('parent') and db_monkey.get('parent'): - monkey_json['parent'] = db_monkey.get('parent') # try to find new monkey parent parent = monkey_json.get('parent') - if (not parent or parent == monkey_json.get('guid')) and monkey_json.has_key('ip_addresses'): + parent_to_add = (monkey_json.get('guid'), None) # default values in case of manual run + if parent and parent != monkey_json.get('guid'): # current parent is known exploit_telem = [x for x in - mongo.db.telemetry.find({'telem_type': {'$eq': 'exploit'}, 'data.machine.ip_addr': - {'$in': monkey_json['ip_addresses']}})] + mongo.db.telemetry.find({'telem_type': {'$eq': 'exploit'}, 'data.result': {'$eq': True}, + 'data.machine.ip_addr': {'$in': monkey_json['ip_addresses']}, + 'monkey_guid': {'$eq': parent}})] if 1 == len(exploit_telem): - monkey_json['parent'] = exploit_telem[0].get('monkey_guid') + parent_to_add = (exploit_telem[0].get('monkey_guid'), exploit_telem[0].get('data').get('exploiter')) + else: + parent_to_add = (parent, None) + elif (not parent or parent == monkey_json.get('guid')) and monkey_json.has_key('ip_addresses'): + exploit_telem = [x for x in + mongo.db.telemetry.find({'telem_type': {'$eq': 'exploit'}, 'data.result': {'$eq': True}, + 'data.machine.ip_addr': {'$in': monkey_json['ip_addresses']}})] + + if 1 == len(exploit_telem): + parent_to_add = (exploit_telem[0].get('monkey_guid'), exploit_telem[0].get('data').get('exploiter')) + + if not db_monkey: + monkey_json['parent'] = [parent_to_add] + else: + monkey_json['parent'] = db_monkey.get('parent') + [parent_to_add] return mongo.db.monkey.update({"guid": monkey_json["guid"]}, {"$set": monkey_json}, @@ -122,7 +136,7 @@ class Telemetry(restful.Resource): monkey_guid = request.args.get('monkey_guid') telem_type = request.args.get('telem_type') timestamp = request.args.get('timestamp') - if "null" == timestamp: #special case to avoid ugly JS code... + if "null" == timestamp: # special case to avoid ugly JS code... timestamp = None result = {'timestamp': datetime.now().isoformat()} @@ -146,37 +160,26 @@ class Telemetry(restful.Resource): # update exploited monkeys parent try: - if telemetry_json.get('telem_type') == 'exploit': - update_parent = [] - for monkey in mongo.db.monkey.find({"ip_addresses": - {'$elemMatch': - {'$eq': telemetry_json['data']['machine']['ip_addr']}}}): - parent = monkey.get('parent') - if parent == monkey.get('guid') or not parent: - update_parent.append(monkey) - if 1 == len(update_parent): - update_parent[0]['parent'] = telemetry_json['monkey_guid'] - mongo.db.monkey.update({"guid": update_parent[0]['guid']}, {"$set": update_parent[0]}, upsert=False) - elif telemetry_json.get('telem_type') == 'tunnel': + if telemetry_json.get('telem_type') == 'tunnel': if telemetry_json['data']: host = telemetry_json['data'].split(":")[-2].replace("//", "") tunnel_host = mongo.db.monkey.find_one({"ip_addresses": host}) mongo.db.monkey.update({"guid": telemetry_json['monkey_guid']}, {'$set': {'tunnel_guid': tunnel_host.get('guid')}}, - upsert=True) + upsert=False) else: mongo.db.monkey.update({"guid": telemetry_json['monkey_guid']}, - {'$unset': {'tunnel_guid':''}}, - upsert=True) + {'$unset': {'tunnel_guid': ''}}, + upsert=False) elif telemetry_json.get('telem_type') == 'state': if telemetry_json['data']['done']: mongo.db.monkey.update({"guid": telemetry_json['monkey_guid']}, {'$set': {'dead': True}}, - upsert=True) + upsert=False) else: mongo.db.monkey.update({"guid": telemetry_json['monkey_guid']}, {'$set': {'dead': False}}, - upsert=True) + upsert=False) except: pass @@ -220,11 +223,24 @@ class MonkeyDownload(restful.Resource): class Root(restful.Resource): - def get(self): - return { - 'status': 'OK', - 'mongo': str(mongo.db), - } + def get(self, action=None): + if not action: + action = request.args.get('action') + if not action: + return { + 'status': 'OK', + 'mongo': str(mongo.db), + } + elif action=="reset": + mongo.db.config.drop() + mongo.db.monkey.drop() + mongo.db.telemetry.drop() + return { + 'status': 'OK', + } + else: + return {'status': 'BAD', + 'reason': 'unknown action'} def normalize_obj(obj): @@ -232,15 +248,15 @@ def normalize_obj(obj): obj['id'] = obj['_id'] del obj['_id'] - for key,value in obj.items(): + for key, value in obj.items(): if type(value) is bson.objectid.ObjectId: obj[key] = str(value) if type(value) is datetime: - obj[key] = str(value) + obj[key] = str(value) if type(value) is dict: obj[key] = normalize_obj(value) if type(value) is list: - for i in range(0,len(value)): + for i in range(0, len(value)): if type(value[i]) is dict: value[i] = normalize_obj(value[i]) return obj @@ -253,10 +269,17 @@ def output_json(obj, code, headers=None): return resp +def update_dead_monkeys(): + mongo.db.monkey.update( + {'keepalive': {'$lte': datetime.now() - timedelta(minutes=10)}, 'dead': {'$ne': True}}, + {'$set': {'dead': True, 'modifytime': datetime.now()}}, upsert=False) + + @app.route('/admin/<path:path>') def send_admin(path): return send_from_directory('admin/ui', path) + DEFAULT_REPRESENTATIONS = {'application/json': output_json} api = restful.Api(app) api.representations = DEFAULT_REPRESENTATIONS