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 # vim swap files
*.swp *.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 - cd monkey_island/cc/ui
- npm ci # See https://docs.npmjs.com/cli/ci.html - npm ci # See https://docs.npmjs.com/cli/ci.html
- eslint ./src --quiet # Test for errors - 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 - eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT # Test for max warnings
after_success: 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!** **Before running performance test make sure browser is not sending requests to island!**
To run telemetry performance test follow these steps: To run telemetry performance test follow these steps:
0. Set `server_config.json` to "standard" (no password protection) setting.
1. Gather monkey telemetries. 1. Gather monkey telemetries.
1. Enable "Export monkey telemetries" in Configuration -> Internal -> Tests if you don't have 1. Enable "Export monkey telemetries" in Configuration -> Internal -> Tests if you don't have
exported telemetries already. exported telemetries already.

View File

@ -132,6 +132,7 @@ class TestMonkeyBlackbox(object):
def test_wmi_pth(self, island_client): def test_wmi_pth(self, island_client):
TestMonkeyBlackbox.run_exploitation_test(island_client, "WMI_PTH.conf", "WMI_PTH") 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): def test_report_generation_performance(self, island_client, quick_performance_tests):
""" """
This test includes the SSH + Elastic + Hadoop + MSSQL machines all in one test 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.") LOGGER.error("This test doesn't support 'quick_performance_tests' option.")
assert False 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): def test_map_generation_performance(self, island_client, quick_performance_tests):
if not quick_performance_tests: if not quick_performance_tests:
TestMonkeyBlackbox.run_performance_test(MapGenerationTest, 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.exploit.tools.helpers import get_monkey_dest_path, build_monkey_commandline, get_monkey_depth
from infection_monkey.model import DROPPER_ARG from infection_monkey.model import DROPPER_ARG
from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload 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__) 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.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
from infection_monkey.model import MONKEY_ARG from infection_monkey.model import MONKEY_ARG
from infection_monkey.network.tools import check_tcp_port, get_interface_to_target 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.exploit_enum import ExploitType
from common.utils.attack_utils import ScanStatus from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem 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.windows_upgrader import WindowsUpgrader
from infection_monkey.post_breach.post_breach_handler import PostBreach 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.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 infection_monkey.telemetry.attack.t1106_telem import T1106Telem
from common.utils.attack_utils import ScanStatus, UsageEnum from common.utils.attack_utils import ScanStatus, UsageEnum
from common.version import get_version 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'] 'kerberos_creds', 'credman_creds', 'tspkg_creds']
PypykatzCredential = NewType('PypykatzCredential', Dict) PypykatzCredential = NewType('PypykatzCredential', Dict)
def get_windows_creds() -> List[WindowsCredentials]: def get_windows_creds() -> List[WindowsCredentials]:
pypy_handle = pypykatz.go_live() pypy_handle = pypykatz.go_live()
logon_data = pypy_handle.to_dict() logon_data = pypy_handle.to_dict()

View File

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

View File

@ -5,11 +5,12 @@ import flask_restful
from flask import Flask, send_from_directory, Response from flask import Flask, send_from_directory, Response
from werkzeug.exceptions import NotFound 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.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.client_run import ClientRun
from monkey_island.cc.resources.edge import Edge 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.local_run import LocalRun
from monkey_island.cc.resources.log import Log from monkey_island.cc.resources.log import Log
from monkey_island.cc.resources.island_logs import IslandLog 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 import Node
from monkey_island.cc.resources.node_states import NodeStates 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.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.remote_run import RemoteRun
from monkey_island.cc.resources.reporting.report import Report from monkey_island.cc.resources.reporting.report import Report
from monkey_island.cc.resources.root import Root 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['MONGO_URI'] = mongo_url
app.config['SECRET_KEY'] = str(uuid.getnode()) app.config['SECRET_KEY'] = str(uuid.getnode())
app.config['JWT_AUTH_URL_RULE'] = '/api/auth' 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): def init_app_services(app):
@ -91,6 +93,8 @@ def init_app_url_rules(app):
def init_api_resources(api): def init_api_resources(api):
api.add_resource(Root, '/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(Monkey, '/api/monkey', '/api/monkey/', '/api/monkey/<string:guid>')
api.add_resource(Bootloader, '/api/bootloader/<string:os>') api.add_resource(Bootloader, '/api/bootloader/<string:os>')
api.add_resource(LocalRun, '/api/local-monkey', '/api/local-monkey/') 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 abc import ABCMeta, abstractmethod
from datetime import timedelta from datetime import timedelta
import os
import hashlib
__author__ = 'itay.mizeretz' __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): class Environment(object, metaclass=ABCMeta):
_ISLAND_PORT = 5000 _ISLAND_PORT = 5000
@ -18,6 +26,53 @@ class Environment(object, metaclass=ABCMeta):
_testing = False _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 @property
def testing(self): def testing(self):
return self._testing return self._testing
@ -26,12 +81,11 @@ class Environment(object, metaclass=ABCMeta):
def testing(self, value): def testing(self, value):
self._testing = value self._testing = value
def __init__(self): def save_config(self):
self.config = None self._config.save_to_file()
self._testing = False # Assume env is not for unit testing.
def set_config(self, config): def get_config(self) -> EnvironmentConfig:
self.config = config return self._config
def get_island_port(self): def get_island_port(self):
return self._ISLAND_PORT return self._ISLAND_PORT
@ -51,21 +105,14 @@ class Environment(object, metaclass=ABCMeta):
hash_obj.update(secret.encode('utf-8')) hash_obj.update(secret.encode('utf-8'))
return hash_obj.hexdigest() return hash_obj.hexdigest()
def get_deployment(self): def get_deployment(self) -> str:
return self._get_from_config('deployment', 'unknown') deployment = 'unknown'
if self._config and self._config.deployment:
deployment = self._config.deployment
return deployment
def is_develop(self): def set_deployment(self, deployment: str):
return self.get_deployment() == 'develop' self._config.deployment = deployment
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
@property @property
def mongo_db_name(self): 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 monkey_island.cc.environment import Environment
from common.cloud.aws.aws_instance import AwsInstance from common.cloud.aws.aws_instance import AwsInstance
@ -6,8 +6,11 @@ __author__ = 'itay.mizeretz'
class AwsEnvironment(Environment): 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. # Not suppressing error here on purpose. This is critical if we're on AWS env.
self.aws_info = AwsInstance() self.aws_info = AwsInstance()
self._instance_id = self._get_instance_id() self._instance_id = self._get_instance_id()
@ -20,6 +23,7 @@ class AwsEnvironment(Environment):
return self.aws_info.get_region() return self.aws_info.get_region()
def get_auth_users(self): def get_auth_users(self):
return [ if self._is_registered():
monkey_island.cc.auth.User(1, 'monkey', self.hash_secret(self._instance_id)) 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 from monkey_island.cc.environment import Environment
import monkey_island.cc.auth
__author__ = 'itay.mizeretz' __author__ = 'itay.mizeretz'
class PasswordEnvironment(Environment): class PasswordEnvironment(Environment):
_credentials_required = True
def get_auth_users(self): def get_auth_users(self):
return [ if self._is_registered():
monkey_island.cc.auth.User(1, self.config['user'], self.config['hash']) 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 from monkey_island.cc.environment import Environment
__author__ = 'itay.mizeretz' __author__ = 'itay.mizeretz'
class StandardEnvironment(Environment): class StandardEnvironment(Environment):
_credentials_required = False
# SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()' # SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()'
NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \ NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \
'8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557' '8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557'
def get_auth_users(self): def get_auth_users(self):
return [ return [User(1, StandardEnvironment.NO_AUTH_CREDS, StandardEnvironment.NO_AUTH_CREDS)]
monkey_island.cc.auth.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): 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. This will cause all mongo connections to happen via `mongomock` instead of using an actual mongodb instance.
""" """
def __init__(self): _credentials_required = True
super(TestingEnvironment, self).__init__()
def __init__(self, config: EnvironmentConfig):
super(TestingEnvironment, self).__init__(config)
self.testing = True self.testing = True
def get_auth_users(self): 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.app import init_app
from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list 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.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.database import is_db_server_up, get_db_version
from monkey_island.cc.resources.monkey_download import MonkeyDownload from monkey_island.cc.resources.monkey_download import MonkeyDownload
from common.version import get_version from common.version import get_version
@ -33,7 +33,7 @@ from monkey_island.cc.setup import setup
def main(should_setup_only=False): def main(should_setup_only=False):
logger.info("Starting bootloader server") 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 = Thread(target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True)
bootloader_server_thread.start() bootloader_server_thread.start()
@ -46,7 +46,7 @@ def start_island_server(should_setup_only):
from tornado.httpserver import HTTPServer from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop from tornado.ioloop import IOLoop
mongo_url = os.environ.get('MONGO_URL', env.get_mongo_url()) mongo_url = os.environ.get('MONGO_URL', env_singleton.env.get_mongo_url())
wait_for_mongo_db_server(mongo_url) wait_for_mongo_db_server(mongo_url)
assert_mongo_db_version(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.") logger.warning("Setup only flag passed. Exiting.")
return 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)) app.run(host='0.0.0.0', debug=True, ssl_context=(crt_path, key_path))
else: else:
http_server = HTTPServer(WSGIContainer(app), http_server = HTTPServer(WSGIContainer(app),
ssl_options={'certfile': os.environ.get('SERVER_CRT', crt_path), ssl_options={'certfile': os.environ.get('SERVER_CRT', crt_path),
'keyfile': os.environ.get('SERVER_KEY', key_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() log_init_info()
IOLoop.instance().start() IOLoop.instance().start()
@ -77,7 +77,7 @@ def log_init_info():
logger.info('Monkey Island Server is running!') logger.info('Monkey Island Server is running!')
logger.info(f"version: {get_version()}") logger.info(f"version: {get_version()}")
logger.info('Listening on the following URLs: {}'.format( 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() MonkeyDownload.log_executable_hashes()

View File

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

View File

@ -1,7 +1,7 @@
import flask_restful import flask_restful
from flask import jsonify, request, json, current_app 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 from monkey_island.cc.services.attack.attack_config import AttackConfig
__author__ = "VakarisZ" __author__ = "VakarisZ"

View File

@ -1,5 +1,5 @@
import flask_restful 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_report import AttackReportService
from monkey_island.cc.services.attack.attack_schema import SCHEMA from monkey_island.cc.services.attack.attack_schema import SCHEMA
from flask import json, current_app 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 flask_jwt import JWT, _jwt_required, JWTError
from werkzeug.security import safe_str_cmp 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' __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): def init_jwt(app):
users = env.get_auth_users() user_store.UserStore.set_users(env_singleton.env.get_auth_users())
username_table = {u.username: u for u in users}
userid_table = {u.id: u for u in users}
def authenticate(username, secret): 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')): if user and safe_str_cmp(user.secret.encode('utf-8'), secret.encode('utf-8')):
return user return user
def identity(payload): def identity(payload):
user_id = payload['identity'] 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) 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 import flask_restful
from flask import request, jsonify, abort 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 from monkey_island.cc.services.config import ConfigService

View File

@ -2,7 +2,7 @@ import logging
import flask_restful 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 from monkey_island.cc.services.island_logs import IslandLogService
__author__ = "Maor.Rayzin" __author__ = "Maor.Rayzin"

View File

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

View File

@ -4,7 +4,7 @@ import flask_restful
from bson import ObjectId from bson import ObjectId
from flask import request 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.database import mongo
from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore
from monkey_island.cc.services.log import LogService from monkey_island.cc.services.log import LogService

View File

@ -3,7 +3,7 @@ import json
import flask_restful import flask_restful
from flask import request, jsonify, abort 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 from monkey_island.cc.services.config import ConfigService
__author__ = 'Barak' __author__ = 'Barak'

View File

@ -1,6 +1,6 @@
import flask_restful 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_edge import NetEdgeService
from monkey_island.cc.services.netmap.net_node import NetNodeService from monkey_island.cc.services.netmap.net_node import NetNodeService

View File

@ -1,7 +1,7 @@
from flask import request from flask import request
import flask_restful 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 from monkey_island.cc.services.node import NodeService
__author__ = 'Barak' __author__ = 'Barak'

View File

@ -1,6 +1,6 @@
import flask_restful 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 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 flask import request, send_from_directory, Response
from monkey_island.cc.services.config import ConfigService 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.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 import os
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
import logging 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 from flask import request, jsonify, make_response
import flask_restful 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 monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
from common.cloud.aws.aws_service import AwsService from common.cloud.aws.aws_service import AwsService

View File

@ -3,7 +3,7 @@ import http.client
import flask_restful import flask_restful
from flask import jsonify 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.report import ReportService
from monkey_island.cc.services.reporting.zero_trust_service import ZeroTrustService from monkey_island.cc.services.reporting.zero_trust_service import ZeroTrustService

View File

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

View File

@ -6,7 +6,7 @@ import dateutil
import flask_restful import flask_restful
from flask import request 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.database import mongo
from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore
from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.node import NodeService

View File

@ -6,7 +6,7 @@ import flask_restful
from flask import request from flask import request
import flask_pymongo 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.database import mongo
from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.node import NodeService

View File

@ -2,7 +2,7 @@ import logging
import flask_restful 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_report import AttackReportService
from monkey_island.cc.services.reporting.report import ReportService from monkey_island.cc.services.reporting.report import ReportService

View File

@ -2,7 +2,7 @@ from bson import json_util
import flask_restful import flask_restful
from flask import request 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 from monkey_island.cc.database import mongo, database

View File

@ -2,7 +2,7 @@ from bson import json_util
import flask_restful import flask_restful
from flask import request 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.database import mongo

View File

@ -1,7 +1,7 @@
import flask_restful import flask_restful
import json 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 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" "deployment": "develop"
} }

View File

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

View File

@ -6,7 +6,7 @@ import boto3
from botocore.exceptions import UnknownServiceError from botocore.exceptions import UnknownServiceError
from common.cloud.aws.aws_instance import AwsInstance 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 from monkey_island.cc.services.reporting.exporter import Exporter
__authors__ = ['maor.rayzin', 'shay.nehmad'] __authors__ = ['maor.rayzin', 'shay.nehmad']
@ -68,7 +68,7 @@ class AWSExporter(Exporter):
# azure and conficker are not relevant issues for an AWS env # 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) product_arn = 'arn:aws:securityhub:{region}:{arn}'.format(region=region, arn=configured_product_arn)
instance_arn = 'arn:aws:ec2:' + str(region) + ':instance:{instance_id}' instance_arn = 'arn:aws:ec2:' + str(region) + ':instance:{instance_id}'
# Not suppressing error here on purpose. # 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.report_exporter_manager import ReportExporterManager
from monkey_island.cc.services.reporting.aws_exporter import AWSExporter from monkey_island.cc.services.reporting.aws_exporter import AWSExporter
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService 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__) logger = logging.getLogger(__name__)
@ -21,7 +21,7 @@ def try_add_aws_exporter_to_manager(manager):
# noinspection PyBroadException # noinspection PyBroadException
try: try:
RemoteRunAwsService.init() 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) manager.add_exporter_to_list(AWSExporter)
except Exception: except Exception:
logger.error("Failed adding aws exporter to manager. Exception info:", exc_info=True) logger.error("Failed adding aws exporter to manager. Exception info:", exc_info=True)

View File

@ -3,7 +3,7 @@ import logging
import requests import requests
from common.version import get_version 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" __author__ = "itay.mizeretz"
@ -40,7 +40,7 @@ class VersionUpdateService:
Checks if newer monkey version is available Checks if newer monkey version is available
:return: False if not, version in string format ('1.6.2') otherwise :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) reply = requests.get(url, timeout=15)
@ -54,4 +54,4 @@ class VersionUpdateService:
@staticmethod @staticmethod
def get_download_link(): 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 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 import Monkey
from monkey_island.cc.models.edge import Edge from monkey_island.cc.models.edge import Edge
from monkey_island.cc.models.zero_trust.finding import Finding 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): class IslandTestCase(unittest.TestCase):
def fail_if_not_testing_env(self): 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 @staticmethod
def clean_monkey_db(): 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-uses-vars": 1,
"react/jsx-key": 1, "react/jsx-key": 1,
"react/prop-types": 0, "react/prop-types": 0,
"react/no-unescaped-entities": 1, "react/no-unescaped-entities": 0,
"react/no-unknown-property": [1, { "ignore": ["class"] }], "react/no-unknown-property": [1, { "ignore": ["class"] }],
"react/no-string-refs": 1, "react/no-string-refs": 1,
"react/display-name": 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/free-solid-svg-icons": "^5.11.2",
"@fortawesome/react-fontawesome": "^0.1.7", "@fortawesome/react-fontawesome": "^0.1.7",
"@kunukn/react-collapse": "^1.2.7", "@kunukn/react-collapse": "^1.2.7",
"bootstrap": "^3.4.1", "bootstrap": "^4.5.0",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"core-js": "^2.6.10", "core-js": "^2.6.10",
"d3": "^5.14.1", "d3": "^5.14.1",
@ -80,19 +80,21 @@
"rainge": "^1.0.1", "rainge": "^1.0.1",
"rc-progress": "^2.5.2", "rc-progress": "^2.5.2",
"react": "^16.12.0", "react": "^16.12.0",
"react-bootstrap": "^0.32.4", "react-bootstrap": "^1.0.1",
"react-copy-to-clipboard": "^5.0.2", "react-copy-to-clipboard": "^5.0.2",
"react-data-components": "^1.2.0", "react-data-components": "^1.2.0",
"react-desktop-notification": "^1.0.9", "react-desktop-notification": "^1.0.9",
"react-dimensions": "^1.3.0", "react-dimensions": "^1.3.0",
"react-event-timeline": "^1.6.3",
"react-hot-loader": "^4.12.20",
"react-dom": "^16.12.0", "react-dom": "^16.12.0",
"react-event-timeline": "^1.6.3",
"react-fa": "^5.0.0", "react-fa": "^5.0.0",
"react-filepond": "^7.0.1", "react-filepond": "^7.0.1",
"react-graph-vis": "^1.0.5", "react-graph-vis": "^1.0.5",
"react-hot-loader": "^4.12.20",
"react-json-tree": "^0.11.2", "react-json-tree": "^0.11.2",
"react-jsonschema-form": "^1.8.0", "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-redux": "^5.1.2",
"react-router-dom": "^4.3.1", "react-router-dom": "^4.3.1",
"react-spinners": "^0.5.13", "react-spinners": "^0.5.13",

View File

@ -1,9 +1,6 @@
import React from 'react'; import React from 'react';
import {BrowserRouter as Router, NavLink, Redirect, Route, Switch} from 'react-router-dom'; import {BrowserRouter as Router, Redirect, Route, Switch} from 'react-router-dom';
import {Col, Grid, Row} from 'react-bootstrap'; import {Container} 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 RunServerPage from 'components/pages/RunServerPage'; import RunServerPage from 'components/pages/RunServerPage';
import ConfigurePage from 'components/pages/ConfigurePage'; import ConfigurePage from 'components/pages/ConfigurePage';
@ -15,6 +12,7 @@ import ReportPage from 'components/pages/ReportPage';
import LicensePage from 'components/pages/LicensePage'; import LicensePage from 'components/pages/LicensePage';
import AuthComponent from 'components/AuthComponent'; import AuthComponent from 'components/AuthComponent';
import LoginPageComponent from 'components/pages/LoginPage'; import LoginPageComponent from 'components/pages/LoginPage';
import RegisterPageComponent from 'components/pages/RegisterPage';
import Notifier from 'react-desktop-notification'; import Notifier from 'react-desktop-notification';
import NotFoundPage from 'components/pages/NotFoundPage'; import NotFoundPage from 'components/pages/NotFoundPage';
@ -24,17 +22,16 @@ import 'react-data-components/css/table-twbs.css';
import 'styles/App.css'; import 'styles/App.css';
import 'react-toggle/style.css'; import 'react-toggle/style.css';
import 'react-table/react-table.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 notificationIcon from '../images/notification-logo-512x512.png';
import {StandardLayoutComponent} from './layouts/StandardLayoutComponent';
const reportZeroTrustRoute = '/report/zeroTrust'; const reportZeroTrustRoute = '/report/zeroTrust';
class AppComponent extends AuthComponent { class AppComponent extends AuthComponent {
updateStatus = () => { updateStatus = () => {
if (this.state.isLoggedIn === false){
return
}
this.auth.loggedIn() this.auth.loggedIn()
.then(res => { .then(res => {
if (this.state.isLoggedIn !== 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) { if (res) {
this.authFetch('/api') this.authFetch('/api')
.then(res => res.json()) .then(res => res.json())
@ -69,11 +75,17 @@ class AppComponent extends AuthComponent {
switch (this.state.isLoggedIn) { switch (this.state.isLoggedIn) {
case true: case true:
return page_component; return page_component;
case false:
switch (this.state.needsRegistration) {
case true:
return <Redirect to={{pathname: '/register'}}/>
case false: case false:
return <Redirect to={{pathname: '/login'}}/>; return <Redirect to={{pathname: '/login'}}/>;
default: default:
return page_component; return page_component;
}
default:
return page_component;
} }
}; };
@ -94,22 +106,18 @@ class AppComponent extends AuthComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
removePBAfiles: false,
completedSteps: { completedSteps: {
run_server: true, run_server: true,
run_monkey: false, run_monkey: false,
infection_done: false, infection_done: false,
report_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() { componentDidMount() {
this.updateStatus(); this.updateStatus();
this.interval = setInterval(this.updateStatus, 10000); this.interval = setInterval(this.updateStatus, 10000);
@ -122,102 +130,53 @@ class AppComponent extends AuthComponent {
render() { render() {
return ( return (
<Router> <Router>
<Grid fluid={true}> <Container fluid>
<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> <Switch>
<Route path='/login' render={() => (<LoginPageComponent onStatusChange={this.updateStatus}/>)}/> <Route path='/login' render={() => (<LoginPageComponent onStatusChange={this.updateStatus}/>)}/>
{this.renderRoute('/', <RunServerPage onStatusChange={this.updateStatus}/>, true)} <Route path='/register' render={() => (<RegisterPageComponent onStatusChange={this.updateStatus}/>)}/>
{this.renderRoute('/configure', <ConfigurePage onStatusChange={this.updateStatus}/>)} {this.renderRoute('/',
{this.renderRoute('/run-monkey', <RunMonkeyPage onStatusChange={this.updateStatus}/>)} <StandardLayoutComponent component={RunServerPage}
{this.renderRoute('/infection/map', <MapPage onStatusChange={this.updateStatus}/>)} completedSteps={this.state.completedSteps}
{this.renderRoute('/infection/telemetry', <TelemetryPage onStatusChange={this.updateStatus}/>)} onStatusChange={this.updateStatus}
{this.renderRoute('/start-over', <StartOverPage 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.redirectTo('/report', '/report/security')}
{this.renderRoute('/report/security', <ReportPage/>)} {this.renderRoute('/report/security',
{this.renderRoute('/report/attack', <ReportPage/>)} <StandardLayoutComponent component={ReportPage}
{this.renderRoute('/report/zeroTrust', <ReportPage/>)} completedSteps={this.state.completedSteps}/>)}
{this.renderRoute('/license', <LicensePage onStatusChange={this.updateStatus}/>)} {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}/> <Route component={NotFoundPage}/>
</Switch> </Switch>
</Col> </Container>
</Row>
</Grid>
</Router> </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 React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import marked from 'marked'; import marked from 'marked';
import '../../../styles/report/AttackReport.scss';
class MitigationsComponent extends React.Component { 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 React from 'react';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'
import {faHandPointLeft} from '@fortawesome/free-solid-svg-icons/faHandPointLeft' import {faHandPointLeft} from '@fortawesome/free-solid-svg-icons/faHandPointLeft'
import {faQuestionCircle} from '@fortawesome/free-solid-svg-icons/faQuestionCircle'
import Toggle from 'react-toggle'; import Toggle from 'react-toggle';
import {OverlayTrigger, Tooltip} from 'react-bootstrap'; import {OverlayTrigger, Tooltip} from 'react-bootstrap';
import download from 'downloadjs' import download from 'downloadjs'
@ -10,8 +11,10 @@ class PreviewPaneComponent extends AuthComponent {
generateToolTip(text) { generateToolTip(text) {
return ( return (
<OverlayTrigger placement="top" overlay={<Tooltip id="tooltip">{text}</Tooltip>}> <OverlayTrigger placement="top"
<a><i className="glyphicon glyphicon-info-sign"/></a> overlay={<Tooltip id="tooltip">{text}</Tooltip>}
delay={{ show: 250, hide: 400 }}>
<a><FontAwesomeIcon icon={faQuestionCircle} style={{'marginRight': '0.5em'}}/></a>
</OverlayTrigger> </OverlayTrigger>
); );
} }
@ -123,8 +126,8 @@ class PreviewPaneComponent extends AuthComponent {
Download Log Download Log
</th> </th>
<td> <td>
<a type="button" className="btn btn-primary" <a type='button'
disabled={!asset.has_log} className={asset.has_log ? 'btn btn-primary' : 'btn btn-primary disabled'}
onClick={() => this.downloadLog(asset)}>Download</a> onClick={() => this.downloadLog(asset)}>Download</a>
</td> </td>
</tr> </tr>
@ -142,7 +145,7 @@ class PreviewPaneComponent extends AuthComponent {
Exploit Timeline&nbsp; Exploit Timeline&nbsp;
{this.generateToolTip('Timeline of exploit attempts. Red is successful. Gray is unsuccessful')} {this.generateToolTip('Timeline of exploit attempts. Red is successful. Gray is unsuccessful')}
</h4> </h4>
<ul className="timeline"> <ul className='timeline'>
{asset.exploits.map(exploit => {asset.exploits.map(exploit =>
<li key={exploit.timestamp}> <li key={exploit.timestamp}>
<div className={'bullet ' + (exploit.result ? 'bad' : '')}/> <div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
@ -167,7 +170,7 @@ class PreviewPaneComponent extends AuthComponent {
assetInfo(asset) { assetInfo(asset) {
return ( return (
<div> <div>
<table className="table table-condensed"> <table className='table table-condensed'>
<tbody> <tbody>
{this.osRow(asset)} {this.osRow(asset)}
{this.ipsRow(asset)} {this.ipsRow(asset)}
@ -183,7 +186,7 @@ class PreviewPaneComponent extends AuthComponent {
infectedAssetInfo(asset) { infectedAssetInfo(asset) {
return ( return (
<div> <div>
<table className="table table-condensed"> <table className='table table-condensed'>
<tbody> <tbody>
{this.osRow(asset)} {this.osRow(asset)}
{this.statusRow(asset)} {this.statusRow(asset)}
@ -202,7 +205,7 @@ class PreviewPaneComponent extends AuthComponent {
scanInfo(edge) { scanInfo(edge) {
return ( return (
<div> <div>
<table className="table table-condensed"> <table className='table table-condensed'>
<tbody> <tbody>
<tr> <tr>
<th>Operating System</th> <th>Operating System</th>
@ -223,7 +226,7 @@ class PreviewPaneComponent extends AuthComponent {
'' : '' :
<div> <div>
<h4 style={{'marginTop': '2em'}}>Timeline</h4> <h4 style={{'marginTop': '2em'}}>Timeline</h4>
<ul className="timeline"> <ul className='timeline'>
{edge.exploits.map(exploit => {edge.exploits.map(exploit =>
<li key={exploit.timestamp}> <li key={exploit.timestamp}>
<div className={'bullet ' + (exploit.result ? 'bad' : '')}/> <div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
@ -278,7 +281,7 @@ class PreviewPaneComponent extends AuthComponent {
} }
return ( return (
<div className="preview-pane"> <div className='preview-pane'>
{!info ? {!info ?
<span> <span>
<FontAwesomeIcon icon={faHandPointLeft} style={{'marginRight': '0.5em'}}/> <FontAwesomeIcon icon={faHandPointLeft} style={{'marginRight': '0.5em'}}/>

View File

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

View File

@ -1,6 +1,8 @@
import React from 'react'; import React from 'react';
import {Col} from 'react-bootstrap'; 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 { class LicensePageComponent extends React.Component {
constructor(props) { constructor(props) {
@ -15,11 +17,13 @@ class LicensePageComponent extends React.Component {
render() { render() {
return ( 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> <h1 className="page-title">License</h1>
<div style={{'fontSize': '1.2em'}}> <div style={{'fontSize': '1.2em'}}>
<p> <p>
Copyright <i className="glyphicon glyphicon-copyright-mark"/> {rainge(2015)} Guardicore Ltd. Copyright <FontAwesomeIcon icon={faCopyright}/> {rainge(2015)} Guardicore Ltd.
<br/> <br/>
Licensed under <a href="https://www.gnu.org/licenses/gpl-3.0.html" rel="noopener noreferrer" target="_blank">GPLv3</a>. Licensed under <a href="https://www.gnu.org/licenses/gpl-3.0.html" rel="noopener noreferrer" target="_blank">GPLv3</a>.
</p> </p>

View File

@ -1,10 +1,14 @@
import React from 'react'; 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 { class LoginPageComponent extends React.Component {
login = () => { login = (event) => {
event.preventDefault()
this.auth.login(this.username, this.password).then(res => { this.auth.login(this.username, this.password).then(res => {
if (res['result']) { if (res['result']) {
this.redirectToHome(); this.redirectToHome();
@ -26,6 +30,10 @@ class LoginPageComponent extends React.Component {
window.location.href = '/'; window.location.href = '/';
}; };
redirectToRegistration = () => {
window.location.href = '/register';
};
constructor(props) { constructor(props) {
super(props); super(props);
this.username = ''; this.username = '';
@ -34,6 +42,13 @@ class LoginPageComponent extends React.Component {
this.state = { this.state = {
failed: false failed: false
}; };
this.auth.needsRegistration()
.then(result => {
if (result) {
this.redirectToRegistration()
}
})
this.auth.loggedIn() this.auth.loggedIn()
.then(res => { .then(res => {
if (res) { if (res) {
@ -44,37 +59,36 @@ class LoginPageComponent extends React.Component {
render() { render() {
return ( return (
<Col xs={12} lg={8}> <Container fluid className={'auth-container'}>
<h1 className="page-title">Login</h1> <Particles className={'particle-background'} params={particleParams}/>
<div className="col-sm-6 col-sm-offset-3" style={{'fontSize': '1.2em'}}> <Row>
<div className="panel panel-default"> <Col xs={12} lg={{span: 6, offset: 3}} md={{span: 7, offset: 3}} className={'auth-block'}>
<div className="panel-heading text-center"> <Row>
<b>Login</b> <Col lg={8} md={8} sm={8}>
</div> <h1 className='auth-title'>Login</h1>
<div className="panel-body"> <div>
<div className="input-group center-block text-center"> <Form className={'auth-form'} onSubmit={this.login}>
<input type="text" className="form-control" placeholder="Username" <Form.Control onChange={evt => this.updateUsername(evt)} type='text' placeholder='Username'/>
onChange={evt => this.updateUsername(evt)}/> <Form.Control onChange={evt => this.updatePassword(evt)} type='password' placeholder='Password'/>
<input type="password" className="form-control" placeholder="Password" <Button id={'auth-button'} type={'submit'}>
onChange={evt => this.updatePassword(evt)}/>
<button type="button" className="btn btn-primary btn-lg" style={{margin: '5px'}}
onClick={() => {
this.login()
}}>
Login Login
</button> </Button>
{ {
this.state.failed ? this.state.failed ?
<div className="alert alert-danger" role="alert">Login failed. Bad credentials.</div> <div className="alert alert-danger" role="alert">Login failed. Bad credentials.</div>
: :
'' ''
} }
</div> </Form>
</div>
</div>
</div> </div>
</Col> </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 React from 'react';
import {Col, Modal} from 'react-bootstrap'; import {Col, Modal, Row} from 'react-bootstrap';
import {Link} from 'react-router-dom'; import {Link} from 'react-router-dom';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import { faStopCircle } from '@fortawesome/free-solid-svg-icons/faStopCircle' import {faStopCircle} from '@fortawesome/free-solid-svg-icons/faStopCircle';
import { faMinus } from '@fortawesome/free-solid-svg-icons/faMinus' import {faMinus} from '@fortawesome/free-solid-svg-icons/faMinus';
import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane'; import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane';
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
import {getOptions, edgeGroupToColor} from 'components/map/MapOptions'; import {getOptions, edgeGroupToColor} from 'components/map/MapOptions';
import AuthComponent from '../AuthComponent'; import AuthComponent from '../AuthComponent';
import '../../styles/Map.scss';
import {faInfoCircle} from "@fortawesome/free-solid-svg-icons/faInfoCircle";
class MapPageComponent extends AuthComponent { class MapPageComponent extends AuthComponent {
constructor(props) { constructor(props) {
@ -153,7 +155,6 @@ class MapPageComponent extends AuthComponent {
</Modal.Body> </Modal.Body>
</Modal> </Modal>
) )
}; };
renderTelemetryEntry(telemetry) { renderTelemetryEntry(telemetry) {
@ -199,7 +200,10 @@ class MapPageComponent extends AuthComponent {
render() { render() {
return ( return (
<div> <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()} {this.renderKillDialogModal()}
<Col xs={12} lg={8}> <Col xs={12} lg={8}>
<h1 className="page-title">3. Infection Map</h1> <h1 className="page-title">3. Infection Map</h1>
@ -215,19 +219,16 @@ class MapPageComponent extends AuthComponent {
<b style={{color: '#aeaeae'}}> | </b> <b style={{color: '#aeaeae'}}> | </b>
<span>Island Communication <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#a9aaa9'}}/></span> <span>Island Communication <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#a9aaa9'}}/></span>
</div> </div>
{this.renderTelemetryConsole()} <div style={{height: '80vh'}} className={'map-window'}>
<div style={{height: '80vh'}}>
<ReactiveGraph graph={this.state.graph} options={getOptions(this.state.nodeStateList)} events={this.events}/>
</div>
{this.renderTelemetryLineCount()} {this.renderTelemetryLineCount()}
{this.renderTelemetryConsole()}
<ReactiveGraph graph={this.state.graph} options={getOptions(this.state.nodeStateList)}
events={this.events}/>
</div>
</Col> </Col>
<Col xs={4}> <Col xs={4}>
<input className="form-control input-block"
placeholder="Find on map"
style={{'marginBottom': '1em'}}/>
<div style={{'overflow': 'auto', 'marginBottom': '1em'}}> <div style={{'overflow': 'auto', 'marginBottom': '1em'}}>
<Link to="/infection/telemetry" className="btn btn-default pull-left" style={{'width': '48%'}}>Monkey <Link to="/infection/telemetry" className="btn btn-light pull-left" style={{'width': '48%'}}>Monkey
Telemetry</Link> Telemetry</Link>
<button onClick={() => this.setState({showKillDialog: true})} className="btn btn-danger pull-right" <button onClick={() => this.setState({showKillDialog: true})} className="btn btn-danger pull-right"
style={{'width': '48%'}}> style={{'width': '48%'}}>
@ -237,14 +238,15 @@ class MapPageComponent extends AuthComponent {
</div> </div>
{this.state.killPressed ? {this.state.killPressed ?
<div className="alert alert-info"> <div className="alert alert-info">
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/> <FontAwesomeIcon icon={faInfoCircle} style={{'marginRight': '5px'}} />
Kill command sent to all monkeys Kill command sent to all monkeys
</div> </div>
: ''} : ''}
<PreviewPaneComponent item={this.state.selected} type={this.state.selectedType}/> <PreviewPaneComponent item={this.state.selected} type={this.state.selectedType}/>
</Col> </Col>
</div> </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 React from 'react';
import {Route} from 'react-router-dom'; import {Route} from 'react-router-dom';
import {Col, Nav, NavItem} from 'react-bootstrap'; import {Col, Nav} from 'react-bootstrap';
import AuthComponent from '../AuthComponent'; import AuthComponent from '../AuthComponent';
import MustRunMonkeyWarning from '../report-components/common/MustRunMonkeyWarning'; import MustRunMonkeyWarning from '../report-components/common/MustRunMonkeyWarning';
import AttackReport from '../report-components/AttackReport' import AttackReport from '../report-components/AttackReport'
@ -109,9 +107,13 @@ class ReportPageComponent extends AuthComponent {
renderNav = () => { renderNav = () => {
return ( return (
<Route render={({history}) => ( <Route render={({history}) => (
<Nav bsStyle='tabs' justified <Nav variant='tabs'
fill
activeKey={this.state.selectedSection} activeKey={this.state.selectedSection}
onSelect={(key) => {this.setSelectedSection(key); history.push(key)}} onSelect={(key) => {
this.setSelectedSection(key);
history.push(key)
}}
className={'report-nav'}> className={'report-nav'}>
{this.state.sections.map(section => this.renderNavButton(section))} {this.state.sections.map(section => this.renderNavButton(section))}
</Nav>)}/>) </Nav>)}/>)
@ -119,11 +121,15 @@ class ReportPageComponent extends AuthComponent {
renderNavButton = (section) => { renderNavButton = (section) => {
return ( return (
<NavItem key={section.key} <Nav.Item>
<Nav.Link key={section.key}
eventKey={section.key} eventKey={section.key}
onSelect={() => {}}> onSelect={() => {
}}>
{section.title} {section.title}
</NavItem>)}; </Nav.Link>
</Nav.Item>)
};
getReportContent() { getReportContent() {
switch (this.state.selectedSection) { switch (this.state.selectedSection) {
@ -145,7 +151,9 @@ class ReportPageComponent extends AuthComponent {
content = <MustRunMonkeyWarning/>; content = <MustRunMonkeyWarning/>;
} }
return ( 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> <h1 className='page-title no-print'>4. Security Reports</h1>
{this.renderNav()} {this.renderNav()}
<MonkeysStillAliveWarning allMonkeysAreDead={this.state.allMonkeysAreDead}/> <MonkeysStillAliveWarning allMonkeysAreDead={this.state.allMonkeysAreDead}/>

View File

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

View File

@ -1,9 +1,12 @@
import React from 'react'; import React from 'react';
import {Col} from 'react-bootstrap'; import {Col, Button} from 'react-bootstrap';
import {Link} from 'react-router-dom'; import {Link} from 'react-router-dom';
import AuthComponent from '../AuthComponent'; import AuthComponent from '../AuthComponent';
import StartOverModal from '../ui-components/StartOverModal'; import StartOverModal from '../ui-components/StartOverModal';
import '../../styles/StartOverPage.scss'; 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 { class StartOverPageComponent extends AuthComponent {
constructor(props) { constructor(props) {
@ -32,7 +35,9 @@ class StartOverPageComponent extends AuthComponent {
render() { render() {
return ( 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} <StartOverModal cleaned = {this.state.cleaned}
showCleanDialog = {this.state.showCleanDialog} showCleanDialog = {this.state.showCleanDialog}
allMonkeysAreDead = {this.state.allMonkeysAreDead} 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 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 you can go ahead and
</p> </p>
<p style={{margin: '20px'}}> <p style={{margin: '20px'}} className={'text-center'}>
<button className="btn btn-danger btn-lg center-block" <Button className="btn btn-danger btn-lg center-block"
onClick={() => { onClick={() => {
this.setState({showCleanDialog: true}); this.setState({showCleanDialog: true});
this.updateMonkeysRunning(); this.updateMonkeysRunning();
} }
}> }>
Reset the Environment Reset the Environment
</button> </Button>
</p> </p>
<div className="alert alert-info"> <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 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, 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. and see the results on the <Link to="/infection/map">Infection Map</Link> without deleting anything.
</div> </div>
{this.state.cleaned ? {this.state.cleaned ?
<div className="alert alert-success"> <div className="alert alert-success">
<i className="glyphicon glyphicon-ok-sign" style={{'marginRight': '5px'}}/> <FontAwesomeIcon icon={faCheck} style={{'marginRight': '5px'}}/>
Environment was reset successfully Environment was reset successfully
</div> </div>
: ''} : ''}

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
import React, {Component} from 'react'; import React, {Component} from 'react';
import * as PropTypes from 'prop-types'; 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 { export default class MonkeysStillAliveWarning extends Component {
render() { render() {
@ -9,7 +11,7 @@ export default class MonkeysStillAliveWarning extends Component {
'' ''
: :
(<p className="alert alert-warning"> (<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 Some monkeys are still running. To get the best report it's best to wait for all of them to finish
running. running.
</p>) </p>)

View File

@ -1,10 +1,12 @@
import React, {Component} from 'react'; import React, {Component} from 'react';
import {NavLink} from 'react-router-dom'; 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 { export default class MustRunMonkeyWarning extends Component {
render() { render() {
return <p className="alert alert-warning"> 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> <b>You have to <NavLink to="/run-monkey">run a monkey</NavLink> before generating a report!</b>
</p> </p>
} }

View File

@ -1,11 +1,14 @@
import React, {Component} from 'react'; import React, {Component} from 'react';
import {Button} from 'react-bootstrap'; import {Button} from 'react-bootstrap';
import * as PropTypes from 'prop-types'; 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 { export default class PrintReportButton extends Component {
render() { render() {
return <div className="text-center no-print"> 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> Report</Button>
</div> </div>
} }

View File

@ -1,17 +1,20 @@
import React, {Component, Fragment} from 'react'; import React, {Component, Fragment} from 'react';
import * as PropTypes from 'prop-types'; 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 { export default class SecurityIssuesGlance extends Component {
render() { render() {
return <Fragment> return <Fragment>
{ {
this.props.issuesFound ? this.props.issuesFound ?
(<p className="alert alert-danger"> (<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! Critical security issues were detected!
</p>) : </p>) :
(<p className="alert alert-success"> (<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. No critical security issues were detected.
</p>) </p>)
} }

View File

@ -38,7 +38,7 @@ class BreachedServersComponent extends React.Component {
<> <>
<p> <p>
The Monkey successfully breached <span 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> </p>
<div className="data-table-container"> <div className="data-table-container">
<ReactTable <ReactTable

View File

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

View File

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

View File

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

View File

@ -32,7 +32,7 @@ export default class EventsButton extends Component {
hideCallback={this.hide} hideCallback={this.hide}
exportFilename={this.props.exportFilename}/> exportFilename={this.props.exportFilename}/>
<div className="text-center" style={{'display': 'grid'}}> <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()} <FontAwesomeIcon icon={faList}/> Events {this.createEventsAmountBadge()}
</Button> </Button>
</div> </div>
@ -41,7 +41,7 @@ export default class EventsButton extends Component {
createEventsAmountBadge() { createEventsAmountBadge() {
const eventsAmountBadgeContent = this.props.event_count > 9 ? '9+' : this.props.event_count; 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> </h3>
<hr/> <hr/>
<p> <p>
There {Pluralize('is', this.props.event_count)} {<div There {Pluralize('is', this.props.event_count)} {
className={'label label-primary'}>{this.props.event_count}</div>} <div className={'badge badge-primary'}>{this.props.event_count}</div>
{Pluralize('event', this.props.event_count)} associated } {Pluralize('event', this.props.event_count)} associated with this finding. {
with this finding. <div className={'badge badge-primary'}>
{<div className={'label label-primary'}>
{this.props.latest_events.length + this.props.oldest_events.length} {this.props.latest_events.length + this.props.oldest_events.length}
</div>} {Pluralize('is', this.props.event_count)} displayed below. </div>
All events can be exported to json. } {Pluralize('is', this.props.event_count)} displayed below. All events can be exported using the Export button.
</p> </p>
{this.props.event_count > 5 ? this.renderButtons() : null} {this.props.event_count > 5 ? this.renderButtons() : null}
<EventsTimeline events={this.props.oldest_events}/> <EventsTimeline events={this.props.oldest_events}/>

View File

@ -5,7 +5,7 @@ import * as PropTypes from 'prop-types';
export default class EventsModalButtons extends Component { export default class EventsModalButtons extends Component {
render() { render() {
return <div className="text-center"> 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}> onClick={this.props.onClickClose}>
Close Close
</button> </button>

View File

@ -5,7 +5,7 @@ import * as PropTypes from 'prop-types';
import PillarLabel from './PillarLabel'; import PillarLabel from './PillarLabel';
import EventsButton from './EventsButton'; import EventsButton from './EventsButton';
const EVENTS_COLUMN_MAX_WIDTH = 160; const EVENTS_COLUMN_MAX_WIDTH = 170;
const PILLARS_COLUMN_MAX_WIDTH = 200; const PILLARS_COLUMN_MAX_WIDTH = 200;
const columns = [ const columns = [
{ {

View File

@ -23,7 +23,7 @@ const pillarToIcon = {
export default class PillarLabel extends Component { export default class PillarLabel extends Component {
render() { 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'}}> return <div className={className} style={{margin: '2px', display: 'inline-block'}}>
<FontAwesomeIcon icon={pillarToIcon[this.props.pillar]}/> {this.props.pillar}</div> <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 StatusLabel from './StatusLabel';
import {ZeroTrustStatuses} from './ZeroTrustPillars'; import {ZeroTrustStatuses} from './ZeroTrustPillars';
import {NavLink} from 'react-router-dom'; import {NavLink} from 'react-router-dom';
import {Panel} from 'react-bootstrap'; import {Card, Collapse} from 'react-bootstrap';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown'; import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown';
class ZeroTrustReportLegend extends Component { class ZeroTrustReportLegend extends Component {
constructor(props, context) {
super(props, context);
this.state = {
open: false,
};
}
render() { render() {
const legendContent = this.getLegendContent(); const legendContent = this.getLegendContent();
const {open} = this.state;
return ( return (
<Panel> <Card>
<Panel.Heading> <Card.Header onClick={() => this.setState({open: !open})}
<Panel.Title toggle> aria-controls='collapse-content'
aria-expanded={open}
className={'collapse-control'}>
<h3><FontAwesomeIcon icon={faChevronDown}/> Legend</h3> <h3><FontAwesomeIcon icon={faChevronDown}/> Legend</h3>
</Panel.Title> </Card.Header>
</Panel.Heading>
<Panel.Collapse> <Collapse in={this.state.open}>
<Panel.Body> <Card.Body>
<div id='collapse-content'>
{legendContent} {legendContent}
</Panel.Body> </div>
</Panel.Collapse> </Card.Body>
</Panel> </Collapse>
</Card>
); );
} }
@ -35,7 +49,8 @@ class ZeroTrustReportLegend extends Component {
<div style={{display: 'inline-block'}}> <div style={{display: 'inline-block'}}>
<StatusLabel showText={true} status={ZeroTrustStatuses.failed}/> <StatusLabel showText={true} status={ZeroTrustStatuses.failed}/>
</div> </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. unmet Zero Trust requirement.
</li> </li>
<li> <li>
@ -55,7 +70,7 @@ class ZeroTrustReportLegend extends Component {
<StatusLabel showText={true} status={ZeroTrustStatuses.unexecuted}/> <StatusLabel showText={true} status={ZeroTrustStatuses.unexecuted}/>
</div> </div>
{'\t'}This status means the test wasn't executed.To activate more tests, refer to the Monkey <NavLink {'\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> </li>
</ul> </ul>
</div>; </div>;

View File

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

View File

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

View File

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

View File

@ -12,21 +12,19 @@ class ArcNode extends React.Component {
return ( return (
<g transform={'rotate(180)'} id={data.node.pillar} key={prefix + 'arcGroup' + index}> <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} placement={data.popover}
overlay={<Popover id={prefix + 'ArcPopover' + index} style={{backgroundColor: data.hex}} overlay={<Popover id={prefix + 'ArcPopover' + index} style={{backgroundColor: data.hex}}>
title={data.node.pillar}>{data.tooltip}</Popover>} rootClose> <Popover.Title>{data.node.pillar}</Popover.Title>
<Popover.Content>{data.tooltip}</Popover.Content>
</Popover>} rootClose>
<path <path
id={prefix + 'Node_' + index} id={prefix + 'Node_' + index}
className={'arcNode'} className={'arcNode'}
data-tooltip={data.tooltip} data-tooltip={data.tooltip}
d={arc()} d={arc()}
fill={data.hex} fill={data.hex}
onClick={this.handleClick.bind(this)}
onMouseEnter={this.handleOver.bind(this)}
onMouseLeave={this.handleOut.bind(this)}
/> />
</OverlayTrigger> </OverlayTrigger>
<text x={0} dy={data.fontStyle.size * 1.2} fontSize={data.fontStyle.size} fill={'white'} textAnchor='middle' <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> </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 = { ArcNode.propTypes = {

View File

@ -10,10 +10,14 @@ class CircularNode extends React.Component {
let translate = 'translate(' + data.cx + ',' + data.cy + ')'; let translate = 'translate(' + data.cx + ',' + data.cy + ')';
return ( return (
<g transform={translate} id={data.node.pillar} key={prefix + 'circularGroup' + index}> <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} overlay={<Popover id={prefix + 'CircularClickPopover' + index}
style={{backgroundColor: data.hex}} style={{backgroundColor: data.hex}}>
title={data.node.pillar}>{data.tooltip}</Popover>} rootClose> <Popover.Title>{data.node.pillar}</Popover.Title>
<Popover.Content>{data.tooltip}</Popover.Content>
</Popover>} rootClose>
<circle <circle
id={prefix + 'Node_' + index} id={prefix + 'Node_' + index}
className={'circularNode'} className={'circularNode'}
@ -21,10 +25,6 @@ class CircularNode extends React.Component {
r={data.r} r={data.r}
opacity={0.8} opacity={0.8}
fill={data.hex} fill={data.hex}
onClick={this.handleClick.bind(this)}
onMouseEnter={this.handleOver.bind(this)}
onMouseLeave={this.handleOut.bind(this)}
/> />
</OverlayTrigger> </OverlayTrigger>
<foreignObject style={{fontSize: data.fontStyle.size, pointerEvents: 'none'}} <foreignObject style={{fontSize: data.fontStyle.size, pointerEvents: 'none'}}
@ -36,24 +36,6 @@ class CircularNode extends React.Component {
</g> </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 = { CircularNode.propTypes = {

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