forked from p15670423/monkey
Merge pull request #688 from guardicore/password_setup
Password setup and bootstrap v4 migration
This commit is contained in:
commit
6cc4c85132
|
@ -90,3 +90,6 @@ profiler_logs/
|
|||
|
||||
# vim swap files
|
||||
*.swp
|
||||
|
||||
# Server config might contain credentials. Don't commit by default.
|
||||
/monkey/monkey_island/cc/server_config.json
|
||||
|
|
|
@ -65,7 +65,7 @@ script:
|
|||
- cd monkey_island/cc/ui
|
||||
- npm ci # See https://docs.npmjs.com/cli/ci.html
|
||||
- eslint ./src --quiet # Test for errors
|
||||
- JS_WARNINGS_AMOUNT_UPPER_LIMIT=90
|
||||
- JS_WARNINGS_AMOUNT_UPPER_LIMIT=70
|
||||
- eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT # Test for max warnings
|
||||
|
||||
after_success:
|
||||
|
|
|
@ -29,6 +29,7 @@ Configure a PyTest configuration with the additional arguments `-s --island=35.2
|
|||
**Before running performance test make sure browser is not sending requests to island!**
|
||||
|
||||
To run telemetry performance test follow these steps:
|
||||
0. Set `server_config.json` to "standard" (no password protection) setting.
|
||||
1. Gather monkey telemetries.
|
||||
1. Enable "Export monkey telemetries" in Configuration -> Internal -> Tests if you don't have
|
||||
exported telemetries already.
|
||||
|
|
|
@ -132,6 +132,7 @@ class TestMonkeyBlackbox(object):
|
|||
def test_wmi_pth(self, island_client):
|
||||
TestMonkeyBlackbox.run_exploitation_test(island_client, "WMI_PTH.conf", "WMI_PTH")
|
||||
|
||||
@pytest.mark.skip(reason="Perfomance test that creates env from fake telemetries is faster, use that instead.")
|
||||
def test_report_generation_performance(self, island_client, quick_performance_tests):
|
||||
"""
|
||||
This test includes the SSH + Elastic + Hadoop + MSSQL machines all in one test
|
||||
|
@ -149,6 +150,7 @@ class TestMonkeyBlackbox(object):
|
|||
LOGGER.error("This test doesn't support 'quick_performance_tests' option.")
|
||||
assert False
|
||||
|
||||
@pytest.mark.skip(reason="Perfomance test that creates env from fake telemetries is faster, use that instead.")
|
||||
def test_map_generation_performance(self, island_client, quick_performance_tests):
|
||||
if not quick_performance_tests:
|
||||
TestMonkeyBlackbox.run_performance_test(MapGenerationTest,
|
||||
|
|
|
@ -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 """
|
|
@ -11,7 +11,7 @@ from infection_monkey.exploit.tools.http_tools import MonkeyHTTPServer
|
|||
from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, build_monkey_commandline, get_monkey_depth
|
||||
from infection_monkey.model import DROPPER_ARG
|
||||
from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload
|
||||
from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError, FailedExploitationError
|
||||
from common.utils.exceptions import ExploitingVulnerableMachineError, FailedExploitationError
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ from infection_monkey.exploit.HostExploiter import HostExploiter
|
|||
from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
|
||||
from infection_monkey.model import MONKEY_ARG
|
||||
from infection_monkey.network.tools import check_tcp_port, get_interface_to_target
|
||||
from infection_monkey.exploit.tools.exceptions import FailedExploitationError
|
||||
from common.utils.exceptions import FailedExploitationError
|
||||
from common.utils.exploit_enum import ExploitType
|
||||
from common.utils.attack_utils import ScanStatus
|
||||
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
|
||||
|
|
|
@ -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"""
|
|
@ -28,7 +28,7 @@ from infection_monkey.telemetry.tunnel_telem import TunnelTelem
|
|||
from infection_monkey.windows_upgrader import WindowsUpgrader
|
||||
from infection_monkey.post_breach.post_breach_handler import PostBreach
|
||||
from infection_monkey.network.tools import get_interface_to_target, is_running_on_server
|
||||
from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError, FailedExploitationError
|
||||
from common.utils.exceptions import ExploitingVulnerableMachineError, FailedExploitationError
|
||||
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
|
||||
from common.utils.attack_utils import ScanStatus, UsageEnum
|
||||
from common.version import get_version
|
||||
|
|
|
@ -9,6 +9,7 @@ CREDENTIAL_TYPES = ['msv_creds', 'wdigest_creds', 'ssp_creds', 'livessp_creds',
|
|||
'kerberos_creds', 'credman_creds', 'tspkg_creds']
|
||||
PypykatzCredential = NewType('PypykatzCredential', Dict)
|
||||
|
||||
|
||||
def get_windows_creds() -> List[WindowsCredentials]:
|
||||
pypy_handle = pypykatz.go_live()
|
||||
logon_data = pypy_handle.to_dict()
|
||||
|
|
|
@ -23,16 +23,20 @@ class TestPypykatzHandler(TestCase):
|
|||
'password': 'canyoufindm3', 'luid': 123086}],
|
||||
'dpapi_creds': [
|
||||
{'credtype': 'dpapi', 'key_guid': '9123-123ae123de4-121239-3123-421f',
|
||||
'masterkey': '6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975ef051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9',
|
||||
'masterkey': '6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975e'
|
||||
'f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9',
|
||||
'sha1_masterkey': 'bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da', 'luid': 123086},
|
||||
{'credtype': 'dpapi', 'key_guid': '9123-123ae123de4-121239-3123-421f',
|
||||
'masterkey': '6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975ef051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9',
|
||||
'masterkey': '6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975e'
|
||||
'f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9',
|
||||
'sha1_masterkey': 'bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da', 'luid': 123086},
|
||||
{'credtype': 'dpapi', 'key_guid': '9123-123ae123de4-121239-3123-421f',
|
||||
'masterkey': '6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975ef051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9',
|
||||
'masterkey': '6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975e'
|
||||
'f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9',
|
||||
'sha1_masterkey': 'bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da', 'luid': 123086},
|
||||
{'credtype': 'dpapi', 'key_guid': '9123-123ae123de4-121239-3123-421f',
|
||||
'masterkey': '6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975ef051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9',
|
||||
'masterkey': '6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975e'
|
||||
'f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9',
|
||||
'sha1_masterkey': 'bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da', 'luid': 123086},
|
||||
{'credtype': 'dpapi', 'key_guid': '9123-123ae123de4-121239-3123-421f'}],
|
||||
'kerberos_creds': [
|
||||
|
|
|
@ -5,11 +5,12 @@ import flask_restful
|
|||
from flask import Flask, send_from_directory, Response
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
from monkey_island.cc.auth import init_jwt
|
||||
from monkey_island.cc.resources.auth.auth import init_jwt
|
||||
from monkey_island.cc.database import mongo, database
|
||||
from monkey_island.cc.environment.environment import env
|
||||
import monkey_island.cc.environment.environment_singleton as env_singleton
|
||||
from monkey_island.cc.resources.client_run import ClientRun
|
||||
from monkey_island.cc.resources.edge import Edge
|
||||
from monkey_island.cc.resources.environment import Environment
|
||||
from monkey_island.cc.resources.local_run import LocalRun
|
||||
from monkey_island.cc.resources.log import Log
|
||||
from monkey_island.cc.resources.island_logs import IslandLog
|
||||
|
@ -22,6 +23,7 @@ from monkey_island.cc.resources.netmap import NetMap
|
|||
from monkey_island.cc.resources.node import Node
|
||||
from monkey_island.cc.resources.node_states import NodeStates
|
||||
from monkey_island.cc.resources.monkey_control.remote_port_check import RemotePortCheck
|
||||
from monkey_island.cc.resources.registration import Registration
|
||||
from monkey_island.cc.resources.remote_run import RemoteRun
|
||||
from monkey_island.cc.resources.reporting.report import Report
|
||||
from monkey_island.cc.resources.root import Root
|
||||
|
@ -69,7 +71,7 @@ def init_app_config(app, mongo_url):
|
|||
app.config['MONGO_URI'] = mongo_url
|
||||
app.config['SECRET_KEY'] = str(uuid.getnode())
|
||||
app.config['JWT_AUTH_URL_RULE'] = '/api/auth'
|
||||
app.config['JWT_EXPIRATION_DELTA'] = env.get_auth_expiration_time()
|
||||
app.config['JWT_EXPIRATION_DELTA'] = env_singleton.env.get_auth_expiration_time()
|
||||
|
||||
|
||||
def init_app_services(app):
|
||||
|
@ -91,6 +93,8 @@ def init_app_url_rules(app):
|
|||
|
||||
def init_api_resources(api):
|
||||
api.add_resource(Root, '/api')
|
||||
api.add_resource(Registration, '/api/registration')
|
||||
api.add_resource(Environment, '/api/environment')
|
||||
api.add_resource(Monkey, '/api/monkey', '/api/monkey/', '/api/monkey/<string:guid>')
|
||||
api.add_resource(Bootloader, '/api/bootloader/<string:os>')
|
||||
api.add_resource(LocalRun, '/api/local-monkey', '/api/local-monkey/')
|
||||
|
|
|
@ -1,10 +1,18 @@
|
|||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from datetime import timedelta
|
||||
import os
|
||||
import hashlib
|
||||
|
||||
__author__ = 'itay.mizeretz'
|
||||
|
||||
from common.utils.exceptions import InvalidRegistrationCredentialsError, \
|
||||
CredentialsNotRequiredError, AlreadyRegisteredError
|
||||
from monkey_island.cc.environment.environment_config import EnvironmentConfig
|
||||
from monkey_island.cc.environment.user_creds import UserCreds
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Environment(object, metaclass=ABCMeta):
|
||||
_ISLAND_PORT = 5000
|
||||
|
@ -18,6 +26,53 @@ class Environment(object, metaclass=ABCMeta):
|
|||
|
||||
_testing = False
|
||||
|
||||
def __init__(self, config: EnvironmentConfig):
|
||||
self._config = config
|
||||
self._testing = False # Assume env is not for unit testing.
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def _credentials_required(self) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_auth_users(self):
|
||||
pass
|
||||
|
||||
def needs_registration(self) -> bool:
|
||||
try:
|
||||
needs_registration = self._try_needs_registration()
|
||||
return needs_registration
|
||||
except (CredentialsNotRequiredError, AlreadyRegisteredError) as e:
|
||||
logger.info(e)
|
||||
return False
|
||||
|
||||
def try_add_user(self, credentials: UserCreds):
|
||||
if not credentials:
|
||||
raise InvalidRegistrationCredentialsError("Missing part of credentials.")
|
||||
if self._try_needs_registration():
|
||||
self._config.add_user(credentials)
|
||||
logger.info(f"New user {credentials.username} registered!")
|
||||
|
||||
def _try_needs_registration(self) -> bool:
|
||||
if not self._credentials_required:
|
||||
raise CredentialsNotRequiredError("Credentials are not required "
|
||||
"for current environment.")
|
||||
else:
|
||||
if self._is_registered():
|
||||
raise AlreadyRegisteredError("User has already been registered. "
|
||||
"Reset credentials or login.")
|
||||
return True
|
||||
|
||||
def _is_registered(self) -> bool:
|
||||
return self._credentials_required and self._is_credentials_set_up()
|
||||
|
||||
def _is_credentials_set_up(self) -> bool:
|
||||
if self._config and self._config.user_creds:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def testing(self):
|
||||
return self._testing
|
||||
|
@ -26,12 +81,11 @@ class Environment(object, metaclass=ABCMeta):
|
|||
def testing(self, value):
|
||||
self._testing = value
|
||||
|
||||
def __init__(self):
|
||||
self.config = None
|
||||
self._testing = False # Assume env is not for unit testing.
|
||||
def save_config(self):
|
||||
self._config.save_to_file()
|
||||
|
||||
def set_config(self, config):
|
||||
self.config = config
|
||||
def get_config(self) -> EnvironmentConfig:
|
||||
return self._config
|
||||
|
||||
def get_island_port(self):
|
||||
return self._ISLAND_PORT
|
||||
|
@ -51,21 +105,14 @@ class Environment(object, metaclass=ABCMeta):
|
|||
hash_obj.update(secret.encode('utf-8'))
|
||||
return hash_obj.hexdigest()
|
||||
|
||||
def get_deployment(self):
|
||||
return self._get_from_config('deployment', 'unknown')
|
||||
def get_deployment(self) -> str:
|
||||
deployment = 'unknown'
|
||||
if self._config and self._config.deployment:
|
||||
deployment = self._config.deployment
|
||||
return deployment
|
||||
|
||||
def is_develop(self):
|
||||
return self.get_deployment() == 'develop'
|
||||
|
||||
def _get_from_config(self, key, default_value=None):
|
||||
val = default_value
|
||||
if self.config is not None:
|
||||
val = self.config.get(key, val)
|
||||
return val
|
||||
|
||||
@abstractmethod
|
||||
def get_auth_users(self):
|
||||
return
|
||||
def set_deployment(self, deployment: str):
|
||||
self._config.deployment = deployment
|
||||
|
||||
@property
|
||||
def mongo_db_name(self):
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import monkey_island.cc.auth
|
||||
from monkey_island.cc.resources.auth.auth_user import User
|
||||
from monkey_island.cc.environment import Environment
|
||||
from common.cloud.aws.aws_instance import AwsInstance
|
||||
|
||||
|
@ -6,8 +6,11 @@ __author__ = 'itay.mizeretz'
|
|||
|
||||
|
||||
class AwsEnvironment(Environment):
|
||||
def __init__(self):
|
||||
super(AwsEnvironment, self).__init__()
|
||||
|
||||
_credentials_required = True
|
||||
|
||||
def __init__(self, config):
|
||||
super(AwsEnvironment, self).__init__(config)
|
||||
# Not suppressing error here on purpose. This is critical if we're on AWS env.
|
||||
self.aws_info = AwsInstance()
|
||||
self._instance_id = self._get_instance_id()
|
||||
|
@ -20,6 +23,7 @@ class AwsEnvironment(Environment):
|
|||
return self.aws_info.get_region()
|
||||
|
||||
def get_auth_users(self):
|
||||
return [
|
||||
monkey_island.cc.auth.User(1, 'monkey', self.hash_secret(self._instance_id))
|
||||
]
|
||||
if self._is_registered():
|
||||
return self._config.get_users()
|
||||
else:
|
||||
return []
|
||||
|
|
|
@ -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
|
|
@ -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 []
|
|
@ -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
|
|
@ -1,12 +1,14 @@
|
|||
from monkey_island.cc.environment import Environment
|
||||
import monkey_island.cc.auth
|
||||
|
||||
__author__ = 'itay.mizeretz'
|
||||
|
||||
|
||||
class PasswordEnvironment(Environment):
|
||||
|
||||
_credentials_required = True
|
||||
|
||||
def get_auth_users(self):
|
||||
return [
|
||||
monkey_island.cc.auth.User(1, self.config['user'], self.config['hash'])
|
||||
]
|
||||
if self._is_registered():
|
||||
return self._config.get_users()
|
||||
else:
|
||||
return []
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import monkey_island.cc.auth
|
||||
from monkey_island.cc.resources.auth.auth_user import User
|
||||
from monkey_island.cc.environment import Environment
|
||||
|
||||
__author__ = 'itay.mizeretz'
|
||||
|
||||
|
||||
class StandardEnvironment(Environment):
|
||||
|
||||
_credentials_required = False
|
||||
|
||||
# SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()'
|
||||
NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \
|
||||
'8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557'
|
||||
|
||||
def get_auth_users(self):
|
||||
return [
|
||||
monkey_island.cc.auth.User(1, StandardEnvironment.NO_AUTH_CREDS, StandardEnvironment.NO_AUTH_CREDS)
|
||||
]
|
||||
return [User(1, StandardEnvironment.NO_AUTH_CREDS, StandardEnvironment.NO_AUTH_CREDS)]
|
||||
|
|
|
@ -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())
|
||||
|
|
@ -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()
|
|
@ -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())
|
|
@ -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")
|
|
@ -1,4 +1,4 @@
|
|||
from monkey_island.cc.environment import Environment
|
||||
from monkey_island.cc.environment import Environment, EnvironmentConfig
|
||||
|
||||
|
||||
class TestingEnvironment(Environment):
|
||||
|
@ -7,8 +7,10 @@ class TestingEnvironment(Environment):
|
|||
This will cause all mongo connections to happen via `mongomock` instead of using an actual mongodb instance.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(TestingEnvironment, self).__init__()
|
||||
_credentials_required = True
|
||||
|
||||
def __init__(self, config: EnvironmentConfig):
|
||||
super(TestingEnvironment, self).__init__(config)
|
||||
self.testing = True
|
||||
|
||||
def get_auth_users(self):
|
||||
|
|
|
@ -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)
|
|
@ -23,7 +23,7 @@ logger = logging.getLogger(__name__)
|
|||
from monkey_island.cc.app import init_app
|
||||
from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list
|
||||
from monkey_island.cc.network_utils import local_ip_addresses
|
||||
from monkey_island.cc.environment.environment import env
|
||||
import monkey_island.cc.environment.environment_singleton as env_singleton
|
||||
from monkey_island.cc.database import is_db_server_up, get_db_version
|
||||
from monkey_island.cc.resources.monkey_download import MonkeyDownload
|
||||
from common.version import get_version
|
||||
|
@ -33,7 +33,7 @@ from monkey_island.cc.setup import setup
|
|||
|
||||
def main(should_setup_only=False):
|
||||
logger.info("Starting bootloader server")
|
||||
mongo_url = os.environ.get('MONGO_URL', env.get_mongo_url())
|
||||
mongo_url = os.environ.get('MONGO_URL', env_singleton.env.get_mongo_url())
|
||||
bootloader_server_thread = Thread(target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True)
|
||||
|
||||
bootloader_server_thread.start()
|
||||
|
@ -46,7 +46,7 @@ def start_island_server(should_setup_only):
|
|||
from tornado.httpserver import HTTPServer
|
||||
from tornado.ioloop import IOLoop
|
||||
|
||||
mongo_url = os.environ.get('MONGO_URL', env.get_mongo_url())
|
||||
mongo_url = os.environ.get('MONGO_URL', env_singleton.env.get_mongo_url())
|
||||
wait_for_mongo_db_server(mongo_url)
|
||||
assert_mongo_db_version(mongo_url)
|
||||
|
||||
|
@ -62,13 +62,13 @@ def start_island_server(should_setup_only):
|
|||
logger.warning("Setup only flag passed. Exiting.")
|
||||
return
|
||||
|
||||
if env.is_debug():
|
||||
if env_singleton.env.is_debug():
|
||||
app.run(host='0.0.0.0', debug=True, ssl_context=(crt_path, key_path))
|
||||
else:
|
||||
http_server = HTTPServer(WSGIContainer(app),
|
||||
ssl_options={'certfile': os.environ.get('SERVER_CRT', crt_path),
|
||||
'keyfile': os.environ.get('SERVER_KEY', key_path)})
|
||||
http_server.listen(env.get_island_port())
|
||||
http_server.listen(env_singleton.env.get_island_port())
|
||||
log_init_info()
|
||||
IOLoop.instance().start()
|
||||
|
||||
|
@ -77,7 +77,7 @@ def log_init_info():
|
|||
logger.info('Monkey Island Server is running!')
|
||||
logger.info(f"version: {get_version()}")
|
||||
logger.info('Listening on the following URLs: {}'.format(
|
||||
", ".join(["https://{}:{}".format(x, env.get_island_port()) for x in local_ip_addresses()])
|
||||
", ".join(["https://{}:{}".format(x, env_singleton.env.get_island_port()) for x in local_ip_addresses()])
|
||||
)
|
||||
)
|
||||
MonkeyDownload.log_executable_hashes()
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
from mongoengine import connect
|
||||
|
||||
from monkey_island.cc.environment.environment import env
|
||||
import monkey_island.cc.environment.environment_singleton as env_singleton
|
||||
|
||||
# This section sets up the DB connection according to the environment.
|
||||
# If testing, use mongomock which only emulates mongo. for more information, see
|
||||
# http://docs.mongoengine.org/guide/mongomock.html .
|
||||
# Otherwise, use an actual mongod instance with connection parameters supplied by env.
|
||||
if env.testing: # See monkey_island.cc.environment.testing
|
||||
if env_singleton.env.testing: # See monkey_island.cc.environment.testing
|
||||
connect('mongoenginetest', host='mongomock://localhost')
|
||||
else:
|
||||
connect(db=env.mongo_db_name, host=env.mongo_db_host, port=env.mongo_db_port)
|
||||
connect(db=env_singleton.env.mongo_db_name, host=env_singleton.env.mongo_db_host, port=env_singleton.env.mongo_db_port)
|
||||
|
||||
# Order of importing matters here, for registering the embedded and referenced documents before using them.
|
||||
from .config import Config # noqa: F401
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import flask_restful
|
||||
from flask import jsonify, request, json, current_app
|
||||
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||
from monkey_island.cc.services.attack.attack_config import AttackConfig
|
||||
|
||||
__author__ = "VakarisZ"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import flask_restful
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||
from monkey_island.cc.services.attack.attack_report import AttackReportService
|
||||
from monkey_island.cc.services.attack.attack_schema import SCHEMA
|
||||
from flask import json, current_app
|
||||
|
|
|
@ -4,34 +4,23 @@ from flask import current_app, abort
|
|||
from flask_jwt import JWT, _jwt_required, JWTError
|
||||
from werkzeug.security import safe_str_cmp
|
||||
|
||||
from monkey_island.cc.environment.environment import env
|
||||
import monkey_island.cc.environment.environment_singleton as env_singleton
|
||||
import monkey_island.cc.resources.auth.user_store as user_store
|
||||
|
||||
__author__ = 'itay.mizeretz'
|
||||
|
||||
|
||||
class User(object):
|
||||
def __init__(self, user_id, username, secret):
|
||||
self.id = user_id
|
||||
self.username = username
|
||||
self.secret = secret
|
||||
|
||||
def __str__(self):
|
||||
return "User(id='%s')" % self.id
|
||||
|
||||
|
||||
def init_jwt(app):
|
||||
users = env.get_auth_users()
|
||||
username_table = {u.username: u for u in users}
|
||||
userid_table = {u.id: u for u in users}
|
||||
user_store.UserStore.set_users(env_singleton.env.get_auth_users())
|
||||
|
||||
def authenticate(username, secret):
|
||||
user = username_table.get(username, None)
|
||||
user = user_store.UserStore.username_table.get(username, None)
|
||||
if user and safe_str_cmp(user.secret.encode('utf-8'), secret.encode('utf-8')):
|
||||
return user
|
||||
|
||||
def identity(payload):
|
||||
user_id = payload['identity']
|
||||
return userid_table.get(user_id, None)
|
||||
return user_store.UserStore.user_id_table.get(user_id, None)
|
||||
|
||||
JWT(app, authenticate, identity)
|
||||
|
|
@ -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
|
|
@ -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}
|
|
@ -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 {}
|
|
@ -3,7 +3,7 @@ import json
|
|||
import flask_restful
|
||||
from flask import request, jsonify, abort
|
||||
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||
from monkey_island.cc.services.config import ConfigService
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
|||
|
||||
import flask_restful
|
||||
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||
from monkey_island.cc.services.island_logs import IslandLogService
|
||||
|
||||
__author__ = "Maor.Rayzin"
|
||||
|
|
|
@ -6,7 +6,7 @@ import sys
|
|||
from flask import request, jsonify, make_response
|
||||
import flask_restful
|
||||
|
||||
from monkey_island.cc.environment.environment import env
|
||||
import monkey_island.cc.environment.environment_singleton as env_singleton
|
||||
from monkey_island.cc.models import Monkey
|
||||
from monkey_island.cc.resources.monkey_download import get_monkey_executable
|
||||
from monkey_island.cc.services.node import NodeService
|
||||
|
@ -43,7 +43,7 @@ def run_local_monkey():
|
|||
|
||||
# run the monkey
|
||||
try:
|
||||
args = ['"%s" m0nk3y -s %s:%s' % (target_path, local_ip_addresses()[0], env.get_island_port())]
|
||||
args = ['"%s" m0nk3y -s %s:%s' % (target_path, local_ip_addresses()[0], env_singleton.env.get_island_port())]
|
||||
if sys.platform == "win32":
|
||||
args = "".join(args)
|
||||
pid = subprocess.Popen(args, shell=True).pid
|
||||
|
|
|
@ -4,7 +4,7 @@ import flask_restful
|
|||
from bson import ObjectId
|
||||
from flask import request
|
||||
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||
from monkey_island.cc.database import mongo
|
||||
from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore
|
||||
from monkey_island.cc.services.log import LogService
|
||||
|
|
|
@ -3,7 +3,7 @@ import json
|
|||
import flask_restful
|
||||
from flask import request, jsonify, abort
|
||||
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||
from monkey_island.cc.services.config import ConfigService
|
||||
|
||||
__author__ = 'Barak'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import flask_restful
|
||||
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||
from monkey_island.cc.services.netmap.net_edge import NetEdgeService
|
||||
from monkey_island.cc.services.netmap.net_node import NetNodeService
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from flask import request
|
||||
import flask_restful
|
||||
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||
from monkey_island.cc.services.node import NodeService
|
||||
|
||||
__author__ = 'Barak'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import flask_restful
|
||||
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||
from monkey_island.cc.services.utils.node_states import NodeStates as NodeStateList
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import flask_restful
|
|||
from flask import request, send_from_directory, Response
|
||||
from monkey_island.cc.services.config import ConfigService
|
||||
from monkey_island.cc.services.post_breach_files import PBA_WINDOWS_FILENAME_PATH, PBA_LINUX_FILENAME_PATH, UPLOADS_DIR
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||
import os
|
||||
from werkzeug.utils import secure_filename
|
||||
import logging
|
||||
|
|
|
@ -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)
|
|
@ -4,7 +4,7 @@ from botocore.exceptions import NoCredentialsError, ClientError
|
|||
from flask import request, jsonify, make_response
|
||||
import flask_restful
|
||||
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
|
||||
from common.cloud.aws.aws_service import AwsService
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import http.client
|
|||
import flask_restful
|
||||
from flask import jsonify
|
||||
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||
from monkey_island.cc.services.reporting.report import ReportService
|
||||
from monkey_island.cc.services.reporting.zero_trust_service import ZeroTrustService
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import threading
|
|||
import flask_restful
|
||||
from flask import request, make_response, jsonify
|
||||
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||
from monkey_island.cc.database import mongo
|
||||
from monkey_island.cc.services.database import Database
|
||||
from monkey_island.cc.services.infection_lifecycle import InfectionLifecycle
|
||||
|
@ -40,5 +40,3 @@ class Root(flask_restful.Resource):
|
|||
ip_addresses=local_ip_addresses(),
|
||||
mongo=str(mongo.db),
|
||||
completed_steps=InfectionLifecycle.get_completed_steps())
|
||||
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import dateutil
|
|||
import flask_restful
|
||||
from flask import request
|
||||
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||
from monkey_island.cc.database import mongo
|
||||
from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore
|
||||
from monkey_island.cc.services.node import NodeService
|
||||
|
|
|
@ -6,7 +6,7 @@ import flask_restful
|
|||
from flask import request
|
||||
import flask_pymongo
|
||||
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||
from monkey_island.cc.database import mongo
|
||||
from monkey_island.cc.services.node import NodeService
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
|||
|
||||
import flask_restful
|
||||
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||
from monkey_island.cc.services.attack.attack_report import AttackReportService
|
||||
from monkey_island.cc.services.reporting.report import ReportService
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ from bson import json_util
|
|||
import flask_restful
|
||||
from flask import request
|
||||
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||
from monkey_island.cc.database import mongo, database
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ from bson import json_util
|
|||
import flask_restful
|
||||
from flask import request
|
||||
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||
from monkey_island.cc.database import mongo
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import flask_restful
|
||||
import json
|
||||
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||
from monkey_island.cc.services.reporting.zero_trust_service import ZeroTrustService
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"server_config": "standard",
|
||||
"server_config": "password",
|
||||
"deployment": "develop"
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ from jsonschema import Draft4Validator, validators
|
|||
import monkey_island.cc.services.post_breach_files
|
||||
|
||||
from monkey_island.cc.database import mongo
|
||||
from monkey_island.cc.environment.environment import env
|
||||
import monkey_island.cc.environment.environment_singleton as env_singleton
|
||||
from monkey_island.cc.network_utils import local_ip_addresses
|
||||
from .config_schema import SCHEMA
|
||||
from monkey_island.cc.encryptor import encryptor
|
||||
|
@ -216,8 +216,8 @@ class ConfigService:
|
|||
@staticmethod
|
||||
def set_server_ips_in_config(config):
|
||||
ips = local_ip_addresses()
|
||||
config["cnc"]["servers"]["command_servers"] = ["%s:%d" % (ip, env.get_island_port()) for ip in ips]
|
||||
config["cnc"]["servers"]["current_server"] = "%s:%d" % (ips[0], env.get_island_port())
|
||||
config["cnc"]["servers"]["command_servers"] = ["%s:%d" % (ip, env_singleton.env.get_island_port()) for ip in ips]
|
||||
config["cnc"]["servers"]["current_server"] = "%s:%d" % (ips[0], env_singleton.env.get_island_port())
|
||||
|
||||
@staticmethod
|
||||
def save_initial_config_if_needed():
|
||||
|
|
|
@ -6,7 +6,7 @@ import boto3
|
|||
from botocore.exceptions import UnknownServiceError
|
||||
|
||||
from common.cloud.aws.aws_instance import AwsInstance
|
||||
from monkey_island.cc.environment.environment import load_server_configuration_from_file
|
||||
from monkey_island.cc.environment import EnvironmentConfig
|
||||
from monkey_island.cc.services.reporting.exporter import Exporter
|
||||
|
||||
__authors__ = ['maor.rayzin', 'shay.nehmad']
|
||||
|
@ -68,7 +68,7 @@ class AWSExporter(Exporter):
|
|||
# azure and conficker are not relevant issues for an AWS env
|
||||
}
|
||||
|
||||
configured_product_arn = load_server_configuration_from_file()['aws'].get('sec_hub_product_arn', '')
|
||||
configured_product_arn = EnvironmentConfig.get_from_file().aws.get('sec_hub_product_arn', '')
|
||||
product_arn = 'arn:aws:securityhub:{region}:{arn}'.format(region=region, arn=configured_product_arn)
|
||||
instance_arn = 'arn:aws:ec2:' + str(region) + ':instance:{instance_id}'
|
||||
# Not suppressing error here on purpose.
|
||||
|
|
|
@ -3,7 +3,7 @@ import logging
|
|||
from monkey_island.cc.services.reporting.report_exporter_manager import ReportExporterManager
|
||||
from monkey_island.cc.services.reporting.aws_exporter import AWSExporter
|
||||
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
|
||||
from monkey_island.cc.environment.environment import env
|
||||
import monkey_island.cc.environment.environment_singleton as env_singleton
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -21,7 +21,7 @@ def try_add_aws_exporter_to_manager(manager):
|
|||
# noinspection PyBroadException
|
||||
try:
|
||||
RemoteRunAwsService.init()
|
||||
if RemoteRunAwsService.is_running_on_aws() and ('aws' == env.get_deployment()):
|
||||
if RemoteRunAwsService.is_running_on_aws() and ('aws' == env_singleton.env.get_deployment()):
|
||||
manager.add_exporter_to_list(AWSExporter)
|
||||
except Exception:
|
||||
logger.error("Failed adding aws exporter to manager. Exception info:", exc_info=True)
|
||||
|
|
|
@ -3,7 +3,7 @@ import logging
|
|||
import requests
|
||||
|
||||
from common.version import get_version
|
||||
from monkey_island.cc.environment.environment import env
|
||||
import monkey_island.cc.environment.environment_singleton as env_singleton
|
||||
|
||||
__author__ = "itay.mizeretz"
|
||||
|
||||
|
@ -40,7 +40,7 @@ class VersionUpdateService:
|
|||
Checks if newer monkey version is available
|
||||
:return: False if not, version in string format ('1.6.2') otherwise
|
||||
"""
|
||||
url = VersionUpdateService.VERSION_SERVER_CHECK_NEW_URL % (env.get_deployment(), get_version())
|
||||
url = VersionUpdateService.VERSION_SERVER_CHECK_NEW_URL % (env_singleton.env.get_deployment(), get_version())
|
||||
|
||||
reply = requests.get(url, timeout=15)
|
||||
|
||||
|
@ -54,4 +54,4 @@ class VersionUpdateService:
|
|||
|
||||
@staticmethod
|
||||
def get_download_link():
|
||||
return VersionUpdateService.VERSION_SERVER_DOWNLOAD_URL % (env.get_deployment(), get_version())
|
||||
return VersionUpdateService.VERSION_SERVER_DOWNLOAD_URL % (env_singleton.env.get_deployment(), get_version())
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import unittest
|
||||
from monkey_island.cc.environment.environment import env
|
||||
import monkey_island.cc.environment.environment_singleton as env_singleton
|
||||
from monkey_island.cc.models import Monkey
|
||||
from monkey_island.cc.models.edge import Edge
|
||||
from monkey_island.cc.models.zero_trust.finding import Finding
|
||||
|
@ -7,7 +7,7 @@ from monkey_island.cc.models.zero_trust.finding import Finding
|
|||
|
||||
class IslandTestCase(unittest.TestCase):
|
||||
def fail_if_not_testing_env(self):
|
||||
self.assertFalse(not env.testing, "Change server_config.json to testing environment.")
|
||||
self.assertFalse(not env_singleton.env.testing, "Change server_config.json to testing environment.")
|
||||
|
||||
@staticmethod
|
||||
def clean_monkey_db():
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -54,7 +54,7 @@
|
|||
"react/jsx-uses-vars": 1,
|
||||
"react/jsx-key": 1,
|
||||
"react/prop-types": 0,
|
||||
"react/no-unescaped-entities": 1,
|
||||
"react/no-unescaped-entities": 0,
|
||||
"react/no-unknown-property": [1, { "ignore": ["class"] }],
|
||||
"react/no-string-refs": 1,
|
||||
"react/display-name": 1,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -63,7 +63,7 @@
|
|||
"@fortawesome/free-solid-svg-icons": "^5.11.2",
|
||||
"@fortawesome/react-fontawesome": "^0.1.7",
|
||||
"@kunukn/react-collapse": "^1.2.7",
|
||||
"bootstrap": "^3.4.1",
|
||||
"bootstrap": "^4.5.0",
|
||||
"classnames": "^2.2.6",
|
||||
"core-js": "^2.6.10",
|
||||
"d3": "^5.14.1",
|
||||
|
@ -80,19 +80,21 @@
|
|||
"rainge": "^1.0.1",
|
||||
"rc-progress": "^2.5.2",
|
||||
"react": "^16.12.0",
|
||||
"react-bootstrap": "^0.32.4",
|
||||
"react-bootstrap": "^1.0.1",
|
||||
"react-copy-to-clipboard": "^5.0.2",
|
||||
"react-data-components": "^1.2.0",
|
||||
"react-desktop-notification": "^1.0.9",
|
||||
"react-dimensions": "^1.3.0",
|
||||
"react-event-timeline": "^1.6.3",
|
||||
"react-hot-loader": "^4.12.20",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-event-timeline": "^1.6.3",
|
||||
"react-fa": "^5.0.0",
|
||||
"react-filepond": "^7.0.1",
|
||||
"react-graph-vis": "^1.0.5",
|
||||
"react-hot-loader": "^4.12.20",
|
||||
"react-json-tree": "^0.11.2",
|
||||
"react-jsonschema-form": "^1.8.0",
|
||||
"react-jsonschema-form-bs4": "^1.7.1",
|
||||
"react-particles-js": "^3.2.1",
|
||||
"react-redux": "^5.1.2",
|
||||
"react-router-dom": "^4.3.1",
|
||||
"react-spinners": "^0.5.13",
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import React from 'react';
|
||||
import {BrowserRouter as Router, NavLink, Redirect, Route, Switch} from 'react-router-dom';
|
||||
import {Col, Grid, Row} from 'react-bootstrap';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faCheck } from '@fortawesome/free-solid-svg-icons/faCheck'
|
||||
import { faUndo } from '@fortawesome/free-solid-svg-icons/faUndo'
|
||||
import {BrowserRouter as Router, Redirect, Route, Switch} from 'react-router-dom';
|
||||
import {Container} from 'react-bootstrap';
|
||||
|
||||
import RunServerPage from 'components/pages/RunServerPage';
|
||||
import ConfigurePage from 'components/pages/ConfigurePage';
|
||||
|
@ -15,6 +12,7 @@ import ReportPage from 'components/pages/ReportPage';
|
|||
import LicensePage from 'components/pages/LicensePage';
|
||||
import AuthComponent from 'components/AuthComponent';
|
||||
import LoginPageComponent from 'components/pages/LoginPage';
|
||||
import RegisterPageComponent from 'components/pages/RegisterPage';
|
||||
import Notifier from 'react-desktop-notification';
|
||||
import NotFoundPage from 'components/pages/NotFoundPage';
|
||||
|
||||
|
@ -24,17 +22,16 @@ import 'react-data-components/css/table-twbs.css';
|
|||
import 'styles/App.css';
|
||||
import 'react-toggle/style.css';
|
||||
import 'react-table/react-table.css';
|
||||
import VersionComponent from './side-menu/VersionComponent';
|
||||
|
||||
import logoImage from '../images/monkey-icon.svg';
|
||||
import infectionMonkeyImage from '../images/infection-monkey.svg';
|
||||
import guardicoreLogoImage from '../images/guardicore-logo.png';
|
||||
import notificationIcon from '../images/notification-logo-512x512.png';
|
||||
import {StandardLayoutComponent} from './layouts/StandardLayoutComponent';
|
||||
|
||||
const reportZeroTrustRoute = '/report/zeroTrust';
|
||||
|
||||
class AppComponent extends AuthComponent {
|
||||
updateStatus = () => {
|
||||
if (this.state.isLoggedIn === false){
|
||||
return
|
||||
}
|
||||
this.auth.loggedIn()
|
||||
.then(res => {
|
||||
if (this.state.isLoggedIn !== res) {
|
||||
|
@ -43,6 +40,15 @@ class AppComponent extends AuthComponent {
|
|||
});
|
||||
}
|
||||
|
||||
if (!res) {
|
||||
this.auth.needsRegistration()
|
||||
.then(result => {
|
||||
this.setState({
|
||||
needsRegistration: result
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
if (res) {
|
||||
this.authFetch('/api')
|
||||
.then(res => res.json())
|
||||
|
@ -70,10 +76,16 @@ class AppComponent extends AuthComponent {
|
|||
case true:
|
||||
return page_component;
|
||||
case false:
|
||||
return <Redirect to={{pathname: '/login'}}/>;
|
||||
switch (this.state.needsRegistration) {
|
||||
case true:
|
||||
return <Redirect to={{pathname: '/register'}}/>
|
||||
case false:
|
||||
return <Redirect to={{pathname: '/login'}}/>;
|
||||
default:
|
||||
return page_component;
|
||||
}
|
||||
default:
|
||||
return page_component;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -85,8 +97,8 @@ class AppComponent extends AuthComponent {
|
|||
};
|
||||
|
||||
redirectTo = (userPath, targetPath) => {
|
||||
let pathQuery = new RegExp(userPath+'[\/]?$', 'g');
|
||||
if(window.location.pathname.match(pathQuery)){
|
||||
let pathQuery = new RegExp(userPath + '[\/]?$', 'g');
|
||||
if (window.location.pathname.match(pathQuery)) {
|
||||
return <Redirect to={{pathname: targetPath}}/>
|
||||
}
|
||||
};
|
||||
|
@ -94,22 +106,18 @@ class AppComponent extends AuthComponent {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
removePBAfiles: false,
|
||||
completedSteps: {
|
||||
run_server: true,
|
||||
run_monkey: false,
|
||||
infection_done: false,
|
||||
report_done: false,
|
||||
isLoggedIn: undefined
|
||||
}
|
||||
isLoggedIn: undefined,
|
||||
needsRegistration: undefined
|
||||
},
|
||||
noAuthLoginAttempted: undefined
|
||||
};
|
||||
}
|
||||
|
||||
// Sets the property that indicates if we need to remove PBA files from state or not
|
||||
setRemovePBAfiles = (rmFiles) => {
|
||||
this.setState({removePBAfiles: rmFiles});
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.updateStatus();
|
||||
this.interval = setInterval(this.updateStatus, 10000);
|
||||
|
@ -122,102 +130,53 @@ class AppComponent extends AuthComponent {
|
|||
render() {
|
||||
return (
|
||||
<Router>
|
||||
<Grid fluid={true}>
|
||||
<Row>
|
||||
<Col sm={3} md={2} className='sidebar'>
|
||||
<div className='header'>
|
||||
<img alt="logo" src={logoImage} style={{width: '5vw', margin: '15px'}}/>
|
||||
<img src={infectionMonkeyImage} style={{width: '15vw'}} alt='Infection Monkey'/>
|
||||
</div>
|
||||
|
||||
<ul className='navigation'>
|
||||
<li>
|
||||
<NavLink to='/' exact={true}>
|
||||
<span className='number'>1.</span>
|
||||
Run Monkey Island Server
|
||||
{this.state.completedSteps.run_server ?
|
||||
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark text-success'/>
|
||||
: ''}
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink to='/run-monkey'>
|
||||
<span className='number'>2.</span>
|
||||
Run Monkey
|
||||
{this.state.completedSteps.run_monkey ?
|
||||
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark text-success'/>
|
||||
: ''}
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink to='/infection/map'>
|
||||
<span className='number'>3.</span>
|
||||
Infection Map
|
||||
{this.state.completedSteps.infection_done ?
|
||||
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark text-success'/>
|
||||
: ''}
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink to='/report/security'
|
||||
isActive={(match, location) => {
|
||||
return (location.pathname === '/report/attack'
|
||||
|| location.pathname === '/report/zeroTrust'
|
||||
|| location.pathname === '/report/security')
|
||||
}}>
|
||||
<span className='number'>4.</span>
|
||||
Security Reports
|
||||
{this.state.completedSteps.report_done ?
|
||||
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark text-success'/>
|
||||
: ''}
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink to='/start-over'>
|
||||
<span className='number'><FontAwesomeIcon icon={faUndo} style={{'marginLeft': '-1px'}}/></span>
|
||||
Start Over
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<hr/>
|
||||
<ul>
|
||||
<li><NavLink to='/configure'>Configuration</NavLink></li>
|
||||
<li><NavLink to='/infection/telemetry'>Log</NavLink></li>
|
||||
</ul>
|
||||
|
||||
<hr/>
|
||||
<div className='guardicore-link text-center' style={{'marginBottom': '0.5em'}}>
|
||||
<span>Powered by</span>
|
||||
<a href='http://www.guardicore.com' rel="noopener noreferrer" target='_blank'>
|
||||
<img src={guardicoreLogoImage} alt='GuardiCore'/>
|
||||
</a>
|
||||
</div>
|
||||
<div className='license-link text-center'>
|
||||
<NavLink to='/license'>License</NavLink>
|
||||
</div>
|
||||
<VersionComponent/>
|
||||
</Col>
|
||||
<Col sm={9} md={10} smOffset={3} mdOffset={2} className='main'>
|
||||
|
||||
<Switch>
|
||||
<Route path='/login' render={() => (<LoginPageComponent onStatusChange={this.updateStatus}/>)}/>
|
||||
{this.renderRoute('/', <RunServerPage onStatusChange={this.updateStatus}/>, true)}
|
||||
{this.renderRoute('/configure', <ConfigurePage onStatusChange={this.updateStatus}/>)}
|
||||
{this.renderRoute('/run-monkey', <RunMonkeyPage onStatusChange={this.updateStatus}/>)}
|
||||
{this.renderRoute('/infection/map', <MapPage onStatusChange={this.updateStatus}/>)}
|
||||
{this.renderRoute('/infection/telemetry', <TelemetryPage onStatusChange={this.updateStatus}/>)}
|
||||
{this.renderRoute('/start-over', <StartOverPage onStatusChange={this.updateStatus}/>)}
|
||||
{this.redirectTo('/report', '/report/security')}
|
||||
{this.renderRoute('/report/security', <ReportPage/>)}
|
||||
{this.renderRoute('/report/attack', <ReportPage/>)}
|
||||
{this.renderRoute('/report/zeroTrust', <ReportPage/>)}
|
||||
{this.renderRoute('/license', <LicensePage onStatusChange={this.updateStatus}/>)}
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
</Col>
|
||||
</Row>
|
||||
</Grid>
|
||||
<Container fluid>
|
||||
<Switch>
|
||||
<Route path='/login' render={() => (<LoginPageComponent onStatusChange={this.updateStatus}/>)}/>
|
||||
<Route path='/register' render={() => (<RegisterPageComponent onStatusChange={this.updateStatus}/>)}/>
|
||||
{this.renderRoute('/',
|
||||
<StandardLayoutComponent component={RunServerPage}
|
||||
completedSteps={this.state.completedSteps}
|
||||
onStatusChange={this.updateStatus}
|
||||
/>,
|
||||
true)}
|
||||
{this.renderRoute('/configure',
|
||||
<StandardLayoutComponent component={ConfigurePage}
|
||||
onStatusChange={this.updateStatus}
|
||||
completedSteps={this.state.completedSteps}/>)}
|
||||
{this.renderRoute('/run-monkey',
|
||||
<StandardLayoutComponent component={RunMonkeyPage}
|
||||
onStatusChange={this.updateStatus}
|
||||
completedSteps={this.state.completedSteps}/>)}
|
||||
{this.renderRoute('/infection/map',
|
||||
<StandardLayoutComponent component={MapPage}
|
||||
onStatusChange={this.updateStatus}
|
||||
completedSteps={this.state.completedSteps}/>)}
|
||||
{this.renderRoute('/infection/telemetry',
|
||||
<StandardLayoutComponent component={TelemetryPage}
|
||||
onStatusChange={this.updateStatus}
|
||||
completedSteps={this.state.completedSteps}/>)}
|
||||
{this.renderRoute('/start-over',
|
||||
<StandardLayoutComponent component={StartOverPage}
|
||||
onStatusChange={this.updateStatus}
|
||||
completedSteps={this.state.completedSteps}/>)}
|
||||
{this.redirectTo('/report', '/report/security')}
|
||||
{this.renderRoute('/report/security',
|
||||
<StandardLayoutComponent component={ReportPage}
|
||||
completedSteps={this.state.completedSteps}/>)}
|
||||
{this.renderRoute('/report/attack',
|
||||
<StandardLayoutComponent component={ReportPage}
|
||||
completedSteps={this.state.completedSteps}/>)}
|
||||
{this.renderRoute('/report/zeroTrust',
|
||||
<StandardLayoutComponent component={ReportPage}
|
||||
completedSteps={this.state.completedSteps}/>)}
|
||||
{this.renderRoute('/license',
|
||||
<StandardLayoutComponent component={LicensePage}
|
||||
onStatusChange={this.updateStatus}
|
||||
completedSteps={this.state.completedSteps}/>)}
|
||||
<Route component={NotFoundPage}/>
|
||||
</Switch>
|
||||
</Container>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import marked from 'marked';
|
||||
import '../../../styles/report/AttackReport.scss';
|
||||
|
||||
|
||||
class MitigationsComponent extends React.Component {
|
||||
|
|
|
@ -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>
|
||||
)}/>
|
||||
)
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faHandPointLeft } from '@fortawesome/free-solid-svg-icons/faHandPointLeft'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'
|
||||
import {faHandPointLeft} from '@fortawesome/free-solid-svg-icons/faHandPointLeft'
|
||||
import {faQuestionCircle} from '@fortawesome/free-solid-svg-icons/faQuestionCircle'
|
||||
import Toggle from 'react-toggle';
|
||||
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
|
||||
import download from 'downloadjs'
|
||||
|
@ -10,8 +11,10 @@ class PreviewPaneComponent extends AuthComponent {
|
|||
|
||||
generateToolTip(text) {
|
||||
return (
|
||||
<OverlayTrigger placement="top" overlay={<Tooltip id="tooltip">{text}</Tooltip>}>
|
||||
<a><i className="glyphicon glyphicon-info-sign"/></a>
|
||||
<OverlayTrigger placement="top"
|
||||
overlay={<Tooltip id="tooltip">{text}</Tooltip>}
|
||||
delay={{ show: 250, hide: 400 }}>
|
||||
<a><FontAwesomeIcon icon={faQuestionCircle} style={{'marginRight': '0.5em'}}/></a>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
}
|
||||
|
@ -123,8 +126,8 @@ class PreviewPaneComponent extends AuthComponent {
|
|||
Download Log
|
||||
</th>
|
||||
<td>
|
||||
<a type="button" className="btn btn-primary"
|
||||
disabled={!asset.has_log}
|
||||
<a type='button'
|
||||
className={asset.has_log ? 'btn btn-primary' : 'btn btn-primary disabled'}
|
||||
onClick={() => this.downloadLog(asset)}>Download</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -142,7 +145,7 @@ class PreviewPaneComponent extends AuthComponent {
|
|||
Exploit Timeline
|
||||
{this.generateToolTip('Timeline of exploit attempts. Red is successful. Gray is unsuccessful')}
|
||||
</h4>
|
||||
<ul className="timeline">
|
||||
<ul className='timeline'>
|
||||
{asset.exploits.map(exploit =>
|
||||
<li key={exploit.timestamp}>
|
||||
<div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
|
||||
|
@ -167,7 +170,7 @@ class PreviewPaneComponent extends AuthComponent {
|
|||
assetInfo(asset) {
|
||||
return (
|
||||
<div>
|
||||
<table className="table table-condensed">
|
||||
<table className='table table-condensed'>
|
||||
<tbody>
|
||||
{this.osRow(asset)}
|
||||
{this.ipsRow(asset)}
|
||||
|
@ -183,7 +186,7 @@ class PreviewPaneComponent extends AuthComponent {
|
|||
infectedAssetInfo(asset) {
|
||||
return (
|
||||
<div>
|
||||
<table className="table table-condensed">
|
||||
<table className='table table-condensed'>
|
||||
<tbody>
|
||||
{this.osRow(asset)}
|
||||
{this.statusRow(asset)}
|
||||
|
@ -202,7 +205,7 @@ class PreviewPaneComponent extends AuthComponent {
|
|||
scanInfo(edge) {
|
||||
return (
|
||||
<div>
|
||||
<table className="table table-condensed">
|
||||
<table className='table table-condensed'>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Operating System</th>
|
||||
|
@ -223,7 +226,7 @@ class PreviewPaneComponent extends AuthComponent {
|
|||
'' :
|
||||
<div>
|
||||
<h4 style={{'marginTop': '2em'}}>Timeline</h4>
|
||||
<ul className="timeline">
|
||||
<ul className='timeline'>
|
||||
{edge.exploits.map(exploit =>
|
||||
<li key={exploit.timestamp}>
|
||||
<div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
|
||||
|
@ -278,7 +281,7 @@ class PreviewPaneComponent extends AuthComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="preview-pane">
|
||||
<div className='preview-pane'>
|
||||
{!info ?
|
||||
<span>
|
||||
<FontAwesomeIcon icon={faHandPointLeft} style={{'marginRight': '0.5em'}}/>
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
import React from 'react';
|
||||
import Form from 'react-jsonschema-form';
|
||||
import {Col, Modal, Nav, NavItem} from 'react-bootstrap';
|
||||
import Form from 'react-jsonschema-form-bs4';
|
||||
import {Col, Modal, Nav, Button} from 'react-bootstrap';
|
||||
import FileSaver from 'file-saver';
|
||||
import AuthComponent from '../AuthComponent';
|
||||
import {FilePond} from 'react-filepond';
|
||||
import 'filepond/dist/filepond.min.css';
|
||||
import ConfigMatrixComponent from '../attack/ConfigMatrixComponent';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle';
|
||||
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
|
||||
import {faExclamationCircle} from '@fortawesome/free-solid-svg-icons/faExclamationCircle';
|
||||
|
||||
const ATTACK_URL = '/api/attack';
|
||||
const CONFIG_URL = '/api/configuration/island';
|
||||
|
@ -169,8 +173,8 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
this.setInitialConfig(res.configuration);
|
||||
this.props.onStatusChange();
|
||||
}).catch(error => {
|
||||
console.log('Bad configuration: ' + error.toString());
|
||||
this.setState({lastAction: 'invalid_configuration'});
|
||||
console.log('Bad configuration: ' + error.toString());
|
||||
this.setState({lastAction: 'invalid_configuration'});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -219,20 +223,21 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
}}>
|
||||
<Modal.Body>
|
||||
<h2>
|
||||
<div className="text-center">Warning</div>
|
||||
<div className='text-center'>Warning</div>
|
||||
</h2>
|
||||
<p className="text-center" style={{'fontSize': '1.2em', 'marginBottom': '2em'}}>
|
||||
<p className='text-center' style={{'fontSize': '1.2em', 'marginBottom': '2em'}}>
|
||||
You have unsubmitted changes. Submit them before proceeding.
|
||||
</p>
|
||||
<div className="text-center">
|
||||
<button type="button"
|
||||
className="btn btn-success btn-lg"
|
||||
<div className='text-center'>
|
||||
<Button type='button'
|
||||
className='btn btn-success'
|
||||
size='lg'
|
||||
style={{margin: '5px'}}
|
||||
onClick={() => {
|
||||
this.setState({showAttackAlert: false})
|
||||
}}>
|
||||
Cancel
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Body>
|
||||
</Modal>)
|
||||
|
@ -452,17 +457,18 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
uiSchema={this.uiSchemas[this.state.selectedSection]}
|
||||
formData={this.state.configuration[this.state.selectedSection]}
|
||||
onChange={this.onChange}
|
||||
noValidate={true}>
|
||||
<button type="submit" className={'hidden'}>Submit</button>
|
||||
noValidate={true}
|
||||
className={'config-form'}>
|
||||
<button type='submit' className={'hidden'}>Submit</button>
|
||||
</Form>
|
||||
</div>)
|
||||
};
|
||||
|
||||
renderBasicNetworkWarning = () => {
|
||||
if (this.state.selectedSection === 'basic_network') {
|
||||
return (<div className="alert alert-info">
|
||||
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
|
||||
The Monkey scans its subnet if "Local network scan" is ticked. Additionally the monkey scans machines
|
||||
return (<div className='alert alert-info'>
|
||||
<FontAwesomeIcon icon={faInfoCircle} style={{'marginRight': '5px'}}/>
|
||||
The Monkey scans its subnet if 'Local network scan' is ticked. Additionally the monkey scans machines
|
||||
according to its range class.
|
||||
</div>)
|
||||
} else {
|
||||
|
@ -471,10 +477,15 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
};
|
||||
|
||||
renderNav = () => {
|
||||
return (<Nav bsStyle="tabs" justified
|
||||
return (<Nav variant='tabs'
|
||||
fill
|
||||
activeKey={this.state.selectedSection} onSelect={this.setSelectedSection}
|
||||
style={{'marginBottom': '2em'}}>
|
||||
{this.state.sections.map(section => <NavItem key={section.key} eventKey={section.key}>{section.title}</NavItem>)}
|
||||
style={{'marginBottom': '2em'}}
|
||||
className={'config-nav'}>
|
||||
{this.state.sections.map(section =>
|
||||
<Nav.Item>
|
||||
<Nav.Link eventKey={section.key}>{section.title}</Nav.Link>
|
||||
</Nav.Item>)}
|
||||
</Nav>)
|
||||
};
|
||||
|
||||
|
@ -491,57 +502,60 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
content = this.renderConfigContent(displayedSchema)
|
||||
}
|
||||
return (
|
||||
<Col xs={12} lg={10}>
|
||||
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
|
||||
lg={{offset: 3, span: 8}} xl={{offset: 2, span: 8}}
|
||||
className={'main'}>
|
||||
{this.renderAttackAlertModal()}
|
||||
<h1 className="page-title">Monkey Configuration</h1>
|
||||
<h1 className='page-title'>Monkey Configuration</h1>
|
||||
{this.renderNav()}
|
||||
{content}
|
||||
<div className="text-center">
|
||||
<button type="submit" onClick={this.onSubmit} className="btn btn-success btn-lg" style={{margin: '5px'}}>
|
||||
<div className='text-center'>
|
||||
<button type='submit' onClick={this.onSubmit} className='btn btn-success btn-lg' style={{margin: '5px'}}>
|
||||
Submit
|
||||
</button>
|
||||
<button type="button" onClick={this.resetConfig} className="btn btn-danger btn-lg" style={{margin: '5px'}}>
|
||||
<button type='button' onClick={this.resetConfig} className='btn btn-danger btn-lg' style={{margin: '5px'}}>
|
||||
Reset to defaults
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className='text-center'>
|
||||
<button onClick={() => document.getElementById('uploadInputInternal').click()}
|
||||
className="btn btn-info btn-lg" style={{margin: '5px'}}>
|
||||
className='btn btn-info btn-lg' style={{margin: '5px'}}>
|
||||
Import Config
|
||||
</button>
|
||||
<input id="uploadInputInternal" type="file" accept=".conf" onChange={this.importConfig} style={{display: 'none'}}/>
|
||||
<button type="button" onClick={this.exportConfig} className="btn btn-info btn-lg" style={{margin: '5px'}}>
|
||||
<input id='uploadInputInternal' type='file' accept='.conf' onChange={this.importConfig}
|
||||
style={{display: 'none'}}/>
|
||||
<button type='button' onClick={this.exportConfig} className='btn btn-info btn-lg' style={{margin: '5px'}}>
|
||||
Export config
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
{this.state.lastAction === 'reset' ?
|
||||
<div className="alert alert-success">
|
||||
<i className="glyphicon glyphicon-ok-sign" style={{'marginRight': '5px'}}/>
|
||||
<div className='alert alert-success'>
|
||||
<FontAwesomeIcon icon={faCheck} style={{'marginRight': '5px'}}/>
|
||||
Configuration reset successfully.
|
||||
</div>
|
||||
: ''}
|
||||
{this.state.lastAction === 'saved' ?
|
||||
<div className="alert alert-success">
|
||||
<i className="glyphicon glyphicon-ok-sign" style={{'marginRight': '5px'}}/>
|
||||
<div className='alert alert-success'>
|
||||
<FontAwesomeIcon icon={faCheck} style={{'marginRight': '5px'}}/>
|
||||
Configuration saved successfully.
|
||||
</div>
|
||||
: ''}
|
||||
{this.state.lastAction === 'import_failure' ?
|
||||
<div className="alert alert-danger">
|
||||
<i className="glyphicon glyphicon-exclamation-sign" style={{'marginRight': '5px'}}/>
|
||||
<div className='alert alert-danger'>
|
||||
<FontAwesomeIcon icon={faExclamationCircle} style={{'marginRight': '5px'}}/>
|
||||
Failed importing configuration. Invalid config file.
|
||||
</div>
|
||||
: ''}
|
||||
{this.state.lastAction === 'invalid_configuration' ?
|
||||
<div className="alert alert-danger">
|
||||
<i className="glyphicon glyphicon-exclamation-sign" style={{'marginRight': '5px'}}/>
|
||||
<div className='alert alert-danger'>
|
||||
<FontAwesomeIcon icon={faExclamationCircle} style={{'marginRight': '5px'}}/>
|
||||
An invalid configuration file was imported or submitted.
|
||||
</div>
|
||||
: ''}
|
||||
{this.state.lastAction === 'import_success' ?
|
||||
<div className="alert alert-success">
|
||||
<i className="glyphicon glyphicon-ok-sign" style={{'marginRight': '5px'}}/>
|
||||
<div className='alert alert-success'>
|
||||
<FontAwesomeIcon icon={faCheck} style={{'marginRight': '5px'}}/>
|
||||
Configuration imported successfully.
|
||||
</div>
|
||||
: ''}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import {Col} from 'react-bootstrap';
|
||||
import rainge from 'rainge'
|
||||
import rainge from 'rainge';
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faCopyright} from "@fortawesome/free-regular-svg-icons";
|
||||
|
||||
class LicensePageComponent extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -15,11 +17,13 @@ class LicensePageComponent extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<Col xs={12} lg={8}>
|
||||
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
|
||||
lg={{offset: 3, span: 9}} xl={{offset: 2, span: 7}}
|
||||
className={'main'}>
|
||||
<h1 className="page-title">License</h1>
|
||||
<div style={{'fontSize': '1.2em'}}>
|
||||
<p>
|
||||
Copyright <i className="glyphicon glyphicon-copyright-mark"/> {rainge(2015)} Guardicore Ltd.
|
||||
Copyright <FontAwesomeIcon icon={faCopyright}/> {rainge(2015)} Guardicore Ltd.
|
||||
<br/>
|
||||
Licensed under <a href="https://www.gnu.org/licenses/gpl-3.0.html" rel="noopener noreferrer" target="_blank">GPLv3</a>.
|
||||
</p>
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import React from 'react';
|
||||
import {Col} from 'react-bootstrap';
|
||||
import {Button, Col, Container, Form, Row} from 'react-bootstrap';
|
||||
|
||||
import AuthService from '../../services/AuthService'
|
||||
import AuthService from '../../services/AuthService';
|
||||
import Particles from "react-particles-js";
|
||||
import {particleParams} from "../../styles/particle-component/AuthPageParams";
|
||||
import monkeyGeneral from "../../images/militant-monkey.svg";
|
||||
|
||||
class LoginPageComponent extends React.Component {
|
||||
login = () => {
|
||||
login = (event) => {
|
||||
event.preventDefault()
|
||||
this.auth.login(this.username, this.password).then(res => {
|
||||
if (res['result']) {
|
||||
this.redirectToHome();
|
||||
|
@ -26,6 +30,10 @@ class LoginPageComponent extends React.Component {
|
|||
window.location.href = '/';
|
||||
};
|
||||
|
||||
redirectToRegistration = () => {
|
||||
window.location.href = '/register';
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.username = '';
|
||||
|
@ -34,6 +42,13 @@ class LoginPageComponent extends React.Component {
|
|||
this.state = {
|
||||
failed: false
|
||||
};
|
||||
|
||||
this.auth.needsRegistration()
|
||||
.then(result => {
|
||||
if (result) {
|
||||
this.redirectToRegistration()
|
||||
}
|
||||
})
|
||||
this.auth.loggedIn()
|
||||
.then(res => {
|
||||
if (res) {
|
||||
|
@ -44,37 +59,36 @@ class LoginPageComponent extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<Col xs={12} lg={8}>
|
||||
<h1 className="page-title">Login</h1>
|
||||
<div className="col-sm-6 col-sm-offset-3" style={{'fontSize': '1.2em'}}>
|
||||
<div className="panel panel-default">
|
||||
<div className="panel-heading text-center">
|
||||
<b>Login</b>
|
||||
</div>
|
||||
<div className="panel-body">
|
||||
<div className="input-group center-block text-center">
|
||||
<input type="text" className="form-control" placeholder="Username"
|
||||
onChange={evt => this.updateUsername(evt)}/>
|
||||
<input type="password" className="form-control" placeholder="Password"
|
||||
onChange={evt => this.updatePassword(evt)}/>
|
||||
<button type="button" className="btn btn-primary btn-lg" style={{margin: '5px'}}
|
||||
onClick={() => {
|
||||
this.login()
|
||||
}}>
|
||||
Login
|
||||
</button>
|
||||
{
|
||||
this.state.failed ?
|
||||
<div className="alert alert-danger" role="alert">Login failed. Bad credentials.</div>
|
||||
:
|
||||
''
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
);
|
||||
<Container fluid className={'auth-container'}>
|
||||
<Particles className={'particle-background'} params={particleParams}/>
|
||||
<Row>
|
||||
<Col xs={12} lg={{span: 6, offset: 3}} md={{span: 7, offset: 3}} className={'auth-block'}>
|
||||
<Row>
|
||||
<Col lg={8} md={8} sm={8}>
|
||||
<h1 className='auth-title'>Login</h1>
|
||||
<div>
|
||||
<Form className={'auth-form'} onSubmit={this.login}>
|
||||
<Form.Control onChange={evt => this.updateUsername(evt)} type='text' placeholder='Username'/>
|
||||
<Form.Control onChange={evt => this.updatePassword(evt)} type='password' placeholder='Password'/>
|
||||
<Button id={'auth-button'} type={'submit'}>
|
||||
Login
|
||||
</Button>
|
||||
{
|
||||
this.state.failed ?
|
||||
<div className="alert alert-danger" role="alert">Login failed. Bad credentials.</div>
|
||||
:
|
||||
''
|
||||
}
|
||||
</Form>
|
||||
</div>
|
||||
</Col>
|
||||
<Col lg={4} md={4} sm={4}>
|
||||
<img alt="infection monkey" className={'monkey-detective'} src={monkeyGeneral}/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import React from 'react';
|
||||
import {Col, Modal} from 'react-bootstrap';
|
||||
import {Col, Modal, Row} from 'react-bootstrap';
|
||||
import {Link} from 'react-router-dom';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faStopCircle } from '@fortawesome/free-solid-svg-icons/faStopCircle'
|
||||
import { faMinus } from '@fortawesome/free-solid-svg-icons/faMinus'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faStopCircle} from '@fortawesome/free-solid-svg-icons/faStopCircle';
|
||||
import {faMinus} from '@fortawesome/free-solid-svg-icons/faMinus';
|
||||
import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane';
|
||||
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
||||
import {getOptions, edgeGroupToColor} from 'components/map/MapOptions';
|
||||
import AuthComponent from '../AuthComponent';
|
||||
import '../../styles/Map.scss';
|
||||
import {faInfoCircle} from "@fortawesome/free-solid-svg-icons/faInfoCircle";
|
||||
|
||||
class MapPageComponent extends AuthComponent {
|
||||
constructor(props) {
|
||||
|
@ -73,7 +75,7 @@ class MapPageComponent extends AuthComponent {
|
|||
};
|
||||
|
||||
updateTelemetryFromServer = () => {
|
||||
if( this.state.telemetryUpdateInProgress ) {
|
||||
if (this.state.telemetryUpdateInProgress) {
|
||||
return
|
||||
}
|
||||
this.setState({telemetryUpdateInProgress: true});
|
||||
|
@ -153,7 +155,6 @@ class MapPageComponent extends AuthComponent {
|
|||
</Modal.Body>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
};
|
||||
|
||||
renderTelemetryEntry(telemetry) {
|
||||
|
@ -174,8 +175,8 @@ class MapPageComponent extends AuthComponent {
|
|||
|
||||
this.setState({
|
||||
isScrolledUp: (element.scrollTop < this.scrollTop),
|
||||
telemetryCurrentLine: Math.trunc(element.scrollTop/telemetryLineHeight)+1,
|
||||
telemetryLines: Math.trunc(element.scrollHeight/telemetryLineHeight)
|
||||
telemetryCurrentLine: Math.trunc(element.scrollTop / telemetryLineHeight) + 1,
|
||||
telemetryLines: Math.trunc(element.scrollHeight / telemetryLineHeight)
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -199,52 +200,53 @@ class MapPageComponent extends AuthComponent {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{this.renderKillDialogModal()}
|
||||
<Col xs={12} lg={8}>
|
||||
<h1 className="page-title">3. Infection Map</h1>
|
||||
</Col>
|
||||
<Col xs={8}>
|
||||
<div className="map-legend">
|
||||
<b>Legend: </b>
|
||||
<span>Exploit <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#cc0200'}}/></span>
|
||||
<b style={{color: '#aeaeae'}}> | </b>
|
||||
<span>Scan <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#ff9900'}}/></span>
|
||||
<b style={{color: '#aeaeae'}}> | </b>
|
||||
<span>Tunnel <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#0158aa'}}/></span>
|
||||
<b style={{color: '#aeaeae'}}> | </b>
|
||||
<span>Island Communication <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#a9aaa9'}}/></span>
|
||||
</div>
|
||||
{this.renderTelemetryConsole()}
|
||||
<div style={{height: '80vh'}}>
|
||||
<ReactiveGraph graph={this.state.graph} options={getOptions(this.state.nodeStateList)} events={this.events}/>
|
||||
</div>
|
||||
{this.renderTelemetryLineCount()}
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<input className="form-control input-block"
|
||||
placeholder="Find on map"
|
||||
style={{'marginBottom': '1em'}}/>
|
||||
|
||||
<div style={{'overflow': 'auto', 'marginBottom': '1em'}}>
|
||||
<Link to="/infection/telemetry" className="btn btn-default pull-left" style={{'width': '48%'}}>Monkey
|
||||
Telemetry</Link>
|
||||
<button onClick={() => this.setState({showKillDialog: true})} className="btn btn-danger pull-right"
|
||||
style={{'width': '48%'}}>
|
||||
<FontAwesomeIcon icon={faStopCircle} style={{'marginRight': '0.5em'}}/>
|
||||
Kill All Monkeys
|
||||
</button>
|
||||
</div>
|
||||
{this.state.killPressed ?
|
||||
<div className="alert alert-info">
|
||||
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
|
||||
Kill command sent to all monkeys
|
||||
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
|
||||
lg={{offset: 3, span: 9}} xl={{offset: 2, span: 10}}
|
||||
className={'main'}>
|
||||
<Row>
|
||||
{this.renderKillDialogModal()}
|
||||
<Col xs={12} lg={8}>
|
||||
<h1 className="page-title">3. Infection Map</h1>
|
||||
</Col>
|
||||
<Col xs={8}>
|
||||
<div className="map-legend">
|
||||
<b>Legend: </b>
|
||||
<span>Exploit <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#cc0200'}}/></span>
|
||||
<b style={{color: '#aeaeae'}}> | </b>
|
||||
<span>Scan <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#ff9900'}}/></span>
|
||||
<b style={{color: '#aeaeae'}}> | </b>
|
||||
<span>Tunnel <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#0158aa'}}/></span>
|
||||
<b style={{color: '#aeaeae'}}> | </b>
|
||||
<span>Island Communication <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#a9aaa9'}}/></span>
|
||||
</div>
|
||||
: ''}
|
||||
<div style={{height: '80vh'}} className={'map-window'}>
|
||||
{this.renderTelemetryLineCount()}
|
||||
{this.renderTelemetryConsole()}
|
||||
<ReactiveGraph graph={this.state.graph} options={getOptions(this.state.nodeStateList)}
|
||||
events={this.events}/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<div style={{'overflow': 'auto', 'marginBottom': '1em'}}>
|
||||
<Link to="/infection/telemetry" className="btn btn-light pull-left" style={{'width': '48%'}}>Monkey
|
||||
Telemetry</Link>
|
||||
<button onClick={() => this.setState({showKillDialog: true})} className="btn btn-danger pull-right"
|
||||
style={{'width': '48%'}}>
|
||||
<FontAwesomeIcon icon={faStopCircle} style={{'marginRight': '0.5em'}}/>
|
||||
Kill All Monkeys
|
||||
</button>
|
||||
</div>
|
||||
{this.state.killPressed ?
|
||||
<div className="alert alert-info">
|
||||
<FontAwesomeIcon icon={faInfoCircle} style={{'marginRight': '5px'}} />
|
||||
Kill command sent to all monkeys
|
||||
</div>
|
||||
: ''}
|
||||
|
||||
<PreviewPaneComponent item={this.state.selected} type={this.state.selectedType}/>
|
||||
</Col>
|
||||
</div>
|
||||
<PreviewPaneComponent item={this.state.selected} type={this.state.selectedType}/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -1,8 +1,6 @@
|
|||
import '../../styles/report/ReportPage.scss';
|
||||
|
||||
import React from 'react';
|
||||
import {Route} from 'react-router-dom';
|
||||
import {Col, Nav, NavItem} from 'react-bootstrap';
|
||||
import {Col, Nav} from 'react-bootstrap';
|
||||
import AuthComponent from '../AuthComponent';
|
||||
import MustRunMonkeyWarning from '../report-components/common/MustRunMonkeyWarning';
|
||||
import AttackReport from '../report-components/AttackReport'
|
||||
|
@ -30,10 +28,10 @@ class ReportPageComponent extends AuthComponent {
|
|||
};
|
||||
}
|
||||
|
||||
static selectReport(reports){
|
||||
static selectReport(reports) {
|
||||
let url = window.location.href;
|
||||
for (let report_name in reports){
|
||||
if (reports.hasOwnProperty(report_name) && url.endsWith(reports[report_name])){
|
||||
for (let report_name in reports) {
|
||||
if (reports.hasOwnProperty(report_name) && url.endsWith(reports[report_name])) {
|
||||
return reports[report_name];
|
||||
}
|
||||
}
|
||||
|
@ -109,24 +107,32 @@ class ReportPageComponent extends AuthComponent {
|
|||
renderNav = () => {
|
||||
return (
|
||||
<Route render={({history}) => (
|
||||
<Nav bsStyle='tabs' justified
|
||||
activeKey={this.state.selectedSection}
|
||||
onSelect={(key) => {this.setSelectedSection(key); history.push(key)}}
|
||||
className={'report-nav'}>
|
||||
{this.state.sections.map(section => this.renderNavButton(section))}
|
||||
</Nav>)}/>)
|
||||
<Nav variant='tabs'
|
||||
fill
|
||||
activeKey={this.state.selectedSection}
|
||||
onSelect={(key) => {
|
||||
this.setSelectedSection(key);
|
||||
history.push(key)
|
||||
}}
|
||||
className={'report-nav'}>
|
||||
{this.state.sections.map(section => this.renderNavButton(section))}
|
||||
</Nav>)}/>)
|
||||
};
|
||||
|
||||
renderNavButton = (section) => {
|
||||
return (
|
||||
<NavItem key={section.key}
|
||||
eventKey={section.key}
|
||||
onSelect={() => {}}>
|
||||
<Nav.Item>
|
||||
<Nav.Link key={section.key}
|
||||
eventKey={section.key}
|
||||
onSelect={() => {
|
||||
}}>
|
||||
{section.title}
|
||||
</NavItem>)};
|
||||
</Nav.Link>
|
||||
</Nav.Item>)
|
||||
};
|
||||
|
||||
getReportContent() {
|
||||
switch(this.state.selectedSection){
|
||||
switch (this.state.selectedSection) {
|
||||
case 'security':
|
||||
return (<SecurityReport report={this.state.securityReport}/>);
|
||||
case 'attack':
|
||||
|
@ -145,7 +151,9 @@ class ReportPageComponent extends AuthComponent {
|
|||
content = <MustRunMonkeyWarning/>;
|
||||
}
|
||||
return (
|
||||
<Col xs={12} lg={12}>
|
||||
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
|
||||
lg={{offset: 3, span: 9}} xl={{offset: 2, span: 10}}
|
||||
className={'report-wrapper'}>
|
||||
<h1 className='page-title no-print'>4. Security Reports</h1>
|
||||
{this.renderNav()}
|
||||
<MonkeysStillAliveWarning allMonkeysAreDead={this.state.allMonkeysAreDead}/>
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import React from 'react';
|
||||
import {css} from '@emotion/core';
|
||||
import {Button, Col, Well, Nav, NavItem, Collapse} from 'react-bootstrap';
|
||||
import {Button, Col, Card, Nav, Collapse, Row} from 'react-bootstrap';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import GridLoader from 'react-spinners/GridLoader';
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faClipboard } from '@fortawesome/free-solid-svg-icons/faClipboard';
|
||||
import { faCheck } from '@fortawesome/free-solid-svg-icons/faCheck';
|
||||
import { faSync } from '@fortawesome/free-solid-svg-icons/faSync';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faClipboard} from '@fortawesome/free-solid-svg-icons/faClipboard';
|
||||
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
|
||||
import {faSync} from '@fortawesome/free-solid-svg-icons/faSync';
|
||||
import {faInfoCircle} from "@fortawesome/free-solid-svg-icons/faInfoCircle";
|
||||
import {faExclamationTriangle} from "@fortawesome/free-solid-svg-icons/faExclamationTriangle";
|
||||
|
||||
import {Link} from 'react-router-dom';
|
||||
import AuthComponent from '../AuthComponent';
|
||||
|
@ -15,8 +17,6 @@ import AwsRunTable from '../run-monkey/AwsRunTable';
|
|||
|
||||
import MissingBinariesModal from '../ui-components/MissingBinariesModal';
|
||||
|
||||
import '../../styles/MonkeyRunPage.scss';
|
||||
|
||||
const loading_css_override = css`
|
||||
display: block;
|
||||
margin-right: auto;
|
||||
|
@ -139,8 +139,9 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
/* If Monkey binaries are missing, change the state accordingly */
|
||||
if (res['error_text'].startsWith('Copy file failed')) {
|
||||
this.setState({
|
||||
showModal: true,
|
||||
errorDetails: res['error_text']}
|
||||
showModal: true,
|
||||
errorDetails: res['error_text']
|
||||
}
|
||||
);
|
||||
}
|
||||
this.setState({
|
||||
|
@ -162,7 +163,7 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
cmdText = RunMonkeyPageComponent.generateWindowsCmd(this.state.selectedIp, is32Bit);
|
||||
}
|
||||
return (
|
||||
<Well key={'cmdDiv' + this.state.selectedIp} className="well-sm" style={{'margin': '0.5em'}}>
|
||||
<Card key={'cmdDiv' + this.state.selectedIp} style={{'margin': '0.5em'}}>
|
||||
<div style={{'overflow': 'auto', 'padding': '0.5em'}}>
|
||||
<CopyToClipboard text={cmdText} className="pull-right btn-sm">
|
||||
<Button style={{margin: '-0.5em'}} title="Copy to Clipboard">
|
||||
|
@ -171,7 +172,7 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
</CopyToClipboard>
|
||||
<code>{cmdText}</code>
|
||||
</div>
|
||||
</Well>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -266,7 +267,7 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
<div style={{'marginBottom': '2em'}}>
|
||||
<div style={{'marginTop': '1em', 'marginBottom': '1em'}}>
|
||||
<p className="alert alert-info">
|
||||
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
|
||||
<FontAwesomeIcon icon={faInfoCircle} style={{'marginRight': '5px'}}/>
|
||||
Not sure what this is? Not seeing your AWS EC2 instances? <a
|
||||
href="https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS-EC2-instances"
|
||||
rel="noopener noreferrer" target="_blank">Read the documentation</a>!
|
||||
|
@ -274,9 +275,9 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
</div>
|
||||
{
|
||||
this.state.ips.length > 1 ?
|
||||
<Nav bsStyle="pills" justified activeKey={this.state.selectedIp} onSelect={this.setSelectedIp}
|
||||
<Nav variant="pills" activeKey={this.state.selectedIp} onSelect={this.setSelectedIp}
|
||||
style={{'marginBottom': '2em'}}>
|
||||
{this.state.ips.map(ip => <NavItem key={ip} eventKey={ip}>{ip}</NavItem>)}
|
||||
{this.state.ips.map(ip => <Nav.Item><Nav.Link eventKey={ip}>{ip}</Nav.Link></Nav.Item>)}
|
||||
</Nav>
|
||||
: <div style={{'marginBottom': '2em'}}/>
|
||||
}
|
||||
|
@ -286,13 +287,14 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
ref={r => (this.awsTable = r)}
|
||||
/>
|
||||
<div style={{'marginTop': '1em'}}>
|
||||
<button
|
||||
<Button
|
||||
onClick={this.runOnAws}
|
||||
className={'btn btn-default btn-md center-block'}
|
||||
disabled={this.state.awsClicked}>
|
||||
Run on selected machines
|
||||
{this.state.awsClicked ? <FontAwesomeIcon icon={faSync} className="text-success" style={{'marginLeft': '5px'}}/> : null}
|
||||
</button>
|
||||
{this.state.awsClicked ?
|
||||
<FontAwesomeIcon icon={faSync} className="text-success" style={{'marginLeft': '5px'}}/> : null}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -306,23 +308,27 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<Col xs={12} lg={8}>
|
||||
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
|
||||
lg={{offset: 3, span: 9}} xl={{offset: 2, span: 7}}
|
||||
className={'main'}>
|
||||
<h1 className="page-title">2. Run the Monkey</h1>
|
||||
<p style={{'marginBottom': '2em', 'fontSize': '1.2em'}}>
|
||||
Go ahead and run the monkey!
|
||||
<i> (Or <Link to="/configure">configure the monkey</Link> to fine tune its behavior)</i>
|
||||
</p>
|
||||
<p>
|
||||
<button onClick={this.runLocalMonkey}
|
||||
className="btn btn-default btn-lg center-block"
|
||||
disabled={this.state.runningOnIslandState !== 'not_running'}>
|
||||
<p className={'text-center'}>
|
||||
<Button onClick={this.runLocalMonkey}
|
||||
variant={'outline-monkey'}
|
||||
size='lg'
|
||||
disabled={this.state.runningOnIslandState !== 'not_running'}
|
||||
>
|
||||
Run on Monkey Island Server
|
||||
{RunMonkeyPageComponent.renderIconByState(this.state.runningOnIslandState)}
|
||||
</button>
|
||||
</Button>
|
||||
<MissingBinariesModal
|
||||
showModal = {this.state.showModal}
|
||||
onClose = {this.closeModal}
|
||||
errorDetails = {this.state.errorDetails}/>
|
||||
showModal={this.state.showModal}
|
||||
onClose={this.closeModal}
|
||||
errorDetails={this.state.errorDetails}/>
|
||||
{
|
||||
// TODO: implement button functionality
|
||||
/*
|
||||
|
@ -339,30 +345,67 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
<p className="text-center">
|
||||
OR
|
||||
</p>
|
||||
<p style={this.state.showManual || !this.state.isOnAws ? {'marginBottom': '2em'} : {}}>
|
||||
<button onClick={this.toggleManual}
|
||||
className={'btn btn-default btn-lg center-block' + (this.state.showManual ? ' active' : '')}>
|
||||
<p className={'text-center'}
|
||||
style={this.state.showManual || !this.state.isOnAws ? {'marginBottom': '2em'} : {}}>
|
||||
<Button onClick={this.toggleManual}
|
||||
variant={'outline-monkey'}
|
||||
size='lg'
|
||||
className={(this.state.showManual ? 'active' : '')}>
|
||||
Run on a machine of your choice
|
||||
</button>
|
||||
</Button>
|
||||
</p>
|
||||
<Collapse in={this.state.showManual}>
|
||||
<div style={{'marginBottom': '2em'}}>
|
||||
<p style={{'fontSize': '1.2em'}}>
|
||||
Choose the operating system where you want to run the monkey
|
||||
{this.state.ips.length > 1 ? ', and the interface to communicate with.' : '.'}
|
||||
Choose the operating system where you want to run the monkey:
|
||||
</p>
|
||||
<Nav bsStyle='pills' id={'bootstrap-override'} className={'runOnOsButtons'}
|
||||
justified activeKey={this.state.selectedOs} onSelect={this.setSelectedOs}>
|
||||
<NavItem key='windows-32' eventKey='windows-32'>Windows (32 bit)</NavItem>
|
||||
<NavItem key='windows-64' eventKey='windows-64'>Windows (64 bit)</NavItem>
|
||||
<NavItem key='linux-32' eventKey='linux-32'>Linux (32 bit)</NavItem>
|
||||
<NavItem key='linux-64' eventKey='linux-64'>Linux (64 bit)</NavItem>
|
||||
</Nav>
|
||||
<Row>
|
||||
<Col>
|
||||
<Nav variant='pills' fill id={'bootstrap-override'} className={'run-on-os-buttons'}
|
||||
activeKey={this.state.selectedOs} onSelect={this.setSelectedOs}>
|
||||
<Nav.Item>
|
||||
<Nav.Link eventKey={'windows-32'}>
|
||||
Windows (32 bit)
|
||||
</Nav.Link>
|
||||
</Nav.Item>
|
||||
<Nav.Item>
|
||||
<Nav.Link eventKey='windows-64'>
|
||||
Windows (64 bit)
|
||||
</Nav.Link>
|
||||
</Nav.Item>
|
||||
<Nav.Item>
|
||||
<Nav.Link eventKey='linux-32'>
|
||||
Linux (32 bit)
|
||||
</Nav.Link>
|
||||
</Nav.Item>
|
||||
<Nav.Item>
|
||||
<Nav.Link eventKey='linux-64'>
|
||||
Linux (64 bit)
|
||||
</Nav.Link>
|
||||
</Nav.Item>
|
||||
</Nav>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{this.state.ips.length > 1 ?
|
||||
<Nav bsStyle="pills" justified activeKey={this.state.selectedIp} onSelect={this.setSelectedIp}
|
||||
style={{'marginBottom': '2em'}}>
|
||||
{this.state.ips.map(ip => <NavItem key={ip} eventKey={ip}>{ip}</NavItem>)}
|
||||
</Nav>
|
||||
<div>
|
||||
<Row>
|
||||
<Col>
|
||||
<p style={{'fontSize': '1.2em'}}>
|
||||
Choose the interface to communicate with:
|
||||
</p>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col>
|
||||
<Nav variant="pills" fill activeKey={this.state.selectedIp} onSelect={this.setSelectedIp}
|
||||
className={'run-on-os-buttons'}>
|
||||
{this.state.ips.map(ip => <Nav.Item>
|
||||
<Nav.Link eventKey={ip}>{ip}</Nav.Link></Nav.Item>)}
|
||||
</Nav>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
: <div style={{'marginBottom': '2em'}}/>
|
||||
}
|
||||
<p style={{'fontSize': '1.2em'}}>
|
||||
|
@ -373,7 +416,7 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
</Collapse>
|
||||
{
|
||||
this.state.isLoadingAws ?
|
||||
<p style={{'marginBottom': '2em', 'align': 'center'}}>
|
||||
<div style={{'marginBottom': '2em', 'align': 'center'}}>
|
||||
<div className='sweet-loading'>
|
||||
<GridLoader
|
||||
css={loading_css_override}
|
||||
|
@ -383,7 +426,7 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
loading={this.state.loading}
|
||||
/>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
{
|
||||
|
@ -396,11 +439,13 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
}
|
||||
{
|
||||
this.state.isOnAws ?
|
||||
<p style={{'marginBottom': '2em'}}>
|
||||
<button onClick={this.toggleAws}
|
||||
className={'btn btn-default btn-lg center-block' + (this.state.showAws ? ' active' : '')}>
|
||||
<p style={{'marginBottom': '2em'}} className={'text-center'}>
|
||||
<Button onClick={this.toggleAws}
|
||||
className={(this.state.showAws ? ' active' : '')}
|
||||
size='lg'
|
||||
variant={'outline-monkey'}>
|
||||
Run on AWS machine of your choice
|
||||
</button>
|
||||
</Button>
|
||||
</p>
|
||||
:
|
||||
null
|
||||
|
@ -409,8 +454,8 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
{
|
||||
this.state.isErrorWhileCollectingAwsMachines ?
|
||||
<div style={{'marginTop': '1em'}}>
|
||||
<p class="alert alert-danger">
|
||||
<i className="glyphicon glyphicon-warning-sign" style={{'marginRight': '5px'}}/>
|
||||
<p className="alert alert-danger">
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} style={{'marginRight': '5px'}}/>
|
||||
Error while collecting AWS machine data. Error
|
||||
message: <code>{this.state.awsMachineCollectionErrorMsg}</code><br/>
|
||||
Are you sure you've set the correct role on your Island AWS machine?<br/>
|
||||
|
|
|
@ -9,7 +9,9 @@ class RunServerPageComponent extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<Col xs={12} lg={8}>
|
||||
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
|
||||
lg={{offset: 3, span: 9}} xl={{offset: 2, span: 7}}
|
||||
className={'main'}>
|
||||
<h1 className="page-title">1. Monkey Island Server</h1>
|
||||
<div style={{'fontSize': '1.2em'}}>
|
||||
<p style={{'marginTop': '30px'}}>Congrats! You have successfully set up the Monkey Island
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import React from 'react';
|
||||
import {Col} from 'react-bootstrap';
|
||||
import {Col, Button} from 'react-bootstrap';
|
||||
import {Link} from 'react-router-dom';
|
||||
import AuthComponent from '../AuthComponent';
|
||||
import StartOverModal from '../ui-components/StartOverModal';
|
||||
import '../../styles/StartOverPage.scss';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faInfoCircle} from "@fortawesome/free-solid-svg-icons/faInfoCircle";
|
||||
import {faCheck} from "@fortawesome/free-solid-svg-icons/faCheck";
|
||||
|
||||
class StartOverPageComponent extends AuthComponent {
|
||||
constructor(props) {
|
||||
|
@ -32,7 +35,9 @@ class StartOverPageComponent extends AuthComponent {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<Col xs={12} lg={8}>
|
||||
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
|
||||
lg={{offset: 3, span: 9}} xl={{offset: 2, span: 7}}
|
||||
className={'main'}>
|
||||
<StartOverModal cleaned = {this.state.cleaned}
|
||||
showCleanDialog = {this.state.showCleanDialog}
|
||||
allMonkeysAreDead = {this.state.allMonkeysAreDead}
|
||||
|
@ -44,25 +49,25 @@ class StartOverPageComponent extends AuthComponent {
|
|||
If you are finished and want to start over with a fresh configuration, erase the logs and clear the map
|
||||
you can go ahead and
|
||||
</p>
|
||||
<p style={{margin: '20px'}}>
|
||||
<button className="btn btn-danger btn-lg center-block"
|
||||
<p style={{margin: '20px'}} className={'text-center'}>
|
||||
<Button className="btn btn-danger btn-lg center-block"
|
||||
onClick={() => {
|
||||
this.setState({showCleanDialog: true});
|
||||
this.updateMonkeysRunning();
|
||||
}
|
||||
}>
|
||||
Reset the Environment
|
||||
</button>
|
||||
</Button>
|
||||
</p>
|
||||
<div className="alert alert-info">
|
||||
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
|
||||
<FontAwesomeIcon icon={faInfoCircle} style={{'marginRight': '5px'}}/>
|
||||
You don't have to reset the environment to keep running monkeys.
|
||||
You can continue and <Link to="/run-monkey">Run More Monkeys</Link> as you wish,
|
||||
and see the results on the <Link to="/infection/map">Infection Map</Link> without deleting anything.
|
||||
</div>
|
||||
{this.state.cleaned ?
|
||||
<div className="alert alert-success">
|
||||
<i className="glyphicon glyphicon-ok-sign" style={{'marginRight': '5px'}}/>
|
||||
<FontAwesomeIcon icon={faCheck} style={{'marginRight': '5px'}}/>
|
||||
Environment was reset successfully
|
||||
</div>
|
||||
: ''}
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import React from 'react';
|
||||
import {Button, Col} from 'react-bootstrap';
|
||||
import JSONTree from 'react-json-tree'
|
||||
import JSONTree from 'react-json-tree';
|
||||
import {DataTable} from 'react-data-components';
|
||||
import AuthComponent from '../AuthComponent';
|
||||
import download from 'downloadjs'
|
||||
import download from 'downloadjs';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
|
||||
import '../../styles/TelemetryPage.scss';
|
||||
import {faDownload} from "@fortawesome/free-solid-svg-icons/faDownload";
|
||||
|
||||
const renderJson = (val) => <JSONTree data={val} level={1} theme="eighties" invertTheme={true}/>;
|
||||
const renderTime = (val) => val.split('.')[0];
|
||||
|
@ -41,9 +45,10 @@ class TelemetryPageComponent extends AuthComponent {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
|
||||
lg={{offset: 3, span: 9}} xl={{offset: 2, span: 7}}
|
||||
className={'main'}>
|
||||
<div>
|
||||
<Col xs={12} lg={8}>
|
||||
<h1 className="page-title">Log</h1>
|
||||
<div className="data-table-container">
|
||||
<DataTable
|
||||
|
@ -55,21 +60,18 @@ class TelemetryPageComponent extends AuthComponent {
|
|||
pageLengthOptions={[20, 50, 100]}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
</div>
|
||||
<div>
|
||||
<Col xs={12} lg={8}>
|
||||
<h1 className="page-title"> Monkey Island Logs </h1>
|
||||
<div className="text-center" style={{marginBottom: '20px'}}>
|
||||
<p style={{'marginBottom': '2em', 'fontSize': '1.2em'}}> Download Monkey Island internal log file </p>
|
||||
<Button bsSize="large" onClick={() => {
|
||||
this.downloadIslandLog();
|
||||
}}>
|
||||
<i className="glyphicon glyphicon-download"/> Download </Button>
|
||||
<FontAwesomeIcon icon={faDownload}/> Download </Button>
|
||||
</div>
|
||||
</Col>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
import {Col, Button} from 'react-bootstrap';
|
||||
import '../../styles/Collapse.scss';
|
||||
import '../../styles/report/AttackReport.scss';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import {faCircle} from '@fortawesome/free-solid-svg-icons/faCircle';
|
||||
import {faRadiation} from '@fortawesome/free-solid-svg-icons/faRadiation';
|
||||
|
@ -101,9 +100,9 @@ class AttackReport extends React.Component {
|
|||
<div>
|
||||
<p>
|
||||
This report shows information about
|
||||
<Button bsStyle={'link'}
|
||||
<Button variant={'link'}
|
||||
href={'https://attack.mitre.org/'}
|
||||
bsSize={'lg'}
|
||||
size={'lg'}
|
||||
className={'attack-link'}
|
||||
target={'_blank'}>
|
||||
Mitre ATT&CK™
|
||||
|
|
|
@ -17,6 +17,8 @@ import PrintReportButton from './common/PrintReportButton';
|
|||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faMinus } from '@fortawesome/free-solid-svg-icons/faMinus';
|
||||
import guardicoreLogoImage from '../../images/guardicore-logo.png'
|
||||
import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons';
|
||||
import '../../styles/App.css';
|
||||
|
||||
|
||||
class ReportPageComponent extends AuthComponent {
|
||||
|
@ -145,7 +147,7 @@ class ReportPageComponent extends AuthComponent {
|
|||
''
|
||||
:
|
||||
<p className="alert alert-info">
|
||||
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} style={{'marginRight': '5px'}}/>
|
||||
To improve the monkey's detection rates, try adding users and passwords and enable the "Local
|
||||
network
|
||||
scan" config value under <b>Basic - Network</b>.
|
||||
|
@ -153,8 +155,8 @@ class ReportPageComponent extends AuthComponent {
|
|||
}
|
||||
<p>
|
||||
The first monkey run was started on <span
|
||||
className="label label-info">{this.state.report.overview.monkey_start_time}</span>. After <span
|
||||
className="label label-info">{this.state.report.overview.monkey_duration}</span>, all monkeys finished
|
||||
className="badge badge-info">{this.state.report.overview.monkey_start_time}</span>. After <span
|
||||
className="badge badge-info">{this.state.report.overview.monkey_duration}</span>, all monkeys finished
|
||||
propagation attempts.
|
||||
</p>
|
||||
<p>
|
||||
|
@ -241,7 +243,7 @@ class ReportPageComponent extends AuthComponent {
|
|||
}).length > 0 ?
|
||||
<div>
|
||||
During this simulated attack the Monkey uncovered <span
|
||||
className="label label-warning">
|
||||
className="badge badge-warning">
|
||||
{this.state.report.overview.issues.filter(function (x) {
|
||||
return x === true;
|
||||
}).length} threats</span>:
|
||||
|
@ -295,7 +297,7 @@ class ReportPageComponent extends AuthComponent {
|
|||
:
|
||||
<div>
|
||||
During this simulated attack the Monkey uncovered <span
|
||||
className="label label-success">0 threats</span>.
|
||||
className="badge badge-success">0 threats</span>.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
@ -379,9 +381,9 @@ class ReportPageComponent extends AuthComponent {
|
|||
<div>
|
||||
<p>
|
||||
The Monkey discovered <span
|
||||
className="label label-warning">{this.state.report.glance.scanned.length}</span> machines and
|
||||
className="badge badge-warning">{this.state.report.glance.scanned.length}</span> machines and
|
||||
successfully breached <span
|
||||
className="label label-danger">{this.state.report.glance.exploited.length}</span> of them.
|
||||
className="badge badge-danger">{this.state.report.glance.exploited.length}</span> of them.
|
||||
</p>
|
||||
<div className="text-center" style={{margin: '10px'}}>
|
||||
<Line style={{width: '300px', marginRight: '5px'}} percent={exploitPercentage} strokeWidth="4"
|
||||
|
@ -441,7 +443,7 @@ class ReportPageComponent extends AuthComponent {
|
|||
}
|
||||
|
||||
generateInfoBadges(data_array) {
|
||||
return data_array.map(badge_data => <span className="label label-info" style={{margin: '2px'}}>{badge_data}</span>);
|
||||
return data_array.map(badge_data => <span className="badge badge-info" style={{margin: '2px'}}>{badge_data}</span>);
|
||||
}
|
||||
|
||||
generateCrossSegmentIssue(crossSegmentIssue) {
|
||||
|
@ -466,21 +468,21 @@ class ReportPageComponent extends AuthComponent {
|
|||
}
|
||||
|
||||
generateShellshockPathListBadges(paths) {
|
||||
return paths.map(path => <span className="label label-warning" style={{margin: '2px'}}>{path}</span>);
|
||||
return paths.map(path => <span className="badge badge-warning" style={{margin: '2px'}}>{path}</span>);
|
||||
}
|
||||
|
||||
generateSmbPasswordIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
|
||||
Change <span className="badge badge-success">{issue.username}</span>'s password to a complex one-use password
|
||||
that is not shared with other computers on the network.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="label label-danger">SMB</span> attack.
|
||||
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
|
||||
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="badge badge-danger">SMB</span> attack.
|
||||
<br/>
|
||||
The Monkey authenticated over the SMB protocol with user <span
|
||||
className="label label-success">{issue.username}</span> and its password.
|
||||
className="badge badge-success">{issue.username}</span> and its password.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
|
@ -489,15 +491,15 @@ class ReportPageComponent extends AuthComponent {
|
|||
generateSmbPthIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
|
||||
Change <span className="badge badge-success">{issue.username}</span>'s password to a complex one-use password
|
||||
that is not shared with other computers on the network.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="label label-danger">SMB</span> attack.
|
||||
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
|
||||
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="badge badge-danger">SMB</span> attack.
|
||||
<br/>
|
||||
The Monkey used a pass-the-hash attack over SMB protocol with user <span
|
||||
className="label label-success">{issue.username}</span>.
|
||||
className="badge badge-success">{issue.username}</span>.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
|
@ -506,15 +508,15 @@ class ReportPageComponent extends AuthComponent {
|
|||
generateWmiPasswordIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
|
||||
Change <span className="badge badge-success">{issue.username}</span>'s password to a complex one-use password
|
||||
that is not shared with other computers on the network.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="label label-danger">WMI</span> attack.
|
||||
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
|
||||
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="badge badge-danger">WMI</span> attack.
|
||||
<br/>
|
||||
The Monkey authenticated over the WMI protocol with user <span
|
||||
className="label label-success">{issue.username}</span> and its password.
|
||||
className="badge badge-success">{issue.username}</span> and its password.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
|
@ -523,15 +525,15 @@ class ReportPageComponent extends AuthComponent {
|
|||
generateWmiPthIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
|
||||
Change <span className="badge badge-success">{issue.username}</span>'s password to a complex one-use password
|
||||
that is not shared with other computers on the network.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="label label-danger">WMI</span> attack.
|
||||
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
|
||||
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="badge badge-danger">WMI</span> attack.
|
||||
<br/>
|
||||
The Monkey used a pass-the-hash attack over WMI protocol with user <span
|
||||
className="label label-success">{issue.username}</span>.
|
||||
className="badge badge-success">{issue.username}</span>.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
|
@ -540,15 +542,15 @@ class ReportPageComponent extends AuthComponent {
|
|||
generateSshIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
|
||||
Change <span className="badge badge-success">{issue.username}</span>'s password to a complex one-use password
|
||||
that is not shared with other computers on the network.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="label label-danger">SSH</span> attack.
|
||||
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
|
||||
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="badge badge-danger">SSH</span> attack.
|
||||
<br/>
|
||||
The Monkey authenticated over the SSH protocol with user <span
|
||||
className="label label-success">{issue.username}</span> and its password.
|
||||
className="badge badge-success">{issue.username}</span> and its password.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
|
@ -557,14 +559,14 @@ class ReportPageComponent extends AuthComponent {
|
|||
generateSshKeysIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Protect <span className="label label-success">{issue.ssh_key}</span> private key with a pass phrase.
|
||||
Protect <span className="badge badge-success">{issue.ssh_key}</span> private key with a pass phrase.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="label label-danger">SSH</span> attack.
|
||||
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
|
||||
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="badge badge-danger">SSH</span> attack.
|
||||
<br/>
|
||||
The Monkey authenticated over the SSH protocol with private key <span
|
||||
className="label label-success">{issue.ssh_key}</span>.
|
||||
className="badge badge-success">{issue.ssh_key}</span>.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
|
@ -574,17 +576,17 @@ class ReportPageComponent extends AuthComponent {
|
|||
generateSambaCryIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
|
||||
Change <span className="badge badge-success">{issue.username}</span>'s password to a complex one-use password
|
||||
that is not shared with other computers on the network.
|
||||
<br/>
|
||||
Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="label label-danger">SambaCry</span> attack.
|
||||
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
|
||||
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="badge badge-danger">SambaCry</span> attack.
|
||||
<br/>
|
||||
The Monkey authenticated over the SMB protocol with user <span
|
||||
className="label label-success">{issue.username}</span> and its password, and used the SambaCry
|
||||
className="badge badge-success">{issue.username}</span> and its password, and used the SambaCry
|
||||
vulnerability.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
|
@ -596,9 +598,9 @@ class ReportPageComponent extends AuthComponent {
|
|||
<li>
|
||||
Update your VSFTPD server to the latest version vsftpd-3.0.3.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) has a backdoor running at port <span
|
||||
className="label label-danger">6200</span>.
|
||||
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
|
||||
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) has a backdoor running at port <span
|
||||
className="badge badge-danger">6200</span>.
|
||||
<br/>
|
||||
The attack was made possible because the VSFTPD server was not patched against CVE-2011-2523.
|
||||
<br/><br/>In July 2011, it was discovered that vsftpd version 2.3.4 downloadable from the master site had been
|
||||
|
@ -621,9 +623,9 @@ class ReportPageComponent extends AuthComponent {
|
|||
<li>
|
||||
Update your Elastic Search server to version 1.4.3 and up.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to an <span
|
||||
className="label label-danger">Elastic Groovy</span> attack.
|
||||
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
|
||||
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to an <span
|
||||
className="badge badge-danger">Elastic Groovy</span> attack.
|
||||
<br/>
|
||||
The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.
|
||||
</CollapsibleWellComponent>
|
||||
|
@ -636,12 +638,12 @@ class ReportPageComponent extends AuthComponent {
|
|||
<li>
|
||||
Update your Bash to a ShellShock-patched version.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="label label-danger">ShellShock</span> attack.
|
||||
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
|
||||
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="badge badge-danger">ShellShock</span> attack.
|
||||
<br/>
|
||||
The attack was made possible because the HTTP server running on TCP port <span
|
||||
className="label label-info">{issue.port}</span> was vulnerable to a shell injection attack on the
|
||||
className="badge badge-info">{issue.port}</span> was vulnerable to a shell injection attack on the
|
||||
paths: {this.generateShellshockPathListBadges(issue.paths)}.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
|
@ -654,8 +656,8 @@ class ReportPageComponent extends AuthComponent {
|
|||
Delete VM Access plugin configuration files.
|
||||
<CollapsibleWellComponent>
|
||||
Credentials could be stolen from <span
|
||||
className="label label-primary">{issue.machine}</span> for the following users <span
|
||||
className="label label-primary">{issue.users}</span>. Read more about the security issue and remediation <a
|
||||
className="badge badge-primary">{issue.machine}</span> for the following users <span
|
||||
className="badge badge-primary">{issue.users}</span>. Read more about the security issue and remediation <a
|
||||
href="https://www.guardicore.com/2018/03/recovering-plaintext-passwords-azure/"
|
||||
>here</a>.
|
||||
</CollapsibleWellComponent>
|
||||
|
@ -668,9 +670,9 @@ class ReportPageComponent extends AuthComponent {
|
|||
<li>
|
||||
Install the latest Windows updates or upgrade to a newer operating system.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="label label-danger">Conficker</span> attack.
|
||||
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
|
||||
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="badge badge-danger">Conficker</span> attack.
|
||||
<br/>
|
||||
The attack was made possible because the target machine used an outdated and unpatched operating system
|
||||
vulnerable to Conficker.
|
||||
|
@ -685,7 +687,7 @@ class ReportPageComponent extends AuthComponent {
|
|||
Segment your network and make sure there is no communication between machines from different segments.
|
||||
<CollapsibleWellComponent>
|
||||
The network can probably be segmented. A monkey instance on <span
|
||||
className="label label-primary">{issue.machine}</span> in the
|
||||
className="badge badge-primary">{issue.machine}</span> in the
|
||||
networks {this.generateInfoBadges(issue.networks)}
|
||||
could directly access the Monkey Island server in the
|
||||
networks {this.generateInfoBadges(issue.server_networks)}.
|
||||
|
@ -725,7 +727,7 @@ class ReportPageComponent extends AuthComponent {
|
|||
admin sharing.
|
||||
<CollapsibleWellComponent>
|
||||
Here is a list of machines which the account <span
|
||||
className="label label-primary">{issue.username}</span> is defined as an administrator:
|
||||
className="badge badge-primary">{issue.username}</span> is defined as an administrator:
|
||||
{this.generateInfoBadges(issue.shared_machines)}
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
|
@ -752,8 +754,8 @@ class ReportPageComponent extends AuthComponent {
|
|||
Use micro-segmentation policies to disable communication other than the required.
|
||||
<CollapsibleWellComponent>
|
||||
Machines are not locked down at port level. Network tunnel was set up from <span
|
||||
className="label label-primary">{issue.machine}</span> to <span
|
||||
className="label label-primary">{issue.dest}</span>.
|
||||
className="badge badge-primary">{issue.machine}</span> to <span
|
||||
className="badge badge-primary">{issue.dest}</span>.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
|
@ -764,9 +766,9 @@ class ReportPageComponent extends AuthComponent {
|
|||
<li>
|
||||
Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.
|
||||
<CollapsibleWellComponent>
|
||||
Struts2 server at <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to <span
|
||||
className="label label-danger">remote code execution</span> attack.
|
||||
Struts2 server at <span className="badge badge-primary">{issue.machine}</span> (<span
|
||||
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to <span
|
||||
className="badge badge-danger">remote code execution</span> attack.
|
||||
<br/>
|
||||
The attack was made possible because the server is using an old version of Jakarta based file upload
|
||||
Multipart parser. For possible work-arounds and more info read <a
|
||||
|
@ -782,9 +784,9 @@ class ReportPageComponent extends AuthComponent {
|
|||
<li>
|
||||
Update Oracle WebLogic server to the latest supported version.
|
||||
<CollapsibleWellComponent>
|
||||
Oracle WebLogic server at <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to one of <span
|
||||
className="label label-danger">remote code execution</span> attacks.
|
||||
Oracle WebLogic server at <span className="badge badge-primary">{issue.machine}</span> (<span
|
||||
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to one of <span
|
||||
className="badge badge-danger">remote code execution</span> attacks.
|
||||
<br/>
|
||||
The attack was made possible due to one of the following vulnerabilities:
|
||||
<a href={'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-10271'}> CVE-2017-10271</a> or
|
||||
|
@ -801,9 +803,9 @@ class ReportPageComponent extends AuthComponent {
|
|||
href="http://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/SecureMode.html">
|
||||
add Kerberos authentication</a>).
|
||||
<CollapsibleWellComponent>
|
||||
The Hadoop server at <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to <span
|
||||
className="label label-danger">remote code execution</span> attack.
|
||||
The Hadoop server at <span className="badge badge-primary">{issue.machine}</span> (<span
|
||||
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to <span
|
||||
className="badge badge-danger">remote code execution</span> attack.
|
||||
<br/>
|
||||
The attack was made possible due to default Hadoop/Yarn configuration being insecure.
|
||||
</CollapsibleWellComponent>
|
||||
|
@ -816,9 +818,9 @@ class ReportPageComponent extends AuthComponent {
|
|||
<li>
|
||||
Disable the xp_cmdshell option.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="label label-danger">MSSQL exploit attack</span>.
|
||||
The machine <span className="badge badge-primary">{issue.machine}</span> (<span
|
||||
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="badge badge-danger">MSSQL exploit attack</span>.
|
||||
<br/>
|
||||
The attack was made possible because the target machine used an outdated MSSQL server configuration allowing
|
||||
the usage of the xp_cmdshell command. To learn more about how to disable this feature, read <a
|
||||
|
|
|
@ -109,8 +109,8 @@ class TechniqueDropdowns extends React.Component{
|
|||
<div className='attack-technique-list-component'>
|
||||
<h3>
|
||||
List of all techniques
|
||||
<Button bsStyle='link'
|
||||
bsSize='large'
|
||||
<Button variant='link'
|
||||
size='lg'
|
||||
onClick={() => this.toggleTechList()}
|
||||
className={classNames({'toggle-btn': true,
|
||||
'toggled-off' : this.state.techniquesHidden,
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import React, {Component} from 'react';
|
||||
import * as PropTypes from 'prop-types';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faExclamationTriangle} from "@fortawesome/free-solid-svg-icons/faExclamationTriangle";
|
||||
|
||||
export default class MonkeysStillAliveWarning extends Component {
|
||||
render() {
|
||||
|
@ -9,7 +11,7 @@ export default class MonkeysStillAliveWarning extends Component {
|
|||
''
|
||||
:
|
||||
(<p className="alert alert-warning">
|
||||
<i className="glyphicon glyphicon-warning-sign" style={{'marginRight': '5px'}}/>
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} style={{'marginRight': '5px'}}/>
|
||||
Some monkeys are still running. To get the best report it's best to wait for all of them to finish
|
||||
running.
|
||||
</p>)
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import React, {Component} from 'react';
|
||||
import {NavLink} from 'react-router-dom';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faExclamationTriangle} from "@fortawesome/free-solid-svg-icons/faExclamationTriangle";
|
||||
|
||||
export default class MustRunMonkeyWarning extends Component {
|
||||
render() {
|
||||
return <p className="alert alert-warning">
|
||||
<i className="glyphicon glyphicon-warning-sign" style={{'marginRight': '5px'}}/>
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} style={{'marginRight': '5px'}}/>
|
||||
<b>You have to <NavLink to="/run-monkey">run a monkey</NavLink> before generating a report!</b>
|
||||
</p>
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import React, {Component} from 'react';
|
||||
import {Button} from 'react-bootstrap';
|
||||
import * as PropTypes from 'prop-types';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faPrint } from '@fortawesome/free-solid-svg-icons/faPrint';
|
||||
|
||||
export default class PrintReportButton extends Component {
|
||||
render() {
|
||||
return <div className="text-center no-print">
|
||||
<Button bsSize="large" onClick={this.props.onClick}><i className="glyphicon glyphicon-print"/> Print
|
||||
<Button size="md" variant={"outline-standard"} onClick={this.props.onClick}>
|
||||
<FontAwesomeIcon icon={faPrint}/> Print
|
||||
Report</Button>
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -1,17 +1,20 @@
|
|||
import React, {Component, Fragment} from 'react';
|
||||
import * as PropTypes from 'prop-types';
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import {faCheck, faExclamationTriangle} from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
export default class SecurityIssuesGlance extends Component {
|
||||
render() {
|
||||
return <Fragment>
|
||||
{
|
||||
this.props.issuesFound ?
|
||||
(<p className="alert alert-danger">
|
||||
<i className="glyphicon glyphicon-exclamation-sign" style={{'marginRight': '5px'}}/>
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} style={{'marginRight': '5px'}}/>
|
||||
Critical security issues were detected!
|
||||
</p>) :
|
||||
(<p className="alert alert-success">
|
||||
<i className="glyphicon glyphicon-ok-sign" style={{'marginRight': '5px'}}/>
|
||||
<FontAwesomeIcon icon={faCheck} style={{'marginRight': '5px'}}/>
|
||||
No critical security issues were detected.
|
||||
</p>)
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ class BreachedServersComponent extends React.Component {
|
|||
<>
|
||||
<p>
|
||||
The Monkey successfully breached <span
|
||||
className="label label-danger">{this.props.data.length}</span> {Pluralize('machine', this.props.data.length)}:
|
||||
className="badge badge-danger">{this.props.data.length}</span> {Pluralize('machine', this.props.data.length)}:
|
||||
</p>
|
||||
<div className="data-table-container">
|
||||
<ReactTable
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import {Button, Collapse, Well} from 'react-bootstrap';
|
||||
import {Button, Collapse, Card} from 'react-bootstrap';
|
||||
|
||||
class CollapsibleWellComponent extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -12,18 +12,19 @@ class CollapsibleWellComponent extends React.Component {
|
|||
render() {
|
||||
let well =
|
||||
(
|
||||
<Well style={{margin: '10px'}}>
|
||||
<Card body>
|
||||
{this.props.children}
|
||||
</Well>
|
||||
</Card>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="no-print">
|
||||
<Button onClick={() => this.setState({open: !this.state.open})} bsStyle="link">
|
||||
<Button onClick={() => this.setState({open: !this.state.open})}
|
||||
variant="link">
|
||||
Read More...
|
||||
</Button>
|
||||
<Collapse in={this.state.open}>
|
||||
<Collapse in={this.state.open} style={{margin: '10px'}}>
|
||||
<div>
|
||||
{well}
|
||||
</div>
|
||||
|
|
|
@ -68,8 +68,8 @@ class PostBreachComponent extends React.Component {
|
|||
<>
|
||||
<p>
|
||||
The Monkey performed <span
|
||||
className="label label-danger">{pbaCount}</span> post-breach {Pluralize('action', pbaCount)} on <span
|
||||
className="label label-warning">{pbaMachines.length}</span> {Pluralize('machine', pbaMachines.length)}:
|
||||
className="badge badge-danger">{pbaCount}</span> post-breach {Pluralize('action', pbaCount)} on <span
|
||||
className="badge badge-warning">{pbaMachines.length}</span> {Pluralize('machine', pbaMachines.length)}:
|
||||
</p>
|
||||
<div className="data-table-container">
|
||||
<ReactTable
|
||||
|
|
|
@ -45,9 +45,9 @@ class ScannedServersComponent extends React.Component {
|
|||
<>
|
||||
<p>
|
||||
The Monkey discovered
|
||||
<span className="label label-danger">{scannedServicesAmount}</span> open
|
||||
<span className="badge badge-danger">{scannedServicesAmount}</span> open
|
||||
{Pluralize('service', scannedServicesAmount)} on
|
||||
<span className="label label-warning">{scannedMachinesCount}</span>
|
||||
<span className="badge badge-warning">{scannedMachinesCount}</span>
|
||||
{Pluralize('machine', scannedMachinesCount)}:
|
||||
</p>
|
||||
<div className="data-table-container">
|
||||
|
|
|
@ -32,7 +32,7 @@ export default class EventsButton extends Component {
|
|||
hideCallback={this.hide}
|
||||
exportFilename={this.props.exportFilename}/>
|
||||
<div className="text-center" style={{'display': 'grid'}}>
|
||||
<Button className="btn btn-primary btn-lg" onClick={this.show}>
|
||||
<Button variant={'monkey-info'} size={'lg'} onClick={this.show}>
|
||||
<FontAwesomeIcon icon={faList}/> Events {this.createEventsAmountBadge()}
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -41,7 +41,7 @@ export default class EventsButton extends Component {
|
|||
|
||||
createEventsAmountBadge() {
|
||||
const eventsAmountBadgeContent = this.props.event_count > 9 ? '9+' : this.props.event_count;
|
||||
return <Badge>{eventsAmountBadgeContent}</Badge>;
|
||||
return <Badge variant={'monkey-info-light'}>{eventsAmountBadgeContent}</Badge>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,14 +26,13 @@ export default class EventsModal extends AuthComponent {
|
|||
</h3>
|
||||
<hr/>
|
||||
<p>
|
||||
There {Pluralize('is', this.props.event_count)} {<div
|
||||
className={'label label-primary'}>{this.props.event_count}</div>}
|
||||
{Pluralize('event', this.props.event_count)} associated
|
||||
with this finding.
|
||||
{<div className={'label label-primary'}>
|
||||
{this.props.latest_events.length + this.props.oldest_events.length}
|
||||
</div>} {Pluralize('is', this.props.event_count)} displayed below.
|
||||
All events can be exported to json.
|
||||
There {Pluralize('is', this.props.event_count)} {
|
||||
<div className={'badge badge-primary'}>{this.props.event_count}</div>
|
||||
} {Pluralize('event', this.props.event_count)} associated with this finding. {
|
||||
<div className={'badge badge-primary'}>
|
||||
{this.props.latest_events.length + this.props.oldest_events.length}
|
||||
</div>
|
||||
} {Pluralize('is', this.props.event_count)} displayed below. All events can be exported using the Export button.
|
||||
</p>
|
||||
{this.props.event_count > 5 ? this.renderButtons() : null}
|
||||
<EventsTimeline events={this.props.oldest_events}/>
|
||||
|
|
|
@ -5,7 +5,7 @@ import * as PropTypes from 'prop-types';
|
|||
export default class EventsModalButtons extends Component {
|
||||
render() {
|
||||
return <div className="text-center">
|
||||
<button type="button" className="btn btn-success btn-lg" style={{margin: '5px'}}
|
||||
<button type="button" className="btn btn-monkey-info btn-lg" style={{margin: '5px'}}
|
||||
onClick={this.props.onClickClose}>
|
||||
Close
|
||||
</button>
|
||||
|
|
|
@ -5,7 +5,7 @@ import * as PropTypes from 'prop-types';
|
|||
import PillarLabel from './PillarLabel';
|
||||
import EventsButton from './EventsButton';
|
||||
|
||||
const EVENTS_COLUMN_MAX_WIDTH = 160;
|
||||
const EVENTS_COLUMN_MAX_WIDTH = 170;
|
||||
const PILLARS_COLUMN_MAX_WIDTH = 200;
|
||||
const columns = [
|
||||
{
|
||||
|
@ -22,7 +22,7 @@ const columns = [
|
|||
latest_events={x.latest_events}
|
||||
oldest_events={x.oldest_events}
|
||||
event_count={x.event_count}
|
||||
exportFilename={'Events_' + x.test_key}/>;
|
||||
exportFilename={'Events_' + x.test_key} />;
|
||||
},
|
||||
maxWidth: EVENTS_COLUMN_MAX_WIDTH
|
||||
},
|
||||
|
|
|
@ -23,7 +23,7 @@ const pillarToIcon = {
|
|||
|
||||
export default class PillarLabel extends Component {
|
||||
render() {
|
||||
const className = 'label ' + statusToLabelType[this.props.status];
|
||||
const className = 'badge ' + statusToLabelType[this.props.status];
|
||||
return <div className={className} style={{margin: '2px', display: 'inline-block'}}>
|
||||
<FontAwesomeIcon icon={pillarToIcon[this.props.pillar]}/> {this.props.pillar}</div>
|
||||
}
|
||||
|
|
|
@ -2,29 +2,43 @@ import React, {Component} from 'react';
|
|||
import StatusLabel from './StatusLabel';
|
||||
import {ZeroTrustStatuses} from './ZeroTrustPillars';
|
||||
import {NavLink} from 'react-router-dom';
|
||||
import {Panel} from 'react-bootstrap';
|
||||
import {Card, Collapse} from 'react-bootstrap';
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faChevronDown } from '@fortawesome/free-solid-svg-icons/faChevronDown';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown';
|
||||
|
||||
|
||||
class ZeroTrustReportLegend extends Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
open: false,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const legendContent = this.getLegendContent();
|
||||
const {open} = this.state;
|
||||
|
||||
return (
|
||||
<Panel>
|
||||
<Panel.Heading>
|
||||
<Panel.Title toggle>
|
||||
<h3><FontAwesomeIcon icon={faChevronDown} /> Legend</h3>
|
||||
</Panel.Title>
|
||||
</Panel.Heading>
|
||||
<Panel.Collapse>
|
||||
<Panel.Body>
|
||||
{legendContent}
|
||||
</Panel.Body>
|
||||
</Panel.Collapse>
|
||||
</Panel>
|
||||
<Card>
|
||||
<Card.Header onClick={() => this.setState({open: !open})}
|
||||
aria-controls='collapse-content'
|
||||
aria-expanded={open}
|
||||
className={'collapse-control'}>
|
||||
<h3><FontAwesomeIcon icon={faChevronDown}/> Legend</h3>
|
||||
</Card.Header>
|
||||
|
||||
<Collapse in={this.state.open}>
|
||||
<Card.Body>
|
||||
<div id='collapse-content'>
|
||||
{legendContent}
|
||||
</div>
|
||||
</Card.Body>
|
||||
</Collapse>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -35,7 +49,8 @@ class ZeroTrustReportLegend extends Component {
|
|||
<div style={{display: 'inline-block'}}>
|
||||
<StatusLabel showText={true} status={ZeroTrustStatuses.failed}/>
|
||||
</div>
|
||||
{'\t'}At least one of the tests related to this component failed. This means that the Infection Monkey detected an
|
||||
{'\t'}At least one of the tests related to this component failed. This means that the Infection Monkey
|
||||
detected an
|
||||
unmet Zero Trust requirement.
|
||||
</li>
|
||||
<li>
|
||||
|
@ -55,7 +70,7 @@ class ZeroTrustReportLegend extends Component {
|
|||
<StatusLabel showText={true} status={ZeroTrustStatuses.unexecuted}/>
|
||||
</div>
|
||||
{'\t'}This status means the test wasn't executed.To activate more tests, refer to the Monkey <NavLink
|
||||
to="/configuration"><u>configuration</u></NavLink> page.
|
||||
to="/configure"><u>configuration</u></NavLink> page.
|
||||
</li>
|
||||
</ul>
|
||||
</div>;
|
||||
|
|
|
@ -3,32 +3,46 @@ import PillarLabel from './PillarLabel';
|
|||
import PrinciplesStatusTable from './PrinciplesStatusTable';
|
||||
import React from 'react';
|
||||
import * as PropTypes from 'prop-types';
|
||||
import {Panel} from 'react-bootstrap';
|
||||
import {Card, Collapse} from 'react-bootstrap';
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faChevronDown } from '@fortawesome/free-solid-svg-icons';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faChevronDown} from '@fortawesome/free-solid-svg-icons';
|
||||
import '../../../styles/report/ZeroTrustReport.scss';
|
||||
|
||||
export default class SinglePillarPrinciplesStatus extends AuthComponent {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
open: false,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {open} = this.state;
|
||||
if (this.props.principlesStatus.length === 0) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<Panel>
|
||||
<Panel.Heading>
|
||||
<Panel.Title toggle>
|
||||
<h3 style={{textAlign: 'center', marginTop: '1px', marginBottom: '1px'}}>
|
||||
<FontAwesomeIcon icon={faChevronDown}/> <PillarLabel pillar={this.props.pillar}
|
||||
status={this.props.pillarsToStatuses[this.props.pillar]}/>
|
||||
</h3>
|
||||
</Panel.Title>
|
||||
</Panel.Heading>
|
||||
<Panel.Collapse>
|
||||
<Panel.Body>
|
||||
<PrinciplesStatusTable principlesStatus={this.props.principlesStatus}/>
|
||||
</Panel.Body>
|
||||
</Panel.Collapse>
|
||||
</Panel>
|
||||
<Card className={'principles-status-card'}>
|
||||
<Card.Header onClick={() => this.setState({open: !open})}
|
||||
aria-controls='collapse-content'
|
||||
aria-expanded={open}
|
||||
className={'collapse-control'}>
|
||||
<h3 style={{textAlign: 'center', marginTop: '1px', marginBottom: '1px'}}>
|
||||
<FontAwesomeIcon icon={faChevronDown}/> <PillarLabel pillar={this.props.pillar}
|
||||
status={this.props.pillarsToStatuses[this.props.pillar]}/>
|
||||
</h3>
|
||||
</Card.Header>
|
||||
<Collapse in={this.state.open}>
|
||||
<Card.Body>
|
||||
<div id={'collapse-content'}>
|
||||
<PrinciplesStatusTable principlesStatus={this.props.principlesStatus}/>
|
||||
</div>
|
||||
</Card.Body>
|
||||
</Collapse>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,10 +15,10 @@ const statusToIcon = {
|
|||
};
|
||||
|
||||
export const statusToLabelType = {
|
||||
'Passed': 'label-success',
|
||||
'Verify': 'label-warning',
|
||||
'Failed': 'label-danger',
|
||||
'Unexecuted': 'label-default'
|
||||
'Passed': 'badge-success',
|
||||
'Verify': 'badge-warning',
|
||||
'Failed': 'badge-danger',
|
||||
'Unexecuted': 'badge-default'
|
||||
};
|
||||
|
||||
export default class StatusLabel extends Component {
|
||||
|
@ -29,7 +29,7 @@ export default class StatusLabel extends Component {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={'label ' + statusToLabelType[this.props.status]} style={{display: 'flow-root'}}>
|
||||
<div className={'badge ' + statusToLabelType[this.props.status]} style={{display: 'flow-root'}}>
|
||||
<FontAwesomeIcon icon={statusToIcon[this.props.status]} size={this.props.size}/>{text}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, {Component} from 'react';
|
||||
import {Col, Grid, Row} from 'react-bootstrap';
|
||||
import {Col, Container, Row} from 'react-bootstrap';
|
||||
import PillarsOverview from './PillarOverview';
|
||||
import ZeroTrustReportLegend from './ReportLegend';
|
||||
import * as PropTypes from 'prop-types';
|
||||
|
@ -8,7 +8,7 @@ export default class SummarySection extends Component {
|
|||
render() {
|
||||
return <div id="summary-section">
|
||||
<h2>Summary</h2>
|
||||
<Grid fluid={true}>
|
||||
<Container fluid>
|
||||
<Row>
|
||||
<Col xs={12} sm={12} md={12} lg={12}>
|
||||
<p>
|
||||
|
@ -28,7 +28,7 @@ export default class SummarySection extends Component {
|
|||
<ZeroTrustReportLegend/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Grid>
|
||||
</Container>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,21 +12,19 @@ class ArcNode extends React.Component {
|
|||
|
||||
return (
|
||||
<g transform={'rotate(180)'} id={data.node.pillar} key={prefix + 'arcGroup' + index}>
|
||||
<OverlayTrigger ref={'overlay'} key={prefix + 'arcOverlayTrigger' + index} trigger={null}
|
||||
<OverlayTrigger key={prefix + 'arcOverlayTrigger' + index}
|
||||
trigger={['hover', 'focus']}
|
||||
placement={data.popover}
|
||||
overlay={<Popover id={prefix + 'ArcPopover' + index} style={{backgroundColor: data.hex}}
|
||||
title={data.node.pillar}>{data.tooltip}</Popover>} rootClose>
|
||||
overlay={<Popover id={prefix + 'ArcPopover' + index} style={{backgroundColor: data.hex}}>
|
||||
<Popover.Title>{data.node.pillar}</Popover.Title>
|
||||
<Popover.Content>{data.tooltip}</Popover.Content>
|
||||
</Popover>} rootClose>
|
||||
<path
|
||||
|
||||
id={prefix + 'Node_' + index}
|
||||
className={'arcNode'}
|
||||
data-tooltip={data.tooltip}
|
||||
d={arc()}
|
||||
fill={data.hex}
|
||||
onClick={this.handleClick.bind(this)}
|
||||
onMouseEnter={this.handleOver.bind(this)}
|
||||
onMouseLeave={this.handleOut.bind(this)}
|
||||
|
||||
/>
|
||||
</OverlayTrigger>
|
||||
<text x={0} dy={data.fontStyle.size * 1.2} fontSize={data.fontStyle.size} fill={'white'} textAnchor='middle'
|
||||
|
@ -39,23 +37,6 @@ class ArcNode extends React.Component {
|
|||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
handleClick() {
|
||||
this.props.disableHover(this.refs.overlay);
|
||||
}
|
||||
|
||||
handleOver() {
|
||||
if (this.props.hover) {
|
||||
this.refs.overlay.show();
|
||||
}
|
||||
}
|
||||
|
||||
handleOut() {
|
||||
if (this.props.hover) {
|
||||
this.refs.overlay.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ArcNode.propTypes = {
|
||||
|
|
|
@ -10,10 +10,14 @@ class CircularNode extends React.Component {
|
|||
let translate = 'translate(' + data.cx + ',' + data.cy + ')';
|
||||
return (
|
||||
<g transform={translate} id={data.node.pillar} key={prefix + 'circularGroup' + index}>
|
||||
<OverlayTrigger ref={'overlay'} key={prefix + 'CircularOverlay' + index} trigger={null} placement={data.popover}
|
||||
<OverlayTrigger key={prefix + 'CircularOverlay' + index}
|
||||
trigger={['hover', 'focus']}
|
||||
placement={data.popover}
|
||||
overlay={<Popover id={prefix + 'CircularClickPopover' + index}
|
||||
style={{backgroundColor: data.hex}}
|
||||
title={data.node.pillar}>{data.tooltip}</Popover>} rootClose>
|
||||
style={{backgroundColor: data.hex}}>
|
||||
<Popover.Title>{data.node.pillar}</Popover.Title>
|
||||
<Popover.Content>{data.tooltip}</Popover.Content>
|
||||
</Popover>} rootClose>
|
||||
<circle
|
||||
id={prefix + 'Node_' + index}
|
||||
className={'circularNode'}
|
||||
|
@ -21,10 +25,6 @@ class CircularNode extends React.Component {
|
|||
r={data.r}
|
||||
opacity={0.8}
|
||||
fill={data.hex}
|
||||
onClick={this.handleClick.bind(this)}
|
||||
onMouseEnter={this.handleOver.bind(this)}
|
||||
onMouseLeave={this.handleOut.bind(this)}
|
||||
|
||||
/>
|
||||
</OverlayTrigger>
|
||||
<foreignObject style={{fontSize: data.fontStyle.size, pointerEvents: 'none'}}
|
||||
|
@ -36,24 +36,6 @@ class CircularNode extends React.Component {
|
|||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
handleClick() {
|
||||
this.props.disableHover(this.refs.overlay);
|
||||
}
|
||||
|
||||
handleOver() {
|
||||
if (this.props.hover) {
|
||||
this.refs.overlay.show();
|
||||
}
|
||||
}
|
||||
|
||||
handleOut() {
|
||||
if (this.props.hover) {
|
||||
this.refs.overlay.hide();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
CircularNode.propTypes = {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue