diff --git a/monkey/common/cloud/aws_instance.py b/monkey/common/cloud/aws_instance.py index f80b44474..748bd8d04 100644 --- a/monkey/common/cloud/aws_instance.py +++ b/monkey/common/cloud/aws_instance.py @@ -1,22 +1,48 @@ +import json import re import urllib2 +import logging + __author__ = 'itay.mizeretz' +AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS = "169.254.169.254" +AWS_LATEST_METADATA_URI_PREFIX = 'http://{0}/latest/'.format(AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS) +ACCOUNT_ID_KEY = "accountId" + + +logger = logging.getLogger(__name__) + class AwsInstance(object): + """ + Class which gives useful information about the current instance you're on. + """ + def __init__(self): + self.instance_id = None + self.region = None + self.account_id = None + try: - self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id', timeout=2).read() + self.instance_id = urllib2.urlopen( + AWS_LATEST_METADATA_URI_PREFIX + 'meta-data/instance-id', timeout=2).read() self.region = self._parse_region( - urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()) - except urllib2.URLError: - self.instance_id = None - self.region = None + urllib2.urlopen(AWS_LATEST_METADATA_URI_PREFIX + 'meta-data/placement/availability-zone').read()) + except urllib2.URLError as e: + logger.error("Failed init of AwsInstance while getting metadata: {}".format(e.message)) + + try: + self.account_id = self._extract_account_id( + urllib2.urlopen( + AWS_LATEST_METADATA_URI_PREFIX + 'dynamic/instance-identity/document', timeout=2).read()) + except urllib2.URLError as e: + logger.error("Failed init of AwsInstance while getting dynamic instance data: {}".format(e.message)) @staticmethod def _parse_region(region_url_response): - # For a list of regions: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html + # For a list of regions, see: + # https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html # This regex will find any AWS region format string in the response. re_phrase = r'((?:us|eu|ap|ca|cn|sa)-[a-z]*-[0-9])' finding = re.findall(re_phrase, region_url_response, re.IGNORECASE) @@ -33,3 +59,21 @@ class AwsInstance(object): def is_aws_instance(self): return self.instance_id is not None + + @staticmethod + def _extract_account_id(instance_identity_document_response): + """ + Extracts the account id from the dynamic/instance-identity/document metadata path. + Based on https://forums.aws.amazon.com/message.jspa?messageID=409028 which has a few more solutions, + in case Amazon break this mechanism. + :param instance_identity_document_response: json returned via the web page ../dynamic/instance-identity/document + :return: The account id + """ + return json.loads(instance_identity_document_response)[ACCOUNT_ID_KEY] + + def get_account_id(self): + """ + :return: the AWS account ID which "owns" this instance. + See https://docs.aws.amazon.com/general/latest/gr/acct-identifiers.html + """ + return self.account_id diff --git a/monkey/common/cloud/aws_service.py b/monkey/common/cloud/aws_service.py index 6479721c8..41bb202bc 100644 --- a/monkey/common/cloud/aws_service.py +++ b/monkey/common/cloud/aws_service.py @@ -1,23 +1,44 @@ +import logging + import boto3 +import botocore from botocore.exceptions import ClientError -__author__ = 'itay.mizeretz' +from common.cloud.aws_instance import AwsInstance + +__author__ = ['itay.mizeretz', 'shay.nehmad'] + +INSTANCE_INFORMATION_LIST_KEY = 'InstanceInformationList' +INSTANCE_ID_KEY = 'InstanceId' +COMPUTER_NAME_KEY = 'ComputerName' +PLATFORM_TYPE_KEY = 'PlatformType' +IP_ADDRESS_KEY = 'IPAddress' + + +logger = logging.getLogger(__name__) + + +def filter_instance_data_from_aws_response(response): + return [{ + 'instance_id': x[INSTANCE_ID_KEY], + 'name': x[COMPUTER_NAME_KEY], + 'os': x[PLATFORM_TYPE_KEY].lower(), + 'ip_address': x[IP_ADDRESS_KEY] + } for x in response[INSTANCE_INFORMATION_LIST_KEY]] class AwsService(object): """ - Supplies various AWS services + A wrapper class around the boto3 client and session modules, which supplies various AWS services. + + This class will assume: + 1. That it's running on an EC2 instance + 2. That the instance is associated with the correct IAM role. See + https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#iam-role for details. """ - access_key_id = None - secret_access_key = None region = None - @staticmethod - def set_auth_params(access_key_id, secret_access_key): - AwsService.access_key_id = access_key_id - AwsService.secret_access_key = secret_access_key - @staticmethod def set_region(region): AwsService.region = region @@ -26,15 +47,11 @@ class AwsService(object): def get_client(client_type, region=None): return boto3.client( client_type, - aws_access_key_id=AwsService.access_key_id, - aws_secret_access_key=AwsService.secret_access_key, region_name=region if region is not None else AwsService.region) @staticmethod def get_session(): - return boto3.session.Session( - aws_access_key_id=AwsService.access_key_id, - aws_secret_access_key=AwsService.secret_access_key) + return boto3.session.Session() @staticmethod def get_regions(): @@ -50,14 +67,22 @@ class AwsService(object): @staticmethod def get_instances(): - return \ - [ - { - 'instance_id': x['InstanceId'], - 'name': x['ComputerName'], - 'os': x['PlatformType'].lower(), - 'ip_address': x['IPAddress'] - } - for x in AwsService.get_client('ssm').describe_instance_information()['InstanceInformationList'] - ] + """ + Get the information for all instances with the relevant roles. + This function will assume that it's running on an EC2 instance with the correct IAM role. + See https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#iam-role for details. + + :raises: botocore.exceptions.ClientError if can't describe local instance information. + :return: All visible instances from this instance + """ + current_instance = AwsInstance() + local_ssm_client = boto3.client("ssm", current_instance.get_region()) + try: + response = local_ssm_client.describe_instance_information() + + filtered_instances_data = filter_instance_data_from_aws_response(response) + return filtered_instances_data + except botocore.exceptions.ClientError as e: + logger.warning("AWS client error while trying to get instances: " + e.message) + raise e diff --git a/monkey/common/cloud/test_filter_instance_data_from_aws_response.py b/monkey/common/cloud/test_filter_instance_data_from_aws_response.py new file mode 100644 index 000000000..8aec518d3 --- /dev/null +++ b/monkey/common/cloud/test_filter_instance_data_from_aws_response.py @@ -0,0 +1,59 @@ +from unittest import TestCase +from aws_service import filter_instance_data_from_aws_response + +import json + + +__author__ = 'shay.nehmad' + + +class TestFilter_instance_data_from_aws_response(TestCase): + def test_filter_instance_data_from_aws_response(self): + json_response_full = """ + { + "InstanceInformationList": [ + { + "ActivationId": "string", + "AgentVersion": "string", + "AssociationOverview": { + "DetailedStatus": "string", + "InstanceAssociationStatusAggregatedCount": { + "string" : 6 + } + }, + "AssociationStatus": "string", + "ComputerName": "string", + "IamRole": "string", + "InstanceId": "string", + "IPAddress": "string", + "IsLatestVersion": "True", + "LastAssociationExecutionDate": 6, + "LastPingDateTime": 6, + "LastSuccessfulAssociationExecutionDate": 6, + "Name": "string", + "PingStatus": "string", + "PlatformName": "string", + "PlatformType": "string", + "PlatformVersion": "string", + "RegistrationDate": 6, + "ResourceType": "string" + } + ], + "NextToken": "string" + } + """ + + json_response_empty = """ + { + "InstanceInformationList": [], + "NextToken": "string" + } + """ + + self.assertEqual(filter_instance_data_from_aws_response(json.loads(json_response_empty)), []) + self.assertEqual( + filter_instance_data_from_aws_response(json.loads(json_response_full)), + [{'instance_id': u'string', + 'ip_address': u'string', + 'name': u'string', + 'os': u'string'}]) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 4f055fec6..b1697f5a5 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -30,6 +30,7 @@ from monkey_island.cc.resources.telemetry_feed import TelemetryFeed from monkey_island.cc.resources.pba_file_download import PBAFileDownload from monkey_island.cc.services.config import ConfigService from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH +from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService from monkey_island.cc.resources.pba_file_upload import FileUpload from monkey_island.cc.resources.attack_telem import AttackTelem @@ -101,6 +102,9 @@ def init_app(mongo_url): database.init() ConfigService.init_config() + # If running on AWS, this will initialize the instance data, which is used "later" in the execution of the island. + RemoteRunAwsService.init() + app.add_url_rule('/', 'serve_home', serve_home) app.add_url_rule('/', 'serve_static_file', serve_static_file) diff --git a/monkey/monkey_island/cc/exporter_init.py b/monkey/monkey_island/cc/exporter_init.py index 3450e98f2..fdf26fe8f 100644 --- a/monkey/monkey_island/cc/exporter_init.py +++ b/monkey/monkey_island/cc/exporter_init.py @@ -1,19 +1,19 @@ -from monkey_island.cc.environment.environment import load_env_from_file, AWS +import logging + from monkey_island.cc.report_exporter_manager import ReportExporterManager from monkey_island.cc.resources.aws_exporter import AWSExporter +from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService -__author__ = 'maor.rayzin' +logger = logging.getLogger(__name__) def populate_exporter_list(): - manager = ReportExporterManager() - if is_aws_exporter_required(): + RemoteRunAwsService.init() + if RemoteRunAwsService.is_running_on_aws(): manager.add_exporter_to_list(AWSExporter) + if len(manager.get_exporters_list()) != 0: + logger.debug( + "Populated exporters list with the following exporters: {0}".format(str(manager.get_exporters_list()))) -def is_aws_exporter_required(): - if str(load_env_from_file()) == AWS: - return True - else: - return False diff --git a/monkey/monkey_island/cc/report_exporter_manager.py b/monkey/monkey_island/cc/report_exporter_manager.py index a6a983a20..5e51a43e1 100644 --- a/monkey/monkey_island/cc/report_exporter_manager.py +++ b/monkey/monkey_island/cc/report_exporter_manager.py @@ -29,6 +29,7 @@ class ReportExporterManager(object): def export(self, report): try: for exporter in self._exporters_set: + logger.debug("Trying to export using " + repr(exporter)) exporter().handle_report(report) except Exception as e: - logger.exception('Failed to export report') + logger.exception('Failed to export report, error: ' + e.message) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index b08c16ae6..7e1f17c48 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -1,53 +1,42 @@ import logging import uuid from datetime import datetime + import boto3 from botocore.exceptions import UnknownServiceError -from monkey_island.cc.resources.exporter import Exporter -from monkey_island.cc.services.config import ConfigService -from monkey_island.cc.environment.environment import load_server_configuration_from_file from common.cloud.aws_instance import AwsInstance +from monkey_island.cc.environment.environment import load_server_configuration_from_file +from monkey_island.cc.resources.exporter import Exporter -__author__ = 'maor.rayzin' - +__authors__ = ['maor.rayzin', 'shay.nehmad'] logger = logging.getLogger(__name__) -AWS_CRED_CONFIG_KEYS = [['cnc', 'aws_config', 'aws_access_key_id'], - ['cnc', 'aws_config', 'aws_secret_access_key'], - ['cnc', 'aws_config', 'aws_account_id']] - class AWSExporter(Exporter): - @staticmethod def handle_report(report_json): - aws = AwsInstance() + findings_list = [] issues_list = report_json['recommendations']['issues'] if not issues_list: logger.info('No issues were found by the monkey, no need to send anything') return True + + current_aws_region = AwsInstance().get_region() + for machine in issues_list: for issue in issues_list[machine]: if issue.get('aws_instance_id', None): - findings_list.append(AWSExporter._prepare_finding(issue, aws.get_region())) + findings_list.append(AWSExporter._prepare_finding(issue, current_aws_region)) - if not AWSExporter._send_findings(findings_list, AWSExporter._get_aws_keys(), aws.get_region()): + if not AWSExporter._send_findings(findings_list, current_aws_region): logger.error('Exporting findings to aws failed') return False return True - @staticmethod - def _get_aws_keys(): - creds_dict = {} - for key in AWS_CRED_CONFIG_KEYS: - creds_dict[key[2]] = str(ConfigService.get_config_value(key)) - - return creds_dict - @staticmethod def merge_two_dicts(x, y): z = x.copy() # start with x's keys and values @@ -82,7 +71,8 @@ class AWSExporter(Exporter): configured_product_arn = load_server_configuration_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}' - account_id = AWSExporter._get_aws_keys().get('aws_account_id', '') + account_id = AwsInstance().get_account_id() + logger.debug("aws account id acquired: {}".format(account_id)) finding = { "SchemaVersion": "2018-10-08", @@ -100,27 +90,26 @@ class AWSExporter(Exporter): return AWSExporter.merge_two_dicts(finding, findings_dict[issue['type']](issue, instance_arn)) @staticmethod - def _send_findings(findings_list, creds_dict, region): + def _send_findings(findings_list, region): try: - if not creds_dict: - logger.info('No AWS access credentials received in configuration') - return False + logger.debug("Trying to acquire securityhub boto3 client in " + region) + security_hub_client = boto3.client('securityhub', region_name=region) + logger.debug("Client acquired: {0}".format(repr(security_hub_client))) - securityhub = boto3.client('securityhub', - aws_access_key_id=creds_dict.get('aws_access_key_id', ''), - aws_secret_access_key=creds_dict.get('aws_secret_access_key', ''), - region_name=region) + # Assumes the machine has the correct IAM role to do this, @see + # https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS-EC2-instances + import_response = security_hub_client.batch_import_findings(Findings=findings_list) + logger.debug("Import findings response: {0}".format(repr(import_response))) - import_response = securityhub.batch_import_findings(Findings=findings_list) if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: return True else: return False except UnknownServiceError as e: - logger.warning('AWS exporter called but AWS-CLI securityhub service is not installed') + logger.warning('AWS exporter called but AWS-CLI securityhub service is not installed. Error: ' + e.message) return False except Exception as e: - logger.exception('AWS security hub findings failed to send.') + logger.exception('AWS security hub findings failed to send. Error: ' + e.message) return False @staticmethod @@ -159,7 +148,7 @@ class AWSExporter(Exporter): title="Weak segmentation - Machines were able to communicate over unused ports.", description="Use micro-segmentation policies to disable communication other than the required.", recommendation="Machines are not locked down at port level. Network tunnel was set up from {0} to {1}" - .format(issue['machine'], issue['dest']), + .format(issue['machine'], issue['dest']), instance_arn=instance_arn, instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None ) @@ -171,9 +160,9 @@ class AWSExporter(Exporter): severity=10, title="Samba servers are vulnerable to 'SambaCry'", description="Change {0} password to a complex one-use password that is not shared with other computers on the network. Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up." \ - .format(issue['username']), + .format(issue['username']), recommendation="The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB protocol with user {2} and its password, and used the SambaCry vulnerability.".format( - issue['machine'], issue['ip_address'], issue['username']), + issue['machine'], issue['ip_address'], issue['username']), instance_arn=instance_arn, instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None ) @@ -185,9 +174,9 @@ class AWSExporter(Exporter): severity=5, title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( - issue['username']), + issue['username']), recommendation="The machine {0}({1}) is vulnerable to a SMB attack. The Monkey used a pass-the-hash attack over SMB protocol with user {2}.".format( - issue['machine'], issue['ip_address'], issue['username']), + issue['machine'], issue['ip_address'], issue['username']), instance_arn=instance_arn, instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None ) @@ -199,9 +188,9 @@ class AWSExporter(Exporter): severity=1, title="Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( - issue['username']), + issue['username']), recommendation="The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with user {2} and its password.".format( - issue['machine'], issue['ip_address'], issue['username']), + issue['machine'], issue['ip_address'], issue['username']), instance_arn=instance_arn, instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None ) @@ -214,7 +203,7 @@ class AWSExporter(Exporter): title="Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", description="Protect {ssh_key} private key with a pass phrase.".format(ssh_key=issue['ssh_key']), recommendation="The machine {machine} ({ip_address}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with private key {ssh_key}.".format( - machine=issue['machine'], ip_address=issue['ip_address'], ssh_key=issue['ssh_key']), + machine=issue['machine'], ip_address=issue['ip_address'], ssh_key=issue['ssh_key']), instance_arn=instance_arn, instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None ) @@ -227,7 +216,7 @@ class AWSExporter(Exporter): title="Elastic Search servers are vulnerable to CVE-2015-1427", description="Update your Elastic Search server to version 1.4.3 and up.", recommendation="The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.".format( - issue['machine'], issue['ip_address']), + issue['machine'], issue['ip_address']), instance_arn=instance_arn, instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None ) @@ -243,7 +232,8 @@ class AWSExporter(Exporter): {0} in the networks {1} \ could directly access the Monkey Island server in the networks {2}.".format(issue['machine'], issue['networks'], - issue['server_networks']), + issue[ + 'server_networks']), instance_arn=instance_arn, instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None ) @@ -269,7 +259,7 @@ class AWSExporter(Exporter): description="Update your Bash to a ShellShock-patched version.", recommendation="The machine {0} ({1}) is vulnerable to a ShellShock attack. " "The attack was made possible because the HTTP server running on TCP port {2} was vulnerable to a shell injection attack on the paths: {3}.".format( - issue['machine'], issue['ip_address'], issue['port'], issue['paths']), + issue['machine'], issue['ip_address'], issue['port'], issue['paths']), instance_arn=instance_arn, instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None ) @@ -281,9 +271,9 @@ class AWSExporter(Exporter): severity=1, title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( - issue['username']), + issue['username']), recommendation="The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format( - issue['machine'], issue['ip_address'], issue['username']), + issue['machine'], issue['ip_address'], issue['username']), instance_arn=instance_arn, instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None ) @@ -296,7 +286,7 @@ class AWSExporter(Exporter): title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.", recommendation="The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey authenticated over the WMI protocol with user {username} and its password.".format( - machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']), + machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']), instance_arn=instance_arn, instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None ) @@ -308,9 +298,9 @@ class AWSExporter(Exporter): severity=1, title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( - issue['username']), + issue['username']), recommendation="The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey used a pass-the-hash attack over WMI protocol with user {username}".format( - machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']), + machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']), instance_arn=instance_arn, instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None ) @@ -322,9 +312,9 @@ class AWSExporter(Exporter): severity=1, title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( - issue['username']), + issue['username']), recommendation="The machine machine ({ip_address}) is vulnerable to a RDP attack. The Monkey authenticated over the RDP protocol with user {username} and its password.".format( - machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']), + machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']), instance_arn=instance_arn, instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None ) @@ -337,7 +327,7 @@ class AWSExporter(Exporter): title="Multiple users have the same password.", description="Some domain users are sharing passwords, this should be fixed by changing passwords.", recommendation="These users are sharing access password: {shared_with}.".format( - shared_with=issue['shared_with']), + shared_with=issue['shared_with']), instance_arn=instance_arn, instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None ) @@ -350,7 +340,7 @@ class AWSExporter(Exporter): title="Shared local administrator account - Different machines have the same account as a local administrator.", description="Make sure the right administrator accounts are managing the right machines, and that there isn\'t an unintentional local admin sharing.", recommendation="Here is a list of machines which the account {username} is defined as an administrator: {shared_machines}".format( - username=issue['username'], shared_machines=issue['shared_machines']), + username=issue['username'], shared_machines=issue['shared_machines']), instance_arn=instance_arn, instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None ) @@ -363,7 +353,7 @@ class AWSExporter(Exporter): title="Mimikatz found login credentials of a user who has admin access to a server defined as critical.", description="This critical machine is open to attacks via strong users with access to it.", recommendation="The services: {services} have been found on the machine thus classifying it as a critical machine. These users has access to it:{threatening_users}.".format( - services=issue['services'], threatening_users=issue['threatening_users']), + services=issue['services'], threatening_users=issue['threatening_users']), instance_arn=instance_arn, instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None ) @@ -377,7 +367,7 @@ class AWSExporter(Exporter): description="Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.", recommendation="Struts2 server at {machine} ({ip_address}) is vulnerable to remote code execution attack." " The attack was made possible because the server is using an old version of Jakarta based file upload Multipart parser.".format( - machine=issue['machine'], ip_address=issue['ip_address']), + machine=issue['machine'], ip_address=issue['ip_address']), instance_arn=instance_arn, instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None ) @@ -392,7 +382,7 @@ class AWSExporter(Exporter): "Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.", recommendation="Oracle WebLogic server at {machine} ({ip_address}) is vulnerable to remote code execution attack." " The attack was made possible due to incorrect permission assignment in Oracle Fusion Middleware (subcomponent: WLS Security).".format( - machine=issue['machine'], ip_address=issue['ip_address']), + machine=issue['machine'], ip_address=issue['ip_address']), instance_arn=instance_arn, instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None ) diff --git a/monkey/monkey_island/cc/resources/remote_run.py b/monkey/monkey_island/cc/resources/remote_run.py index ccd9fbbbe..675c292c1 100644 --- a/monkey/monkey_island/cc/resources/remote_run.py +++ b/monkey/monkey_island/cc/resources/remote_run.py @@ -1,4 +1,6 @@ import json + +from botocore.exceptions import NoCredentialsError, ClientError from flask import request, jsonify, make_response import flask_restful @@ -6,6 +8,11 @@ from monkey_island.cc.auth import jwt_required from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService from common.cloud.aws_service import AwsService +CLIENT_ERROR_FORMAT = "ClientError, error message: '{}'. Probably, the IAM role that has been associated with the " \ + "instance doesn't permit SSM calls. " +NO_CREDS_ERROR_FORMAT = "NoCredentialsError, error message: '{}'. Probably, no IAM role has been associated with the " \ + "instance. " + class RemoteRun(flask_restful.Resource): def __init__(self): @@ -24,10 +31,14 @@ class RemoteRun(flask_restful.Resource): is_aws = RemoteRunAwsService.is_running_on_aws() resp = {'is_aws': is_aws} if is_aws: - is_auth = RemoteRunAwsService.update_aws_auth_params() - resp['auth'] = is_auth - if is_auth: + try: resp['instances'] = AwsService.get_instances() + except NoCredentialsError as e: + resp['error'] = NO_CREDS_ERROR_FORMAT.format(e.message) + return jsonify(resp) + except ClientError as e: + resp['error'] = CLIENT_ERROR_FORMAT.format(e.message) + return jsonify(resp) return jsonify(resp) return {} @@ -37,11 +48,9 @@ class RemoteRun(flask_restful.Resource): body = json.loads(request.data) resp = {} if body.get('type') == 'aws': - is_auth = RemoteRunAwsService.update_aws_auth_params() - resp['auth'] = is_auth - if is_auth: - result = self.run_aws_monkeys(body) - resp['result'] = result + RemoteRunAwsService.update_aws_region_authless() + result = self.run_aws_monkeys(body) + resp['result'] = result return jsonify(resp) # default action diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index f01ff90d6..01fdcc51c 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -54,7 +54,7 @@ class TelemetryFeed(flask_restful.Resource): @staticmethod def get_state_telem_brief(telem): if telem['data']['done']: - return '''Monkey finishing it's execution.''' + return '''Monkey finishing its execution.''' else: return 'Monkey started.' diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 171fbb7bf..c52ceb3f4 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -26,14 +26,6 @@ ENCRYPTED_CONFIG_ARRAYS = \ ['internal', 'exploits', 'exploit_ssh_keys'] ] -# This should be used for config values of string type -ENCRYPTED_CONFIG_STRINGS = \ - [ - ['cnc', 'aws_config', 'aws_access_key_id'], - ['cnc', 'aws_config', 'aws_account_id'], - ['cnc', 'aws_config', 'aws_secret_access_key'] - ] - class ConfigService: default_config = None @@ -76,8 +68,6 @@ class ConfigService: if should_decrypt: if config_key_as_arr in ENCRYPTED_CONFIG_ARRAYS: config = [encryptor.dec(x) for x in config] - elif config_key_as_arr in ENCRYPTED_CONFIG_STRINGS: - config = encryptor.dec(config) return config @staticmethod @@ -243,11 +233,8 @@ class ConfigService: """ Same as decrypt_config but for a flat configuration """ - if is_island: - keys = [config_arr_as_array[2] for config_arr_as_array in - (ENCRYPTED_CONFIG_ARRAYS + ENCRYPTED_CONFIG_STRINGS)] - else: - keys = [config_arr_as_array[2] for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS] + keys = [config_arr_as_array[2] for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS] + for key in keys: if isinstance(flat_config[key], collections.Sequence) and not isinstance(flat_config[key], string_types): # Check if we are decrypting ssh key pair @@ -261,7 +248,7 @@ class ConfigService: @staticmethod def _encrypt_or_decrypt_config(config, is_decrypt=False): - for config_arr_as_array in (ENCRYPTED_CONFIG_ARRAYS + ENCRYPTED_CONFIG_STRINGS): + for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS: config_arr = config parent_config_arr = None diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 382b591db..3ec3c7212 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -695,31 +695,6 @@ SCHEMA = { } } }, - 'aws_config': { - 'title': 'AWS Configuration', - 'type': 'object', - 'description': 'These credentials will be used in order to export the monkey\'s findings to the AWS Security Hub.', - 'properties': { - 'aws_account_id': { - 'title': 'AWS account ID', - 'type': 'string', - 'description': 'Your AWS account ID that is subscribed to security hub feeds', - 'default': '' - }, - 'aws_access_key_id': { - 'title': 'AWS access key ID', - 'type': 'string', - 'description': 'Your AWS public access key ID, can be found in the IAM user interface in the AWS console.', - 'default': '' - }, - 'aws_secret_access_key': { - 'title': 'AWS secret access key', - 'type': 'string', - 'description': 'Your AWS secret access key id, you can get this after creating a public access key in the console.', - 'default': '' - } - } - } } }, "exploits": { diff --git a/monkey/monkey_island/cc/services/remote_run_aws.py b/monkey/monkey_island/cc/services/remote_run_aws.py index 7cc26008d..78df00721 100644 --- a/monkey/monkey_island/cc/services/remote_run_aws.py +++ b/monkey/monkey_island/cc/services/remote_run_aws.py @@ -46,22 +46,12 @@ class RemoteRunAwsService: return RemoteRunAwsService.aws_instance.is_aws_instance() @staticmethod - def update_aws_auth_params(): + def update_aws_region_authless(): """ - Updates the AWS authentication parameters according to config - :return: True if new params allow successful authentication. False otherwise + Updates the AWS region without auth params (via IAM role) """ - access_key_id = ConfigService.get_config_value(['cnc', 'aws_config', 'aws_access_key_id'], False, True) - secret_access_key = ConfigService.get_config_value(['cnc', 'aws_config', 'aws_secret_access_key'], False, True) - - if (access_key_id != AwsService.access_key_id) or (secret_access_key != AwsService.secret_access_key): - AwsService.set_auth_params(access_key_id, secret_access_key) - RemoteRunAwsService.is_auth = AwsService.test_client() - AwsService.set_region(RemoteRunAwsService.aws_instance.region) - return RemoteRunAwsService.is_auth - @staticmethod def get_bitness(instances): """ diff --git a/monkey/monkey_island/cc/ui/.babelrc b/monkey/monkey_island/cc/ui/.babelrc index 9e8720a99..31130e826 100644 --- a/monkey/monkey_island/cc/ui/.babelrc +++ b/monkey/monkey_island/cc/ui/.babelrc @@ -1,4 +1,4 @@ { - "presets": ["es2015", "stage-0", "react"] - + "presets": ["es2015", "stage-0", "react"], + "plugins": ["emotion"] } diff --git a/monkey/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json index 81322b43f..58208ef24 100644 --- a/monkey/monkey_island/cc/ui/package-lock.json +++ b/monkey/monkey_island/cc/ui/package-lock.json @@ -53,6 +53,36 @@ "@babel/types": "7.0.0-beta.44" } }, + "@babel/helper-module-imports": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", + "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", + "requires": { + "@babel/types": "^7.0.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.0.tgz", + "integrity": "sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + } + } + }, "@babel/helper-split-export-declaration": { "version": "7.0.0-beta.44", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.44.tgz", @@ -221,6 +251,86 @@ } } }, + "@emotion/cache": { + "version": "10.0.9", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.9.tgz", + "integrity": "sha512-f7MblpE2xoimC4fCMZ9pivmsIn7hyWRIvY75owMDi8pdOSeh+w5tH3r4hBJv/LLrwiMM7cTQURqTPcYoL5pWnw==", + "requires": { + "@emotion/sheet": "0.9.2", + "@emotion/stylis": "0.8.3", + "@emotion/utils": "0.11.1", + "@emotion/weak-memoize": "0.2.2" + } + }, + "@emotion/core": { + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.0.10.tgz", + "integrity": "sha512-U1aE2cOWUscjc8ZJ3Cx32udOzLeRoJwGxBH93xQD850oQFpwPKZARzdUtdc9SByUOwzSFYxhDhrpXnV34FJmWg==", + "requires": { + "@emotion/cache": "^10.0.9", + "@emotion/css": "^10.0.9", + "@emotion/serialize": "^0.11.6", + "@emotion/sheet": "0.9.2", + "@emotion/utils": "0.11.1" + } + }, + "@emotion/css": { + "version": "10.0.9", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.9.tgz", + "integrity": "sha512-jtHhUSWw+L7yxYgNtC+KJ3Ory90/jiAtpG1qT+gTQQ/RR5AMiigs9/lDHu/vnwljaq2S48FoKb/FZZMlJcC4bw==", + "requires": { + "@emotion/serialize": "^0.11.6", + "@emotion/utils": "0.11.1", + "babel-plugin-emotion": "^10.0.9" + } + }, + "@emotion/hash": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.1.tgz", + "integrity": "sha512-OYpa/Sg+2GDX+jibUfpZVn1YqSVRpYmTLF2eyAfrFTIJSbwyIrc+YscayoykvaOME/wV4BV0Sa0yqdMrgse6mA==" + }, + "@emotion/memoize": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.1.tgz", + "integrity": "sha512-Qv4LTqO11jepd5Qmlp3M1YEjBumoTHcHFdgPTQ+sFlIL5myi/7xu/POwP7IRu6odBdmLXdtIs1D6TuW6kbwbbg==" + }, + "@emotion/serialize": { + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.6.tgz", + "integrity": "sha512-n4zVv2qGLmspF99jaEUwnMV0fnEGsyUMsC/8KZKUSUTZMYljHE+j+B6rSD8PIFtaUIhHaxCG2JawN6L+OgLN0Q==", + "requires": { + "@emotion/hash": "0.7.1", + "@emotion/memoize": "0.7.1", + "@emotion/unitless": "0.7.3", + "@emotion/utils": "0.11.1", + "csstype": "^2.5.7" + } + }, + "@emotion/sheet": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.2.tgz", + "integrity": "sha512-pVBLzIbC/QCHDKJF2E82V2H/W/B004mDFQZiyo/MSR+VC4pV5JLG0TF/zgQDFvP3fZL/5RTPGEmXlYJBMUuJ+A==" + }, + "@emotion/stylis": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.3.tgz", + "integrity": "sha512-M3nMfJ6ndJMYloSIbYEBq6G3eqoYD41BpDOxreE8j0cb4fzz/5qvmqU9Mb2hzsXcCnIlGlWhS03PCzVGvTAe0Q==" + }, + "@emotion/unitless": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.3.tgz", + "integrity": "sha512-4zAPlpDEh2VwXswwr/t8xGNDGg8RQiPxtxZ3qQEXyQsBV39ptTdESCjuBvGze1nLMVrxmTIKmnO/nAV8Tqjjzg==" + }, + "@emotion/utils": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.1.tgz", + "integrity": "sha512-8M3VN0hetwhsJ8dH8VkVy7xo5/1VoBsDOk/T4SJOeXwTO1c4uIqVNx2qyecLFnnUWD5vvUqHQ1gASSeUN6zcTg==" + }, + "@emotion/weak-memoize": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.2.tgz", + "integrity": "sha512-n/VQ4mbfr81aqkx/XmVicOLjviMuy02eenSdJY33SVA7S2J42EU0P1H0mOogfYedb3wXA0d/LVtBrgTSm04WEA==" + }, "@webassemblyjs/ast": { "version": "1.7.8", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.8.tgz", @@ -575,7 +685,6 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", - "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -663,6 +772,11 @@ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, "asn1": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", @@ -1266,6 +1380,55 @@ "babel-runtime": "^6.22.0" } }, + "babel-plugin-emotion": { + "version": "10.0.9", + "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.0.9.tgz", + "integrity": "sha512-IfWP12e9/wHtWHxVTzD692Nbcmrmcz2tip7acp6YUqtrP7slAyr5B+69hyZ8jd55GsyNSZwryNnmuDEVe0j+7w==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@emotion/hash": "0.7.1", + "@emotion/memoize": "0.7.1", + "@emotion/serialize": "^0.11.6", + "babel-plugin-macros": "^2.0.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^1.0.5", + "find-root": "^1.1.0", + "source-map": "^0.5.7" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } + } + }, + "babel-plugin-macros": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.5.1.tgz", + "integrity": "sha512-xN3KhAxPzsJ6OQTktCanNpIFnnMsCV+t8OloKxIL72D6+SUZYFn9qfklPgef5HyyDtzYZqqb+fs1S12+gQY82Q==", + "requires": { + "@babel/runtime": "^7.4.2", + "cosmiconfig": "^5.2.0", + "resolve": "^1.10.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.3.tgz", + "integrity": "sha512-9lsJwJLxDh/T3Q3SZszfWOTkk3pHbkmH+3KY+zwIDmsNlxsumuhS2TH3NIpktU4kNvfzy+k3eLT7aTJSPTo0OA==", + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, + "regenerator-runtime": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", + "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" + } + } + }, "babel-plugin-syntax-async-functions": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", @@ -1335,8 +1498,7 @@ "babel-plugin-syntax-jsx": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", - "dev": true + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" }, "babel-plugin-syntax-object-rest-spread": { "version": "6.13.0", @@ -2525,6 +2687,21 @@ } } }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "requires": { + "callsites": "^2.0.0" + }, + "dependencies": { + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" + } + } + }, "caller-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", @@ -2631,6 +2808,11 @@ "supports-color": "^2.0.0" } }, + "change-emitter": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz", + "integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU=" + }, "chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -3018,8 +3200,7 @@ "convert-source-map": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", - "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", - "dev": true + "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=" }, "cookie": { "version": "0.3.1", @@ -3056,7 +3237,7 @@ "copy-to-clipboard": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz", - "integrity": "sha1-9OgvSogw3ORma3643tDJvMMTq6k=", + "integrity": "sha512-c3GdeY8qxCHGezVb1EFQfHYK/8NZRemgcTIzPq7PuxjHAf/raKibn2QdhHPb/y6q74PMgH6yizaDZlRmw6QyKw==", "requires": { "toggle-selection": "^1.0.3" } @@ -3143,6 +3324,42 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, + "cosmiconfig": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.0.tgz", + "integrity": "sha512-nxt+Nfc3JAqf4WIWd0jXLjTJZmsPLrA9DDc4nRw2KFJQJK7DNooqSXrNI7tzLG50CF8axczly5UV929tBmh/7g==", + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.0", + "parse-json": "^4.0.0" + }, + "dependencies": { + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } + } + }, "create-ecdh": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", @@ -3322,6 +3539,11 @@ "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", "dev": true }, + "csstype": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.3.tgz", + "integrity": "sha512-rINUZXOkcBmoHWEyu7JdHu5JMzkGRoMX4ov9830WNgxf5UYxcBUO0QTKAqeJ5EZfSdlrcJYkC8WwfVW7JYi4yg==" + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -3975,7 +4197,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "requires": { "is-arrayish": "^0.2.1" } @@ -4061,8 +4282,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { "version": "1.8.1", @@ -4419,8 +4639,7 @@ "esutils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" }, "etag": { "version": "1.8.1", @@ -4816,6 +5035,27 @@ "websocket-driver": ">=0.5.1" } }, + "fbjs": { + "version": "0.8.17", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", + "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", + "requires": { + "core-js": "^1.0.0", + "isomorphic-fetch": "^2.1.1", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^0.7.18" + }, + "dependencies": { + "core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" + } + } + }, "fd-slicer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", @@ -4945,6 +5185,11 @@ "pkg-dir": "^2.0.0" } }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", @@ -5222,8 +5467,8 @@ "dev": true, "optional": true, "requires": { - "delegates": "1.0.0", - "readable-stream": "2.2.9" + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" } }, "asn1": { @@ -5268,14 +5513,13 @@ "dev": true, "optional": true, "requires": { - "tweetnacl": "0.14.5" + "tweetnacl": "^0.14.3" } }, "block-stream": { "version": "0.0.9", "bundled": true, "dev": true, - "optional": true, "requires": { "inherits": "~2.0.0" } @@ -5284,7 +5528,6 @@ "version": "2.10.1", "bundled": true, "dev": true, - "optional": true, "requires": { "hoek": "2.x.x" } @@ -5293,7 +5536,6 @@ "version": "1.1.7", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^0.4.1", "concat-map": "0.0.1" @@ -5320,14 +5562,12 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "combined-stream": { "version": "1.0.5", "bundled": true, "dev": true, - "optional": true, "requires": { "delayed-stream": "~1.0.0" } @@ -5335,20 +5575,17 @@ "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "cryptiles": { "version": "2.0.5", @@ -5394,8 +5631,7 @@ "delayed-stream": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "delegates": { "version": "1.0.0", @@ -5421,8 +5657,7 @@ "extsprintf": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "forever-agent": { "version": "0.6.1", @@ -5436,27 +5671,25 @@ "dev": true, "optional": true, "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.15" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.5", + "mime-types": "^2.1.12" } }, "fs.realpath": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "fstream": { "version": "1.0.11", "bundled": true, "dev": true, - "optional": true, "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.1" + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" } }, "fstream-ignore": { @@ -5465,9 +5698,9 @@ "dev": true, "optional": true, "requires": { - "fstream": "1.0.11", - "inherits": "2.0.3", - "minimatch": "3.0.4" + "fstream": "^1.0.0", + "inherits": "2", + "minimatch": "^3.0.0" } }, "gauge": { @@ -5476,14 +5709,14 @@ "dev": true, "optional": true, "requires": { - "aproba": "1.1.1", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" } }, "getpass": { @@ -5507,21 +5740,19 @@ "version": "7.1.2", "bundled": true, "dev": true, - "optional": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "graceful-fs": { "version": "4.1.11", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "har-schema": { "version": "1.0.5", @@ -5551,17 +5782,16 @@ "dev": true, "optional": true, "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" + "boom": "2.x.x", + "cryptiles": "2.x.x", + "hoek": "2.x.x", + "sntp": "1.x.x" } }, "hoek": { "version": "2.16.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "http-signature": { "version": "1.1.1", @@ -5569,19 +5799,18 @@ "dev": true, "optional": true, "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.0", - "sshpk": "1.13.0" + "assert-plus": "^0.2.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "inflight": { "version": "1.0.6", "bundled": true, "dev": true, - "optional": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -5600,9 +5829,8 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "is-typedarray": { @@ -5614,8 +5842,7 @@ "isarray": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "isstream": { "version": "0.1.2", @@ -5688,38 +5915,33 @@ "mime-db": { "version": "1.27.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "mime-types": { "version": "2.1.15", "bundled": true, "dev": true, - "optional": true, "requires": { - "mime-db": "1.27.0" + "mime-db": "~1.27.0" } }, "minimatch": { "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { - "brace-expansion": "1.1.7" + "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "mkdirp": { "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -5736,15 +5958,15 @@ "dev": true, "optional": true, "requires": { - "mkdirp": "0.5.1", - "nopt": "4.0.1", - "npmlog": "4.1.0", - "rc": "1.2.1", - "request": "2.81.0", - "rimraf": "2.6.1", - "semver": "5.3.0", - "tar": "2.2.1", - "tar-pack": "3.4.0" + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "request": "^2.81.0", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^2.2.1", + "tar-pack": "^3.4.0" } }, "nopt": { @@ -5753,8 +5975,8 @@ "dev": true, "optional": true, "requires": { - "abbrev": "1.1.0", - "osenv": "0.1.4" + "abbrev": "1", + "osenv": "^0.1.4" } }, "npmlog": { @@ -5763,17 +5985,16 @@ "dev": true, "optional": true, "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" } }, "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "oauth-sign": { "version": "0.8.2", @@ -5793,7 +6014,7 @@ "dev": true, "optional": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "os-homedir": { @@ -5814,15 +6035,14 @@ "dev": true, "optional": true, "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" } }, "path-is-absolute": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "performance-now": { "version": "0.2.0", @@ -5833,8 +6053,7 @@ "process-nextick-args": { "version": "1.0.7", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "punycode": { "version": "1.4.1", @@ -5854,10 +6073,10 @@ "dev": true, "optional": true, "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.4", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" + "deep-extend": "~0.4.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" }, "dependencies": { "minimist": { @@ -5872,15 +6091,14 @@ "version": "2.2.9", "bundled": true, "dev": true, - "optional": true, "requires": { - "buffer-shims": "1.0.0", - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "1.0.1", - "util-deprecate": "1.0.2" + "buffer-shims": "~1.0.0", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~1.0.0", + "util-deprecate": "~1.0.1" } }, "request": { @@ -5889,44 +6107,42 @@ "dev": true, "optional": true, "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.15", - "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.0.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.2", - "tunnel-agent": "0.6.0", - "uuid": "3.0.1" + "aws-sign2": "~0.6.0", + "aws4": "^1.2.1", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.0", + "forever-agent": "~0.6.1", + "form-data": "~2.1.1", + "har-validator": "~4.2.1", + "hawk": "~3.1.3", + "http-signature": "~1.1.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.7", + "oauth-sign": "~0.8.1", + "performance-now": "^0.2.0", + "qs": "~6.4.0", + "safe-buffer": "^5.0.1", + "stringstream": "~0.0.4", + "tough-cookie": "~2.3.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.0.0" } }, "rimraf": { "version": "2.6.1", "bundled": true, "dev": true, - "optional": true, "requires": { - "glob": "7.1.2" + "glob": "^7.0.5" } }, "safe-buffer": { "version": "5.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "semver": { "version": "5.3.0", @@ -5952,7 +6168,7 @@ "dev": true, "optional": true, "requires": { - "hoek": "2.16.3" + "hoek": "2.x.x" } }, "sshpk": { @@ -5961,15 +6177,15 @@ "dev": true, "optional": true, "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jodid25519": "1.0.2", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jodid25519": "^1.0.0", + "jsbn": "~0.1.0", + "tweetnacl": "~0.14.0" }, "dependencies": { "assert-plus": { @@ -5984,20 +6200,18 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "string_decoder": { "version": "1.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { - "safe-buffer": "5.0.1" + "safe-buffer": "^5.0.1" } }, "stringstream": { @@ -6010,9 +6224,8 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "strip-json-comments": { @@ -6025,11 +6238,10 @@ "version": "2.2.1", "bundled": true, "dev": true, - "optional": true, "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" } }, "tar-pack": { @@ -6038,14 +6250,14 @@ "dev": true, "optional": true, "requires": { - "debug": "2.6.8", - "fstream": "1.0.11", - "fstream-ignore": "1.0.5", - "once": "1.4.0", - "readable-stream": "2.2.9", - "rimraf": "2.6.1", - "tar": "2.2.1", - "uid-number": "0.0.6" + "debug": "^2.2.0", + "fstream": "^1.0.10", + "fstream-ignore": "^1.0.5", + "once": "^1.3.3", + "readable-stream": "^2.1.4", + "rimraf": "^2.5.1", + "tar": "^2.2.1", + "uid-number": "^0.0.6" } }, "tough-cookie": { @@ -6054,7 +6266,7 @@ "dev": true, "optional": true, "requires": { - "punycode": "1.4.1" + "punycode": "^1.4.1" } }, "tunnel-agent": { @@ -6063,7 +6275,7 @@ "dev": true, "optional": true, "requires": { - "safe-buffer": "5.0.1" + "safe-buffer": "^5.0.1" } }, "tweetnacl": { @@ -6081,8 +6293,7 @@ "util-deprecate": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "uuid": { "version": "3.0.1", @@ -6105,7 +6316,7 @@ "dev": true, "optional": true, "requires": { - "string-width": "1.0.2" + "string-width": "^1.0.2" } }, "wrappy": { @@ -7076,6 +7287,30 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "dependencies": { + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + } + } + }, "import-local": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", @@ -7316,8 +7551,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-binary-path": { "version": "1.0.1", @@ -7383,6 +7617,11 @@ } } }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" + }, "is-dotfile": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", @@ -7527,8 +7766,7 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-symbol": { "version": "1.0.1", @@ -7599,6 +7837,15 @@ } } }, + "isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", + "requires": { + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" + } + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -7698,8 +7945,7 @@ "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, "json-schema": { "version": "0.2.3", @@ -8080,7 +8326,7 @@ "dev": true, "optional": true, "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -8253,7 +8499,7 @@ "dev": true, "optional": true, "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -9680,6 +9926,15 @@ "lower-case": "^1.1.1" } }, + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "requires": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + }, "node-forge": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", @@ -13007,6 +13262,11 @@ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -13282,6 +13542,14 @@ "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", "dev": true }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + } + }, "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -13856,6 +14124,16 @@ } } }, + "react-spinners": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.5.4.tgz", + "integrity": "sha512-jo7BE8prvnZNL7xNrQL16geVXH6LmaWrhvvXnmUwz2MhFO14bhlAdCLuKCwqmUJ37kGphH0C2CJRThwkjfpVzw==", + "requires": { + "@emotion/core": "^10.0.4", + "prop-types": "^15.5.10", + "recompose": "0.27.1 - 0.30.0" + } + }, "react-table": { "version": "6.8.6", "resolved": "https://registry.npmjs.org/react-table/-/react-table-6.8.6.tgz", @@ -13990,6 +14268,19 @@ } } }, + "recompose": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.30.0.tgz", + "integrity": "sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w==", + "requires": { + "@babel/runtime": "^7.0.0", + "change-emitter": "^0.1.2", + "fbjs": "^0.8.1", + "hoist-non-react-statics": "^2.3.1", + "react-lifecycles-compat": "^3.0.2", + "symbol-observable": "^1.0.4" + } + }, "redent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", @@ -14246,6 +14537,14 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, + "resolve": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "requires": { + "path-parse": "^1.0.6" + } + }, "resolve-cwd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", @@ -14272,7 +14571,7 @@ "resolve-pathname": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz", - "integrity": "sha1-fpriHtgV/WOrGJre7mTcgx7vqHk=" + "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==" }, "resolve-url": { "version": "0.2.1", @@ -14560,8 +14859,7 @@ "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "dev": true + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, "setprototypeof": { "version": "1.1.0", @@ -15035,8 +15333,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { "version": "1.14.1", @@ -15682,6 +15979,11 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "ua-parser-js": { + "version": "0.7.19", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.19.tgz", + "integrity": "sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ==" + }, "uglify-js": { "version": "2.8.29", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", @@ -16083,7 +16385,7 @@ "value-equal": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz", - "integrity": "sha1-xb3S9U7gk8BIOdcc4uR1imiQq8c=" + "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==" }, "vary": { "version": "1.1.2", @@ -18991,6 +19293,11 @@ "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", "dev": true }, + "whatwg-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", + "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" + }, "which": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index 6eb6868dd..8218b89ae 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -93,6 +93,8 @@ "react-table": "^6.8.6", "react-toggle": "^4.0.1", "redux": "^4.0.0", - "sha3": "^2.0.0" + "sha3": "^2.0.0", + "react-spinners": "^0.5.4", + "@emotion/core": "^10.0.10" } } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js index 55e7279c0..e67b40728 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js @@ -1,11 +1,20 @@ import React from 'react'; -import {Button, Col, Well, Nav, NavItem, Collapse, Form, FormControl, FormGroup} from 'react-bootstrap'; +import { css } from '@emotion/core'; +import {Button, Col, Well, Nav, NavItem, Collapse} from 'react-bootstrap'; import CopyToClipboard from 'react-copy-to-clipboard'; +import GridLoader from 'react-spinners/GridLoader'; + import {Icon} from 'react-fa'; import {Link} from 'react-router-dom'; import AuthComponent from '../AuthComponent'; import AwsRunTable from "../run-monkey/AwsRunTable"; +const loading_css_override = css` + display: block; + margin-right: auto; + margin-left: auto; +`; + class RunMonkeyPageComponent extends AuthComponent { constructor(props) { @@ -20,12 +29,12 @@ class RunMonkeyPageComponent extends AuthComponent { showManual: false, showAws: false, isOnAws: false, - isAwsAuth: false, awsUpdateClicked: false, awsUpdateFailed: false, - awsKeyId: '', - awsSecretKey: '', - awsMachines: [] + awsMachines: [], + isLoadingAws: true, + isErrorWhileCollectingAwsMachines: false, + awsMachineCollectionErrorMsg: '' }; } @@ -48,13 +57,7 @@ class RunMonkeyPageComponent extends AuthComponent { }); this.fetchAwsInfo(); - this.fetchConfig() - .then(config => { - this.setState({ - awsKeyId: config['cnc']['aws_config']['aws_access_key_id'], - awsSecretKey: config['cnc']['aws_config']['aws_secret_access_key'] - }); - }); + this.fetchConfig(); this.authFetch('/api/client-monkey') .then(res => res.json()) @@ -75,17 +78,29 @@ class RunMonkeyPageComponent extends AuthComponent { .then(res =>{ let is_aws = res['is_aws']; if (is_aws) { - this.setState({isOnAws: true, awsMachines: res['instances'], isAwsAuth: res['auth']}); + // On AWS! + // Checks if there was an error while collecting the aws machines. + let is_error_while_collecting_aws_machines = (res['error'] != null); + if (is_error_while_collecting_aws_machines) { + // There was an error. Finish loading, and display error message. + this.setState({isOnAws: true, isErrorWhileCollectingAwsMachines: true, awsMachineCollectionErrorMsg: res['error'], isLoadingAws: false}); + } else { + // No error! Finish loading and display machines for user + this.setState({isOnAws: true, awsMachines: res['instances'], isLoadingAws: false}); + } + } else { + // Not on AWS. Finish loading and don't display the AWS div. + this.setState({isOnAws: false, isLoadingAws: false}); } }); } - generateLinuxCmd(ip, is32Bit) { + static generateLinuxCmd(ip, is32Bit) { let bitText = is32Bit ? '32' : '64'; return `wget --no-check-certificate https://${ip}:5000/api/monkey/download/monkey-linux-${bitText}; chmod +x monkey-linux-${bitText}; ./monkey-linux-${bitText} m0nk3y -s ${ip}:5000` } - generateWindowsCmd(ip, is32Bit) { + static generateWindowsCmd(ip, is32Bit) { let bitText = is32Bit ? '32' : '64'; return `powershell [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}; (New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/monkey-windows-${bitText}.exe','.\\monkey.exe'); ;Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s ${ip}:5000';`; } @@ -118,9 +133,9 @@ class RunMonkeyPageComponent extends AuthComponent { let is32Bit = (this.state.selectedOs.split('-')[1] === '32'); let cmdText = ''; if (isLinux) { - cmdText = this.generateLinuxCmd(this.state.selectedIp, is32Bit); + cmdText = RunMonkeyPageComponent.generateLinuxCmd(this.state.selectedIp, is32Bit); } else { - cmdText = this.generateWindowsCmd(this.state.selectedIp, is32Bit); + cmdText = RunMonkeyPageComponent.generateWindowsCmd(this.state.selectedIp, is32Bit); } return ( @@ -148,7 +163,7 @@ class RunMonkeyPageComponent extends AuthComponent { }); }; - renderIconByState(state) { + static renderIconByState(state) { if (state === 'running') { return } else if (state === 'installing') { @@ -204,19 +219,6 @@ class RunMonkeyPageComponent extends AuthComponent { }); }); }; - - updateAwsKeyId = (evt) => { - this.setState({ - awsKeyId: evt.target.value - }); - }; - - updateAwsSecretKey = (evt) => { - this.setState({ - awsSecretKey: evt.target.value - }); - }; - fetchConfig() { return this.authFetch('/api/configuration/island') .then(res => res.json()) @@ -224,41 +226,6 @@ class RunMonkeyPageComponent extends AuthComponent { return res.configuration; }) } - - updateAwsKeys = () => { - this.setState({ - awsUpdateClicked: true, - awsUpdateFailed: false - }); - this.fetchConfig() - .then(config => { - let new_config = config; - new_config['cnc']['aws_config']['aws_access_key_id'] = this.state.awsKeyId; - new_config['cnc']['aws_config']['aws_secret_access_key'] = this.state.awsSecretKey; - return new_config; - }) - .then(new_config => { - this.authFetch('/api/configuration/island', - { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify(new_config) - }) - .then(res => res.json()) - .then(res => { - this.fetchAwsInfo() - .then(res => { - if (!this.state.isAwsAuth) { - this.setState({ - awsUpdateClicked: false, - awsUpdateFailed: true - }) - } - }); - }); - }); - }; - instanceIdToInstance = (instance_id) => { let instance = this.state.awsMachines.find( function (inst) { @@ -268,9 +235,15 @@ class RunMonkeyPageComponent extends AuthComponent { }; - renderAuthAwsDiv() { + renderAwsMachinesDiv() { return (
+
+

+ + Not sure what this is? Not seeing your AWS EC2 instances? Read the documentation! +

+
{ this.state.ips.length > 1 ?
+ { + this.state.isLoadingAws ? +

+

+ +
+

+ : null + } { this.state.isOnAws ?

@@ -432,7 +366,17 @@ class RunMonkeyPageComponent extends AuthComponent { } { - this.state.isAwsAuth ? this.renderAuthAwsDiv() : this.renderNotAuthAwsDiv() + this.state.isErrorWhileCollectingAwsMachines ? +

+

+ + Error while collecting AWS machine data. Error message: {this.state.awsMachineCollectionErrorMsg}
+ Are you sure you've set the correct role on your Island AWS machine?
+ Not sure what this is? Read the documentation! +

+
+ : + this.renderAwsMachinesDiv() } diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt index 0203a1cea..5e0faea19 100644 --- a/monkey/monkey_island/requirements.txt +++ b/monkey/monkey_island/requirements.txt @@ -15,9 +15,10 @@ ipaddress enum34 pycryptodome boto3 +botocore +PyInstaller awscli bson cffi -PyInstaller virtualenv -wheel \ No newline at end of file +wheel