forked from p15670423/monkey
Merge pull request #303 from guardicore/hotfix/281-aws-roles-hotfix-for-amazon
Hotfix/281 aws roles hotfix for amazon.
Approved by me and @acepace 👍
This commit is contained in:
commit
9613a57e10
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'}])
|
|
@ -29,6 +29,7 @@ from monkey_island.cc.resources.telemetry import Telemetry
|
|||
from monkey_island.cc.resources.telemetry_feed import TelemetryFeed
|
||||
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
|
||||
|
||||
__author__ = 'Barak'
|
||||
|
||||
|
@ -98,6 +99,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('/<path:static_path>', 'serve_static_file', serve_static_file)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.'
|
||||
|
||||
|
|
|
@ -25,14 +25,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
|
||||
|
@ -75,8 +67,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
|
||||
|
@ -233,11 +223,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
|
||||
|
@ -251,7 +238,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
|
||||
|
||||
|
|
|
@ -643,31 +643,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": {
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"presets": ["es2015", "stage-0", "react"]
|
||||
|
||||
"presets": ["es2015", "stage-0", "react"],
|
||||
"plugins": ["emotion"]
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -91,6 +91,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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<Well key={'cmdDiv'+this.state.selectedIp} className="well-sm" style={{'margin': '0.5em'}}>
|
||||
|
@ -148,7 +163,7 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
});
|
||||
};
|
||||
|
||||
renderIconByState(state) {
|
||||
static renderIconByState(state) {
|
||||
if (state === 'running') {
|
||||
return <Icon name="check" className="text-success" style={{'marginLeft': '5px'}}/>
|
||||
} 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 (
|
||||
<div style={{'marginBottom': '2em'}}>
|
||||
<div style={{'marginTop': '1em', 'marginBottom': '1em'}}>
|
||||
<p className="alert alert-info">
|
||||
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
|
||||
Not sure what this is? Not seeing your AWS EC2 instances? <a href="https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS-EC2-instances">Read the documentation</a>!
|
||||
</p>
|
||||
</div>
|
||||
{
|
||||
this.state.ips.length > 1 ?
|
||||
<Nav bsStyle="pills" justified activeKey={this.state.selectedIp} onSelect={this.setSelectedIp}
|
||||
|
@ -296,60 +269,6 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderNotAuthAwsDiv() {
|
||||
return (
|
||||
<div style={{'marginBottom': '2em'}}>
|
||||
<p style={{'fontSize': '1.2em'}}>
|
||||
You haven't set your AWS account details or they're incorrect. Please enter them below to proceed.
|
||||
</p>
|
||||
<div style={{'marginTop': '1em'}}>
|
||||
<div className="col-sm-12">
|
||||
<div className="col-sm-6 col-sm-offset-3" style={{'fontSize': '1.2em'}}>
|
||||
<div className="panel panel-default">
|
||||
<div className="panel-body">
|
||||
<div className="input-group center-block text-center">
|
||||
<input type="text" className="form-control" placeholder="AWS Access Key ID"
|
||||
value={this.state.awsKeyId}
|
||||
onChange={evt => this.updateAwsKeyId(evt)}/>
|
||||
<input type="text" className="form-control" placeholder="AWS Secret Access Key"
|
||||
value={this.state.awsSecretKey}
|
||||
onChange={evt => this.updateAwsSecretKey(evt)}/>
|
||||
<Button
|
||||
onClick={this.updateAwsKeys}
|
||||
className={'btn btn-default btn-md center-block'}
|
||||
disabled={this.state.awsUpdateClicked}
|
||||
variant="primary">
|
||||
Update AWS details
|
||||
{ this.state.awsUpdateClicked ? <Icon name="refresh" className="text-success" style={{'marginLeft': '5px'}}/> : null }
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-sm-8 col-sm-offset-2" style={{'fontSize': '1.2em'}}>
|
||||
<p className="alert alert-info">
|
||||
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
|
||||
In order to remotely run commands on AWS EC2 instances, please make sure you have
|
||||
the <a href="https://docs.aws.amazon.com/console/ec2/run-command/prereqs" target="_blank">prerequisites</a> and if the
|
||||
instances don't show up, check the
|
||||
AWS <a href="https://docs.aws.amazon.com/console/ec2/run-command/troubleshooting" target="_blank">troubleshooting guide</a>.
|
||||
</p>
|
||||
</div>
|
||||
{
|
||||
this.state.awsUpdateFailed ?
|
||||
<div className="col-sm-8 col-sm-offset-2" style={{'fontSize': '1.2em'}}>
|
||||
<p className="alert alert-danger" role="alert">Authentication failed.</p>
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Col xs={12} lg={8}>
|
||||
|
@ -364,7 +283,7 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
disabled={this.state.runningOnIslandState !== 'not_running'}
|
||||
>
|
||||
Run on Monkey Island Server
|
||||
{ this.renderIconByState(this.state.runningOnIslandState) }
|
||||
{ RunMonkeyPageComponent.renderIconByState(this.state.runningOnIslandState) }
|
||||
</button>
|
||||
{
|
||||
// TODO: implement button functionality
|
||||
|
@ -412,6 +331,21 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
{this.generateCmdDiv()}
|
||||
</div>
|
||||
</Collapse>
|
||||
{
|
||||
this.state.isLoadingAws ?
|
||||
<p style={{'marginBottom': '2em', 'align': 'center'}}>
|
||||
<div className='sweet-loading'>
|
||||
<GridLoader
|
||||
css={loading_css_override}
|
||||
sizeUnit={"px"}
|
||||
size={30}
|
||||
color={'#ffcc00'}
|
||||
loading={this.state.loading}
|
||||
/>
|
||||
</div>
|
||||
</p>
|
||||
: null
|
||||
}
|
||||
{
|
||||
this.state.isOnAws ?
|
||||
<p className="text-center">
|
||||
|
@ -432,7 +366,17 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
}
|
||||
<Collapse in={this.state.showAws}>
|
||||
{
|
||||
this.state.isAwsAuth ? this.renderAuthAwsDiv() : this.renderNotAuthAwsDiv()
|
||||
this.state.isErrorWhileCollectingAwsMachines ?
|
||||
<div style={{'marginTop': '1em'}}>
|
||||
<p class="alert alert-danger">
|
||||
<i className="glyphicon glyphicon-warning-sign" style={{'marginRight': '5px'}}/>
|
||||
Error while collecting AWS machine data. Error message: <code>{this.state.awsMachineCollectionErrorMsg}</code><br/>
|
||||
Are you sure you've set the correct role on your Island AWS machine?<br/>
|
||||
Not sure what this is? <a href="https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS-EC2-instances">Read the documentation</a>!
|
||||
</p>
|
||||
</div>
|
||||
:
|
||||
this.renderAwsMachinesDiv()
|
||||
}
|
||||
|
||||
</Collapse>
|
||||
|
|
|
@ -15,8 +15,9 @@ ipaddress
|
|||
enum34
|
||||
pycryptodome
|
||||
boto3
|
||||
botocore
|
||||
PyInstaller
|
||||
awscli
|
||||
cffi
|
||||
PyInstaller
|
||||
virtualenv
|
||||
wheel
|
||||
wheel
|
||||
|
|
Loading…
Reference in New Issue