forked from p15670423/monkey
Refactored to have node state list only on backend and more CR fixes
This commit is contained in:
parent
1e7775a2bc
commit
7475cff288
|
@ -19,6 +19,7 @@ from monkey_island.cc.resources.island_configuration import IslandConfiguration
|
|||
from monkey_island.cc.resources.monkey_download import MonkeyDownload
|
||||
from monkey_island.cc.resources.netmap import NetMap
|
||||
from monkey_island.cc.resources.node import Node
|
||||
from monkey_island.cc.resources.node_states import NodeStates
|
||||
from monkey_island.cc.resources.remote_run import RemoteRun
|
||||
from monkey_island.cc.resources.reporting.report import Report
|
||||
from monkey_island.cc.resources.root import Root
|
||||
|
@ -98,6 +99,7 @@ def init_api_resources(api):
|
|||
api.add_resource(NetMap, '/api/netmap', '/api/netmap/')
|
||||
api.add_resource(Edge, '/api/netmap/edge', '/api/netmap/edge/')
|
||||
api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/')
|
||||
api.add_resource(NodeStates, '/api/netmap/nodeStates')
|
||||
|
||||
# report_type: zero_trust or security
|
||||
api.add_resource(
|
||||
|
|
|
@ -2,13 +2,14 @@ from http.server import HTTPServer, BaseHTTPRequestHandler
|
|||
from socketserver import ThreadingMixIn
|
||||
from urllib import parse
|
||||
import urllib3
|
||||
import logging
|
||||
|
||||
import requests
|
||||
import pymongo
|
||||
|
||||
# Disable "unverified certificate" warnings when sending requests to island
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class BootloaderHttpServer(ThreadingMixIn, HTTPServer):
|
||||
|
||||
|
@ -30,13 +31,17 @@ class BootloaderHTTPRequestHandler(BaseHTTPRequestHandler):
|
|||
island_server_path = parse.urljoin(island_server_path, self.path[1:])
|
||||
r = requests.post(url=island_server_path, data=post_data, verify=False)
|
||||
|
||||
if r.status_code != 200:
|
||||
self.send_response(404)
|
||||
else:
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(r.content)
|
||||
self.connection.close()
|
||||
try:
|
||||
if r.status_code != 200:
|
||||
self.send_response(404)
|
||||
else:
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(r.content)
|
||||
except Exception as e:
|
||||
logger.error("Failed to respond to bootloader: {}".format(e))
|
||||
finally:
|
||||
self.connection.close()
|
||||
|
||||
@staticmethod
|
||||
def get_bootloader_resource_path_from_config(config):
|
||||
|
|
|
@ -30,17 +30,13 @@ from monkey_island.cc.bootloader_server import BootloaderHttpServer
|
|||
|
||||
|
||||
def main():
|
||||
|
||||
logger.info("Starting bootloader server")
|
||||
mongo_url = os.environ.get('MONGO_URL', env.get_mongo_url())
|
||||
bootloader_server_thread = Thread(target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True)
|
||||
# island_server_thread = Thread(target=start_island_server)
|
||||
|
||||
bootloader_server_thread.start()
|
||||
#island_server_thread.start()
|
||||
start_island_server()
|
||||
bootloader_server_thread.join()
|
||||
#island_server_thread.join()
|
||||
|
||||
|
||||
def start_island_server():
|
||||
|
|
|
@ -12,21 +12,21 @@ class Bootloader(flask_restful.Resource):
|
|||
# Used by monkey. can't secure.
|
||||
def post(self, os):
|
||||
if os == 'linux':
|
||||
data = Bootloader.parse_bootloader_request_linux(request.data)
|
||||
data = Bootloader.get_request_contents_linux(request.data)
|
||||
elif os == 'windows':
|
||||
data = Bootloader.parse_bootloader_request_windows(request.data)
|
||||
data = Bootloader.get_request_contents_windows(request.data)
|
||||
else:
|
||||
return make_response({"status": "OS_NOT_FOUND"}, 404)
|
||||
|
||||
resp = BootloaderService.parse_bootloader_telem(data)
|
||||
result = BootloaderService.parse_bootloader_telem(data)
|
||||
|
||||
if resp:
|
||||
if result:
|
||||
return make_response({"status": "RUN"}, 200)
|
||||
else:
|
||||
return make_response({"status": "ABORT"}, 200)
|
||||
|
||||
@staticmethod
|
||||
def parse_bootloader_request_linux(request_data: bytes) -> Dict[str, str]:
|
||||
def get_request_contents_linux(request_data: bytes) -> Dict[str, str]:
|
||||
parsed_data = json.loads(request_data.decode().replace("\n", "")
|
||||
.replace("NAME=\"", "")
|
||||
.replace("\"\"", "\"")
|
||||
|
@ -34,5 +34,5 @@ class Bootloader(flask_restful.Resource):
|
|||
return parsed_data
|
||||
|
||||
@staticmethod
|
||||
def parse_bootloader_request_windows(request_data: bytes) -> Dict[str, str]:
|
||||
def get_request_contents_windows(request_data: bytes) -> Dict[str, str]:
|
||||
return json.loads(request_data.decode("utf-16", "ignore"))
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
from flask import request
|
||||
import flask_restful
|
||||
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.services.utils.node_states import NodeStates as NodeStateList
|
||||
|
||||
|
||||
class NodeStates(flask_restful.Resource):
|
||||
@jwt_required()
|
||||
def get(self):
|
||||
return {'node_states': [state.value for state in NodeStateList]}
|
|
@ -3,8 +3,8 @@ from typing import Dict, List
|
|||
from bson import ObjectId
|
||||
|
||||
from monkey_island.cc.database import mongo
|
||||
from monkey_island.cc.services.node import NodeService, NodeNotFoundException
|
||||
from monkey_island.cc.services.utils.node_groups import NodeGroups
|
||||
from monkey_island.cc.services.node import NodeService, NodeCreationException
|
||||
from monkey_island.cc.services.utils.node_states import NodeStates
|
||||
from monkey_island.cc.services.utils.bootloader_config import SUPPORTED_WINDOWS_VERSIONS, MIN_GLIBC_VERSION
|
||||
|
||||
|
||||
|
@ -22,7 +22,7 @@ class BootloaderService:
|
|||
will_monkey_run = BootloaderService.is_os_compatible(telem)
|
||||
try:
|
||||
node = NodeService.get_or_create_node_from_bootloader_telem(telem, will_monkey_run)
|
||||
except NodeNotFoundException:
|
||||
except NodeCreationException:
|
||||
# Didn't find the node, but allow monkey to run anyways
|
||||
return True
|
||||
|
||||
|
@ -32,13 +32,13 @@ class BootloaderService:
|
|||
return will_monkey_run
|
||||
|
||||
@staticmethod
|
||||
def get_next_node_state(node: Dict, system: str, will_monkey_run: bool) -> NodeGroups:
|
||||
def get_next_node_state(node: Dict, system: str, will_monkey_run: bool) -> NodeStates:
|
||||
group_keywords = [system, 'monkey']
|
||||
if 'group' in node and node['group'] == 'island':
|
||||
group_keywords.extend(['island', 'starting'])
|
||||
else:
|
||||
group_keywords.append('starting') if will_monkey_run else group_keywords.append('old')
|
||||
node_group = NodeGroups.get_group_by_keywords(group_keywords)
|
||||
node_group = NodeStates.get_by_keywords(group_keywords)
|
||||
return node_group
|
||||
|
||||
@staticmethod
|
||||
|
@ -56,7 +56,7 @@ class BootloaderService:
|
|||
|
||||
@staticmethod
|
||||
def is_windows_version_supported(windows_version) -> bool:
|
||||
return SUPPORTED_WINDOWS_VERSIONS.get(windows_version)
|
||||
return SUPPORTED_WINDOWS_VERSIONS.get(windows_version, True)
|
||||
|
||||
@staticmethod
|
||||
def is_glibc_supported(glibc_version_string) -> bool:
|
||||
|
|
|
@ -10,7 +10,7 @@ from monkey_island.cc.models import Monkey
|
|||
from monkey_island.cc.services.edge import EdgeService
|
||||
from monkey_island.cc.utils import local_ip_addresses, is_local_ips
|
||||
from monkey_island.cc import models
|
||||
from monkey_island.cc.services.utils.node_groups import NodeGroups
|
||||
from monkey_island.cc.services.utils.node_states import NodeStates
|
||||
|
||||
__author__ = "itay.mizeretz"
|
||||
|
||||
|
@ -134,7 +134,7 @@ class NodeService:
|
|||
keywords.append(NodeService.get_monkey_os(monkey))
|
||||
if not Monkey.get_single_monkey_by_id(monkey["_id"]).is_dead():
|
||||
keywords.append("running")
|
||||
return NodeGroups.get_group_by_keywords(keywords).value
|
||||
return NodeStates.get_by_keywords(keywords).value
|
||||
|
||||
@staticmethod
|
||||
def get_node_group(node) -> str:
|
||||
|
@ -142,7 +142,7 @@ class NodeService:
|
|||
return node['group']
|
||||
node_type = "exploited" if node.get("exploited") else "clean"
|
||||
node_os = NodeService.get_node_os(node)
|
||||
return NodeGroups.get_group_by_keywords([node_type, node_os]).value
|
||||
return NodeStates.get_by_keywords([node_type, node_os]).value
|
||||
|
||||
@staticmethod
|
||||
def monkey_to_net_node(monkey, for_report=False):
|
||||
|
@ -174,7 +174,7 @@ class NodeService:
|
|||
}
|
||||
|
||||
@staticmethod
|
||||
def set_node_group(node_id: str, node_group: NodeGroups):
|
||||
def set_node_group(node_id: str, node_group: NodeStates):
|
||||
mongo.db.node.update({"_id": node_id},
|
||||
{'$set': {'group': node_group.value}},
|
||||
upsert=False)
|
||||
|
@ -240,8 +240,7 @@ class NodeService:
|
|||
@staticmethod
|
||||
def get_or_create_node_from_bootloader_telem(bootloader_telem: Dict, will_monkey_run: bool) -> Dict:
|
||||
if is_local_ips(bootloader_telem['ips']):
|
||||
raise NodeNotFoundException("Bootloader ran on island, no need to create new node.")
|
||||
#return NodeService.get_monkey_island_pseudo_net_node()
|
||||
raise NodeCreationException("Bootloader ran on island, no need to create new node.")
|
||||
|
||||
new_node = mongo.db.node.find_one({"domain_name": bootloader_telem['hostname'],
|
||||
"ip_addresses": bootloader_telem['ips']})
|
||||
|
@ -255,7 +254,7 @@ class NodeService:
|
|||
mongo.db.edge.update({"_id": edge["_id"]},
|
||||
{'$set': {'tunnel': bool(bootloader_telem['tunnel']),
|
||||
'ip_address': bootloader_telem['ips'][0],
|
||||
'group': NodeGroups.get_group_by_keywords(['island']).value}},
|
||||
'group': NodeStates.get_by_keywords(['island']).value}},
|
||||
upsert=False)
|
||||
return new_node
|
||||
|
||||
|
@ -407,5 +406,5 @@ class NodeService:
|
|||
def get_hostname_by_id(node_id):
|
||||
return NodeService.get_node_hostname(mongo.db.monkey.find_one({'_id': node_id}, {'hostname': 1}))
|
||||
|
||||
class NodeNotFoundException(Exception):
|
||||
class NodeCreationException(Exception):
|
||||
pass
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
from unittest import TestCase
|
||||
|
||||
from monkey_island.cc.services.utils.node_groups import NodeGroups, NoGroupsFoundException
|
||||
|
||||
|
||||
class TestNodeGroups(TestCase):
|
||||
|
||||
def test_get_group_by_keywords(self):
|
||||
tst1 = NodeGroups.get_group_by_keywords(['island']) == NodeGroups.ISLAND
|
||||
tst2 = NodeGroups.get_group_by_keywords(['running', 'linux', 'monkey']) == NodeGroups.MONKEY_LINUX_RUNNING
|
||||
tst3 = NodeGroups.get_group_by_keywords(['monkey', 'linux', 'running']) == NodeGroups.MONKEY_LINUX_RUNNING
|
||||
tst4 = False
|
||||
try:
|
||||
NodeGroups.get_group_by_keywords(['bogus', 'values', 'from', 'long', 'list', 'should', 'fail'])
|
||||
except NoGroupsFoundException:
|
||||
tst4 = True
|
||||
self.assertTrue(tst1 and tst2 and tst3 and tst4)
|
||||
|
|
@ -5,8 +5,7 @@ from typing import List
|
|||
import collections
|
||||
|
||||
|
||||
# This list must correspond to the one on front end in src/components/map/MapOptions.js
|
||||
class NodeGroups(Enum):
|
||||
class NodeStates(Enum):
|
||||
CLEAN_UNKNOWN = 'clean_unknown'
|
||||
CLEAN_LINUX = 'clean_linux'
|
||||
CLEAN_WINDOWS = 'clean_windows'
|
||||
|
@ -33,8 +32,8 @@ class NodeGroups(Enum):
|
|||
MONKEY_LINUX_OLD = 'monkey_linux_old'
|
||||
|
||||
@staticmethod
|
||||
def get_group_by_keywords(keywords: List) -> NodeGroups:
|
||||
potential_groups = [i for i in NodeGroups if NodeGroups._is_group_from_keywords(i, keywords)]
|
||||
def get_by_keywords(keywords: List) -> NodeStates:
|
||||
potential_groups = [i for i in NodeStates if NodeStates._is_state_from_keywords(i, keywords)]
|
||||
if len(potential_groups) > 1:
|
||||
raise MultipleGroupsFoundException("Multiple groups contain provided keywords. "
|
||||
"Manually build group string to ensure keyword order.")
|
||||
|
@ -44,7 +43,7 @@ class NodeGroups(Enum):
|
|||
return potential_groups[0]
|
||||
|
||||
@staticmethod
|
||||
def _is_group_from_keywords(group, keywords) -> bool:
|
||||
def _is_state_from_keywords(group, keywords) -> bool:
|
||||
group_keywords = group.value.split("_")
|
||||
return collections.Counter(group_keywords) == collections.Counter(keywords)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
from unittest import TestCase
|
||||
|
||||
from monkey_island.cc.services.utils.node_states import NodeStates, NoGroupsFoundException
|
||||
|
||||
|
||||
class TestNodeGroups(TestCase):
|
||||
|
||||
def test_get_group_by_keywords(self):
|
||||
tst1 = NodeStates.get_by_keywords(['island']) == NodeStates.ISLAND
|
||||
tst2 = NodeStates.get_by_keywords(['running', 'linux', 'monkey']) == NodeStates.MONKEY_LINUX_RUNNING
|
||||
tst3 = NodeStates.get_by_keywords(['monkey', 'linux', 'running']) == NodeStates.MONKEY_LINUX_RUNNING
|
||||
tst4 = False
|
||||
try:
|
||||
NodeStates.get_by_keywords(['bogus', 'values', 'from', 'long', 'list', 'should', 'fail'])
|
||||
except NoGroupsFoundException:
|
||||
tst4 = True
|
||||
self.assertTrue(tst1)
|
||||
self.assertTrue(tst2)
|
||||
self.assertTrue(tst3)
|
||||
self.assertTrue(tst4)
|
||||
|
|
@ -1,19 +1,11 @@
|
|||
// This list must correspond to the one on back end in cc/services/utils/node_groups.py
|
||||
const groupNames = ['clean_unknown', 'clean_linux', 'clean_windows', 'exploited_linux', 'exploited_windows', 'island',
|
||||
'island_monkey_linux', 'island_monkey_linux_running', 'island_monkey_linux_starting', 'island_monkey_windows',
|
||||
'island_monkey_windows_running', 'island_monkey_windows_starting', 'manual_linux', 'manual_linux_running',
|
||||
'manual_windows', 'manual_windows_running', 'monkey_linux', 'monkey_linux_running', 'monkey_windows',
|
||||
'monkey_windows_running', 'monkey_windows_starting', 'monkey_linux_starting', 'monkey_windows_old',
|
||||
'monkey_linux_old' ];
|
||||
|
||||
let getGroupsOptions = () => {
|
||||
let getGroupsOptions = (stateList) => {
|
||||
let groupOptions = {};
|
||||
for (let groupName of groupNames) {
|
||||
groupOptions[groupName] =
|
||||
for (let stateName of stateList) {
|
||||
groupOptions[stateName] =
|
||||
{
|
||||
shape: 'image',
|
||||
size: 50,
|
||||
image: require('../../images/nodes/' + groupName + '.png')
|
||||
image: require('../../images/nodes/' + stateName + '.png')
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -55,11 +47,11 @@ export const basic_options = {
|
|||
}
|
||||
};
|
||||
|
||||
export const options = (() => {
|
||||
export function getOptions(stateList) {
|
||||
let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */
|
||||
opts.groups = getGroupsOptions();
|
||||
opts.groups = getGroupsOptions(stateList);
|
||||
return opts;
|
||||
})();
|
||||
}
|
||||
|
||||
export const optionsPth = (() => {
|
||||
let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */
|
||||
|
|
|
@ -253,11 +253,14 @@ class PreviewPaneComponent extends AuthComponent {
|
|||
info = this.scanInfo(this.props.item);
|
||||
break;
|
||||
case 'node':
|
||||
if(this.props.item.group.includes('monkey') && this.props.item.group.includes('starting')){
|
||||
if (this.props.item.group.includes('monkey') && this.props.item.group.includes('starting')) {
|
||||
info = this.assetInfo(this.props.item);
|
||||
} else if (this.props.item.group.includes('monkey', 'manual')) {
|
||||
info = this.infectedAssetInfo(this.props.item)
|
||||
} else if (this.props.item.group !== 'island') {
|
||||
info = this.assetInfo(this.props.item)
|
||||
} else {
|
||||
info = this.props.item.group.includes('monkey', 'manual') ? this.infectedAssetInfo(this.props.item) :
|
||||
this.props.item.group !== 'island' ? this.assetInfo(this.props.item) : this.islandAssetInfo();
|
||||
info = this.islandAssetInfo();
|
||||
}
|
||||
break;
|
||||
case 'island_edge':
|
||||
|
|
|
@ -5,7 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|||
import { faStopCircle, faMinus } from '@fortawesome/free-solid-svg-icons'
|
||||
import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane';
|
||||
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
||||
import {options, edgeGroupToColor} from 'components/map/MapOptions';
|
||||
import {getOptions, edgeGroupToColor} from 'components/map/MapOptions';
|
||||
import AuthComponent from '../AuthComponent';
|
||||
|
||||
class MapPageComponent extends AuthComponent {
|
||||
|
@ -13,6 +13,7 @@ class MapPageComponent extends AuthComponent {
|
|||
super(props);
|
||||
this.state = {
|
||||
graph: {nodes: [], edges: []},
|
||||
nodeStateList:[],
|
||||
selected: null,
|
||||
selectedType: null,
|
||||
killPressed: false,
|
||||
|
@ -27,6 +28,7 @@ class MapPageComponent extends AuthComponent {
|
|||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.getNodeStateListFromServer();
|
||||
this.updateMapFromServer();
|
||||
this.interval = setInterval(this.timedEvents, 5000);
|
||||
}
|
||||
|
@ -35,6 +37,14 @@ class MapPageComponent extends AuthComponent {
|
|||
clearInterval(this.interval);
|
||||
}
|
||||
|
||||
getNodeStateListFromServer = () => {
|
||||
this.authFetch('/api/netmap/nodeStates')
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
this.setState({nodeStateList: res.node_states});
|
||||
});
|
||||
};
|
||||
|
||||
timedEvents = () => {
|
||||
this.updateMapFromServer();
|
||||
this.updateTelemetryFromServer();
|
||||
|
@ -168,7 +178,7 @@ class MapPageComponent extends AuthComponent {
|
|||
</div>
|
||||
{this.renderTelemetryConsole()}
|
||||
<div style={{height: '80vh'}}>
|
||||
<ReactiveGraph graph={this.state.graph} options={options} events={this.events}/>
|
||||
<ReactiveGraph graph={this.state.graph} options={getOptions(this.state.nodeStateList)} events={this.events}/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
|
|
Loading…
Reference in New Issue