Merge branch 'nadler/pth' of github.com:guardicore/monkey into nadler/pth
|
@ -4,6 +4,8 @@
|
||||||
* [ ] Have you added an explanation of what your changes do and why you'd like to include them?
|
* [ ] Have you added an explanation of what your changes do and why you'd like to include them?
|
||||||
* [ ] Have you successfully tested your changes locally?
|
* [ ] Have you successfully tested your changes locally?
|
||||||
|
|
||||||
|
* Example screenshot/log transcript of the feature working
|
||||||
|
|
||||||
## Changes
|
## Changes
|
||||||
-
|
-
|
||||||
-
|
-
|
||||||
|
|
After Width: | Height: | Size: 153 KiB |
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
@ -8,7 +8,9 @@ Welcome to the Infection Monkey!
|
||||||
|
|
||||||
The Infection Monkey is an open source security tool for testing a data center's resiliency to perimeter breaches and internal server infection. The Monkey uses various methods to self propagate across a data center and reports success to a centralized Monkey Island server.
|
The Infection Monkey is an open source security tool for testing a data center's resiliency to perimeter breaches and internal server infection. The Monkey uses various methods to self propagate across a data center and reports success to a centralized Monkey Island server.
|
||||||
|
|
||||||
![Infection Monkey map](.github/map-full.png)
|
<img src=".github/map-full.png" >
|
||||||
|
|
||||||
|
<img src=".github/Security-overview.png" width="800" height="500">
|
||||||
|
|
||||||
The Infection Monkey is comprised of two parts:
|
The Infection Monkey is comprised of two parts:
|
||||||
* Monkey - A tool which infects other machines and propagates to them
|
* Monkey - A tool which infects other machines and propagates to them
|
||||||
|
|
Before Width: | Height: | Size: 232 KiB After Width: | Height: | Size: 108 KiB |
|
@ -1,22 +1,26 @@
|
||||||
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import bson
|
import bson
|
||||||
from bson.json_util import dumps
|
|
||||||
from flask import Flask, send_from_directory, redirect, make_response
|
|
||||||
import flask_restful
|
import flask_restful
|
||||||
|
from bson.json_util import dumps
|
||||||
|
from flask import Flask, send_from_directory, make_response
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
|
from cc.auth import init_jwt
|
||||||
from cc.database import mongo
|
from cc.database import mongo
|
||||||
|
from cc.environment.environment import env
|
||||||
from cc.resources.client_run import ClientRun
|
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.local_run import LocalRun
|
||||||
from cc.resources.telemetry import Telemetry
|
from cc.resources.monkey import Monkey
|
||||||
from cc.resources.monkey_configuration import MonkeyConfiguration
|
from cc.resources.monkey_configuration import MonkeyConfiguration
|
||||||
from cc.resources.monkey_download import MonkeyDownload
|
from cc.resources.monkey_download import MonkeyDownload
|
||||||
from cc.resources.netmap import NetMap
|
from cc.resources.netmap import NetMap
|
||||||
from cc.resources.edge import Edge
|
|
||||||
from cc.resources.node import Node
|
from cc.resources.node import Node
|
||||||
from cc.resources.report import Report
|
from cc.resources.report import Report
|
||||||
from cc.resources.root import Root
|
from cc.resources.root import Root
|
||||||
|
from cc.resources.telemetry import Telemetry
|
||||||
from cc.resources.telemetry_feed import TelemetryFeed
|
from cc.resources.telemetry_feed import TelemetryFeed
|
||||||
from cc.services.config import ConfigService
|
from cc.services.config import ConfigService
|
||||||
|
|
||||||
|
@ -70,6 +74,12 @@ def init_app(mongo_url):
|
||||||
api.representations = {'application/json': output_json}
|
api.representations = {'application/json': output_json}
|
||||||
|
|
||||||
app.config['MONGO_URI'] = mongo_url
|
app.config['MONGO_URI'] = mongo_url
|
||||||
|
|
||||||
|
app.config['SECRET_KEY'] = os.urandom(32)
|
||||||
|
app.config['JWT_AUTH_URL_RULE'] = '/api/auth'
|
||||||
|
app.config['JWT_EXPIRATION_DELTA'] = env.get_auth_expiration_time()
|
||||||
|
|
||||||
|
init_jwt(app)
|
||||||
mongo.init_app(app)
|
mongo.init_app(app)
|
||||||
|
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from flask import current_app, abort
|
||||||
|
from flask_jwt import JWT, _jwt_required, JWTError
|
||||||
|
from werkzeug.security import safe_str_cmp
|
||||||
|
|
||||||
|
from cc.environment.environment import env
|
||||||
|
|
||||||
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
|
||||||
|
class User(object):
|
||||||
|
def __init__(self, id, username, secret):
|
||||||
|
self.id = id
|
||||||
|
self.username = username
|
||||||
|
self.secret = secret
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "User(id='%s')" % self.id
|
||||||
|
|
||||||
|
|
||||||
|
def init_jwt(app):
|
||||||
|
users = env.get_auth_users()
|
||||||
|
username_table = {u.username: u for u in users}
|
||||||
|
userid_table = {u.id: u for u in users}
|
||||||
|
|
||||||
|
def authenticate(username, secret):
|
||||||
|
user = username_table.get(username, None)
|
||||||
|
if user and safe_str_cmp(user.secret.encode('utf-8'), secret.encode('utf-8')):
|
||||||
|
return user
|
||||||
|
|
||||||
|
def identity(payload):
|
||||||
|
user_id = payload['identity']
|
||||||
|
return userid_table.get(user_id, None)
|
||||||
|
|
||||||
|
if env.is_auth_enabled():
|
||||||
|
JWT(app, authenticate, identity)
|
||||||
|
|
||||||
|
|
||||||
|
def jwt_required(realm=None):
|
||||||
|
def wrapper(fn):
|
||||||
|
@wraps(fn)
|
||||||
|
def decorator(*args, **kwargs):
|
||||||
|
if env.is_auth_enabled():
|
||||||
|
try:
|
||||||
|
_jwt_required(realm or current_app.config['JWT_DEFAULT_REALM'])
|
||||||
|
except JWTError:
|
||||||
|
abort(401)
|
||||||
|
return fn(*args, **kwargs)
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
return wrapper
|
|
@ -0,0 +1,33 @@
|
||||||
|
import abc
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
|
||||||
|
class Environment(object):
|
||||||
|
__metaclass__ = abc.ABCMeta
|
||||||
|
|
||||||
|
_ISLAND_PORT = 5000
|
||||||
|
_MONGO_URL = "mongodb://localhost:27017/monkeyisland"
|
||||||
|
_DEBUG_SERVER = False
|
||||||
|
_AUTH_EXPIRATION_TIME = timedelta(hours=1)
|
||||||
|
|
||||||
|
def get_island_port(self):
|
||||||
|
return self._ISLAND_PORT
|
||||||
|
|
||||||
|
def get_mongo_url(self):
|
||||||
|
return self._MONGO_URL
|
||||||
|
|
||||||
|
def is_debug(self):
|
||||||
|
return self._DEBUG_SERVER
|
||||||
|
|
||||||
|
def get_auth_expiration_time(self):
|
||||||
|
return self._AUTH_EXPIRATION_TIME
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def is_auth_enabled(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_auth_users(self):
|
||||||
|
return
|
|
@ -0,0 +1,24 @@
|
||||||
|
import urllib2
|
||||||
|
|
||||||
|
import cc.auth
|
||||||
|
from cc.environment import Environment
|
||||||
|
|
||||||
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
|
||||||
|
class AwsEnvironment(Environment):
|
||||||
|
def __init__(self):
|
||||||
|
super(AwsEnvironment, self).__init__()
|
||||||
|
self._instance_id = AwsEnvironment._get_instance_id()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_instance_id():
|
||||||
|
return urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read()
|
||||||
|
|
||||||
|
def is_auth_enabled(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_auth_users(self):
|
||||||
|
return [
|
||||||
|
cc.auth.User(1, 'monkey', self._instance_id)
|
||||||
|
]
|
|
@ -0,0 +1,23 @@
|
||||||
|
import json
|
||||||
|
import standard
|
||||||
|
import aws
|
||||||
|
|
||||||
|
ENV_DICT = {
|
||||||
|
'standard': standard.StandardEnvironment,
|
||||||
|
'aws': aws.AwsEnvironment
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def load_env_from_file():
|
||||||
|
with open('server_config.json', 'r') as f:
|
||||||
|
config_content = f.read()
|
||||||
|
config_json = json.loads(config_content)
|
||||||
|
return config_json['server_config']
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
__env_type = load_env_from_file()
|
||||||
|
env = ENV_DICT[__env_type]()
|
||||||
|
except Exception:
|
||||||
|
print('Failed initializing environment: %s' % __env_type)
|
||||||
|
raise
|
|
@ -0,0 +1,12 @@
|
||||||
|
from cc.environment import Environment
|
||||||
|
|
||||||
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
|
||||||
|
class StandardEnvironment(Environment):
|
||||||
|
|
||||||
|
def is_auth_enabled(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_auth_users(self):
|
||||||
|
return []
|
|
@ -1,5 +0,0 @@
|
||||||
__author__ = 'itay.mizeretz'
|
|
||||||
|
|
||||||
ISLAND_PORT = 5000
|
|
||||||
DEFAULT_MONGO_URL = "mongodb://localhost:27017/monkeyisland"
|
|
||||||
DEBUG_SERVER = False
|
|
|
@ -11,7 +11,7 @@ if BASE_PATH not in sys.path:
|
||||||
|
|
||||||
from cc.app import init_app
|
from cc.app import init_app
|
||||||
from cc.utils import local_ip_addresses
|
from cc.utils import local_ip_addresses
|
||||||
from cc.island_config import DEFAULT_MONGO_URL, ISLAND_PORT, DEBUG_SERVER
|
from cc.environment.environment import env
|
||||||
from cc.database import is_db_server_up
|
from cc.database import is_db_server_up
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -19,20 +19,20 @@ if __name__ == '__main__':
|
||||||
from tornado.httpserver import HTTPServer
|
from tornado.httpserver import HTTPServer
|
||||||
from tornado.ioloop import IOLoop
|
from tornado.ioloop import IOLoop
|
||||||
|
|
||||||
mongo_url = os.environ.get('MONGO_URL', DEFAULT_MONGO_URL)
|
mongo_url = os.environ.get('MONGO_URL', env.get_mongo_url())
|
||||||
|
|
||||||
while not is_db_server_up(mongo_url):
|
while not is_db_server_up(mongo_url):
|
||||||
print('Waiting for MongoDB server')
|
print('Waiting for MongoDB server')
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
app = init_app(mongo_url)
|
app = init_app(mongo_url)
|
||||||
if DEBUG_SERVER:
|
if env.is_debug():
|
||||||
app.run(host='0.0.0.0', debug=True, ssl_context=('server.crt', 'server.key'))
|
app.run(host='0.0.0.0', debug=True, ssl_context=('server.crt', 'server.key'))
|
||||||
else:
|
else:
|
||||||
http_server = HTTPServer(WSGIContainer(app),
|
http_server = HTTPServer(WSGIContainer(app),
|
||||||
ssl_options={'certfile': os.environ.get('SERVER_CRT', 'server.crt'),
|
ssl_options={'certfile': os.environ.get('SERVER_CRT', 'server.crt'),
|
||||||
'keyfile': os.environ.get('SERVER_KEY', 'server.key')})
|
'keyfile': os.environ.get('SERVER_KEY', 'server.key')})
|
||||||
http_server.listen(ISLAND_PORT)
|
http_server.listen(env.get_island_port())
|
||||||
print('Monkey Island Server is running on https://{}:{}'.format(local_ip_addresses()[0], ISLAND_PORT))
|
print('Monkey Island Server is running on https://{}:{}'.format(local_ip_addresses()[0], env.get_island_port()))
|
||||||
IOLoop.instance().start()
|
IOLoop.instance().start()
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@ import sys
|
||||||
from flask import request, jsonify, make_response
|
from flask import request, jsonify, make_response
|
||||||
import flask_restful
|
import flask_restful
|
||||||
|
|
||||||
|
from cc.environment.environment import env
|
||||||
from cc.resources.monkey_download import get_monkey_executable
|
from cc.resources.monkey_download import get_monkey_executable
|
||||||
from cc.island_config import ISLAND_PORT
|
|
||||||
from cc.services.node import NodeService
|
from cc.services.node import NodeService
|
||||||
from cc.utils import local_ip_addresses
|
from cc.utils import local_ip_addresses
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ def run_local_monkey():
|
||||||
|
|
||||||
# run the monkey
|
# run the monkey
|
||||||
try:
|
try:
|
||||||
args = ['"%s" m0nk3y -s %s:%s' % (target_path, local_ip_addresses()[0], ISLAND_PORT)]
|
args = ['"%s" m0nk3y -s %s:%s' % (target_path, local_ip_addresses()[0], env.get_island_port())]
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
args = "".join(args)
|
args = "".join(args)
|
||||||
pid = subprocess.Popen(args, shell=True).pid
|
pid = subprocess.Popen(args, shell=True).pid
|
||||||
|
|
|
@ -15,23 +15,20 @@ __author__ = 'Barak'
|
||||||
|
|
||||||
|
|
||||||
class Monkey(flask_restful.Resource):
|
class Monkey(flask_restful.Resource):
|
||||||
|
|
||||||
|
# Used by monkey. can't secure.
|
||||||
def get(self, guid=None, **kw):
|
def get(self, guid=None, **kw):
|
||||||
NodeService.update_dead_monkeys() # refresh monkeys status
|
NodeService.update_dead_monkeys() # refresh monkeys status
|
||||||
if not guid:
|
if not guid:
|
||||||
guid = request.args.get('guid')
|
guid = request.args.get('guid')
|
||||||
timestamp = request.args.get('timestamp')
|
|
||||||
|
|
||||||
if guid:
|
if guid:
|
||||||
monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid})
|
monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid})
|
||||||
return monkey_json
|
return monkey_json
|
||||||
else:
|
|
||||||
result = {'timestamp': datetime.now().isoformat()}
|
|
||||||
find_filter = {}
|
|
||||||
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
|
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Used by monkey. can't secure.
|
||||||
def patch(self, guid):
|
def patch(self, guid):
|
||||||
monkey_json = json.loads(request.data)
|
monkey_json = json.loads(request.data)
|
||||||
update = {"$set": {'modifytime': datetime.now()}}
|
update = {"$set": {'modifytime': datetime.now()}}
|
||||||
|
@ -51,6 +48,7 @@ class Monkey(flask_restful.Resource):
|
||||||
|
|
||||||
return mongo.db.monkey.update({"_id": monkey["_id"]}, update, upsert=False)
|
return mongo.db.monkey.update({"_id": monkey["_id"]}, update, upsert=False)
|
||||||
|
|
||||||
|
# Used by monkey. can't secure.
|
||||||
def post(self, **kw):
|
def post(self, **kw):
|
||||||
monkey_json = json.loads(request.data)
|
monkey_json = json.loads(request.data)
|
||||||
monkey_json['creds'] = []
|
monkey_json['creds'] = []
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from flask import request, jsonify
|
|
||||||
import flask_restful
|
import flask_restful
|
||||||
|
from flask import request, jsonify
|
||||||
|
|
||||||
from cc.database import mongo
|
from cc.auth import jwt_required
|
||||||
from cc.services.config import ConfigService
|
from cc.services.config import ConfigService
|
||||||
|
|
||||||
__author__ = 'Barak'
|
__author__ = 'Barak'
|
||||||
|
|
||||||
|
|
||||||
class MonkeyConfiguration(flask_restful.Resource):
|
class MonkeyConfiguration(flask_restful.Resource):
|
||||||
|
@jwt_required()
|
||||||
def get(self):
|
def get(self):
|
||||||
return jsonify(schema=ConfigService.get_config_schema(), configuration=ConfigService.get_config())
|
return jsonify(schema=ConfigService.get_config_schema(), configuration=ConfigService.get_config())
|
||||||
|
|
||||||
|
@jwt_required()
|
||||||
def post(self):
|
def post(self):
|
||||||
config_json = json.loads(request.data)
|
config_json = json.loads(request.data)
|
||||||
if config_json.has_key('reset'):
|
if config_json.has_key('reset'):
|
||||||
|
@ -20,4 +22,3 @@ class MonkeyConfiguration(flask_restful.Resource):
|
||||||
else:
|
else:
|
||||||
ConfigService.update_config(config_json)
|
ConfigService.update_config(config_json)
|
||||||
return self.get()
|
return self.get()
|
||||||
|
|
||||||
|
|
|
@ -47,9 +47,12 @@ def get_monkey_executable(host_os, machine):
|
||||||
|
|
||||||
|
|
||||||
class MonkeyDownload(flask_restful.Resource):
|
class MonkeyDownload(flask_restful.Resource):
|
||||||
|
|
||||||
|
# Used by monkey. can't secure.
|
||||||
def get(self, path):
|
def get(self, path):
|
||||||
return send_from_directory('binaries', path)
|
return send_from_directory('binaries', path)
|
||||||
|
|
||||||
|
# Used by monkey. can't secure.
|
||||||
def post(self):
|
def post(self):
|
||||||
host_json = json.loads(request.data)
|
host_json = json.loads(request.data)
|
||||||
host_os = host_json.get('os')
|
host_os = host_json.get('os')
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import flask_restful
|
import flask_restful
|
||||||
|
|
||||||
|
from cc.auth import jwt_required
|
||||||
from cc.services.edge import EdgeService
|
from cc.services.edge import EdgeService
|
||||||
from cc.services.node import NodeService
|
from cc.services.node import NodeService
|
||||||
from cc.database import mongo
|
from cc.database import mongo
|
||||||
|
@ -8,6 +9,7 @@ __author__ = 'Barak'
|
||||||
|
|
||||||
|
|
||||||
class NetMap(flask_restful.Resource):
|
class NetMap(flask_restful.Resource):
|
||||||
|
@jwt_required()
|
||||||
def get(self, **kw):
|
def get(self, **kw):
|
||||||
monkeys = [NodeService.monkey_to_net_node(x) for x in mongo.db.monkey.find({})]
|
monkeys = [NodeService.monkey_to_net_node(x) for x in mongo.db.monkey.find({})]
|
||||||
nodes = [NodeService.node_to_net_node(x) for x in mongo.db.node.find({})]
|
nodes = [NodeService.node_to_net_node(x) for x in mongo.db.node.find({})]
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
from flask import request
|
from flask import request
|
||||||
import flask_restful
|
import flask_restful
|
||||||
|
|
||||||
|
from cc.auth import jwt_required
|
||||||
from cc.services.node import NodeService
|
from cc.services.node import NodeService
|
||||||
|
|
||||||
__author__ = 'Barak'
|
__author__ = 'Barak'
|
||||||
|
|
||||||
|
|
||||||
class Node(flask_restful.Resource):
|
class Node(flask_restful.Resource):
|
||||||
|
@jwt_required()
|
||||||
def get(self):
|
def get(self):
|
||||||
node_id = request.args.get('id')
|
node_id = request.args.get('id')
|
||||||
if node_id:
|
if node_id:
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import flask_restful
|
import flask_restful
|
||||||
|
|
||||||
|
from cc.auth import jwt_required
|
||||||
from cc.services.report import ReportService
|
from cc.services.report import ReportService
|
||||||
|
|
||||||
__author__ = "itay.mizeretz"
|
__author__ = "itay.mizeretz"
|
||||||
|
|
||||||
|
|
||||||
class Report(flask_restful.Resource):
|
class Report(flask_restful.Resource):
|
||||||
|
|
||||||
|
@jwt_required()
|
||||||
def get(self):
|
def get(self):
|
||||||
return ReportService.get_report()
|
return ReportService.get_report()
|
||||||
|
|
|
@ -3,6 +3,7 @@ from datetime import datetime
|
||||||
import flask_restful
|
import flask_restful
|
||||||
from flask import request, make_response, jsonify
|
from flask import request, make_response, jsonify
|
||||||
|
|
||||||
|
from cc.auth import jwt_required
|
||||||
from cc.database import mongo
|
from cc.database import mongo
|
||||||
from cc.services.config import ConfigService
|
from cc.services.config import ConfigService
|
||||||
from cc.services.node import NodeService
|
from cc.services.node import NodeService
|
||||||
|
@ -13,6 +14,8 @@ __author__ = 'Barak'
|
||||||
|
|
||||||
|
|
||||||
class Root(flask_restful.Resource):
|
class Root(flask_restful.Resource):
|
||||||
|
|
||||||
|
@jwt_required()
|
||||||
def get(self, action=None):
|
def get(self, action=None):
|
||||||
if not action:
|
if not action:
|
||||||
action = request.args.get('action')
|
action = request.args.get('action')
|
||||||
|
|
|
@ -7,6 +7,7 @@ import dateutil
|
||||||
import flask_restful
|
import flask_restful
|
||||||
from flask import request
|
from flask import request
|
||||||
|
|
||||||
|
from cc.auth import jwt_required
|
||||||
from cc.database import mongo
|
from cc.database import mongo
|
||||||
from cc.services.config import ConfigService
|
from cc.services.config import ConfigService
|
||||||
from cc.services.edge import EdgeService
|
from cc.services.edge import EdgeService
|
||||||
|
@ -16,6 +17,7 @@ __author__ = 'Barak'
|
||||||
|
|
||||||
|
|
||||||
class Telemetry(flask_restful.Resource):
|
class Telemetry(flask_restful.Resource):
|
||||||
|
@jwt_required()
|
||||||
def get(self, **kw):
|
def get(self, **kw):
|
||||||
monkey_guid = request.args.get('monkey_guid')
|
monkey_guid = request.args.get('monkey_guid')
|
||||||
telem_type = request.args.get('telem_type')
|
telem_type = request.args.get('telem_type')
|
||||||
|
@ -36,6 +38,7 @@ class Telemetry(flask_restful.Resource):
|
||||||
result['objects'] = self.telemetry_to_displayed_telemetry(mongo.db.telemetry.find(find_filter))
|
result['objects'] = self.telemetry_to_displayed_telemetry(mongo.db.telemetry.find(find_filter))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
# Used by monkey. can't secure.
|
||||||
def post(self):
|
def post(self):
|
||||||
telemetry_json = json.loads(request.data)
|
telemetry_json = json.loads(request.data)
|
||||||
telemetry_json['timestamp'] = datetime.now()
|
telemetry_json['timestamp'] = datetime.now()
|
||||||
|
|
|
@ -5,6 +5,7 @@ import flask_restful
|
||||||
from flask import request
|
from flask import request
|
||||||
import flask_pymongo
|
import flask_pymongo
|
||||||
|
|
||||||
|
from cc.auth import jwt_required
|
||||||
from cc.database import mongo
|
from cc.database import mongo
|
||||||
from cc.services.node import NodeService
|
from cc.services.node import NodeService
|
||||||
|
|
||||||
|
@ -12,6 +13,7 @@ __author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
|
||||||
class TelemetryFeed(flask_restful.Resource):
|
class TelemetryFeed(flask_restful.Resource):
|
||||||
|
@jwt_required()
|
||||||
def get(self, **kw):
|
def get(self, **kw):
|
||||||
timestamp = request.args.get('timestamp')
|
timestamp = request.args.get('timestamp')
|
||||||
if "null" == timestamp or timestamp is None: # special case to avoid ugly JS code...
|
if "null" == timestamp or timestamp is None: # special case to avoid ugly JS code...
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"server_config": "standard"
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
from cc.database import mongo
|
from cc.database import mongo
|
||||||
from jsonschema import Draft4Validator, validators
|
from jsonschema import Draft4Validator, validators
|
||||||
|
|
||||||
from cc.island_config import ISLAND_PORT
|
from cc.environment.environment import env
|
||||||
from cc.utils import local_ip_addresses
|
from cc.utils import local_ip_addresses
|
||||||
|
|
||||||
__author__ = "itay.mizeretz"
|
__author__ = "itay.mizeretz"
|
||||||
|
@ -885,8 +885,8 @@ class ConfigService:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set_server_ips_in_config(config):
|
def set_server_ips_in_config(config):
|
||||||
ips = local_ip_addresses()
|
ips = local_ip_addresses()
|
||||||
config["cnc"]["servers"]["command_servers"] = ["%s:%d" % (ip, ISLAND_PORT) for ip in ips]
|
config["cnc"]["servers"]["command_servers"] = ["%s:%d" % (ip, env.get_island_port()) for ip in ips]
|
||||||
config["cnc"]["servers"]["current_server"] = "%s:%d" % (ips[0], ISLAND_PORT)
|
config["cnc"]["servers"]["current_server"] = "%s:%d" % (ips[0], env.get_island_port())
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def save_initial_config_if_needed():
|
def save_initial_config_if_needed():
|
||||||
|
|
|
@ -65,6 +65,8 @@
|
||||||
"core-js": "^2.5.1",
|
"core-js": "^2.5.1",
|
||||||
"fetch": "^1.1.0",
|
"fetch": "^1.1.0",
|
||||||
"js-file-download": "^0.4.1",
|
"js-file-download": "^0.4.1",
|
||||||
|
"json-loader": "^0.5.7",
|
||||||
|
"jwt-decode": "^2.2.0",
|
||||||
"normalize.css": "^4.0.0",
|
"normalize.css": "^4.0.0",
|
||||||
"prop-types": "^15.5.10",
|
"prop-types": "^15.5.10",
|
||||||
"rc-progress": "^2.2.5",
|
"rc-progress": "^2.2.5",
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import React from 'react';
|
||||||
|
import AuthService from '../services/AuthService';
|
||||||
|
|
||||||
|
class AuthComponent extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.auth = new AuthService();
|
||||||
|
this.authFetch = this.auth.authFetch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AuthComponent;
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {NavLink, Route, BrowserRouter as Router} from 'react-router-dom';
|
import {BrowserRouter as Router, NavLink, Redirect, Route} from 'react-router-dom';
|
||||||
import {Col, Grid, Row} from 'react-bootstrap';
|
import {Col, Grid, Row} from 'react-bootstrap';
|
||||||
import {Icon} from 'react-fa';
|
import {Icon} from 'react-fa';
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ import TelemetryPage from 'components/pages/TelemetryPage';
|
||||||
import StartOverPage from 'components/pages/StartOverPage';
|
import StartOverPage from 'components/pages/StartOverPage';
|
||||||
import ReportPage from 'components/pages/ReportPage';
|
import ReportPage from 'components/pages/ReportPage';
|
||||||
import LicensePage from 'components/pages/LicensePage';
|
import LicensePage from 'components/pages/LicensePage';
|
||||||
|
import AuthComponent from 'components/AuthComponent';
|
||||||
|
import LoginPageComponent from 'components/pages/LoginPage';
|
||||||
|
|
||||||
require('normalize.css/normalize.css');
|
require('normalize.css/normalize.css');
|
||||||
require('react-data-components/css/table-twbs.css');
|
require('react-data-components/css/table-twbs.css');
|
||||||
|
@ -22,7 +24,43 @@ let logoImage = require('../images/monkey-icon.svg');
|
||||||
let infectionMonkeyImage = require('../images/infection-monkey.svg');
|
let infectionMonkeyImage = require('../images/infection-monkey.svg');
|
||||||
let guardicoreLogoImage = require('../images/guardicore-logo.png');
|
let guardicoreLogoImage = require('../images/guardicore-logo.png');
|
||||||
|
|
||||||
class AppComponent extends React.Component {
|
class AppComponent extends AuthComponent {
|
||||||
|
updateStatus = () => {
|
||||||
|
if (this.auth.loggedIn()){
|
||||||
|
this.authFetch('/api')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
// This check is used to prevent unnecessary re-rendering
|
||||||
|
let isChanged = false;
|
||||||
|
for (let step in this.state.completedSteps) {
|
||||||
|
if (this.state.completedSteps[step] !== res['completed_steps'][step]) {
|
||||||
|
isChanged = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isChanged) {
|
||||||
|
this.setState({completedSteps: res['completed_steps']});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
renderRoute = (route_path, page_component, is_exact_path = false) => {
|
||||||
|
let render_func = (props) => {
|
||||||
|
if (this.auth.loggedIn()) {
|
||||||
|
return page_component;
|
||||||
|
} else {
|
||||||
|
return <Redirect to={{pathname: '/login'}}/>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (is_exact_path) {
|
||||||
|
return <Route exact path={route_path} render={render_func}/>;
|
||||||
|
} else {
|
||||||
|
return <Route path={route_path} render={render_func}/>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -35,24 +73,6 @@ class AppComponent extends React.Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
updateStatus = () => {
|
|
||||||
fetch('/api')
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(res => {
|
|
||||||
// This check is used to prevent unnecessary re-rendering
|
|
||||||
let isChanged = false;
|
|
||||||
for (let step in this.state.completedSteps) {
|
|
||||||
if (this.state.completedSteps[step] !== res['completed_steps'][step]) {
|
|
||||||
isChanged = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isChanged) {
|
|
||||||
this.setState({completedSteps: res['completed_steps']});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.updateStatus();
|
this.updateStatus();
|
||||||
this.interval = setInterval(this.updateStatus, 2000);
|
this.interval = setInterval(this.updateStatus, 2000);
|
||||||
|
@ -78,7 +98,7 @@ class AppComponent extends React.Component {
|
||||||
<NavLink to="/" exact={true}>
|
<NavLink to="/" exact={true}>
|
||||||
<span className="number">1.</span>
|
<span className="number">1.</span>
|
||||||
Run Monkey Island Server
|
Run Monkey Island Server
|
||||||
{ this.state.completedSteps.run_server ?
|
{this.state.completedSteps.run_server ?
|
||||||
<Icon name="check" className="pull-right checkmark text-success"/>
|
<Icon name="check" className="pull-right checkmark text-success"/>
|
||||||
: ''}
|
: ''}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
@ -87,7 +107,7 @@ class AppComponent extends React.Component {
|
||||||
<NavLink to="/run-monkey">
|
<NavLink to="/run-monkey">
|
||||||
<span className="number">2.</span>
|
<span className="number">2.</span>
|
||||||
Run Monkey
|
Run Monkey
|
||||||
{ this.state.completedSteps.run_monkey ?
|
{this.state.completedSteps.run_monkey ?
|
||||||
<Icon name="check" className="pull-right checkmark text-success"/>
|
<Icon name="check" className="pull-right checkmark text-success"/>
|
||||||
: ''}
|
: ''}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
@ -96,7 +116,7 @@ class AppComponent extends React.Component {
|
||||||
<NavLink to="/infection/map">
|
<NavLink to="/infection/map">
|
||||||
<span className="number">3.</span>
|
<span className="number">3.</span>
|
||||||
Infection Map
|
Infection Map
|
||||||
{ this.state.completedSteps.infection_done ?
|
{this.state.completedSteps.infection_done ?
|
||||||
<Icon name="check" className="pull-right checkmark text-success"/>
|
<Icon name="check" className="pull-right checkmark text-success"/>
|
||||||
: ''}
|
: ''}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
@ -105,7 +125,7 @@ class AppComponent extends React.Component {
|
||||||
<NavLink to="/report">
|
<NavLink to="/report">
|
||||||
<span className="number">4.</span>
|
<span className="number">4.</span>
|
||||||
Security Report
|
Security Report
|
||||||
{ this.state.completedSteps.report_done ?
|
{this.state.completedSteps.report_done ?
|
||||||
<Icon name="check" className="pull-right checkmark text-success"/>
|
<Icon name="check" className="pull-right checkmark text-success"/>
|
||||||
: ''}
|
: ''}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
@ -136,14 +156,15 @@ class AppComponent extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col sm={9} md={10} smOffset={3} mdOffset={2} className="main">
|
<Col sm={9} md={10} smOffset={3} mdOffset={2} className="main">
|
||||||
<Route exact path="/" render={(props) => ( <RunServerPage onStatusChange={this.updateStatus} /> )} />
|
<Route path='/login' render={(props) => (<LoginPageComponent onStatusChange={this.updateStatus}/>)}/>
|
||||||
<Route path="/configure" render={(props) => ( <ConfigurePage onStatusChange={this.updateStatus} /> )} />
|
{this.renderRoute('/', <RunServerPage onStatusChange={this.updateStatus}/>, true)}
|
||||||
<Route path="/run-monkey" render={(props) => ( <RunMonkeyPage onStatusChange={this.updateStatus} /> )} />
|
{this.renderRoute('/configure', <ConfigurePage onStatusChange={this.updateStatus}/>)}
|
||||||
<Route path="/infection/map" render={(props) => ( <MapPage onStatusChange={this.updateStatus} /> )} />
|
{this.renderRoute('/run-monkey', <RunMonkeyPage onStatusChange={this.updateStatus}/>)}
|
||||||
<Route path="/infection/telemetry" render={(props) => ( <TelemetryPage onStatusChange={this.updateStatus} /> )} />
|
{this.renderRoute('/infection/map', <MapPage onStatusChange={this.updateStatus}/>)}
|
||||||
<Route path="/start-over" render={(props) => ( <StartOverPage onStatusChange={this.updateStatus} /> )} />
|
{this.renderRoute('/infection/telemetry', <TelemetryPage onStatusChange={this.updateStatus}/>)}
|
||||||
<Route path="/report" render={(props) => ( <ReportPage onStatusChange={this.updateStatus} /> )} />
|
{this.renderRoute('/start-over', <StartOverPage onStatusChange={this.updateStatus}/>)}
|
||||||
<Route path="/license" render={(props) => ( <LicensePage onStatusChange={this.updateStatus} /> )} />
|
{this.renderRoute('/report', <ReportPage onStatusChange={this.updateStatus}/>)}
|
||||||
|
{this.renderRoute('/license', <LicensePage onStatusChange={this.updateStatus}/>)}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -2,8 +2,9 @@ import React from 'react';
|
||||||
import {Icon} from 'react-fa';
|
import {Icon} from 'react-fa';
|
||||||
import Toggle from 'react-toggle';
|
import Toggle from 'react-toggle';
|
||||||
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
|
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
|
||||||
|
import AuthComponent from '../../AuthComponent';
|
||||||
|
|
||||||
class PreviewPaneComponent extends React.Component {
|
class PreviewPaneComponent extends AuthComponent {
|
||||||
|
|
||||||
generateToolTip(text) {
|
generateToolTip(text) {
|
||||||
return (
|
return (
|
||||||
|
@ -64,7 +65,7 @@ class PreviewPaneComponent extends React.Component {
|
||||||
forceKill(event, asset) {
|
forceKill(event, asset) {
|
||||||
let newConfig = asset.config;
|
let newConfig = asset.config;
|
||||||
newConfig['alive'] = !event.target.checked;
|
newConfig['alive'] = !event.target.checked;
|
||||||
fetch('/api/monkey/' + asset.guid,
|
this.authFetch('/api/monkey/' + asset.guid,
|
||||||
{
|
{
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
|
|
@ -2,8 +2,9 @@ import React from 'react';
|
||||||
import Form from 'react-jsonschema-form';
|
import Form from 'react-jsonschema-form';
|
||||||
import {Col, Nav, NavItem} from 'react-bootstrap';
|
import {Col, Nav, NavItem} from 'react-bootstrap';
|
||||||
import fileDownload from 'js-file-download';
|
import fileDownload from 'js-file-download';
|
||||||
|
import AuthComponent from '../AuthComponent';
|
||||||
|
|
||||||
class ConfigurePageComponent extends React.Component {
|
class ConfigurePageComponent extends AuthComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
@ -23,7 +24,7 @@ class ConfigurePageComponent extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
fetch('/api/configuration')
|
this.authFetch('/api/configuration')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
let sections = [];
|
let sections = [];
|
||||||
|
@ -43,7 +44,7 @@ class ConfigurePageComponent extends React.Component {
|
||||||
onSubmit = ({formData}) => {
|
onSubmit = ({formData}) => {
|
||||||
this.currentFormData = formData;
|
this.currentFormData = formData;
|
||||||
this.updateConfigSection();
|
this.updateConfigSection();
|
||||||
fetch('/api/configuration',
|
this.authFetch('/api/configuration',
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
@ -82,7 +83,7 @@ class ConfigurePageComponent extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
resetConfig = () => {
|
resetConfig = () => {
|
||||||
fetch('/api/configuration',
|
this.authFetch('/api/configuration',
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
@ -126,7 +127,7 @@ class ConfigurePageComponent extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
updateMonkeysRunning = () => {
|
updateMonkeysRunning = () => {
|
||||||
fetch('/api')
|
this.authFetch('/api')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
// This check is used to prevent unnecessary re-rendering
|
// This check is used to prevent unnecessary re-rendering
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {Col} from 'react-bootstrap';
|
||||||
|
|
||||||
|
import AuthService from '../../services/AuthService'
|
||||||
|
|
||||||
|
class LoginPageComponent extends React.Component {
|
||||||
|
login = () => {
|
||||||
|
this.auth.login(this.username, this.password).then(res => {
|
||||||
|
if (res['result']) {
|
||||||
|
this.redirectToHome();
|
||||||
|
} else {
|
||||||
|
this.setState({failed: true});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
updateUsername = (evt) => {
|
||||||
|
this.username = evt.target.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
updatePassword = (evt) => {
|
||||||
|
this.password = evt.target.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
redirectToHome = () => {
|
||||||
|
window.location.href = '/';
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.username = '';
|
||||||
|
this.password = '';
|
||||||
|
this.auth = new AuthService();
|
||||||
|
this.state = {
|
||||||
|
failed: false
|
||||||
|
};
|
||||||
|
if (this.auth.loggedIn()) {
|
||||||
|
this.redirectToHome();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Col xs={12} lg={8}>
|
||||||
|
<h1 className="page-title">Login</h1>
|
||||||
|
<div className="col-sm-6 col-sm-offset-3" style={{'fontSize': '1.2em'}}>
|
||||||
|
<div className="panel panel-default">
|
||||||
|
<div className="panel-heading text-center">
|
||||||
|
<b>Login</b>
|
||||||
|
</div>
|
||||||
|
<div className="panel-body">
|
||||||
|
<div className="input-group center-block text-center">
|
||||||
|
<input type="text" className="form-control" placeholder="Username"
|
||||||
|
onChange={evt => this.updateUsername(evt)}/>
|
||||||
|
<input type="password" className="form-control" placeholder="Password"
|
||||||
|
onChange={evt => this.updatePassword(evt)}/>
|
||||||
|
<button type="button" className="btn btn-primary btn-lg" style={{margin: '5px'}}
|
||||||
|
onClick={() => {
|
||||||
|
this.login()
|
||||||
|
}}>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
{
|
||||||
|
this.state.failed ?
|
||||||
|
<div className="alert alert-danger" role="alert">Login failed. Bad credentials.</div>
|
||||||
|
:
|
||||||
|
''
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LoginPageComponent;
|
|
@ -6,8 +6,9 @@ import PreviewPane from 'components/map/preview-pane/PreviewPane';
|
||||||
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
||||||
import {ModalContainer, ModalDialog} from 'react-modal-dialog';
|
import {ModalContainer, ModalDialog} from 'react-modal-dialog';
|
||||||
import {options, edgeGroupToColor} from 'components/map/MapOptions';
|
import {options, edgeGroupToColor} from 'components/map/MapOptions';
|
||||||
|
import AuthComponent from '../AuthComponent';
|
||||||
|
|
||||||
class MapPageComponent extends React.Component {
|
class MapPageComponent extends AuthComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -40,7 +41,7 @@ class MapPageComponent extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
updateMapFromServer = () => {
|
updateMapFromServer = () => {
|
||||||
fetch('/api/netmap')
|
this.authFetch('/api/netmap')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
res.edges.forEach(edge => {
|
res.edges.forEach(edge => {
|
||||||
|
@ -52,7 +53,7 @@ class MapPageComponent extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
updateTelemetryFromServer = () => {
|
updateTelemetryFromServer = () => {
|
||||||
fetch('/api/telemetry-feed?timestamp='+this.state.telemetryLastTimestamp)
|
this.authFetch('/api/telemetry-feed?timestamp='+this.state.telemetryLastTimestamp)
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
let newTelem = this.state.telemetry.concat(res['telemetries']);
|
let newTelem = this.state.telemetry.concat(res['telemetries']);
|
||||||
|
@ -68,7 +69,7 @@ class MapPageComponent extends React.Component {
|
||||||
|
|
||||||
selectionChanged(event) {
|
selectionChanged(event) {
|
||||||
if (event.nodes.length === 1) {
|
if (event.nodes.length === 1) {
|
||||||
fetch('/api/netmap/node?id=' + event.nodes[0])
|
this.authFetch('/api/netmap/node?id=' + event.nodes[0])
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => this.setState({selected: res, selectedType: 'node'}));
|
.then(res => this.setState({selected: res, selectedType: 'node'}));
|
||||||
}
|
}
|
||||||
|
@ -80,7 +81,7 @@ class MapPageComponent extends React.Component {
|
||||||
if (displayedEdge['group'] === 'island') {
|
if (displayedEdge['group'] === 'island') {
|
||||||
this.setState({selected: displayedEdge, selectedType: 'island_edge'});
|
this.setState({selected: displayedEdge, selectedType: 'island_edge'});
|
||||||
} else {
|
} else {
|
||||||
fetch('/api/netmap/edge?id=' + event.edges[0])
|
this.authFetch('/api/netmap/edge?id=' + event.edges[0])
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => this.setState({selected: res.edge, selectedType: 'edge'}));
|
.then(res => this.setState({selected: res.edge, selectedType: 'edge'}));
|
||||||
}
|
}
|
||||||
|
@ -91,7 +92,7 @@ class MapPageComponent extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
killAllMonkeys = () => {
|
killAllMonkeys = () => {
|
||||||
fetch('/api?action=killall')
|
this.authFetch('/api?action=killall')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => this.setState({killPressed: (res.status === 'OK')}));
|
.then(res => this.setState({killPressed: (res.status === 'OK')}));
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,11 +7,12 @@ import {edgeGroupToColor, options} from 'components/map/MapOptions';
|
||||||
import StolenPasswords from 'components/report-components/StolenPasswords';
|
import StolenPasswords from 'components/report-components/StolenPasswords';
|
||||||
import CollapsibleWellComponent from 'components/report-components/CollapsibleWell';
|
import CollapsibleWellComponent from 'components/report-components/CollapsibleWell';
|
||||||
import {Line} from 'rc-progress';
|
import {Line} from 'rc-progress';
|
||||||
|
import AuthComponent from '../AuthComponent';
|
||||||
|
|
||||||
let guardicoreLogoImage = require('../../images/guardicore-logo.png');
|
let guardicoreLogoImage = require('../../images/guardicore-logo.png');
|
||||||
let monkeyLogoImage = require('../../images/monkey-icon.svg');
|
let monkeyLogoImage = require('../../images/monkey-icon.svg');
|
||||||
|
|
||||||
class ReportPageComponent extends React.Component {
|
class ReportPageComponent extends AuthComponent {
|
||||||
|
|
||||||
Issue =
|
Issue =
|
||||||
{
|
{
|
||||||
|
@ -76,7 +77,7 @@ class ReportPageComponent extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateMonkeysRunning = () => {
|
updateMonkeysRunning = () => {
|
||||||
return fetch('/api')
|
return this.authFetch('/api')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
// This check is used to prevent unnecessary re-rendering
|
// This check is used to prevent unnecessary re-rendering
|
||||||
|
@ -89,7 +90,7 @@ class ReportPageComponent extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
updateMapFromServer = () => {
|
updateMapFromServer = () => {
|
||||||
fetch('/api/netmap')
|
this.authFetch('/api/netmap')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
res.edges.forEach(edge => {
|
res.edges.forEach(edge => {
|
||||||
|
@ -102,7 +103,7 @@ class ReportPageComponent extends React.Component {
|
||||||
|
|
||||||
getReportFromServer(res) {
|
getReportFromServer(res) {
|
||||||
if (res['completed_steps']['run_monkey']) {
|
if (res['completed_steps']['run_monkey']) {
|
||||||
fetch('/api/report')
|
this.authFetch('/api/report')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|
|
@ -3,8 +3,9 @@ import {Button, Col, Well, Nav, NavItem, Collapse} from 'react-bootstrap';
|
||||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||||
import {Icon} from 'react-fa';
|
import {Icon} from 'react-fa';
|
||||||
import {Link} from 'react-router-dom';
|
import {Link} from 'react-router-dom';
|
||||||
|
import AuthComponent from '../AuthComponent';
|
||||||
|
|
||||||
class RunMonkeyPageComponent extends React.Component {
|
class RunMonkeyPageComponent extends AuthComponent {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -19,14 +20,14 @@ class RunMonkeyPageComponent extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
fetch('/api')
|
this.authFetch('/api')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => this.setState({
|
.then(res => this.setState({
|
||||||
ips: res['ip_addresses'],
|
ips: res['ip_addresses'],
|
||||||
selectedIp: res['ip_addresses'][0]
|
selectedIp: res['ip_addresses'][0]
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fetch('/api/local-monkey')
|
this.authFetch('/api/local-monkey')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res =>{
|
.then(res =>{
|
||||||
if (res['is_running']) {
|
if (res['is_running']) {
|
||||||
|
@ -36,7 +37,7 @@ class RunMonkeyPageComponent extends React.Component {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
fetch('/api/client-monkey')
|
this.authFetch('/api/client-monkey')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res['is_running']) {
|
if (res['is_running']) {
|
||||||
|
@ -60,7 +61,7 @@ class RunMonkeyPageComponent extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
runLocalMonkey = () => {
|
runLocalMonkey = () => {
|
||||||
fetch('/api/local-monkey',
|
this.authFetch('/api/local-monkey',
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
|
|
@ -12,7 +12,8 @@ class RunServerPageComponent extends React.Component {
|
||||||
<Col xs={12} lg={8}>
|
<Col xs={12} lg={8}>
|
||||||
<h1 className="page-title">1. Monkey Island Server</h1>
|
<h1 className="page-title">1. Monkey Island Server</h1>
|
||||||
<div style={{'fontSize': '1.2em'}}>
|
<div style={{'fontSize': '1.2em'}}>
|
||||||
<p style={{'marginTop': '30px'}}>Congrats! You have successfully set up the Monkey Island server. 👏 👏</p>
|
<p style={{'marginTop': '30px'}}>Congrats! You have successfully set up the Monkey Island
|
||||||
|
server. 👏 👏</p>
|
||||||
<p>
|
<p>
|
||||||
The Infection Monkey is an open source security tool for testing a data center's resiliency to perimeter
|
The Infection Monkey is an open source security tool for testing a data center's resiliency to perimeter
|
||||||
breaches and internal server infections.
|
breaches and internal server infections.
|
||||||
|
@ -20,7 +21,8 @@ class RunServerPageComponent extends React.Component {
|
||||||
center and reports to this Monkey Island Command and Control server.
|
center and reports to this Monkey Island Command and Control server.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
To read more about the Monkey, visit <a href="http://infectionmonkey.com" target="_blank">infectionmonkey.com</a>
|
To read more about the Monkey, visit <a href="http://infectionmonkey.com"
|
||||||
|
target="_blank">infectionmonkey.com</a>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Go ahead and <Link to="/run-monkey">run the monkey</Link>.
|
Go ahead and <Link to="/run-monkey">run the monkey</Link>.
|
||||||
|
|
|
@ -2,8 +2,9 @@ import React from 'react';
|
||||||
import {Col} from 'react-bootstrap';
|
import {Col} from 'react-bootstrap';
|
||||||
import {Link} from 'react-router-dom';
|
import {Link} from 'react-router-dom';
|
||||||
import {ModalContainer, ModalDialog} from 'react-modal-dialog';
|
import {ModalContainer, ModalDialog} from 'react-modal-dialog';
|
||||||
|
import AuthComponent from '../AuthComponent';
|
||||||
|
|
||||||
class StartOverPageComponent extends React.Component {
|
class StartOverPageComponent extends AuthComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
@ -15,7 +16,7 @@ class StartOverPageComponent extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateMonkeysRunning = () => {
|
updateMonkeysRunning = () => {
|
||||||
fetch('/api')
|
this.authFetch('/api')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
// This check is used to prevent unnecessary re-rendering
|
// This check is used to prevent unnecessary re-rendering
|
||||||
|
@ -104,7 +105,7 @@ class StartOverPageComponent extends React.Component {
|
||||||
this.setState({
|
this.setState({
|
||||||
cleaned: false
|
cleaned: false
|
||||||
});
|
});
|
||||||
fetch('/api?action=reset')
|
this.authFetch('/api?action=reset')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res['status'] === 'OK') {
|
if (res['status'] === 'OK') {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import {Col} from 'react-bootstrap';
|
import {Col} from 'react-bootstrap';
|
||||||
import JSONTree from 'react-json-tree'
|
import JSONTree from 'react-json-tree'
|
||||||
import {DataTable} from 'react-data-components';
|
import {DataTable} from 'react-data-components';
|
||||||
|
import AuthComponent from '../AuthComponent';
|
||||||
|
|
||||||
const renderJson = (val) => <JSONTree data={val} level={1} theme="eighties" invertTheme={true} />;
|
const renderJson = (val) => <JSONTree data={val} level={1} theme="eighties" invertTheme={true} />;
|
||||||
const renderTime = (val) => val.split('.')[0];
|
const renderTime = (val) => val.split('.')[0];
|
||||||
|
@ -13,7 +14,7 @@ const columns = [
|
||||||
{ title: 'Details', prop: 'data', render: renderJson, width: '40%' }
|
{ title: 'Details', prop: 'data', render: renderJson, width: '40%' }
|
||||||
];
|
];
|
||||||
|
|
||||||
class TelemetryPageComponent extends React.Component {
|
class TelemetryPageComponent extends AuthComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -22,7 +23,7 @@ class TelemetryPageComponent extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount = () => {
|
componentDidMount = () => {
|
||||||
fetch('/api/telemetry')
|
this.authFetch('/api/telemetry')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => this.setState({data: res.objects}));
|
.then(res => this.setState({data: res.objects}));
|
||||||
};
|
};
|
||||||
|
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 108 KiB |
|
@ -0,0 +1,9 @@
|
||||||
|
import BaseConfig from './BaseConfig';
|
||||||
|
|
||||||
|
class AwsConfig extends BaseConfig{
|
||||||
|
isAuthEnabled() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AwsConfig;
|
|
@ -0,0 +1,8 @@
|
||||||
|
class BaseConfig {
|
||||||
|
|
||||||
|
isAuthEnabled() {
|
||||||
|
throw new Error('Abstract function');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BaseConfig;
|
|
@ -0,0 +1,12 @@
|
||||||
|
import StandardConfig from './StandardConfig';
|
||||||
|
import AwsConfig from './AwsConfig';
|
||||||
|
|
||||||
|
const SERVER_CONFIG_JSON = require('json-loader!../../../server_config.json');
|
||||||
|
|
||||||
|
const CONFIG_DICT =
|
||||||
|
{
|
||||||
|
'standard': StandardConfig,
|
||||||
|
'aws': AwsConfig
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SERVER_CONFIG = new CONFIG_DICT[SERVER_CONFIG_JSON['server_config']]();
|
|
@ -0,0 +1,10 @@
|
||||||
|
import BaseConfig from './BaseConfig';
|
||||||
|
|
||||||
|
class StandardConfig extends BaseConfig {
|
||||||
|
|
||||||
|
isAuthEnabled () {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StandardConfig;
|
|
@ -0,0 +1,106 @@
|
||||||
|
import decode from 'jwt-decode';
|
||||||
|
import {SERVER_CONFIG} from '../server_config/ServerConfig';
|
||||||
|
|
||||||
|
export default class AuthService {
|
||||||
|
AUTH_ENABLED = SERVER_CONFIG.isAuthEnabled();
|
||||||
|
|
||||||
|
login = (username, password) => {
|
||||||
|
if (this.AUTH_ENABLED) {
|
||||||
|
return this._login(username, password);
|
||||||
|
} else {
|
||||||
|
return {result: true};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
authFetch = (url, options) => {
|
||||||
|
if (this.AUTH_ENABLED) {
|
||||||
|
return this._authFetch(url, options);
|
||||||
|
} else {
|
||||||
|
return fetch(url, options);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_login = (username, password) => {
|
||||||
|
return this._authFetch('/api/auth', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
username,
|
||||||
|
password
|
||||||
|
})
|
||||||
|
}).then(response => response.json())
|
||||||
|
.then(res => {
|
||||||
|
if (res.hasOwnProperty('access_token')) {
|
||||||
|
this._setToken(res['access_token']);
|
||||||
|
return {result: true};
|
||||||
|
} else {
|
||||||
|
this._removeToken();
|
||||||
|
return {result: false};
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
_authFetch = (url, options = {}) => {
|
||||||
|
const headers = {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.loggedIn()) {
|
||||||
|
headers['Authorization'] = 'JWT ' + this._getToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.hasOwnProperty('headers')) {
|
||||||
|
for (let header in headers) {
|
||||||
|
options['headers'][header] = headers[header];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
options['headers'] = headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(url, options)
|
||||||
|
.then(res => {
|
||||||
|
if (res.status === 401) {
|
||||||
|
this._removeToken();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
loggedIn() {
|
||||||
|
if (!this.AUTH_ENABLED) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = this._getToken();
|
||||||
|
return ((token !== null) && !this._isTokenExpired(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
if (this.AUTH_ENABLED) {
|
||||||
|
this._removeToken();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_isTokenExpired(token) {
|
||||||
|
try {
|
||||||
|
return decode(token)['exp'] < Date.now() / 1000;
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_setToken(idToken) {
|
||||||
|
localStorage.setItem('jwt', idToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
_removeToken() {
|
||||||
|
localStorage.removeItem('jwt');
|
||||||
|
}
|
||||||
|
|
||||||
|
_getToken() {
|
||||||
|
return localStorage.getItem('jwt')
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ click
|
||||||
flask
|
flask
|
||||||
Flask-Pymongo
|
Flask-Pymongo
|
||||||
Flask-Restful
|
Flask-Restful
|
||||||
|
Flask-JWT
|
||||||
jsonschema
|
jsonschema
|
||||||
netifaces
|
netifaces
|
||||||
ipaddress
|
ipaddress
|
||||||
|
|
|
@ -8,6 +8,7 @@ click
|
||||||
flask
|
flask
|
||||||
Flask-Pymongo
|
Flask-Pymongo
|
||||||
Flask-Restful
|
Flask-Restful
|
||||||
|
Flask-JWT
|
||||||
jsonschema
|
jsonschema
|
||||||
netifaces
|
netifaces
|
||||||
ipaddress
|
ipaddress
|
||||||
|
|