Merge pull request #688 from guardicore/password_setup

Password setup and bootstrap v4 migration
This commit is contained in:
VakarisZ 2020-06-25 11:52:33 +03:00 committed by GitHub
commit 6cc4c85132
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1749 changed files with 29523 additions and 6068 deletions

3
.gitignore vendored
View File

@ -90,3 +90,6 @@ profiler_logs/
# vim swap files
*.swp
# Server config might contain credentials. Don't commit by default.
/monkey/monkey_island/cc/server_config.json

View File

@ -65,7 +65,7 @@ script:
- cd monkey_island/cc/ui
- npm ci # See https://docs.npmjs.com/cli/ci.html
- eslint ./src --quiet # Test for errors
- JS_WARNINGS_AMOUNT_UPPER_LIMIT=90
- JS_WARNINGS_AMOUNT_UPPER_LIMIT=70
- eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT # Test for max warnings
after_success:

View File

@ -29,6 +29,7 @@ Configure a PyTest configuration with the additional arguments `-s --island=35.2
**Before running performance test make sure browser is not sending requests to island!**
To run telemetry performance test follow these steps:
0. Set `server_config.json` to "standard" (no password protection) setting.
1. Gather monkey telemetries.
1. Enable "Export monkey telemetries" in Configuration -> Internal -> Tests if you don't have
exported telemetries already.

View File

@ -132,6 +132,7 @@ class TestMonkeyBlackbox(object):
def test_wmi_pth(self, island_client):
TestMonkeyBlackbox.run_exploitation_test(island_client, "WMI_PTH.conf", "WMI_PTH")
@pytest.mark.skip(reason="Perfomance test that creates env from fake telemetries is faster, use that instead.")
def test_report_generation_performance(self, island_client, quick_performance_tests):
"""
This test includes the SSH + Elastic + Hadoop + MSSQL machines all in one test
@ -149,6 +150,7 @@ class TestMonkeyBlackbox(object):
LOGGER.error("This test doesn't support 'quick_performance_tests' option.")
assert False
@pytest.mark.skip(reason="Perfomance test that creates env from fake telemetries is faster, use that instead.")
def test_map_generation_performance(self, island_client, quick_performance_tests):
if not quick_performance_tests:
TestMonkeyBlackbox.run_performance_test(MapGenerationTest,

View File

@ -0,0 +1,22 @@
class ExploitingVulnerableMachineError(Exception):
""" Raise when exploiter failed, but machine is vulnerable """
class FailedExploitationError(Exception):
""" Raise when exploiter fails instead of returning False """
class InvalidRegistrationCredentialsError(Exception):
""" Raise when server config file changed and island needs to restart """
class RegistrationNotNeededError(Exception):
""" Raise to indicate the reason why registration is not required """
class CredentialsNotRequiredError(RegistrationNotNeededError):
""" Raise to indicate the reason why registration is not required """
class AlreadyRegisteredError(RegistrationNotNeededError):
""" Raise to indicate the reason why registration is not required """

View File

@ -11,7 +11,7 @@ from infection_monkey.exploit.tools.http_tools import MonkeyHTTPServer
from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, build_monkey_commandline, get_monkey_depth
from infection_monkey.model import DROPPER_ARG
from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload
from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError, FailedExploitationError
from common.utils.exceptions import ExploitingVulnerableMachineError, FailedExploitationError
LOG = logging.getLogger(__name__)

View File

@ -9,7 +9,7 @@ from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
from infection_monkey.model import MONKEY_ARG
from infection_monkey.network.tools import check_tcp_port, get_interface_to_target
from infection_monkey.exploit.tools.exceptions import FailedExploitationError
from common.utils.exceptions import FailedExploitationError
from common.utils.exploit_enum import ExploitType
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem

View File

@ -1,6 +0,0 @@
class ExploitingVulnerableMachineError(Exception):
""" Raise when exploiter failed, but machine is vulnerable"""
class FailedExploitationError(Exception):
""" Raise when exploiter fails instead of returning False"""

View File

@ -28,7 +28,7 @@ from infection_monkey.telemetry.tunnel_telem import TunnelTelem
from infection_monkey.windows_upgrader import WindowsUpgrader
from infection_monkey.post_breach.post_breach_handler import PostBreach
from infection_monkey.network.tools import get_interface_to_target, is_running_on_server
from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError, FailedExploitationError
from common.utils.exceptions import ExploitingVulnerableMachineError, FailedExploitationError
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
from common.utils.attack_utils import ScanStatus, UsageEnum
from common.version import get_version

View File

@ -9,6 +9,7 @@ CREDENTIAL_TYPES = ['msv_creds', 'wdigest_creds', 'ssp_creds', 'livessp_creds',
'kerberos_creds', 'credman_creds', 'tspkg_creds']
PypykatzCredential = NewType('PypykatzCredential', Dict)
def get_windows_creds() -> List[WindowsCredentials]:
pypy_handle = pypykatz.go_live()
logon_data = pypy_handle.to_dict()

View File

@ -23,16 +23,20 @@ class TestPypykatzHandler(TestCase):
'password': 'canyoufindm3', 'luid': 123086}],
'dpapi_creds': [
{'credtype': 'dpapi', 'key_guid': '9123-123ae123de4-121239-3123-421f',
'masterkey': '6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975ef051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9',
'masterkey': '6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975e'
'f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9',
'sha1_masterkey': 'bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da', 'luid': 123086},
{'credtype': 'dpapi', 'key_guid': '9123-123ae123de4-121239-3123-421f',
'masterkey': '6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975ef051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9',
'masterkey': '6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975e'
'f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9',
'sha1_masterkey': 'bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da', 'luid': 123086},
{'credtype': 'dpapi', 'key_guid': '9123-123ae123de4-121239-3123-421f',
'masterkey': '6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975ef051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9',
'masterkey': '6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975e'
'f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9',
'sha1_masterkey': 'bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da', 'luid': 123086},
{'credtype': 'dpapi', 'key_guid': '9123-123ae123de4-121239-3123-421f',
'masterkey': '6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975ef051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9',
'masterkey': '6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975e'
'f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9',
'sha1_masterkey': 'bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da', 'luid': 123086},
{'credtype': 'dpapi', 'key_guid': '9123-123ae123de4-121239-3123-421f'}],
'kerberos_creds': [

View File

@ -5,11 +5,12 @@ import flask_restful
from flask import Flask, send_from_directory, Response
from werkzeug.exceptions import NotFound
from monkey_island.cc.auth import init_jwt
from monkey_island.cc.resources.auth.auth import init_jwt
from monkey_island.cc.database import mongo, database
from monkey_island.cc.environment.environment import env
import monkey_island.cc.environment.environment_singleton as env_singleton
from monkey_island.cc.resources.client_run import ClientRun
from monkey_island.cc.resources.edge import Edge
from monkey_island.cc.resources.environment import Environment
from monkey_island.cc.resources.local_run import LocalRun
from monkey_island.cc.resources.log import Log
from monkey_island.cc.resources.island_logs import IslandLog
@ -22,6 +23,7 @@ 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.monkey_control.remote_port_check import RemotePortCheck
from monkey_island.cc.resources.registration import Registration
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
@ -69,7 +71,7 @@ def init_app_config(app, mongo_url):
app.config['MONGO_URI'] = mongo_url
app.config['SECRET_KEY'] = str(uuid.getnode())
app.config['JWT_AUTH_URL_RULE'] = '/api/auth'
app.config['JWT_EXPIRATION_DELTA'] = env.get_auth_expiration_time()
app.config['JWT_EXPIRATION_DELTA'] = env_singleton.env.get_auth_expiration_time()
def init_app_services(app):
@ -91,6 +93,8 @@ def init_app_url_rules(app):
def init_api_resources(api):
api.add_resource(Root, '/api')
api.add_resource(Registration, '/api/registration')
api.add_resource(Environment, '/api/environment')
api.add_resource(Monkey, '/api/monkey', '/api/monkey/', '/api/monkey/<string:guid>')
api.add_resource(Bootloader, '/api/bootloader/<string:os>')
api.add_resource(LocalRun, '/api/local-monkey', '/api/local-monkey/')

View File

@ -1,10 +1,18 @@
import hashlib
import logging
import os
from abc import ABCMeta, abstractmethod
from datetime import timedelta
import os
import hashlib
__author__ = 'itay.mizeretz'
from common.utils.exceptions import InvalidRegistrationCredentialsError, \
CredentialsNotRequiredError, AlreadyRegisteredError
from monkey_island.cc.environment.environment_config import EnvironmentConfig
from monkey_island.cc.environment.user_creds import UserCreds
logger = logging.getLogger(__name__)
class Environment(object, metaclass=ABCMeta):
_ISLAND_PORT = 5000
@ -18,6 +26,53 @@ class Environment(object, metaclass=ABCMeta):
_testing = False
def __init__(self, config: EnvironmentConfig):
self._config = config
self._testing = False # Assume env is not for unit testing.
@property
@abstractmethod
def _credentials_required(self) -> bool:
pass
@abstractmethod
def get_auth_users(self):
pass
def needs_registration(self) -> bool:
try:
needs_registration = self._try_needs_registration()
return needs_registration
except (CredentialsNotRequiredError, AlreadyRegisteredError) as e:
logger.info(e)
return False
def try_add_user(self, credentials: UserCreds):
if not credentials:
raise InvalidRegistrationCredentialsError("Missing part of credentials.")
if self._try_needs_registration():
self._config.add_user(credentials)
logger.info(f"New user {credentials.username} registered!")
def _try_needs_registration(self) -> bool:
if not self._credentials_required:
raise CredentialsNotRequiredError("Credentials are not required "
"for current environment.")
else:
if self._is_registered():
raise AlreadyRegisteredError("User has already been registered. "
"Reset credentials or login.")
return True
def _is_registered(self) -> bool:
return self._credentials_required and self._is_credentials_set_up()
def _is_credentials_set_up(self) -> bool:
if self._config and self._config.user_creds:
return True
else:
return False
@property
def testing(self):
return self._testing
@ -26,12 +81,11 @@ class Environment(object, metaclass=ABCMeta):
def testing(self, value):
self._testing = value
def __init__(self):
self.config = None
self._testing = False # Assume env is not for unit testing.
def save_config(self):
self._config.save_to_file()
def set_config(self, config):
self.config = config
def get_config(self) -> EnvironmentConfig:
return self._config
def get_island_port(self):
return self._ISLAND_PORT
@ -51,21 +105,14 @@ class Environment(object, metaclass=ABCMeta):
hash_obj.update(secret.encode('utf-8'))
return hash_obj.hexdigest()
def get_deployment(self):
return self._get_from_config('deployment', 'unknown')
def get_deployment(self) -> str:
deployment = 'unknown'
if self._config and self._config.deployment:
deployment = self._config.deployment
return deployment
def is_develop(self):
return self.get_deployment() == 'develop'
def _get_from_config(self, key, default_value=None):
val = default_value
if self.config is not None:
val = self.config.get(key, val)
return val
@abstractmethod
def get_auth_users(self):
return
def set_deployment(self, deployment: str):
self._config.deployment = deployment
@property
def mongo_db_name(self):

View File

@ -1,4 +1,4 @@
import monkey_island.cc.auth
from monkey_island.cc.resources.auth.auth_user import User
from monkey_island.cc.environment import Environment
from common.cloud.aws.aws_instance import AwsInstance
@ -6,8 +6,11 @@ __author__ = 'itay.mizeretz'
class AwsEnvironment(Environment):
def __init__(self):
super(AwsEnvironment, self).__init__()
_credentials_required = True
def __init__(self, config):
super(AwsEnvironment, self).__init__(config)
# Not suppressing error here on purpose. This is critical if we're on AWS env.
self.aws_info = AwsInstance()
self._instance_id = self._get_instance_id()
@ -20,6 +23,7 @@ class AwsEnvironment(Environment):
return self.aws_info.get_region()
def get_auth_users(self):
return [
monkey_island.cc.auth.User(1, 'monkey', self.hash_secret(self._instance_id))
]
if self._is_registered():
return self._config.get_users()
else:
return []

View File

@ -1,49 +0,0 @@
import json
import logging
import os
env = None
from monkey_island.cc.environment import standard
from monkey_island.cc.environment import testing
from monkey_island.cc.environment import aws
from monkey_island.cc.environment import password
from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH
__author__ = 'itay.mizeretz'
logger = logging.getLogger(__name__)
AWS = 'aws'
STANDARD = 'standard'
PASSWORD = 'password'
TESTING = 'testing'
ENV_DICT = {
STANDARD: standard.StandardEnvironment,
AWS: aws.AwsEnvironment,
PASSWORD: password.PasswordEnvironment,
TESTING: testing.TestingEnvironment
}
def load_server_configuration_from_file():
with open(os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc/server_config.json'), 'r') as f:
config_content = f.read()
return json.loads(config_content)
def load_env_from_file():
loaded_config_json = load_server_configuration_from_file()
return loaded_config_json['server_config']
try:
config_json = load_server_configuration_from_file()
__env_type = config_json['server_config']
env = ENV_DICT[__env_type]()
env.set_config(config_json)
logger.info('Monkey\'s env is: {0}'.format(env.__class__.__name__))
except Exception:
logger.error('Failed initializing environment', exc_info=True)
raise

View File

@ -0,0 +1,69 @@
from __future__ import annotations
import json
import os
from typing import List, Dict
from monkey_island.cc.resources.auth.auth_user import User
from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH
from monkey_island.cc.environment.user_creds import UserCreds
from monkey_island.cc.resources.auth.user_store import UserStore
class EnvironmentConfig:
def __init__(self,
server_config: str,
deployment: str,
user_creds: UserCreds,
aws=None):
self.server_config = server_config
self.deployment = deployment
self.user_creds = user_creds
self.aws = aws
@staticmethod
def get_from_json(config_json: str) -> EnvironmentConfig:
data = json.loads(config_json)
return EnvironmentConfig.get_from_dict(data)
@staticmethod
def get_from_dict(dict_data: Dict) -> EnvironmentConfig:
user_creds = UserCreds.get_from_dict(dict_data)
aws = dict_data['aws'] if 'aws' in dict_data else None
return EnvironmentConfig(server_config=dict_data['server_config'],
deployment=dict_data['deployment'],
user_creds=user_creds,
aws=aws)
def save_to_file(self):
file_path = EnvironmentConfig.get_config_file_path()
with open(file_path, 'w') as f:
f.write(json.dumps(self.to_dict(), indent=2))
@staticmethod
def get_from_file() -> EnvironmentConfig:
file_path = EnvironmentConfig.get_config_file_path()
with open(file_path, 'r') as f:
config_content = f.read()
return EnvironmentConfig.get_from_json(config_content)
@staticmethod
def get_config_file_path() -> str:
return os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'server_config.json')
def to_dict(self) -> Dict:
config_dict = {'server_config': self.server_config,
'deployment': self.deployment}
if self.aws:
config_dict.update({'aws': self.aws})
config_dict.update(self.user_creds.to_dict())
return config_dict
def add_user(self, credentials: UserCreds):
self.user_creds = credentials
self.save_to_file()
UserStore.set_users(self.get_users())
def get_users(self) -> List[User]:
auth_user = self.user_creds.to_auth_user()
return [auth_user] if auth_user else []

View File

@ -0,0 +1,52 @@
import logging
env = None
import monkey_island.cc.resources.auth.user_store as user_store
from monkey_island.cc.environment import standard, EnvironmentConfig
from monkey_island.cc.environment import testing
from monkey_island.cc.environment import aws
from monkey_island.cc.environment import password
__author__ = 'itay.mizeretz'
logger = logging.getLogger(__name__)
AWS = 'aws'
STANDARD = 'standard'
PASSWORD = 'password'
TESTING = 'testing'
ENV_DICT = {
STANDARD: standard.StandardEnvironment,
AWS: aws.AwsEnvironment,
PASSWORD: password.PasswordEnvironment,
TESTING: testing.TestingEnvironment
}
def set_env(env_type: str, env_config: EnvironmentConfig):
global env
if env_type in ENV_DICT:
env = ENV_DICT[env_type](env_config)
def set_to_standard():
global env
if env:
env_config = env.get_config()
env_config.server_config = 'standard'
set_env('standard', env_config)
env.save_config()
user_store.UserStore.set_users(env.get_auth_users())
try:
config = EnvironmentConfig.get_from_file()
__env_type = config.server_config
set_env(__env_type, config)
# noinspection PyUnresolvedReferences
logger.info('Monkey\'s env is: {0}'.format(env.__class__.__name__))
except Exception:
logger.error('Failed initializing environment', exc_info=True)
raise

View File

@ -1,12 +1,14 @@
from monkey_island.cc.environment import Environment
import monkey_island.cc.auth
__author__ = 'itay.mizeretz'
class PasswordEnvironment(Environment):
_credentials_required = True
def get_auth_users(self):
return [
monkey_island.cc.auth.User(1, self.config['user'], self.config['hash'])
]
if self._is_registered():
return self._config.get_users()
else:
return []

View File

@ -1,15 +1,16 @@
import monkey_island.cc.auth
from monkey_island.cc.resources.auth.auth_user import User
from monkey_island.cc.environment import Environment
__author__ = 'itay.mizeretz'
class StandardEnvironment(Environment):
_credentials_required = False
# SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()'
NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \
'8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557'
def get_auth_users(self):
return [
monkey_island.cc.auth.User(1, StandardEnvironment.NO_AUTH_CREDS, StandardEnvironment.NO_AUTH_CREDS)
]
return [User(1, StandardEnvironment.NO_AUTH_CREDS, StandardEnvironment.NO_AUTH_CREDS)]

View File

@ -0,0 +1,112 @@
import json
import os
from typing import Dict
from unittest import TestCase
from unittest.mock import patch, MagicMock
from common.utils.exceptions import InvalidRegistrationCredentialsError, AlreadyRegisteredError, \
CredentialsNotRequiredError, RegistrationNotNeededError
from monkey_island.cc.environment import Environment, EnvironmentConfig, UserCreds
import monkey_island.cc.testing.environment.server_config_mocks as config_mocks
def get_server_config_file_path_test_version():
return os.path.join(os.getcwd(), 'test_config.json')
class TestEnvironment(TestCase):
class EnvironmentCredentialsNotRequired(Environment):
def __init__(self):
config = EnvironmentConfig('test', 'test', UserCreds())
super().__init__(config)
_credentials_required = False
def get_auth_users(self):
return []
class EnvironmentCredentialsRequired(Environment):
def __init__(self):
config = EnvironmentConfig('test', 'test', UserCreds())
super().__init__(config)
_credentials_required = True
def get_auth_users(self):
return []
class EnvironmentAlreadyRegistered(Environment):
def __init__(self):
config = EnvironmentConfig('test', 'test', UserCreds('test_user', 'test_secret'))
super().__init__(config)
_credentials_required = True
def get_auth_users(self):
return [1, "Test_username", "Test_secret"]
@patch.object(target=EnvironmentConfig, attribute="save_to_file", new=MagicMock())
def test_try_add_user(self):
env = TestEnvironment.EnvironmentCredentialsRequired()
credentials = UserCreds(username="test", password_hash="1231234")
env.try_add_user(credentials)
credentials = UserCreds(username="test")
with self.assertRaises(InvalidRegistrationCredentialsError):
env.try_add_user(credentials)
env = TestEnvironment.EnvironmentCredentialsNotRequired()
credentials = UserCreds(username="test", password_hash="1231234")
with self.assertRaises(RegistrationNotNeededError):
env.try_add_user(credentials)
def test_try_needs_registration(self):
env = TestEnvironment.EnvironmentAlreadyRegistered()
with self.assertRaises(AlreadyRegisteredError):
env._try_needs_registration()
env = TestEnvironment.EnvironmentCredentialsNotRequired()
with self.assertRaises(CredentialsNotRequiredError):
env._try_needs_registration()
env = TestEnvironment.EnvironmentCredentialsRequired()
self.assertTrue(env._try_needs_registration())
def test_needs_registration(self):
env = TestEnvironment.EnvironmentCredentialsRequired()
self._test_bool_env_method("needs_registration", env, config_mocks.CONFIG_WITH_CREDENTIALS, False)
self._test_bool_env_method("needs_registration", env, config_mocks.CONFIG_NO_CREDENTIALS, True)
self._test_bool_env_method("needs_registration", env, config_mocks.CONFIG_PARTIAL_CREDENTIALS, True)
env = TestEnvironment.EnvironmentCredentialsNotRequired()
self._test_bool_env_method("needs_registration", env, config_mocks.CONFIG_STANDARD_ENV, False)
self._test_bool_env_method("needs_registration", env, config_mocks.CONFIG_STANDARD_WITH_CREDENTIALS, False)
def test_is_registered(self):
env = TestEnvironment.EnvironmentCredentialsRequired()
self._test_bool_env_method("_is_registered", env, config_mocks.CONFIG_WITH_CREDENTIALS, True)
self._test_bool_env_method("_is_registered", env, config_mocks.CONFIG_NO_CREDENTIALS, False)
self._test_bool_env_method("_is_registered", env, config_mocks.CONFIG_PARTIAL_CREDENTIALS, False)
env = TestEnvironment.EnvironmentCredentialsNotRequired()
self._test_bool_env_method("_is_registered", env, config_mocks.CONFIG_STANDARD_ENV, False)
self._test_bool_env_method("_is_registered", env, config_mocks.CONFIG_STANDARD_WITH_CREDENTIALS, False)
def test_is_credentials_set_up(self):
env = TestEnvironment.EnvironmentCredentialsRequired()
self._test_bool_env_method("_is_credentials_set_up", env, config_mocks.CONFIG_NO_CREDENTIALS, False)
self._test_bool_env_method("_is_credentials_set_up", env, config_mocks.CONFIG_WITH_CREDENTIALS, True)
self._test_bool_env_method("_is_credentials_set_up", env, config_mocks.CONFIG_PARTIAL_CREDENTIALS, False)
env = TestEnvironment.EnvironmentCredentialsNotRequired()
self._test_bool_env_method("_is_credentials_set_up", env, config_mocks.CONFIG_STANDARD_ENV, False)
def _test_bool_env_method(self, method_name: str, env: Environment, config: Dict, expected_result: bool):
env._config = EnvironmentConfig.get_from_json(json.dumps(config))
method = getattr(env, method_name)
if expected_result:
self.assertTrue(method())
else:
self.assertFalse(method())

View File

@ -1,25 +0,0 @@
from monkey_island.cc.auth import User
from monkey_island.cc.testing.IslandTestCase import IslandTestCase
from monkey_island.cc.environment.aws import AwsEnvironment
import hashlib
class TestAwsEnvironment(IslandTestCase):
def test_get_auth_users(self):
env = AwsEnvironment()
# This is "injecting" the instance id to the env. This is the UTs aren't always executed on the same AWS machine
# (might not be an AWS machine at all).
# Perhaps it would have been more elegant to create a Mock, but not worth it for
# this small test.
env._instance_id = "i-666"
hash_obj = hashlib.sha3_512()
hash_obj.update(b"i-666")
auth_users = env.get_auth_users()
assert isinstance(auth_users, list)
assert len(auth_users) == 1
auth_user = auth_users[0]
assert isinstance(auth_user, User)
assert auth_user.id == 1
assert auth_user.username == "monkey"
assert auth_user.secret == hash_obj.hexdigest()

View File

@ -0,0 +1,99 @@
import json
import os
import platform
from typing import Dict
from unittest import TestCase
from unittest.mock import patch, MagicMock
from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH
from monkey_island.cc.environment.environment_config import EnvironmentConfig
from monkey_island.cc.environment.user_creds import UserCreds
import monkey_island.cc.testing.environment.server_config_mocks as config_mocks
def get_server_config_file_path_test_version():
return os.path.join(os.getcwd(), 'test_config.json')
class TestEnvironmentConfig(TestCase):
def test_get_from_json(self):
self._test_get_from_json(config_mocks.CONFIG_WITH_CREDENTIALS)
self._test_get_from_json(config_mocks.CONFIG_NO_CREDENTIALS)
self._test_get_from_json(config_mocks.CONFIG_PARTIAL_CREDENTIALS)
def _test_get_from_json(self, config: Dict):
config_json = json.dumps(config)
env_config_object = EnvironmentConfig.get_from_json(config_json)
self.assertEqual(config['server_config'], env_config_object.server_config)
self.assertEqual(config['deployment'], env_config_object.deployment)
if 'user' in config:
self.assertEqual(config['user'], env_config_object.user_creds.username)
if 'password_hash' in config:
self.assertEqual(config['password_hash'], env_config_object.user_creds.password_hash)
if 'aws' in config:
self.assertEqual(config['aws'], env_config_object.aws)
def test_save_to_file(self):
self._test_save_to_file(config_mocks.CONFIG_WITH_CREDENTIALS)
self._test_save_to_file(config_mocks.CONFIG_NO_CREDENTIALS)
self._test_save_to_file(config_mocks.CONFIG_PARTIAL_CREDENTIALS)
@patch.object(target=EnvironmentConfig, attribute="get_config_file_path",
new=MagicMock(return_value=get_server_config_file_path_test_version()))
def _test_save_to_file(self, config: Dict):
user_creds = UserCreds.get_from_dict(config)
env_config = EnvironmentConfig(server_config=config['server_config'],
deployment=config['deployment'],
user_creds=user_creds)
env_config.save_to_file()
file_path = get_server_config_file_path_test_version()
with open(file_path, 'r') as f:
content_from_file = f.read()
os.remove(file_path)
self.assertDictEqual(config, json.loads(content_from_file))
def test_get_server_config_file_path(self):
if platform.system() == "Windows":
server_file_path = MONKEY_ISLAND_ABS_PATH + "\cc\server_config.json"
else:
server_file_path = MONKEY_ISLAND_ABS_PATH + "/cc/server_config.json"
self.assertEqual(EnvironmentConfig.get_config_file_path(), server_file_path)
def test_get_from_dict(self):
config_dict = config_mocks.CONFIG_WITH_CREDENTIALS
env_conf = EnvironmentConfig.get_from_dict(config_dict)
self.assertEqual(env_conf.server_config, config_dict['server_config'])
self.assertEqual(env_conf.deployment, config_dict['deployment'])
self.assertEqual(env_conf.user_creds.username, config_dict['user'])
self.assertEqual(env_conf.aws, None)
config_dict = config_mocks.CONFIG_BOGUS_VALUES
env_conf = EnvironmentConfig.get_from_dict(config_dict)
self.assertEqual(env_conf.server_config, config_dict['server_config'])
self.assertEqual(env_conf.deployment, config_dict['deployment'])
self.assertEqual(env_conf.user_creds.username, config_dict['user'])
self.assertEqual(env_conf.aws, config_dict['aws'])
def test_to_dict(self):
conf_json1 = json.dumps(config_mocks.CONFIG_WITH_CREDENTIALS)
self._test_to_dict(EnvironmentConfig.get_from_json(conf_json1))
conf_json2 = json.dumps(config_mocks.CONFIG_NO_CREDENTIALS)
self._test_to_dict(EnvironmentConfig.get_from_json(conf_json2))
conf_json3 = json.dumps(config_mocks.CONFIG_PARTIAL_CREDENTIALS)
self._test_to_dict(EnvironmentConfig.get_from_json(conf_json3))
def _test_to_dict(self, env_config_object: EnvironmentConfig):
test_dict = {'server_config': env_config_object.server_config,
'deployment': env_config_object.deployment}
user_creds = env_config_object.user_creds
if user_creds.username:
test_dict.update({'user': user_creds.username})
if user_creds.password_hash:
test_dict.update({'password_hash': user_creds.password_hash})
self.assertDictEqual(test_dict, env_config_object.to_dict())

View File

@ -0,0 +1,38 @@
from unittest import TestCase
from monkey_island.cc.environment.user_creds import UserCreds
class TestUserCreds(TestCase):
def test_to_dict(self):
user_creds = UserCreds()
self.assertDictEqual(user_creds.to_dict(), {})
user_creds = UserCreds(username="Test")
self.assertDictEqual(user_creds.to_dict(), {'user': "Test"})
user_creds = UserCreds(password_hash="abc1231234")
self.assertDictEqual(user_creds.to_dict(), {'password_hash': "abc1231234"})
user_creds = UserCreds(username="Test", password_hash="abc1231234")
self.assertDictEqual(user_creds.to_dict(), {'user': "Test", 'password_hash': "abc1231234"})
def test_to_auth_user(self):
user_creds = UserCreds(username="Test", password_hash="abc1231234")
auth_user = user_creds.to_auth_user()
self.assertEqual(auth_user.id, 1)
self.assertEqual(auth_user.username, "Test")
self.assertEqual(auth_user.secret, "abc1231234")
user_creds = UserCreds(username="Test")
auth_user = user_creds.to_auth_user()
self.assertEqual(auth_user.id, 1)
self.assertEqual(auth_user.username, "Test")
self.assertEqual(auth_user.secret, "")
user_creds = UserCreds(password_hash="abc1231234")
auth_user = user_creds.to_auth_user()
self.assertEqual(auth_user.id, 1)
self.assertEqual(auth_user.username, "")
self.assertEqual(auth_user.secret, "abc1231234")

View File

@ -1,4 +1,4 @@
from monkey_island.cc.environment import Environment
from monkey_island.cc.environment import Environment, EnvironmentConfig
class TestingEnvironment(Environment):
@ -7,8 +7,10 @@ class TestingEnvironment(Environment):
This will cause all mongo connections to happen via `mongomock` instead of using an actual mongodb instance.
"""
def __init__(self):
super(TestingEnvironment, self).__init__()
_credentials_required = True
def __init__(self, config: EnvironmentConfig):
super(TestingEnvironment, self).__init__(config)
self.testing = True
def get_auth_users(self):

View File

@ -0,0 +1,41 @@
from __future__ import annotations
import json
from typing import Dict
from monkey_island.cc.resources.auth.auth_user import User
class UserCreds:
def __init__(self, username="", password_hash=""):
self.username = username
self.password_hash = password_hash
def __bool__(self) -> bool:
return bool(self.username and self.password_hash)
def to_dict(self) -> Dict:
cred_dict = {}
if self.username:
cred_dict.update({'user': self.username})
if self.password_hash:
cred_dict.update({'password_hash': self.password_hash})
return cred_dict
def to_auth_user(self) -> User:
return User(1, self.username, self.password_hash)
@staticmethod
def get_from_dict(data_dict: Dict) -> UserCreds:
creds = UserCreds()
if 'user' in data_dict:
creds.username = data_dict['user']
if 'password_hash' in data_dict:
creds.password_hash = data_dict['password_hash']
return creds
@staticmethod
def get_from_json(json_data: bytes) -> UserCreds:
cred_dict = json.loads(json_data)
return UserCreds.get_from_dict(cred_dict)

View File

@ -23,7 +23,7 @@ logger = logging.getLogger(__name__)
from monkey_island.cc.app import init_app
from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list
from monkey_island.cc.network_utils import local_ip_addresses
from monkey_island.cc.environment.environment import env
import monkey_island.cc.environment.environment_singleton as env_singleton
from monkey_island.cc.database import is_db_server_up, get_db_version
from monkey_island.cc.resources.monkey_download import MonkeyDownload
from common.version import get_version
@ -33,7 +33,7 @@ from monkey_island.cc.setup import setup
def main(should_setup_only=False):
logger.info("Starting bootloader server")
mongo_url = os.environ.get('MONGO_URL', env.get_mongo_url())
mongo_url = os.environ.get('MONGO_URL', env_singleton.env.get_mongo_url())
bootloader_server_thread = Thread(target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True)
bootloader_server_thread.start()
@ -46,7 +46,7 @@ def start_island_server(should_setup_only):
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
mongo_url = os.environ.get('MONGO_URL', env.get_mongo_url())
mongo_url = os.environ.get('MONGO_URL', env_singleton.env.get_mongo_url())
wait_for_mongo_db_server(mongo_url)
assert_mongo_db_version(mongo_url)
@ -62,13 +62,13 @@ def start_island_server(should_setup_only):
logger.warning("Setup only flag passed. Exiting.")
return
if env.is_debug():
if env_singleton.env.is_debug():
app.run(host='0.0.0.0', debug=True, ssl_context=(crt_path, key_path))
else:
http_server = HTTPServer(WSGIContainer(app),
ssl_options={'certfile': os.environ.get('SERVER_CRT', crt_path),
'keyfile': os.environ.get('SERVER_KEY', key_path)})
http_server.listen(env.get_island_port())
http_server.listen(env_singleton.env.get_island_port())
log_init_info()
IOLoop.instance().start()
@ -77,7 +77,7 @@ def log_init_info():
logger.info('Monkey Island Server is running!')
logger.info(f"version: {get_version()}")
logger.info('Listening on the following URLs: {}'.format(
", ".join(["https://{}:{}".format(x, env.get_island_port()) for x in local_ip_addresses()])
", ".join(["https://{}:{}".format(x, env_singleton.env.get_island_port()) for x in local_ip_addresses()])
)
)
MonkeyDownload.log_executable_hashes()

View File

@ -1,15 +1,15 @@
from mongoengine import connect
from monkey_island.cc.environment.environment import env
import monkey_island.cc.environment.environment_singleton as env_singleton
# This section sets up the DB connection according to the environment.
# If testing, use mongomock which only emulates mongo. for more information, see
# http://docs.mongoengine.org/guide/mongomock.html .
# Otherwise, use an actual mongod instance with connection parameters supplied by env.
if env.testing: # See monkey_island.cc.environment.testing
if env_singleton.env.testing: # See monkey_island.cc.environment.testing
connect('mongoenginetest', host='mongomock://localhost')
else:
connect(db=env.mongo_db_name, host=env.mongo_db_host, port=env.mongo_db_port)
connect(db=env_singleton.env.mongo_db_name, host=env_singleton.env.mongo_db_host, port=env_singleton.env.mongo_db_port)
# Order of importing matters here, for registering the embedded and referenced documents before using them.
from .config import Config # noqa: F401

View File

@ -1,7 +1,7 @@
import flask_restful
from flask import jsonify, request, json, current_app
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.attack.attack_config import AttackConfig
__author__ = "VakarisZ"

View File

@ -1,5 +1,5 @@
import flask_restful
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.attack.attack_report import AttackReportService
from monkey_island.cc.services.attack.attack_schema import SCHEMA
from flask import json, current_app

View File

@ -4,34 +4,23 @@ from flask import current_app, abort
from flask_jwt import JWT, _jwt_required, JWTError
from werkzeug.security import safe_str_cmp
from monkey_island.cc.environment.environment import env
import monkey_island.cc.environment.environment_singleton as env_singleton
import monkey_island.cc.resources.auth.user_store as user_store
__author__ = 'itay.mizeretz'
class User(object):
def __init__(self, user_id, username, secret):
self.id = user_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}
user_store.UserStore.set_users(env_singleton.env.get_auth_users())
def authenticate(username, secret):
user = username_table.get(username, None)
user = user_store.UserStore.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)
return user_store.UserStore.user_id_table.get(user_id, None)
JWT(app, authenticate, identity)

View File

@ -0,0 +1,11 @@
__author__ = 'itay.mizeretz'
class User(object):
def __init__(self, user_id, username, secret):
self.id = user_id
self.username = username
self.secret = secret
def __str__(self):
return "User(id='%s')" % self.id

View File

@ -0,0 +1,15 @@
from typing import List
from monkey_island.cc.resources.auth.auth_user import User
class UserStore:
users = []
username_table = {}
user_id_table = {}
@staticmethod
def set_users(users: List[User]):
UserStore.users = users
UserStore.username_table = {u.username: u for u in UserStore.users}
UserStore.user_id_table = {u.id: u for u in UserStore.users}

View File

@ -0,0 +1,19 @@
import json
import logging
from flask import request
import flask_restful
import monkey_island.cc.environment.environment_singleton as env_singleton
logger = logging.getLogger(__name__)
class Environment(flask_restful.Resource):
def patch(self):
env_data = json.loads(request.data)
if env_data['server_config'] == "standard":
if env_singleton.env.needs_registration():
env_singleton.set_to_standard()
logger.warning("No user registered, Island on standard mode - no credentials required to access.")
return {}

View File

@ -3,7 +3,7 @@ import json
import flask_restful
from flask import request, jsonify, abort
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.config import ConfigService

View File

@ -2,7 +2,7 @@ import logging
import flask_restful
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.island_logs import IslandLogService
__author__ = "Maor.Rayzin"

View File

@ -6,7 +6,7 @@ import sys
from flask import request, jsonify, make_response
import flask_restful
from monkey_island.cc.environment.environment import env
import monkey_island.cc.environment.environment_singleton as env_singleton
from monkey_island.cc.models import Monkey
from monkey_island.cc.resources.monkey_download import get_monkey_executable
from monkey_island.cc.services.node import NodeService
@ -43,7 +43,7 @@ def run_local_monkey():
# run the monkey
try:
args = ['"%s" m0nk3y -s %s:%s' % (target_path, local_ip_addresses()[0], env.get_island_port())]
args = ['"%s" m0nk3y -s %s:%s' % (target_path, local_ip_addresses()[0], env_singleton.env.get_island_port())]
if sys.platform == "win32":
args = "".join(args)
pid = subprocess.Popen(args, shell=True).pid

View File

@ -4,7 +4,7 @@ import flask_restful
from bson import ObjectId
from flask import request
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.database import mongo
from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore
from monkey_island.cc.services.log import LogService

View File

@ -3,7 +3,7 @@ import json
import flask_restful
from flask import request, jsonify, abort
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.config import ConfigService
__author__ = 'Barak'

View File

@ -1,6 +1,6 @@
import flask_restful
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.netmap.net_edge import NetEdgeService
from monkey_island.cc.services.netmap.net_node import NetNodeService

View File

@ -1,7 +1,7 @@
from flask import request
import flask_restful
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.node import NodeService
__author__ = 'Barak'

View File

@ -1,6 +1,6 @@
import flask_restful
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.utils.node_states import NodeStates as NodeStateList

View File

@ -2,7 +2,7 @@ import flask_restful
from flask import request, send_from_directory, Response
from monkey_island.cc.services.config import ConfigService
from monkey_island.cc.services.post_breach_files import PBA_WINDOWS_FILENAME_PATH, PBA_LINUX_FILENAME_PATH, UPLOADS_DIR
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.resources.auth.auth import jwt_required
import os
from werkzeug.utils import secure_filename
import logging

View File

@ -0,0 +1,19 @@
import flask_restful
from flask import request, make_response
from common.utils.exceptions import InvalidRegistrationCredentialsError, RegistrationNotNeededError
import monkey_island.cc.environment.environment_singleton as env_singleton
from monkey_island.cc.environment.user_creds import UserCreds
class Registration(flask_restful.Resource):
def get(self):
return {'needs_registration': env_singleton.env.needs_registration()}
def post(self):
credentials = UserCreds.get_from_json(request.data)
try:
env_singleton.env.try_add_user(credentials)
return make_response({"error": ""}, 200)
except (InvalidRegistrationCredentialsError, RegistrationNotNeededError) as e:
return make_response({"error": str(e)}, 400)

View File

@ -4,7 +4,7 @@ from botocore.exceptions import NoCredentialsError, ClientError
from flask import request, jsonify, make_response
import flask_restful
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
from common.cloud.aws.aws_service import AwsService

View File

@ -3,7 +3,7 @@ import http.client
import flask_restful
from flask import jsonify
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.reporting.report import ReportService
from monkey_island.cc.services.reporting.zero_trust_service import ZeroTrustService

View File

@ -4,7 +4,7 @@ import threading
import flask_restful
from flask import request, make_response, jsonify
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.database import mongo
from monkey_island.cc.services.database import Database
from monkey_island.cc.services.infection_lifecycle import InfectionLifecycle
@ -40,5 +40,3 @@ class Root(flask_restful.Resource):
ip_addresses=local_ip_addresses(),
mongo=str(mongo.db),
completed_steps=InfectionLifecycle.get_completed_steps())

View File

@ -6,7 +6,7 @@ import dateutil
import flask_restful
from flask import request
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.database import mongo
from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore
from monkey_island.cc.services.node import NodeService

View File

@ -6,7 +6,7 @@ import flask_restful
from flask import request
import flask_pymongo
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.database import mongo
from monkey_island.cc.services.node import NodeService

View File

@ -2,7 +2,7 @@ import logging
import flask_restful
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.attack.attack_report import AttackReportService
from monkey_island.cc.services.reporting.report import ReportService

View File

@ -2,7 +2,7 @@ from bson import json_util
import flask_restful
from flask import request
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.database import mongo, database

View File

@ -2,7 +2,7 @@ from bson import json_util
import flask_restful
from flask import request
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.database import mongo

View File

@ -1,7 +1,7 @@
import flask_restful
import json
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.reporting.zero_trust_service import ZeroTrustService

View File

@ -1,4 +1,4 @@
{
"server_config": "standard",
"server_config": "password",
"deployment": "develop"
}

View File

@ -6,7 +6,7 @@ from jsonschema import Draft4Validator, validators
import monkey_island.cc.services.post_breach_files
from monkey_island.cc.database import mongo
from monkey_island.cc.environment.environment import env
import monkey_island.cc.environment.environment_singleton as env_singleton
from monkey_island.cc.network_utils import local_ip_addresses
from .config_schema import SCHEMA
from monkey_island.cc.encryptor import encryptor
@ -216,8 +216,8 @@ class ConfigService:
@staticmethod
def set_server_ips_in_config(config):
ips = local_ip_addresses()
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], env.get_island_port())
config["cnc"]["servers"]["command_servers"] = ["%s:%d" % (ip, env_singleton.env.get_island_port()) for ip in ips]
config["cnc"]["servers"]["current_server"] = "%s:%d" % (ips[0], env_singleton.env.get_island_port())
@staticmethod
def save_initial_config_if_needed():

View File

@ -6,7 +6,7 @@ import boto3
from botocore.exceptions import UnknownServiceError
from common.cloud.aws.aws_instance import AwsInstance
from monkey_island.cc.environment.environment import load_server_configuration_from_file
from monkey_island.cc.environment import EnvironmentConfig
from monkey_island.cc.services.reporting.exporter import Exporter
__authors__ = ['maor.rayzin', 'shay.nehmad']
@ -68,7 +68,7 @@ class AWSExporter(Exporter):
# azure and conficker are not relevant issues for an AWS env
}
configured_product_arn = load_server_configuration_from_file()['aws'].get('sec_hub_product_arn', '')
configured_product_arn = EnvironmentConfig.get_from_file().aws.get('sec_hub_product_arn', '')
product_arn = 'arn:aws:securityhub:{region}:{arn}'.format(region=region, arn=configured_product_arn)
instance_arn = 'arn:aws:ec2:' + str(region) + ':instance:{instance_id}'
# Not suppressing error here on purpose.

View File

@ -3,7 +3,7 @@ import logging
from monkey_island.cc.services.reporting.report_exporter_manager import ReportExporterManager
from monkey_island.cc.services.reporting.aws_exporter import AWSExporter
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
from monkey_island.cc.environment.environment import env
import monkey_island.cc.environment.environment_singleton as env_singleton
logger = logging.getLogger(__name__)
@ -21,7 +21,7 @@ def try_add_aws_exporter_to_manager(manager):
# noinspection PyBroadException
try:
RemoteRunAwsService.init()
if RemoteRunAwsService.is_running_on_aws() and ('aws' == env.get_deployment()):
if RemoteRunAwsService.is_running_on_aws() and ('aws' == env_singleton.env.get_deployment()):
manager.add_exporter_to_list(AWSExporter)
except Exception:
logger.error("Failed adding aws exporter to manager. Exception info:", exc_info=True)

View File

@ -3,7 +3,7 @@ import logging
import requests
from common.version import get_version
from monkey_island.cc.environment.environment import env
import monkey_island.cc.environment.environment_singleton as env_singleton
__author__ = "itay.mizeretz"
@ -40,7 +40,7 @@ class VersionUpdateService:
Checks if newer monkey version is available
:return: False if not, version in string format ('1.6.2') otherwise
"""
url = VersionUpdateService.VERSION_SERVER_CHECK_NEW_URL % (env.get_deployment(), get_version())
url = VersionUpdateService.VERSION_SERVER_CHECK_NEW_URL % (env_singleton.env.get_deployment(), get_version())
reply = requests.get(url, timeout=15)
@ -54,4 +54,4 @@ class VersionUpdateService:
@staticmethod
def get_download_link():
return VersionUpdateService.VERSION_SERVER_DOWNLOAD_URL % (env.get_deployment(), get_version())
return VersionUpdateService.VERSION_SERVER_DOWNLOAD_URL % (env_singleton.env.get_deployment(), get_version())

View File

@ -1,5 +1,5 @@
import unittest
from monkey_island.cc.environment.environment import env
import monkey_island.cc.environment.environment_singleton as env_singleton
from monkey_island.cc.models import Monkey
from monkey_island.cc.models.edge import Edge
from monkey_island.cc.models.zero_trust.finding import Finding
@ -7,7 +7,7 @@ from monkey_island.cc.models.zero_trust.finding import Finding
class IslandTestCase(unittest.TestCase):
def fail_if_not_testing_env(self):
self.assertFalse(not env.testing, "Change server_config.json to testing environment.")
self.assertFalse(not env_singleton.env.testing, "Change server_config.json to testing environment.")
@staticmethod
def clean_monkey_db():

View File

@ -0,0 +1,41 @@
# Username:test Password:test
CONFIG_WITH_CREDENTIALS = {
"server_config": "password",
"deployment": "develop",
"user": "test",
"password_hash": "9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a"
"4f2e9b3ca9f484f521d0ce464345cc1aec96779149c14"
}
CONFIG_NO_CREDENTIALS = {
"server_config": "password",
"deployment": "develop"
}
CONFIG_PARTIAL_CREDENTIALS = {
"server_config": "password",
"deployment": "develop",
"user": "test"
}
CONFIG_BOGUS_VALUES = {
"server_config": "password",
"deployment": "develop",
"user": "test",
"aws": "test",
"test": "test",
"test2": "test2"
}
CONFIG_STANDARD_ENV = {
"server_config": "standard",
"deployment": "develop"
}
CONFIG_STANDARD_WITH_CREDENTIALS = {
"server_config": "standard",
"deployment": "develop",
"user": "test",
"password_hash": "9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a"
"4f2e9b3ca9f484f521d0ce464345cc1aec96779149c14"
}

View File

@ -54,7 +54,7 @@
"react/jsx-uses-vars": 1,
"react/jsx-key": 1,
"react/prop-types": 0,
"react/no-unescaped-entities": 1,
"react/no-unescaped-entities": 0,
"react/no-unknown-property": [1, { "ignore": ["class"] }],
"react/no-string-refs": 1,
"react/display-name": 1,

File diff suppressed because it is too large Load Diff

View File

@ -63,7 +63,7 @@
"@fortawesome/free-solid-svg-icons": "^5.11.2",
"@fortawesome/react-fontawesome": "^0.1.7",
"@kunukn/react-collapse": "^1.2.7",
"bootstrap": "^3.4.1",
"bootstrap": "^4.5.0",
"classnames": "^2.2.6",
"core-js": "^2.6.10",
"d3": "^5.14.1",
@ -80,19 +80,21 @@
"rainge": "^1.0.1",
"rc-progress": "^2.5.2",
"react": "^16.12.0",
"react-bootstrap": "^0.32.4",
"react-bootstrap": "^1.0.1",
"react-copy-to-clipboard": "^5.0.2",
"react-data-components": "^1.2.0",
"react-desktop-notification": "^1.0.9",
"react-dimensions": "^1.3.0",
"react-event-timeline": "^1.6.3",
"react-hot-loader": "^4.12.20",
"react-dom": "^16.12.0",
"react-event-timeline": "^1.6.3",
"react-fa": "^5.0.0",
"react-filepond": "^7.0.1",
"react-graph-vis": "^1.0.5",
"react-hot-loader": "^4.12.20",
"react-json-tree": "^0.11.2",
"react-jsonschema-form": "^1.8.0",
"react-jsonschema-form-bs4": "^1.7.1",
"react-particles-js": "^3.2.1",
"react-redux": "^5.1.2",
"react-router-dom": "^4.3.1",
"react-spinners": "^0.5.13",

View File

@ -1,9 +1,6 @@
import React from 'react';
import {BrowserRouter as Router, NavLink, Redirect, Route, Switch} from 'react-router-dom';
import {Col, Grid, Row} from 'react-bootstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faCheck } from '@fortawesome/free-solid-svg-icons/faCheck'
import { faUndo } from '@fortawesome/free-solid-svg-icons/faUndo'
import {BrowserRouter as Router, Redirect, Route, Switch} from 'react-router-dom';
import {Container} from 'react-bootstrap';
import RunServerPage from 'components/pages/RunServerPage';
import ConfigurePage from 'components/pages/ConfigurePage';
@ -15,6 +12,7 @@ import ReportPage from 'components/pages/ReportPage';
import LicensePage from 'components/pages/LicensePage';
import AuthComponent from 'components/AuthComponent';
import LoginPageComponent from 'components/pages/LoginPage';
import RegisterPageComponent from 'components/pages/RegisterPage';
import Notifier from 'react-desktop-notification';
import NotFoundPage from 'components/pages/NotFoundPage';
@ -24,17 +22,16 @@ import 'react-data-components/css/table-twbs.css';
import 'styles/App.css';
import 'react-toggle/style.css';
import 'react-table/react-table.css';
import VersionComponent from './side-menu/VersionComponent';
import logoImage from '../images/monkey-icon.svg';
import infectionMonkeyImage from '../images/infection-monkey.svg';
import guardicoreLogoImage from '../images/guardicore-logo.png';
import notificationIcon from '../images/notification-logo-512x512.png';
import {StandardLayoutComponent} from './layouts/StandardLayoutComponent';
const reportZeroTrustRoute = '/report/zeroTrust';
class AppComponent extends AuthComponent {
updateStatus = () => {
if (this.state.isLoggedIn === false){
return
}
this.auth.loggedIn()
.then(res => {
if (this.state.isLoggedIn !== res) {
@ -43,6 +40,15 @@ class AppComponent extends AuthComponent {
});
}
if (!res) {
this.auth.needsRegistration()
.then(result => {
this.setState({
needsRegistration: result
});
})
}
if (res) {
this.authFetch('/api')
.then(res => res.json())
@ -70,10 +76,16 @@ class AppComponent extends AuthComponent {
case true:
return page_component;
case false:
return <Redirect to={{pathname: '/login'}}/>;
switch (this.state.needsRegistration) {
case true:
return <Redirect to={{pathname: '/register'}}/>
case false:
return <Redirect to={{pathname: '/login'}}/>;
default:
return page_component;
}
default:
return page_component;
}
};
@ -85,8 +97,8 @@ class AppComponent extends AuthComponent {
};
redirectTo = (userPath, targetPath) => {
let pathQuery = new RegExp(userPath+'[\/]?$', 'g');
if(window.location.pathname.match(pathQuery)){
let pathQuery = new RegExp(userPath + '[\/]?$', 'g');
if (window.location.pathname.match(pathQuery)) {
return <Redirect to={{pathname: targetPath}}/>
}
};
@ -94,22 +106,18 @@ class AppComponent extends AuthComponent {
constructor(props) {
super(props);
this.state = {
removePBAfiles: false,
completedSteps: {
run_server: true,
run_monkey: false,
infection_done: false,
report_done: false,
isLoggedIn: undefined
}
isLoggedIn: undefined,
needsRegistration: undefined
},
noAuthLoginAttempted: undefined
};
}
// Sets the property that indicates if we need to remove PBA files from state or not
setRemovePBAfiles = (rmFiles) => {
this.setState({removePBAfiles: rmFiles});
};
componentDidMount() {
this.updateStatus();
this.interval = setInterval(this.updateStatus, 10000);
@ -122,102 +130,53 @@ class AppComponent extends AuthComponent {
render() {
return (
<Router>
<Grid fluid={true}>
<Row>
<Col sm={3} md={2} className='sidebar'>
<div className='header'>
<img alt="logo" src={logoImage} style={{width: '5vw', margin: '15px'}}/>
<img src={infectionMonkeyImage} style={{width: '15vw'}} alt='Infection Monkey'/>
</div>
<ul className='navigation'>
<li>
<NavLink to='/' exact={true}>
<span className='number'>1.</span>
Run Monkey Island Server
{this.state.completedSteps.run_server ?
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark text-success'/>
: ''}
</NavLink>
</li>
<li>
<NavLink to='/run-monkey'>
<span className='number'>2.</span>
Run Monkey
{this.state.completedSteps.run_monkey ?
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark text-success'/>
: ''}
</NavLink>
</li>
<li>
<NavLink to='/infection/map'>
<span className='number'>3.</span>
Infection Map
{this.state.completedSteps.infection_done ?
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark text-success'/>
: ''}
</NavLink>
</li>
<li>
<NavLink to='/report/security'
isActive={(match, location) => {
return (location.pathname === '/report/attack'
|| location.pathname === '/report/zeroTrust'
|| location.pathname === '/report/security')
}}>
<span className='number'>4.</span>
Security Reports
{this.state.completedSteps.report_done ?
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark text-success'/>
: ''}
</NavLink>
</li>
<li>
<NavLink to='/start-over'>
<span className='number'><FontAwesomeIcon icon={faUndo} style={{'marginLeft': '-1px'}}/></span>
Start Over
</NavLink>
</li>
</ul>
<hr/>
<ul>
<li><NavLink to='/configure'>Configuration</NavLink></li>
<li><NavLink to='/infection/telemetry'>Log</NavLink></li>
</ul>
<hr/>
<div className='guardicore-link text-center' style={{'marginBottom': '0.5em'}}>
<span>Powered by</span>
<a href='http://www.guardicore.com' rel="noopener noreferrer" target='_blank'>
<img src={guardicoreLogoImage} alt='GuardiCore'/>
</a>
</div>
<div className='license-link text-center'>
<NavLink to='/license'>License</NavLink>
</div>
<VersionComponent/>
</Col>
<Col sm={9} md={10} smOffset={3} mdOffset={2} className='main'>
<Switch>
<Route path='/login' render={() => (<LoginPageComponent onStatusChange={this.updateStatus}/>)}/>
{this.renderRoute('/', <RunServerPage onStatusChange={this.updateStatus}/>, true)}
{this.renderRoute('/configure', <ConfigurePage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/run-monkey', <RunMonkeyPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/infection/map', <MapPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/infection/telemetry', <TelemetryPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/start-over', <StartOverPage onStatusChange={this.updateStatus}/>)}
{this.redirectTo('/report', '/report/security')}
{this.renderRoute('/report/security', <ReportPage/>)}
{this.renderRoute('/report/attack', <ReportPage/>)}
{this.renderRoute('/report/zeroTrust', <ReportPage/>)}
{this.renderRoute('/license', <LicensePage onStatusChange={this.updateStatus}/>)}
<Route component={NotFoundPage} />
</Switch>
</Col>
</Row>
</Grid>
<Container fluid>
<Switch>
<Route path='/login' render={() => (<LoginPageComponent onStatusChange={this.updateStatus}/>)}/>
<Route path='/register' render={() => (<RegisterPageComponent onStatusChange={this.updateStatus}/>)}/>
{this.renderRoute('/',
<StandardLayoutComponent component={RunServerPage}
completedSteps={this.state.completedSteps}
onStatusChange={this.updateStatus}
/>,
true)}
{this.renderRoute('/configure',
<StandardLayoutComponent component={ConfigurePage}
onStatusChange={this.updateStatus}
completedSteps={this.state.completedSteps}/>)}
{this.renderRoute('/run-monkey',
<StandardLayoutComponent component={RunMonkeyPage}
onStatusChange={this.updateStatus}
completedSteps={this.state.completedSteps}/>)}
{this.renderRoute('/infection/map',
<StandardLayoutComponent component={MapPage}
onStatusChange={this.updateStatus}
completedSteps={this.state.completedSteps}/>)}
{this.renderRoute('/infection/telemetry',
<StandardLayoutComponent component={TelemetryPage}
onStatusChange={this.updateStatus}
completedSteps={this.state.completedSteps}/>)}
{this.renderRoute('/start-over',
<StandardLayoutComponent component={StartOverPage}
onStatusChange={this.updateStatus}
completedSteps={this.state.completedSteps}/>)}
{this.redirectTo('/report', '/report/security')}
{this.renderRoute('/report/security',
<StandardLayoutComponent component={ReportPage}
completedSteps={this.state.completedSteps}/>)}
{this.renderRoute('/report/attack',
<StandardLayoutComponent component={ReportPage}
completedSteps={this.state.completedSteps}/>)}
{this.renderRoute('/report/zeroTrust',
<StandardLayoutComponent component={ReportPage}
completedSteps={this.state.completedSteps}/>)}
{this.renderRoute('/license',
<StandardLayoutComponent component={LicensePage}
onStatusChange={this.updateStatus}
completedSteps={this.state.completedSteps}/>)}
<Route component={NotFoundPage}/>
</Switch>
</Container>
</Router>
);
}

View File

@ -0,0 +1,93 @@
import logoImage from '../images/monkey-icon.svg';
import infectionMonkeyImage from '../images/infection-monkey.svg';
import {NavLink} from 'react-router-dom';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
import {faUndo} from '@fortawesome/free-solid-svg-icons/faUndo';
import guardicoreLogoImage from '../images/guardicore-logo.png';
import VersionComponent from './side-menu/VersionComponent';
import React from 'react';
class SideNavComponent extends React.Component {
render() {
return (
<>
<div className='header'>
<img alt='logo' src={logoImage} style={{width: '5vw', margin: '15px'}}/>
<img src={infectionMonkeyImage} style={{width: '15vw'}} alt='Infection Monkey'/>
</div>
<ul className='navigation'>
<li>
<NavLink to='/' exact={true}>
<span className='number'>1.</span>
Run Monkey Island Server
{this.props.completedSteps.run_server ?
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark'/>
: ''}
</NavLink>
</li>
<li>
<NavLink to='/run-monkey'>
<span className='number'>2.</span>
Run Monkey
{this.props.completedSteps.run_monkey ?
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark'/>
: ''}
</NavLink>
</li>
<li>
<NavLink to='/infection/map'>
<span className='number'>3.</span>
Infection Map
{this.props.completedSteps.infection_done ?
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark'/>
: ''}
</NavLink>
</li>
<li>
<NavLink to='/report/security'
isActive={(match, location) => {
return (location.pathname === '/report/attack'
|| location.pathname === '/report/zeroTrust'
|| location.pathname === '/report/security')
}}>
<span className='number'>4.</span>
Security Reports
{this.props.completedSteps.report_done ?
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark'/>
: ''}
</NavLink>
</li>
<li>
<NavLink to='/start-over'>
<span className='number'><FontAwesomeIcon icon={faUndo} style={{'marginLeft': '-1px'}}/></span>
Start Over
</NavLink>
</li>
</ul>
<hr/>
<ul>
<li><NavLink to='/configure'>Configuration</NavLink></li>
<li><NavLink to='/infection/telemetry'>Log</NavLink></li>
</ul>
<hr/>
<div className='guardicore-link text-center' style={{'marginBottom': '0.5em'}}>
<span>Powered by</span>
<a href='http://www.guardicore.com' rel='noopener noreferrer' target='_blank'>
<img src={guardicoreLogoImage} alt='GuardiCore'/>
</a>
</div>
<div className='license-link text-center'>
<NavLink to='/license'>License</NavLink>
</div>
<VersionComponent/>
</>)
}
}
export default SideNavComponent;

View File

@ -1,7 +1,6 @@
import React from 'react';
import ReactTable from 'react-table';
import marked from 'marked';
import '../../../styles/report/AttackReport.scss';
class MitigationsComponent extends React.Component {

View File

@ -0,0 +1,15 @@
import React from 'react'
import {Route} from 'react-router-dom'
import SideNavComponent from '../SideNavComponent'
import {Col, Row} from 'react-bootstrap';
export const StandardLayoutComponent = ({component: Component, ...rest}) => (
<Route {...rest} render={() => (
<Row>
<Col sm={3} md={3} lg={3} xl={2} className='sidebar'>
<SideNavComponent completedSteps={rest['completedSteps']}/>
</Col>
<Component {...rest} />
</Row>
)}/>
)

View File

@ -1,6 +1,7 @@
import React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faHandPointLeft } from '@fortawesome/free-solid-svg-icons/faHandPointLeft'
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'
import {faHandPointLeft} from '@fortawesome/free-solid-svg-icons/faHandPointLeft'
import {faQuestionCircle} from '@fortawesome/free-solid-svg-icons/faQuestionCircle'
import Toggle from 'react-toggle';
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
import download from 'downloadjs'
@ -10,8 +11,10 @@ class PreviewPaneComponent extends AuthComponent {
generateToolTip(text) {
return (
<OverlayTrigger placement="top" overlay={<Tooltip id="tooltip">{text}</Tooltip>}>
<a><i className="glyphicon glyphicon-info-sign"/></a>
<OverlayTrigger placement="top"
overlay={<Tooltip id="tooltip">{text}</Tooltip>}
delay={{ show: 250, hide: 400 }}>
<a><FontAwesomeIcon icon={faQuestionCircle} style={{'marginRight': '0.5em'}}/></a>
</OverlayTrigger>
);
}
@ -123,8 +126,8 @@ class PreviewPaneComponent extends AuthComponent {
Download Log
</th>
<td>
<a type="button" className="btn btn-primary"
disabled={!asset.has_log}
<a type='button'
className={asset.has_log ? 'btn btn-primary' : 'btn btn-primary disabled'}
onClick={() => this.downloadLog(asset)}>Download</a>
</td>
</tr>
@ -142,7 +145,7 @@ class PreviewPaneComponent extends AuthComponent {
Exploit Timeline&nbsp;
{this.generateToolTip('Timeline of exploit attempts. Red is successful. Gray is unsuccessful')}
</h4>
<ul className="timeline">
<ul className='timeline'>
{asset.exploits.map(exploit =>
<li key={exploit.timestamp}>
<div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
@ -167,7 +170,7 @@ class PreviewPaneComponent extends AuthComponent {
assetInfo(asset) {
return (
<div>
<table className="table table-condensed">
<table className='table table-condensed'>
<tbody>
{this.osRow(asset)}
{this.ipsRow(asset)}
@ -183,7 +186,7 @@ class PreviewPaneComponent extends AuthComponent {
infectedAssetInfo(asset) {
return (
<div>
<table className="table table-condensed">
<table className='table table-condensed'>
<tbody>
{this.osRow(asset)}
{this.statusRow(asset)}
@ -202,7 +205,7 @@ class PreviewPaneComponent extends AuthComponent {
scanInfo(edge) {
return (
<div>
<table className="table table-condensed">
<table className='table table-condensed'>
<tbody>
<tr>
<th>Operating System</th>
@ -223,7 +226,7 @@ class PreviewPaneComponent extends AuthComponent {
'' :
<div>
<h4 style={{'marginTop': '2em'}}>Timeline</h4>
<ul className="timeline">
<ul className='timeline'>
{edge.exploits.map(exploit =>
<li key={exploit.timestamp}>
<div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
@ -278,7 +281,7 @@ class PreviewPaneComponent extends AuthComponent {
}
return (
<div className="preview-pane">
<div className='preview-pane'>
{!info ?
<span>
<FontAwesomeIcon icon={faHandPointLeft} style={{'marginRight': '0.5em'}}/>

View File

@ -1,11 +1,15 @@
import React from 'react';
import Form from 'react-jsonschema-form';
import {Col, Modal, Nav, NavItem} from 'react-bootstrap';
import Form from 'react-jsonschema-form-bs4';
import {Col, Modal, Nav, Button} from 'react-bootstrap';
import FileSaver from 'file-saver';
import AuthComponent from '../AuthComponent';
import {FilePond} from 'react-filepond';
import 'filepond/dist/filepond.min.css';
import ConfigMatrixComponent from '../attack/ConfigMatrixComponent';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle';
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
import {faExclamationCircle} from '@fortawesome/free-solid-svg-icons/faExclamationCircle';
const ATTACK_URL = '/api/attack';
const CONFIG_URL = '/api/configuration/island';
@ -169,8 +173,8 @@ class ConfigurePageComponent extends AuthComponent {
this.setInitialConfig(res.configuration);
this.props.onStatusChange();
}).catch(error => {
console.log('Bad configuration: ' + error.toString());
this.setState({lastAction: 'invalid_configuration'});
console.log('Bad configuration: ' + error.toString());
this.setState({lastAction: 'invalid_configuration'});
});
};
@ -219,20 +223,21 @@ class ConfigurePageComponent extends AuthComponent {
}}>
<Modal.Body>
<h2>
<div className="text-center">Warning</div>
<div className='text-center'>Warning</div>
</h2>
<p className="text-center" style={{'fontSize': '1.2em', 'marginBottom': '2em'}}>
<p className='text-center' style={{'fontSize': '1.2em', 'marginBottom': '2em'}}>
You have unsubmitted changes. Submit them before proceeding.
</p>
<div className="text-center">
<button type="button"
className="btn btn-success btn-lg"
<div className='text-center'>
<Button type='button'
className='btn btn-success'
size='lg'
style={{margin: '5px'}}
onClick={() => {
this.setState({showAttackAlert: false})
}}>
Cancel
</button>
</Button>
</div>
</Modal.Body>
</Modal>)
@ -452,17 +457,18 @@ class ConfigurePageComponent extends AuthComponent {
uiSchema={this.uiSchemas[this.state.selectedSection]}
formData={this.state.configuration[this.state.selectedSection]}
onChange={this.onChange}
noValidate={true}>
<button type="submit" className={'hidden'}>Submit</button>
noValidate={true}
className={'config-form'}>
<button type='submit' className={'hidden'}>Submit</button>
</Form>
</div>)
};
renderBasicNetworkWarning = () => {
if (this.state.selectedSection === 'basic_network') {
return (<div className="alert alert-info">
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
The Monkey scans its subnet if "Local network scan" is ticked. Additionally the monkey scans machines
return (<div className='alert alert-info'>
<FontAwesomeIcon icon={faInfoCircle} style={{'marginRight': '5px'}}/>
The Monkey scans its subnet if 'Local network scan' is ticked. Additionally the monkey scans machines
according to its range class.
</div>)
} else {
@ -471,10 +477,15 @@ class ConfigurePageComponent extends AuthComponent {
};
renderNav = () => {
return (<Nav bsStyle="tabs" justified
return (<Nav variant='tabs'
fill
activeKey={this.state.selectedSection} onSelect={this.setSelectedSection}
style={{'marginBottom': '2em'}}>
{this.state.sections.map(section => <NavItem key={section.key} eventKey={section.key}>{section.title}</NavItem>)}
style={{'marginBottom': '2em'}}
className={'config-nav'}>
{this.state.sections.map(section =>
<Nav.Item>
<Nav.Link eventKey={section.key}>{section.title}</Nav.Link>
</Nav.Item>)}
</Nav>)
};
@ -491,57 +502,60 @@ class ConfigurePageComponent extends AuthComponent {
content = this.renderConfigContent(displayedSchema)
}
return (
<Col xs={12} lg={10}>
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
lg={{offset: 3, span: 8}} xl={{offset: 2, span: 8}}
className={'main'}>
{this.renderAttackAlertModal()}
<h1 className="page-title">Monkey Configuration</h1>
<h1 className='page-title'>Monkey Configuration</h1>
{this.renderNav()}
{content}
<div className="text-center">
<button type="submit" onClick={this.onSubmit} className="btn btn-success btn-lg" style={{margin: '5px'}}>
<div className='text-center'>
<button type='submit' onClick={this.onSubmit} className='btn btn-success btn-lg' style={{margin: '5px'}}>
Submit
</button>
<button type="button" onClick={this.resetConfig} className="btn btn-danger btn-lg" style={{margin: '5px'}}>
<button type='button' onClick={this.resetConfig} className='btn btn-danger btn-lg' style={{margin: '5px'}}>
Reset to defaults
</button>
</div>
<div className="text-center">
<div className='text-center'>
<button onClick={() => document.getElementById('uploadInputInternal').click()}
className="btn btn-info btn-lg" style={{margin: '5px'}}>
className='btn btn-info btn-lg' style={{margin: '5px'}}>
Import Config
</button>
<input id="uploadInputInternal" type="file" accept=".conf" onChange={this.importConfig} style={{display: 'none'}}/>
<button type="button" onClick={this.exportConfig} className="btn btn-info btn-lg" style={{margin: '5px'}}>
<input id='uploadInputInternal' type='file' accept='.conf' onChange={this.importConfig}
style={{display: 'none'}}/>
<button type='button' onClick={this.exportConfig} className='btn btn-info btn-lg' style={{margin: '5px'}}>
Export config
</button>
</div>
<div>
{this.state.lastAction === 'reset' ?
<div className="alert alert-success">
<i className="glyphicon glyphicon-ok-sign" style={{'marginRight': '5px'}}/>
<div className='alert alert-success'>
<FontAwesomeIcon icon={faCheck} style={{'marginRight': '5px'}}/>
Configuration reset successfully.
</div>
: ''}
{this.state.lastAction === 'saved' ?
<div className="alert alert-success">
<i className="glyphicon glyphicon-ok-sign" style={{'marginRight': '5px'}}/>
<div className='alert alert-success'>
<FontAwesomeIcon icon={faCheck} style={{'marginRight': '5px'}}/>
Configuration saved successfully.
</div>
: ''}
{this.state.lastAction === 'import_failure' ?
<div className="alert alert-danger">
<i className="glyphicon glyphicon-exclamation-sign" style={{'marginRight': '5px'}}/>
<div className='alert alert-danger'>
<FontAwesomeIcon icon={faExclamationCircle} style={{'marginRight': '5px'}}/>
Failed importing configuration. Invalid config file.
</div>
: ''}
{this.state.lastAction === 'invalid_configuration' ?
<div className="alert alert-danger">
<i className="glyphicon glyphicon-exclamation-sign" style={{'marginRight': '5px'}}/>
<div className='alert alert-danger'>
<FontAwesomeIcon icon={faExclamationCircle} style={{'marginRight': '5px'}}/>
An invalid configuration file was imported or submitted.
</div>
: ''}
{this.state.lastAction === 'import_success' ?
<div className="alert alert-success">
<i className="glyphicon glyphicon-ok-sign" style={{'marginRight': '5px'}}/>
<div className='alert alert-success'>
<FontAwesomeIcon icon={faCheck} style={{'marginRight': '5px'}}/>
Configuration imported successfully.
</div>
: ''}

View File

@ -1,6 +1,8 @@
import React from 'react';
import {Col} from 'react-bootstrap';
import rainge from 'rainge'
import rainge from 'rainge';
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faCopyright} from "@fortawesome/free-regular-svg-icons";
class LicensePageComponent extends React.Component {
constructor(props) {
@ -15,11 +17,13 @@ class LicensePageComponent extends React.Component {
render() {
return (
<Col xs={12} lg={8}>
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
lg={{offset: 3, span: 9}} xl={{offset: 2, span: 7}}
className={'main'}>
<h1 className="page-title">License</h1>
<div style={{'fontSize': '1.2em'}}>
<p>
Copyright <i className="glyphicon glyphicon-copyright-mark"/> {rainge(2015)} Guardicore Ltd.
Copyright <FontAwesomeIcon icon={faCopyright}/> {rainge(2015)} Guardicore Ltd.
<br/>
Licensed under <a href="https://www.gnu.org/licenses/gpl-3.0.html" rel="noopener noreferrer" target="_blank">GPLv3</a>.
</p>

View File

@ -1,10 +1,14 @@
import React from 'react';
import {Col} from 'react-bootstrap';
import {Button, Col, Container, Form, Row} from 'react-bootstrap';
import AuthService from '../../services/AuthService'
import AuthService from '../../services/AuthService';
import Particles from "react-particles-js";
import {particleParams} from "../../styles/particle-component/AuthPageParams";
import monkeyGeneral from "../../images/militant-monkey.svg";
class LoginPageComponent extends React.Component {
login = () => {
login = (event) => {
event.preventDefault()
this.auth.login(this.username, this.password).then(res => {
if (res['result']) {
this.redirectToHome();
@ -26,6 +30,10 @@ class LoginPageComponent extends React.Component {
window.location.href = '/';
};
redirectToRegistration = () => {
window.location.href = '/register';
};
constructor(props) {
super(props);
this.username = '';
@ -34,6 +42,13 @@ class LoginPageComponent extends React.Component {
this.state = {
failed: false
};
this.auth.needsRegistration()
.then(result => {
if (result) {
this.redirectToRegistration()
}
})
this.auth.loggedIn()
.then(res => {
if (res) {
@ -44,37 +59,36 @@ class LoginPageComponent extends React.Component {
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>
);
<Container fluid className={'auth-container'}>
<Particles className={'particle-background'} params={particleParams}/>
<Row>
<Col xs={12} lg={{span: 6, offset: 3}} md={{span: 7, offset: 3}} className={'auth-block'}>
<Row>
<Col lg={8} md={8} sm={8}>
<h1 className='auth-title'>Login</h1>
<div>
<Form className={'auth-form'} onSubmit={this.login}>
<Form.Control onChange={evt => this.updateUsername(evt)} type='text' placeholder='Username'/>
<Form.Control onChange={evt => this.updatePassword(evt)} type='password' placeholder='Password'/>
<Button id={'auth-button'} type={'submit'}>
Login
</Button>
{
this.state.failed ?
<div className="alert alert-danger" role="alert">Login failed. Bad credentials.</div>
:
''
}
</Form>
</div>
</Col>
<Col lg={4} md={4} sm={4}>
<img alt="infection monkey" className={'monkey-detective'} src={monkeyGeneral}/>
</Col>
</Row>
</Col>
</Row>
</Container>)
}
}

View File

@ -1,13 +1,15 @@
import React from 'react';
import {Col, Modal} from 'react-bootstrap';
import {Col, Modal, Row} from 'react-bootstrap';
import {Link} from 'react-router-dom';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faStopCircle } from '@fortawesome/free-solid-svg-icons/faStopCircle'
import { faMinus } from '@fortawesome/free-solid-svg-icons/faMinus'
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faStopCircle} from '@fortawesome/free-solid-svg-icons/faStopCircle';
import {faMinus} from '@fortawesome/free-solid-svg-icons/faMinus';
import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane';
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
import {getOptions, edgeGroupToColor} from 'components/map/MapOptions';
import AuthComponent from '../AuthComponent';
import '../../styles/Map.scss';
import {faInfoCircle} from "@fortawesome/free-solid-svg-icons/faInfoCircle";
class MapPageComponent extends AuthComponent {
constructor(props) {
@ -73,7 +75,7 @@ class MapPageComponent extends AuthComponent {
};
updateTelemetryFromServer = () => {
if( this.state.telemetryUpdateInProgress ) {
if (this.state.telemetryUpdateInProgress) {
return
}
this.setState({telemetryUpdateInProgress: true});
@ -153,7 +155,6 @@ class MapPageComponent extends AuthComponent {
</Modal.Body>
</Modal>
)
};
renderTelemetryEntry(telemetry) {
@ -174,8 +175,8 @@ class MapPageComponent extends AuthComponent {
this.setState({
isScrolledUp: (element.scrollTop < this.scrollTop),
telemetryCurrentLine: Math.trunc(element.scrollTop/telemetryLineHeight)+1,
telemetryLines: Math.trunc(element.scrollHeight/telemetryLineHeight)
telemetryCurrentLine: Math.trunc(element.scrollTop / telemetryLineHeight) + 1,
telemetryLines: Math.trunc(element.scrollHeight / telemetryLineHeight)
});
}
@ -199,52 +200,53 @@ class MapPageComponent extends AuthComponent {
render() {
return (
<div>
{this.renderKillDialogModal()}
<Col xs={12} lg={8}>
<h1 className="page-title">3. Infection Map</h1>
</Col>
<Col xs={8}>
<div className="map-legend">
<b>Legend: </b>
<span>Exploit <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#cc0200'}}/></span>
<b style={{color: '#aeaeae'}}> | </b>
<span>Scan <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#ff9900'}}/></span>
<b style={{color: '#aeaeae'}}> | </b>
<span>Tunnel <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#0158aa'}}/></span>
<b style={{color: '#aeaeae'}}> | </b>
<span>Island Communication <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#a9aaa9'}}/></span>
</div>
{this.renderTelemetryConsole()}
<div style={{height: '80vh'}}>
<ReactiveGraph graph={this.state.graph} options={getOptions(this.state.nodeStateList)} events={this.events}/>
</div>
{this.renderTelemetryLineCount()}
</Col>
<Col xs={4}>
<input className="form-control input-block"
placeholder="Find on map"
style={{'marginBottom': '1em'}}/>
<div style={{'overflow': 'auto', 'marginBottom': '1em'}}>
<Link to="/infection/telemetry" className="btn btn-default pull-left" style={{'width': '48%'}}>Monkey
Telemetry</Link>
<button onClick={() => this.setState({showKillDialog: true})} className="btn btn-danger pull-right"
style={{'width': '48%'}}>
<FontAwesomeIcon icon={faStopCircle} style={{'marginRight': '0.5em'}}/>
Kill All Monkeys
</button>
</div>
{this.state.killPressed ?
<div className="alert alert-info">
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
Kill command sent to all monkeys
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
lg={{offset: 3, span: 9}} xl={{offset: 2, span: 10}}
className={'main'}>
<Row>
{this.renderKillDialogModal()}
<Col xs={12} lg={8}>
<h1 className="page-title">3. Infection Map</h1>
</Col>
<Col xs={8}>
<div className="map-legend">
<b>Legend: </b>
<span>Exploit <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#cc0200'}}/></span>
<b style={{color: '#aeaeae'}}> | </b>
<span>Scan <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#ff9900'}}/></span>
<b style={{color: '#aeaeae'}}> | </b>
<span>Tunnel <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#0158aa'}}/></span>
<b style={{color: '#aeaeae'}}> | </b>
<span>Island Communication <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#a9aaa9'}}/></span>
</div>
: ''}
<div style={{height: '80vh'}} className={'map-window'}>
{this.renderTelemetryLineCount()}
{this.renderTelemetryConsole()}
<ReactiveGraph graph={this.state.graph} options={getOptions(this.state.nodeStateList)}
events={this.events}/>
</div>
</Col>
<Col xs={4}>
<div style={{'overflow': 'auto', 'marginBottom': '1em'}}>
<Link to="/infection/telemetry" className="btn btn-light pull-left" style={{'width': '48%'}}>Monkey
Telemetry</Link>
<button onClick={() => this.setState({showKillDialog: true})} className="btn btn-danger pull-right"
style={{'width': '48%'}}>
<FontAwesomeIcon icon={faStopCircle} style={{'marginRight': '0.5em'}}/>
Kill All Monkeys
</button>
</div>
{this.state.killPressed ?
<div className="alert alert-info">
<FontAwesomeIcon icon={faInfoCircle} style={{'marginRight': '5px'}} />
Kill command sent to all monkeys
</div>
: ''}
<PreviewPaneComponent item={this.state.selected} type={this.state.selectedType}/>
</Col>
</div>
<PreviewPaneComponent item={this.state.selected} type={this.state.selectedType}/>
</Col>
</Row>
</Col>
);
}
}

View File

@ -0,0 +1,130 @@
import React from 'react';
import {Row, Col, Container, Form, Button} from 'react-bootstrap';
import Particles from 'react-particles-js';
import AuthService from '../../services/AuthService';
import {particleParams} from '../../styles/particle-component/AuthPageParams';
import monkeyDetective from '../../images/detective-monkey.svg';
class RegisterPageComponent extends React.Component {
NO_AUTH_API_ENDPOINT = '/api/environment';
register = (event) => {
event.preventDefault();
this.auth.register(this.username, this.password).then(res => {
this.setState({failed: false, error: ''});
if (res['result']) {
this.redirectToHome();
} else {
this.setState({
failed: true,
error: res['error']
});
}
});
};
setNoAuth = () => {
let options = {}
options['headers'] = {
'Accept': 'application/json',
'Content-Type': 'application/json'
};
options['method'] = 'PATCH'
options['body'] = JSON.stringify({'server_config': 'standard'})
return fetch(this.NO_AUTH_API_ENDPOINT, options)
.then(res => {
if (res.status === 200) {
this.auth.attemptNoAuthLogin().then(() => {
this.redirectToHome();
});
}
this.setState({
failed: true,
error: res['error']
});
})
}
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,
loading: false
};
this.auth.needsRegistration()
.then(result => {
if (!result) {
this.redirectToHome()
}
})
}
render() {
return (
<Container fluid className={'auth-container'}>
<Particles className={'particle-background'} params={particleParams}/>
<Row>
<Col xs={12} lg={{span: 6, offset: 3}} md={{span: 7, offset: 3}}
className={'auth-block'}>
<Row>
<Col lg={8} md={8} sm={8}>
<h1 className='reg-title'>First time?</h1>
<h3 className='reg-subtitle'>Let's secure your Monkey Island!</h3>
<div>
<Form className={'auth-form'} onSubmit={this.register} >
<Form.Control onChange={evt => this.updateUsername(evt)} type='text' placeholder='Username'/>
<Form.Control onChange={evt => this.updatePassword(evt)} type='password' placeholder='Password'/>
<Button id={'auth-button'} type={'submit'} >
Let's go!
</Button>
<Row>
<Col>
<a href='#' onClick={this.setNoAuth} className={'no-auth-link'}>
I want anyone to access the island
</a>
</Col>
</Row>
<Row>
<Col>
{
this.state.failed ?
<div className='alert alert-danger' role='alert'>{this.state.error}</div>
:
''
}
</Col>
</Row>
</Form>
</div>
</Col>
<Col lg={4} md={4} sm={4}>
<img alt="infection monkey" className={'monkey-detective'} src={monkeyDetective}/>
</Col>
</Row>
</Col>
</Row>
</Container>
);
}
}
export default RegisterPageComponent;

View File

@ -1,8 +1,6 @@
import '../../styles/report/ReportPage.scss';
import React from 'react';
import {Route} from 'react-router-dom';
import {Col, Nav, NavItem} from 'react-bootstrap';
import {Col, Nav} from 'react-bootstrap';
import AuthComponent from '../AuthComponent';
import MustRunMonkeyWarning from '../report-components/common/MustRunMonkeyWarning';
import AttackReport from '../report-components/AttackReport'
@ -30,10 +28,10 @@ class ReportPageComponent extends AuthComponent {
};
}
static selectReport(reports){
static selectReport(reports) {
let url = window.location.href;
for (let report_name in reports){
if (reports.hasOwnProperty(report_name) && url.endsWith(reports[report_name])){
for (let report_name in reports) {
if (reports.hasOwnProperty(report_name) && url.endsWith(reports[report_name])) {
return reports[report_name];
}
}
@ -109,24 +107,32 @@ class ReportPageComponent extends AuthComponent {
renderNav = () => {
return (
<Route render={({history}) => (
<Nav bsStyle='tabs' justified
activeKey={this.state.selectedSection}
onSelect={(key) => {this.setSelectedSection(key); history.push(key)}}
className={'report-nav'}>
{this.state.sections.map(section => this.renderNavButton(section))}
</Nav>)}/>)
<Nav variant='tabs'
fill
activeKey={this.state.selectedSection}
onSelect={(key) => {
this.setSelectedSection(key);
history.push(key)
}}
className={'report-nav'}>
{this.state.sections.map(section => this.renderNavButton(section))}
</Nav>)}/>)
};
renderNavButton = (section) => {
return (
<NavItem key={section.key}
eventKey={section.key}
onSelect={() => {}}>
<Nav.Item>
<Nav.Link key={section.key}
eventKey={section.key}
onSelect={() => {
}}>
{section.title}
</NavItem>)};
</Nav.Link>
</Nav.Item>)
};
getReportContent() {
switch(this.state.selectedSection){
switch (this.state.selectedSection) {
case 'security':
return (<SecurityReport report={this.state.securityReport}/>);
case 'attack':
@ -145,7 +151,9 @@ class ReportPageComponent extends AuthComponent {
content = <MustRunMonkeyWarning/>;
}
return (
<Col xs={12} lg={12}>
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
lg={{offset: 3, span: 9}} xl={{offset: 2, span: 10}}
className={'report-wrapper'}>
<h1 className='page-title no-print'>4. Security Reports</h1>
{this.renderNav()}
<MonkeysStillAliveWarning allMonkeysAreDead={this.state.allMonkeysAreDead}/>

View File

@ -1,13 +1,15 @@
import React from 'react';
import {css} from '@emotion/core';
import {Button, Col, Well, Nav, NavItem, Collapse} from 'react-bootstrap';
import {Button, Col, Card, Nav, Collapse, Row} from 'react-bootstrap';
import CopyToClipboard from 'react-copy-to-clipboard';
import GridLoader from 'react-spinners/GridLoader';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faClipboard } from '@fortawesome/free-solid-svg-icons/faClipboard';
import { faCheck } from '@fortawesome/free-solid-svg-icons/faCheck';
import { faSync } from '@fortawesome/free-solid-svg-icons/faSync';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faClipboard} from '@fortawesome/free-solid-svg-icons/faClipboard';
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
import {faSync} from '@fortawesome/free-solid-svg-icons/faSync';
import {faInfoCircle} from "@fortawesome/free-solid-svg-icons/faInfoCircle";
import {faExclamationTriangle} from "@fortawesome/free-solid-svg-icons/faExclamationTriangle";
import {Link} from 'react-router-dom';
import AuthComponent from '../AuthComponent';
@ -15,8 +17,6 @@ import AwsRunTable from '../run-monkey/AwsRunTable';
import MissingBinariesModal from '../ui-components/MissingBinariesModal';
import '../../styles/MonkeyRunPage.scss';
const loading_css_override = css`
display: block;
margin-right: auto;
@ -139,8 +139,9 @@ class RunMonkeyPageComponent extends AuthComponent {
/* If Monkey binaries are missing, change the state accordingly */
if (res['error_text'].startsWith('Copy file failed')) {
this.setState({
showModal: true,
errorDetails: res['error_text']}
showModal: true,
errorDetails: res['error_text']
}
);
}
this.setState({
@ -162,7 +163,7 @@ class RunMonkeyPageComponent extends AuthComponent {
cmdText = RunMonkeyPageComponent.generateWindowsCmd(this.state.selectedIp, is32Bit);
}
return (
<Well key={'cmdDiv' + this.state.selectedIp} className="well-sm" style={{'margin': '0.5em'}}>
<Card key={'cmdDiv' + this.state.selectedIp} style={{'margin': '0.5em'}}>
<div style={{'overflow': 'auto', 'padding': '0.5em'}}>
<CopyToClipboard text={cmdText} className="pull-right btn-sm">
<Button style={{margin: '-0.5em'}} title="Copy to Clipboard">
@ -171,7 +172,7 @@ class RunMonkeyPageComponent extends AuthComponent {
</CopyToClipboard>
<code>{cmdText}</code>
</div>
</Well>
</Card>
)
}
@ -266,7 +267,7 @@ class RunMonkeyPageComponent extends AuthComponent {
<div style={{'marginBottom': '2em'}}>
<div style={{'marginTop': '1em', 'marginBottom': '1em'}}>
<p className="alert alert-info">
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
<FontAwesomeIcon icon={faInfoCircle} style={{'marginRight': '5px'}}/>
Not sure what this is? Not seeing your AWS EC2 instances? <a
href="https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS-EC2-instances"
rel="noopener noreferrer" target="_blank">Read the documentation</a>!
@ -274,9 +275,9 @@ class RunMonkeyPageComponent extends AuthComponent {
</div>
{
this.state.ips.length > 1 ?
<Nav bsStyle="pills" justified activeKey={this.state.selectedIp} onSelect={this.setSelectedIp}
<Nav variant="pills" activeKey={this.state.selectedIp} onSelect={this.setSelectedIp}
style={{'marginBottom': '2em'}}>
{this.state.ips.map(ip => <NavItem key={ip} eventKey={ip}>{ip}</NavItem>)}
{this.state.ips.map(ip => <Nav.Item><Nav.Link eventKey={ip}>{ip}</Nav.Link></Nav.Item>)}
</Nav>
: <div style={{'marginBottom': '2em'}}/>
}
@ -286,13 +287,14 @@ class RunMonkeyPageComponent extends AuthComponent {
ref={r => (this.awsTable = r)}
/>
<div style={{'marginTop': '1em'}}>
<button
<Button
onClick={this.runOnAws}
className={'btn btn-default btn-md center-block'}
disabled={this.state.awsClicked}>
Run on selected machines
{this.state.awsClicked ? <FontAwesomeIcon icon={faSync} className="text-success" style={{'marginLeft': '5px'}}/> : null}
</button>
{this.state.awsClicked ?
<FontAwesomeIcon icon={faSync} className="text-success" style={{'marginLeft': '5px'}}/> : null}
</Button>
</div>
</div>
)
@ -306,23 +308,27 @@ class RunMonkeyPageComponent extends AuthComponent {
render() {
return (
<Col xs={12} lg={8}>
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
lg={{offset: 3, span: 9}} xl={{offset: 2, span: 7}}
className={'main'}>
<h1 className="page-title">2. Run the Monkey</h1>
<p style={{'marginBottom': '2em', 'fontSize': '1.2em'}}>
Go ahead and run the monkey!
<i> (Or <Link to="/configure">configure the monkey</Link> to fine tune its behavior)</i>
</p>
<p>
<button onClick={this.runLocalMonkey}
className="btn btn-default btn-lg center-block"
disabled={this.state.runningOnIslandState !== 'not_running'}>
<p className={'text-center'}>
<Button onClick={this.runLocalMonkey}
variant={'outline-monkey'}
size='lg'
disabled={this.state.runningOnIslandState !== 'not_running'}
>
Run on Monkey Island Server
{RunMonkeyPageComponent.renderIconByState(this.state.runningOnIslandState)}
</button>
</Button>
<MissingBinariesModal
showModal = {this.state.showModal}
onClose = {this.closeModal}
errorDetails = {this.state.errorDetails}/>
showModal={this.state.showModal}
onClose={this.closeModal}
errorDetails={this.state.errorDetails}/>
{
// TODO: implement button functionality
/*
@ -339,30 +345,67 @@ class RunMonkeyPageComponent extends AuthComponent {
<p className="text-center">
OR
</p>
<p style={this.state.showManual || !this.state.isOnAws ? {'marginBottom': '2em'} : {}}>
<button onClick={this.toggleManual}
className={'btn btn-default btn-lg center-block' + (this.state.showManual ? ' active' : '')}>
<p className={'text-center'}
style={this.state.showManual || !this.state.isOnAws ? {'marginBottom': '2em'} : {}}>
<Button onClick={this.toggleManual}
variant={'outline-monkey'}
size='lg'
className={(this.state.showManual ? 'active' : '')}>
Run on a machine of your choice
</button>
</Button>
</p>
<Collapse in={this.state.showManual}>
<div style={{'marginBottom': '2em'}}>
<p style={{'fontSize': '1.2em'}}>
Choose the operating system where you want to run the monkey
{this.state.ips.length > 1 ? ', and the interface to communicate with.' : '.'}
Choose the operating system where you want to run the monkey:
</p>
<Nav bsStyle='pills' id={'bootstrap-override'} className={'runOnOsButtons'}
justified activeKey={this.state.selectedOs} onSelect={this.setSelectedOs}>
<NavItem key='windows-32' eventKey='windows-32'>Windows (32 bit)</NavItem>
<NavItem key='windows-64' eventKey='windows-64'>Windows (64 bit)</NavItem>
<NavItem key='linux-32' eventKey='linux-32'>Linux (32 bit)</NavItem>
<NavItem key='linux-64' eventKey='linux-64'>Linux (64 bit)</NavItem>
</Nav>
<Row>
<Col>
<Nav variant='pills' fill id={'bootstrap-override'} className={'run-on-os-buttons'}
activeKey={this.state.selectedOs} onSelect={this.setSelectedOs}>
<Nav.Item>
<Nav.Link eventKey={'windows-32'}>
Windows (32 bit)
</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link eventKey='windows-64'>
Windows (64 bit)
</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link eventKey='linux-32'>
Linux (32 bit)
</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link eventKey='linux-64'>
Linux (64 bit)
</Nav.Link>
</Nav.Item>
</Nav>
</Col>
</Row>
{this.state.ips.length > 1 ?
<Nav bsStyle="pills" justified activeKey={this.state.selectedIp} onSelect={this.setSelectedIp}
style={{'marginBottom': '2em'}}>
{this.state.ips.map(ip => <NavItem key={ip} eventKey={ip}>{ip}</NavItem>)}
</Nav>
<div>
<Row>
<Col>
<p style={{'fontSize': '1.2em'}}>
Choose the interface to communicate with:
</p>
</Col>
</Row>
<Row>
<Col>
<Nav variant="pills" fill activeKey={this.state.selectedIp} onSelect={this.setSelectedIp}
className={'run-on-os-buttons'}>
{this.state.ips.map(ip => <Nav.Item>
<Nav.Link eventKey={ip}>{ip}</Nav.Link></Nav.Item>)}
</Nav>
</Col>
</Row>
</div>
: <div style={{'marginBottom': '2em'}}/>
}
<p style={{'fontSize': '1.2em'}}>
@ -373,7 +416,7 @@ class RunMonkeyPageComponent extends AuthComponent {
</Collapse>
{
this.state.isLoadingAws ?
<p style={{'marginBottom': '2em', 'align': 'center'}}>
<div style={{'marginBottom': '2em', 'align': 'center'}}>
<div className='sweet-loading'>
<GridLoader
css={loading_css_override}
@ -383,7 +426,7 @@ class RunMonkeyPageComponent extends AuthComponent {
loading={this.state.loading}
/>
</div>
</p>
</div>
: null
}
{
@ -396,11 +439,13 @@ class RunMonkeyPageComponent extends AuthComponent {
}
{
this.state.isOnAws ?
<p style={{'marginBottom': '2em'}}>
<button onClick={this.toggleAws}
className={'btn btn-default btn-lg center-block' + (this.state.showAws ? ' active' : '')}>
<p style={{'marginBottom': '2em'}} className={'text-center'}>
<Button onClick={this.toggleAws}
className={(this.state.showAws ? ' active' : '')}
size='lg'
variant={'outline-monkey'}>
Run on AWS machine of your choice
</button>
</Button>
</p>
:
null
@ -409,8 +454,8 @@ class RunMonkeyPageComponent extends AuthComponent {
{
this.state.isErrorWhileCollectingAwsMachines ?
<div style={{'marginTop': '1em'}}>
<p class="alert alert-danger">
<i className="glyphicon glyphicon-warning-sign" style={{'marginRight': '5px'}}/>
<p className="alert alert-danger">
<FontAwesomeIcon icon={faExclamationTriangle} style={{'marginRight': '5px'}}/>
Error while collecting AWS machine data. Error
message: <code>{this.state.awsMachineCollectionErrorMsg}</code><br/>
Are you sure you've set the correct role on your Island AWS machine?<br/>

View File

@ -9,7 +9,9 @@ class RunServerPageComponent extends React.Component {
render() {
return (
<Col xs={12} lg={8}>
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
lg={{offset: 3, span: 9}} xl={{offset: 2, span: 7}}
className={'main'}>
<h1 className="page-title">1. Monkey Island Server</h1>
<div style={{'fontSize': '1.2em'}}>
<p style={{'marginTop': '30px'}}>Congrats! You have successfully set up the Monkey Island

View File

@ -1,9 +1,12 @@
import React from 'react';
import {Col} from 'react-bootstrap';
import {Col, Button} from 'react-bootstrap';
import {Link} from 'react-router-dom';
import AuthComponent from '../AuthComponent';
import StartOverModal from '../ui-components/StartOverModal';
import '../../styles/StartOverPage.scss';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faInfoCircle} from "@fortawesome/free-solid-svg-icons/faInfoCircle";
import {faCheck} from "@fortawesome/free-solid-svg-icons/faCheck";
class StartOverPageComponent extends AuthComponent {
constructor(props) {
@ -32,7 +35,9 @@ class StartOverPageComponent extends AuthComponent {
render() {
return (
<Col xs={12} lg={8}>
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
lg={{offset: 3, span: 9}} xl={{offset: 2, span: 7}}
className={'main'}>
<StartOverModal cleaned = {this.state.cleaned}
showCleanDialog = {this.state.showCleanDialog}
allMonkeysAreDead = {this.state.allMonkeysAreDead}
@ -44,25 +49,25 @@ class StartOverPageComponent extends AuthComponent {
If you are finished and want to start over with a fresh configuration, erase the logs and clear the map
you can go ahead and
</p>
<p style={{margin: '20px'}}>
<button className="btn btn-danger btn-lg center-block"
<p style={{margin: '20px'}} className={'text-center'}>
<Button className="btn btn-danger btn-lg center-block"
onClick={() => {
this.setState({showCleanDialog: true});
this.updateMonkeysRunning();
}
}>
Reset the Environment
</button>
</Button>
</p>
<div className="alert alert-info">
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
<FontAwesomeIcon icon={faInfoCircle} style={{'marginRight': '5px'}}/>
You don't have to reset the environment to keep running monkeys.
You can continue and <Link to="/run-monkey">Run More Monkeys</Link> as you wish,
and see the results on the <Link to="/infection/map">Infection Map</Link> without deleting anything.
</div>
{this.state.cleaned ?
<div className="alert alert-success">
<i className="glyphicon glyphicon-ok-sign" style={{'marginRight': '5px'}}/>
<FontAwesomeIcon icon={faCheck} style={{'marginRight': '5px'}}/>
Environment was reset successfully
</div>
: ''}

View File

@ -1,9 +1,13 @@
import React from 'react';
import {Button, Col} from 'react-bootstrap';
import JSONTree from 'react-json-tree'
import JSONTree from 'react-json-tree';
import {DataTable} from 'react-data-components';
import AuthComponent from '../AuthComponent';
import download from 'downloadjs'
import download from 'downloadjs';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import '../../styles/TelemetryPage.scss';
import {faDownload} from "@fortawesome/free-solid-svg-icons/faDownload";
const renderJson = (val) => <JSONTree data={val} level={1} theme="eighties" invertTheme={true}/>;
const renderTime = (val) => val.split('.')[0];
@ -41,9 +45,10 @@ class TelemetryPageComponent extends AuthComponent {
render() {
return (
<div>
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
lg={{offset: 3, span: 9}} xl={{offset: 2, span: 7}}
className={'main'}>
<div>
<Col xs={12} lg={8}>
<h1 className="page-title">Log</h1>
<div className="data-table-container">
<DataTable
@ -55,21 +60,18 @@ class TelemetryPageComponent extends AuthComponent {
pageLengthOptions={[20, 50, 100]}
/>
</div>
</Col>
</div>
<div>
<Col xs={12} lg={8}>
<h1 className="page-title"> Monkey Island Logs </h1>
<div className="text-center" style={{marginBottom: '20px'}}>
<p style={{'marginBottom': '2em', 'fontSize': '1.2em'}}> Download Monkey Island internal log file </p>
<Button bsSize="large" onClick={() => {
this.downloadIslandLog();
}}>
<i className="glyphicon glyphicon-download"/> Download </Button>
<FontAwesomeIcon icon={faDownload}/> Download </Button>
</div>
</Col>
</div>
</div>
</Col>
);
}
}

View File

@ -1,7 +1,6 @@
import React from 'react';
import {Col, Button} from 'react-bootstrap';
import '../../styles/Collapse.scss';
import '../../styles/report/AttackReport.scss';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {faCircle} from '@fortawesome/free-solid-svg-icons/faCircle';
import {faRadiation} from '@fortawesome/free-solid-svg-icons/faRadiation';
@ -101,9 +100,9 @@ class AttackReport extends React.Component {
<div>
<p>
This report shows information about
<Button bsStyle={'link'}
<Button variant={'link'}
href={'https://attack.mitre.org/'}
bsSize={'lg'}
size={'lg'}
className={'attack-link'}
target={'_blank'}>
Mitre ATT&CK

View File

@ -17,6 +17,8 @@ import PrintReportButton from './common/PrintReportButton';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faMinus } from '@fortawesome/free-solid-svg-icons/faMinus';
import guardicoreLogoImage from '../../images/guardicore-logo.png'
import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons';
import '../../styles/App.css';
class ReportPageComponent extends AuthComponent {
@ -145,7 +147,7 @@ class ReportPageComponent extends AuthComponent {
''
:
<p className="alert alert-info">
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
<FontAwesomeIcon icon={faExclamationTriangle} style={{'marginRight': '5px'}}/>
To improve the monkey's detection rates, try adding users and passwords and enable the "Local
network
scan" config value under <b>Basic - Network</b>.
@ -153,8 +155,8 @@ class ReportPageComponent extends AuthComponent {
}
<p>
The first monkey run was started on <span
className="label label-info">{this.state.report.overview.monkey_start_time}</span>. After <span
className="label label-info">{this.state.report.overview.monkey_duration}</span>, all monkeys finished
className="badge badge-info">{this.state.report.overview.monkey_start_time}</span>. After <span
className="badge badge-info">{this.state.report.overview.monkey_duration}</span>, all monkeys finished
propagation attempts.
</p>
<p>
@ -241,7 +243,7 @@ class ReportPageComponent extends AuthComponent {
}).length > 0 ?
<div>
During this simulated attack the Monkey uncovered <span
className="label label-warning">
className="badge badge-warning">
{this.state.report.overview.issues.filter(function (x) {
return x === true;
}).length} threats</span>:
@ -295,7 +297,7 @@ class ReportPageComponent extends AuthComponent {
:
<div>
During this simulated attack the Monkey uncovered <span
className="label label-success">0 threats</span>.
className="badge badge-success">0 threats</span>.
</div>
}
</div>
@ -379,9 +381,9 @@ class ReportPageComponent extends AuthComponent {
<div>
<p>
The Monkey discovered <span
className="label label-warning">{this.state.report.glance.scanned.length}</span> machines and
className="badge badge-warning">{this.state.report.glance.scanned.length}</span> machines and
successfully breached <span
className="label label-danger">{this.state.report.glance.exploited.length}</span> of them.
className="badge badge-danger">{this.state.report.glance.exploited.length}</span> of them.
</p>
<div className="text-center" style={{margin: '10px'}}>
<Line style={{width: '300px', marginRight: '5px'}} percent={exploitPercentage} strokeWidth="4"
@ -441,7 +443,7 @@ class ReportPageComponent extends AuthComponent {
}
generateInfoBadges(data_array) {
return data_array.map(badge_data => <span className="label label-info" style={{margin: '2px'}}>{badge_data}</span>);
return data_array.map(badge_data => <span className="badge badge-info" style={{margin: '2px'}}>{badge_data}</span>);
}
generateCrossSegmentIssue(crossSegmentIssue) {
@ -466,21 +468,21 @@ class ReportPageComponent extends AuthComponent {
}
generateShellshockPathListBadges(paths) {
return paths.map(path => <span className="label label-warning" style={{margin: '2px'}}>{path}</span>);
return paths.map(path => <span className="badge badge-warning" style={{margin: '2px'}}>{path}</span>);
}
generateSmbPasswordIssue(issue) {
return (
<li>
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
Change <span className="badge badge-success">{issue.username}</span>'s password to a complex one-use password
that is not shared with other computers on the network.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="label label-danger">SMB</span> attack.
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="badge badge-danger">SMB</span> attack.
<br/>
The Monkey authenticated over the SMB protocol with user <span
className="label label-success">{issue.username}</span> and its password.
className="badge badge-success">{issue.username}</span> and its password.
</CollapsibleWellComponent>
</li>
);
@ -489,15 +491,15 @@ class ReportPageComponent extends AuthComponent {
generateSmbPthIssue(issue) {
return (
<li>
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
Change <span className="badge badge-success">{issue.username}</span>'s password to a complex one-use password
that is not shared with other computers on the network.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="label label-danger">SMB</span> attack.
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="badge badge-danger">SMB</span> attack.
<br/>
The Monkey used a pass-the-hash attack over SMB protocol with user <span
className="label label-success">{issue.username}</span>.
className="badge badge-success">{issue.username}</span>.
</CollapsibleWellComponent>
</li>
);
@ -506,15 +508,15 @@ class ReportPageComponent extends AuthComponent {
generateWmiPasswordIssue(issue) {
return (
<li>
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
Change <span className="badge badge-success">{issue.username}</span>'s password to a complex one-use password
that is not shared with other computers on the network.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="label label-danger">WMI</span> attack.
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="badge badge-danger">WMI</span> attack.
<br/>
The Monkey authenticated over the WMI protocol with user <span
className="label label-success">{issue.username}</span> and its password.
className="badge badge-success">{issue.username}</span> and its password.
</CollapsibleWellComponent>
</li>
);
@ -523,15 +525,15 @@ class ReportPageComponent extends AuthComponent {
generateWmiPthIssue(issue) {
return (
<li>
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
Change <span className="badge badge-success">{issue.username}</span>'s password to a complex one-use password
that is not shared with other computers on the network.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="label label-danger">WMI</span> attack.
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="badge badge-danger">WMI</span> attack.
<br/>
The Monkey used a pass-the-hash attack over WMI protocol with user <span
className="label label-success">{issue.username}</span>.
className="badge badge-success">{issue.username}</span>.
</CollapsibleWellComponent>
</li>
);
@ -540,15 +542,15 @@ class ReportPageComponent extends AuthComponent {
generateSshIssue(issue) {
return (
<li>
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
Change <span className="badge badge-success">{issue.username}</span>'s password to a complex one-use password
that is not shared with other computers on the network.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="label label-danger">SSH</span> attack.
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="badge badge-danger">SSH</span> attack.
<br/>
The Monkey authenticated over the SSH protocol with user <span
className="label label-success">{issue.username}</span> and its password.
className="badge badge-success">{issue.username}</span> and its password.
</CollapsibleWellComponent>
</li>
);
@ -557,14 +559,14 @@ class ReportPageComponent extends AuthComponent {
generateSshKeysIssue(issue) {
return (
<li>
Protect <span className="label label-success">{issue.ssh_key}</span> private key with a pass phrase.
Protect <span className="badge badge-success">{issue.ssh_key}</span> private key with a pass phrase.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="label label-danger">SSH</span> attack.
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="badge badge-danger">SSH</span> attack.
<br/>
The Monkey authenticated over the SSH protocol with private key <span
className="label label-success">{issue.ssh_key}</span>.
className="badge badge-success">{issue.ssh_key}</span>.
</CollapsibleWellComponent>
</li>
);
@ -574,17 +576,17 @@ class ReportPageComponent extends AuthComponent {
generateSambaCryIssue(issue) {
return (
<li>
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
Change <span className="badge badge-success">{issue.username}</span>'s password to a complex one-use password
that is not shared with other computers on the network.
<br/>
Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="label label-danger">SambaCry</span> attack.
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="badge badge-danger">SambaCry</span> attack.
<br/>
The Monkey authenticated over the SMB protocol with user <span
className="label label-success">{issue.username}</span> and its password, and used the SambaCry
className="badge badge-success">{issue.username}</span> and its password, and used the SambaCry
vulnerability.
</CollapsibleWellComponent>
</li>
@ -596,9 +598,9 @@ class ReportPageComponent extends AuthComponent {
<li>
Update your VSFTPD server to the latest version vsftpd-3.0.3.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) has a backdoor running at port <span
className="label label-danger">6200</span>.
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) has a backdoor running at port <span
className="badge badge-danger">6200</span>.
<br/>
The attack was made possible because the VSFTPD server was not patched against CVE-2011-2523.
<br/><br/>In July 2011, it was discovered that vsftpd version 2.3.4 downloadable from the master site had been
@ -621,9 +623,9 @@ class ReportPageComponent extends AuthComponent {
<li>
Update your Elastic Search server to version 1.4.3 and up.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to an <span
className="label label-danger">Elastic Groovy</span> attack.
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to an <span
className="badge badge-danger">Elastic Groovy</span> attack.
<br/>
The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.
</CollapsibleWellComponent>
@ -636,12 +638,12 @@ class ReportPageComponent extends AuthComponent {
<li>
Update your Bash to a ShellShock-patched version.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="label label-danger">ShellShock</span> attack.
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="badge badge-danger">ShellShock</span> attack.
<br/>
The attack was made possible because the HTTP server running on TCP port <span
className="label label-info">{issue.port}</span> was vulnerable to a shell injection attack on the
className="badge badge-info">{issue.port}</span> was vulnerable to a shell injection attack on the
paths: {this.generateShellshockPathListBadges(issue.paths)}.
</CollapsibleWellComponent>
</li>
@ -654,8 +656,8 @@ class ReportPageComponent extends AuthComponent {
Delete VM Access plugin configuration files.
<CollapsibleWellComponent>
Credentials could be stolen from <span
className="label label-primary">{issue.machine}</span> for the following users <span
className="label label-primary">{issue.users}</span>. Read more about the security issue and remediation <a
className="badge badge-primary">{issue.machine}</span> for the following users <span
className="badge badge-primary">{issue.users}</span>. Read more about the security issue and remediation <a
href="https://www.guardicore.com/2018/03/recovering-plaintext-passwords-azure/"
>here</a>.
</CollapsibleWellComponent>
@ -668,9 +670,9 @@ class ReportPageComponent extends AuthComponent {
<li>
Install the latest Windows updates or upgrade to a newer operating system.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="label label-danger">Conficker</span> attack.
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="badge badge-danger">Conficker</span> attack.
<br/>
The attack was made possible because the target machine used an outdated and unpatched operating system
vulnerable to Conficker.
@ -685,7 +687,7 @@ class ReportPageComponent extends AuthComponent {
Segment your network and make sure there is no communication between machines from different segments.
<CollapsibleWellComponent>
The network can probably be segmented. A monkey instance on <span
className="label label-primary">{issue.machine}</span> in the
className="badge badge-primary">{issue.machine}</span> in the
networks {this.generateInfoBadges(issue.networks)}
could directly access the Monkey Island server in the
networks {this.generateInfoBadges(issue.server_networks)}.
@ -725,7 +727,7 @@ class ReportPageComponent extends AuthComponent {
admin sharing.
<CollapsibleWellComponent>
Here is a list of machines which the account <span
className="label label-primary">{issue.username}</span> is defined as an administrator:
className="badge badge-primary">{issue.username}</span> is defined as an administrator:
{this.generateInfoBadges(issue.shared_machines)}
</CollapsibleWellComponent>
</li>
@ -752,8 +754,8 @@ class ReportPageComponent extends AuthComponent {
Use micro-segmentation policies to disable communication other than the required.
<CollapsibleWellComponent>
Machines are not locked down at port level. Network tunnel was set up from <span
className="label label-primary">{issue.machine}</span> to <span
className="label label-primary">{issue.dest}</span>.
className="badge badge-primary">{issue.machine}</span> to <span
className="badge badge-primary">{issue.dest}</span>.
</CollapsibleWellComponent>
</li>
);
@ -764,9 +766,9 @@ class ReportPageComponent extends AuthComponent {
<li>
Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.
<CollapsibleWellComponent>
Struts2 server at <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to <span
className="label label-danger">remote code execution</span> attack.
Struts2 server at <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to <span
className="badge badge-danger">remote code execution</span> attack.
<br/>
The attack was made possible because the server is using an old version of Jakarta based file upload
Multipart parser. For possible work-arounds and more info read <a
@ -782,9 +784,9 @@ class ReportPageComponent extends AuthComponent {
<li>
Update Oracle WebLogic server to the latest supported version.
<CollapsibleWellComponent>
Oracle WebLogic server at <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to one of <span
className="label label-danger">remote code execution</span> attacks.
Oracle WebLogic server at <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to one of <span
className="badge badge-danger">remote code execution</span> attacks.
<br/>
The attack was made possible due to one of the following vulnerabilities:
<a href={'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-10271'}> CVE-2017-10271</a> or
@ -801,9 +803,9 @@ class ReportPageComponent extends AuthComponent {
href="http://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/SecureMode.html">
add Kerberos authentication</a>).
<CollapsibleWellComponent>
The Hadoop server at <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to <span
className="label label-danger">remote code execution</span> attack.
The Hadoop server at <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to <span
className="badge badge-danger">remote code execution</span> attack.
<br/>
The attack was made possible due to default Hadoop/Yarn configuration being insecure.
</CollapsibleWellComponent>
@ -816,9 +818,9 @@ class ReportPageComponent extends AuthComponent {
<li>
Disable the xp_cmdshell option.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="label label-danger">MSSQL exploit attack</span>.
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="badge badge-danger">MSSQL exploit attack</span>.
<br/>
The attack was made possible because the target machine used an outdated MSSQL server configuration allowing
the usage of the xp_cmdshell command. To learn more about how to disable this feature, read <a

View File

@ -109,8 +109,8 @@ class TechniqueDropdowns extends React.Component{
<div className='attack-technique-list-component'>
<h3>
List of all techniques
<Button bsStyle='link'
bsSize='large'
<Button variant='link'
size='lg'
onClick={() => this.toggleTechList()}
className={classNames({'toggle-btn': true,
'toggled-off' : this.state.techniquesHidden,

View File

@ -1,5 +1,7 @@
import React, {Component} from 'react';
import * as PropTypes from 'prop-types';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faExclamationTriangle} from "@fortawesome/free-solid-svg-icons/faExclamationTriangle";
export default class MonkeysStillAliveWarning extends Component {
render() {
@ -9,7 +11,7 @@ export default class MonkeysStillAliveWarning extends Component {
''
:
(<p className="alert alert-warning">
<i className="glyphicon glyphicon-warning-sign" style={{'marginRight': '5px'}}/>
<FontAwesomeIcon icon={faExclamationTriangle} style={{'marginRight': '5px'}}/>
Some monkeys are still running. To get the best report it's best to wait for all of them to finish
running.
</p>)

View File

@ -1,10 +1,12 @@
import React, {Component} from 'react';
import {NavLink} from 'react-router-dom';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faExclamationTriangle} from "@fortawesome/free-solid-svg-icons/faExclamationTriangle";
export default class MustRunMonkeyWarning extends Component {
render() {
return <p className="alert alert-warning">
<i className="glyphicon glyphicon-warning-sign" style={{'marginRight': '5px'}}/>
<FontAwesomeIcon icon={faExclamationTriangle} style={{'marginRight': '5px'}}/>
<b>You have to <NavLink to="/run-monkey">run a monkey</NavLink> before generating a report!</b>
</p>
}

View File

@ -1,11 +1,14 @@
import React, {Component} from 'react';
import {Button} from 'react-bootstrap';
import * as PropTypes from 'prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPrint } from '@fortawesome/free-solid-svg-icons/faPrint';
export default class PrintReportButton extends Component {
render() {
return <div className="text-center no-print">
<Button bsSize="large" onClick={this.props.onClick}><i className="glyphicon glyphicon-print"/> Print
<Button size="md" variant={"outline-standard"} onClick={this.props.onClick}>
<FontAwesomeIcon icon={faPrint}/> Print
Report</Button>
</div>
}

View File

@ -1,17 +1,20 @@
import React, {Component, Fragment} from 'react';
import * as PropTypes from 'prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {faCheck, faExclamationTriangle} from '@fortawesome/free-solid-svg-icons';
export default class SecurityIssuesGlance extends Component {
render() {
return <Fragment>
{
this.props.issuesFound ?
(<p className="alert alert-danger">
<i className="glyphicon glyphicon-exclamation-sign" style={{'marginRight': '5px'}}/>
<FontAwesomeIcon icon={faExclamationTriangle} style={{'marginRight': '5px'}}/>
Critical security issues were detected!
</p>) :
(<p className="alert alert-success">
<i className="glyphicon glyphicon-ok-sign" style={{'marginRight': '5px'}}/>
<FontAwesomeIcon icon={faCheck} style={{'marginRight': '5px'}}/>
No critical security issues were detected.
</p>)
}

View File

@ -38,7 +38,7 @@ class BreachedServersComponent extends React.Component {
<>
<p>
The Monkey successfully breached <span
className="label label-danger">{this.props.data.length}</span> {Pluralize('machine', this.props.data.length)}:
className="badge badge-danger">{this.props.data.length}</span> {Pluralize('machine', this.props.data.length)}:
</p>
<div className="data-table-container">
<ReactTable

View File

@ -1,5 +1,5 @@
import React from 'react';
import {Button, Collapse, Well} from 'react-bootstrap';
import {Button, Collapse, Card} from 'react-bootstrap';
class CollapsibleWellComponent extends React.Component {
constructor(props) {
@ -12,18 +12,19 @@ class CollapsibleWellComponent extends React.Component {
render() {
let well =
(
<Well style={{margin: '10px'}}>
<Card body>
{this.props.children}
</Well>
</Card>
);
return (
<div>
<div className="no-print">
<Button onClick={() => this.setState({open: !this.state.open})} bsStyle="link">
<Button onClick={() => this.setState({open: !this.state.open})}
variant="link">
Read More...
</Button>
<Collapse in={this.state.open}>
<Collapse in={this.state.open} style={{margin: '10px'}}>
<div>
{well}
</div>

View File

@ -68,8 +68,8 @@ class PostBreachComponent extends React.Component {
<>
<p>
The Monkey performed <span
className="label label-danger">{pbaCount}</span> post-breach {Pluralize('action', pbaCount)} on <span
className="label label-warning">{pbaMachines.length}</span> {Pluralize('machine', pbaMachines.length)}:
className="badge badge-danger">{pbaCount}</span> post-breach {Pluralize('action', pbaCount)} on <span
className="badge badge-warning">{pbaMachines.length}</span> {Pluralize('machine', pbaMachines.length)}:
</p>
<div className="data-table-container">
<ReactTable

View File

@ -45,9 +45,9 @@ class ScannedServersComponent extends React.Component {
<>
<p>
The Monkey discovered&nbsp;
<span className="label label-danger">{scannedServicesAmount}</span> open&nbsp;
<span className="badge badge-danger">{scannedServicesAmount}</span> open&nbsp;
{Pluralize('service', scannedServicesAmount)} on&nbsp;
<span className="label label-warning">{scannedMachinesCount}</span>&nbsp;
<span className="badge badge-warning">{scannedMachinesCount}</span>&nbsp;
{Pluralize('machine', scannedMachinesCount)}:
</p>
<div className="data-table-container">

View File

@ -32,7 +32,7 @@ export default class EventsButton extends Component {
hideCallback={this.hide}
exportFilename={this.props.exportFilename}/>
<div className="text-center" style={{'display': 'grid'}}>
<Button className="btn btn-primary btn-lg" onClick={this.show}>
<Button variant={'monkey-info'} size={'lg'} onClick={this.show}>
<FontAwesomeIcon icon={faList}/> Events {this.createEventsAmountBadge()}
</Button>
</div>
@ -41,7 +41,7 @@ export default class EventsButton extends Component {
createEventsAmountBadge() {
const eventsAmountBadgeContent = this.props.event_count > 9 ? '9+' : this.props.event_count;
return <Badge>{eventsAmountBadgeContent}</Badge>;
return <Badge variant={'monkey-info-light'}>{eventsAmountBadgeContent}</Badge>;
}
}

View File

@ -26,14 +26,13 @@ export default class EventsModal extends AuthComponent {
</h3>
<hr/>
<p>
There {Pluralize('is', this.props.event_count)} {<div
className={'label label-primary'}>{this.props.event_count}</div>}
{Pluralize('event', this.props.event_count)} associated
with this finding.
{<div className={'label label-primary'}>
{this.props.latest_events.length + this.props.oldest_events.length}
</div>} {Pluralize('is', this.props.event_count)} displayed below.
All events can be exported to json.
There {Pluralize('is', this.props.event_count)} {
<div className={'badge badge-primary'}>{this.props.event_count}</div>
} {Pluralize('event', this.props.event_count)} associated with this finding. {
<div className={'badge badge-primary'}>
{this.props.latest_events.length + this.props.oldest_events.length}
</div>
} {Pluralize('is', this.props.event_count)} displayed below. All events can be exported using the Export button.
</p>
{this.props.event_count > 5 ? this.renderButtons() : null}
<EventsTimeline events={this.props.oldest_events}/>

View File

@ -5,7 +5,7 @@ import * as PropTypes from 'prop-types';
export default class EventsModalButtons extends Component {
render() {
return <div className="text-center">
<button type="button" className="btn btn-success btn-lg" style={{margin: '5px'}}
<button type="button" className="btn btn-monkey-info btn-lg" style={{margin: '5px'}}
onClick={this.props.onClickClose}>
Close
</button>

View File

@ -5,7 +5,7 @@ import * as PropTypes from 'prop-types';
import PillarLabel from './PillarLabel';
import EventsButton from './EventsButton';
const EVENTS_COLUMN_MAX_WIDTH = 160;
const EVENTS_COLUMN_MAX_WIDTH = 170;
const PILLARS_COLUMN_MAX_WIDTH = 200;
const columns = [
{
@ -22,7 +22,7 @@ const columns = [
latest_events={x.latest_events}
oldest_events={x.oldest_events}
event_count={x.event_count}
exportFilename={'Events_' + x.test_key}/>;
exportFilename={'Events_' + x.test_key} />;
},
maxWidth: EVENTS_COLUMN_MAX_WIDTH
},

View File

@ -23,7 +23,7 @@ const pillarToIcon = {
export default class PillarLabel extends Component {
render() {
const className = 'label ' + statusToLabelType[this.props.status];
const className = 'badge ' + statusToLabelType[this.props.status];
return <div className={className} style={{margin: '2px', display: 'inline-block'}}>
<FontAwesomeIcon icon={pillarToIcon[this.props.pillar]}/> {this.props.pillar}</div>
}

View File

@ -2,29 +2,43 @@ import React, {Component} from 'react';
import StatusLabel from './StatusLabel';
import {ZeroTrustStatuses} from './ZeroTrustPillars';
import {NavLink} from 'react-router-dom';
import {Panel} from 'react-bootstrap';
import {Card, Collapse} from 'react-bootstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faChevronDown } from '@fortawesome/free-solid-svg-icons/faChevronDown';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown';
class ZeroTrustReportLegend extends Component {
constructor(props, context) {
super(props, context);
this.state = {
open: false,
};
}
render() {
const legendContent = this.getLegendContent();
const {open} = this.state;
return (
<Panel>
<Panel.Heading>
<Panel.Title toggle>
<h3><FontAwesomeIcon icon={faChevronDown} /> Legend</h3>
</Panel.Title>
</Panel.Heading>
<Panel.Collapse>
<Panel.Body>
{legendContent}
</Panel.Body>
</Panel.Collapse>
</Panel>
<Card>
<Card.Header onClick={() => this.setState({open: !open})}
aria-controls='collapse-content'
aria-expanded={open}
className={'collapse-control'}>
<h3><FontAwesomeIcon icon={faChevronDown}/> Legend</h3>
</Card.Header>
<Collapse in={this.state.open}>
<Card.Body>
<div id='collapse-content'>
{legendContent}
</div>
</Card.Body>
</Collapse>
</Card>
);
}
@ -35,7 +49,8 @@ class ZeroTrustReportLegend extends Component {
<div style={{display: 'inline-block'}}>
<StatusLabel showText={true} status={ZeroTrustStatuses.failed}/>
</div>
{'\t'}At least one of the tests related to this component failed. This means that the Infection Monkey detected an
{'\t'}At least one of the tests related to this component failed. This means that the Infection Monkey
detected an
unmet Zero Trust requirement.
</li>
<li>
@ -55,7 +70,7 @@ class ZeroTrustReportLegend extends Component {
<StatusLabel showText={true} status={ZeroTrustStatuses.unexecuted}/>
</div>
{'\t'}This status means the test wasn't executed.To activate more tests, refer to the Monkey <NavLink
to="/configuration"><u>configuration</u></NavLink> page.
to="/configure"><u>configuration</u></NavLink> page.
</li>
</ul>
</div>;

View File

@ -3,32 +3,46 @@ import PillarLabel from './PillarLabel';
import PrinciplesStatusTable from './PrinciplesStatusTable';
import React from 'react';
import * as PropTypes from 'prop-types';
import {Panel} from 'react-bootstrap';
import {Card, Collapse} from 'react-bootstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faChevronDown } from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faChevronDown} from '@fortawesome/free-solid-svg-icons';
import '../../../styles/report/ZeroTrustReport.scss';
export default class SinglePillarPrinciplesStatus extends AuthComponent {
constructor(props, context) {
super(props, context);
this.state = {
open: false,
};
}
render() {
const {open} = this.state;
if (this.props.principlesStatus.length === 0) {
return null;
} else {
return (
<Panel>
<Panel.Heading>
<Panel.Title toggle>
<h3 style={{textAlign: 'center', marginTop: '1px', marginBottom: '1px'}}>
<FontAwesomeIcon icon={faChevronDown}/> <PillarLabel pillar={this.props.pillar}
status={this.props.pillarsToStatuses[this.props.pillar]}/>
</h3>
</Panel.Title>
</Panel.Heading>
<Panel.Collapse>
<Panel.Body>
<PrinciplesStatusTable principlesStatus={this.props.principlesStatus}/>
</Panel.Body>
</Panel.Collapse>
</Panel>
<Card className={'principles-status-card'}>
<Card.Header onClick={() => this.setState({open: !open})}
aria-controls='collapse-content'
aria-expanded={open}
className={'collapse-control'}>
<h3 style={{textAlign: 'center', marginTop: '1px', marginBottom: '1px'}}>
<FontAwesomeIcon icon={faChevronDown}/> <PillarLabel pillar={this.props.pillar}
status={this.props.pillarsToStatuses[this.props.pillar]}/>
</h3>
</Card.Header>
<Collapse in={this.state.open}>
<Card.Body>
<div id={'collapse-content'}>
<PrinciplesStatusTable principlesStatus={this.props.principlesStatus}/>
</div>
</Card.Body>
</Collapse>
</Card>
);
}
}

View File

@ -15,10 +15,10 @@ const statusToIcon = {
};
export const statusToLabelType = {
'Passed': 'label-success',
'Verify': 'label-warning',
'Failed': 'label-danger',
'Unexecuted': 'label-default'
'Passed': 'badge-success',
'Verify': 'badge-warning',
'Failed': 'badge-danger',
'Unexecuted': 'badge-default'
};
export default class StatusLabel extends Component {
@ -29,7 +29,7 @@ export default class StatusLabel extends Component {
}
return (
<div className={'label ' + statusToLabelType[this.props.status]} style={{display: 'flow-root'}}>
<div className={'badge ' + statusToLabelType[this.props.status]} style={{display: 'flow-root'}}>
<FontAwesomeIcon icon={statusToIcon[this.props.status]} size={this.props.size}/>{text}
</div>
);

View File

@ -1,5 +1,5 @@
import React, {Component} from 'react';
import {Col, Grid, Row} from 'react-bootstrap';
import {Col, Container, Row} from 'react-bootstrap';
import PillarsOverview from './PillarOverview';
import ZeroTrustReportLegend from './ReportLegend';
import * as PropTypes from 'prop-types';
@ -8,7 +8,7 @@ export default class SummarySection extends Component {
render() {
return <div id="summary-section">
<h2>Summary</h2>
<Grid fluid={true}>
<Container fluid>
<Row>
<Col xs={12} sm={12} md={12} lg={12}>
<p>
@ -28,7 +28,7 @@ export default class SummarySection extends Component {
<ZeroTrustReportLegend/>
</Col>
</Row>
</Grid>
</Container>
</div>
}
}

View File

@ -12,21 +12,19 @@ class ArcNode extends React.Component {
return (
<g transform={'rotate(180)'} id={data.node.pillar} key={prefix + 'arcGroup' + index}>
<OverlayTrigger ref={'overlay'} key={prefix + 'arcOverlayTrigger' + index} trigger={null}
<OverlayTrigger key={prefix + 'arcOverlayTrigger' + index}
trigger={['hover', 'focus']}
placement={data.popover}
overlay={<Popover id={prefix + 'ArcPopover' + index} style={{backgroundColor: data.hex}}
title={data.node.pillar}>{data.tooltip}</Popover>} rootClose>
overlay={<Popover id={prefix + 'ArcPopover' + index} style={{backgroundColor: data.hex}}>
<Popover.Title>{data.node.pillar}</Popover.Title>
<Popover.Content>{data.tooltip}</Popover.Content>
</Popover>} rootClose>
<path
id={prefix + 'Node_' + index}
className={'arcNode'}
data-tooltip={data.tooltip}
d={arc()}
fill={data.hex}
onClick={this.handleClick.bind(this)}
onMouseEnter={this.handleOver.bind(this)}
onMouseLeave={this.handleOut.bind(this)}
/>
</OverlayTrigger>
<text x={0} dy={data.fontStyle.size * 1.2} fontSize={data.fontStyle.size} fill={'white'} textAnchor='middle'
@ -39,23 +37,6 @@ class ArcNode extends React.Component {
</g>
);
}
handleClick() {
this.props.disableHover(this.refs.overlay);
}
handleOver() {
if (this.props.hover) {
this.refs.overlay.show();
}
}
handleOut() {
if (this.props.hover) {
this.refs.overlay.hide();
}
}
}
ArcNode.propTypes = {

View File

@ -10,10 +10,14 @@ class CircularNode extends React.Component {
let translate = 'translate(' + data.cx + ',' + data.cy + ')';
return (
<g transform={translate} id={data.node.pillar} key={prefix + 'circularGroup' + index}>
<OverlayTrigger ref={'overlay'} key={prefix + 'CircularOverlay' + index} trigger={null} placement={data.popover}
<OverlayTrigger key={prefix + 'CircularOverlay' + index}
trigger={['hover', 'focus']}
placement={data.popover}
overlay={<Popover id={prefix + 'CircularClickPopover' + index}
style={{backgroundColor: data.hex}}
title={data.node.pillar}>{data.tooltip}</Popover>} rootClose>
style={{backgroundColor: data.hex}}>
<Popover.Title>{data.node.pillar}</Popover.Title>
<Popover.Content>{data.tooltip}</Popover.Content>
</Popover>} rootClose>
<circle
id={prefix + 'Node_' + index}
className={'circularNode'}
@ -21,10 +25,6 @@ class CircularNode extends React.Component {
r={data.r}
opacity={0.8}
fill={data.hex}
onClick={this.handleClick.bind(this)}
onMouseEnter={this.handleOver.bind(this)}
onMouseLeave={this.handleOut.bind(this)}
/>
</OverlayTrigger>
<foreignObject style={{fontSize: data.fontStyle.size, pointerEvents: 'none'}}
@ -36,24 +36,6 @@ class CircularNode extends React.Component {
</g>
);
}
handleClick() {
this.props.disableHover(this.refs.overlay);
}
handleOver() {
if (this.props.hover) {
this.refs.overlay.show();
}
}
handleOut() {
if (this.props.hover) {
this.refs.overlay.hide();
}
}
}
CircularNode.propTypes = {

Some files were not shown because too many files have changed in this diff Show More