From 378baa7139c1e5d94380d57e9e49eb5cb0f3531e Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 3 Feb 2019 14:18:08 +0200 Subject: [PATCH] Add most infrastrucure for running AWS commands --- .../common/cloud/{aws.py => aws_instance.py} | 2 +- monkey/common/cloud/aws_service.py | 41 +++++++++++++++++++ monkey/common/cmd/__init__.py | 0 monkey/common/cmd/aws_cmd_result.py | 20 +++++++++ monkey/common/cmd/aws_cmd_runner.py | 39 ++++++++++++++++++ monkey/common/cmd/cmd_result.py | 12 ++++++ monkey/common/cmd/cmd_runner.py | 22 ++++++++++ .../system_info/aws_collector.py | 4 +- monkey/monkey_island/cc/environment/aws.py | 4 +- .../cc/resources/aws_exporter.py | 4 +- .../monkey_island/cc/resources/remote_run.py | 20 +++++++++ 11 files changed, 161 insertions(+), 7 deletions(-) rename monkey/common/cloud/{aws.py => aws_instance.py} (95%) create mode 100644 monkey/common/cloud/aws_service.py create mode 100644 monkey/common/cmd/__init__.py create mode 100644 monkey/common/cmd/aws_cmd_result.py create mode 100644 monkey/common/cmd/aws_cmd_runner.py create mode 100644 monkey/common/cmd/cmd_result.py create mode 100644 monkey/common/cmd/cmd_runner.py create mode 100644 monkey/monkey_island/cc/resources/remote_run.py diff --git a/monkey/common/cloud/aws.py b/monkey/common/cloud/aws_instance.py similarity index 95% rename from monkey/common/cloud/aws.py rename to monkey/common/cloud/aws_instance.py index 7937815ef..86b8d1a34 100644 --- a/monkey/common/cloud/aws.py +++ b/monkey/common/cloud/aws_instance.py @@ -3,7 +3,7 @@ import urllib2 __author__ = 'itay.mizeretz' -class AWS(object): +class AwsInstance(object): def __init__(self): try: self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read() diff --git a/monkey/common/cloud/aws_service.py b/monkey/common/cloud/aws_service.py new file mode 100644 index 000000000..0cc641356 --- /dev/null +++ b/monkey/common/cloud/aws_service.py @@ -0,0 +1,41 @@ +import boto3 + +__author__ = 'itay.mizeretz' + + +class AwsService(object): + """ + Supplies various AWS services + """ + + # TODO: consider changing from static to singleton, and generally change design + 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 + + @staticmethod + 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) + + @staticmethod + def get_regions(): + return AwsService.get_session().get_available_regions('ssm') diff --git a/monkey/common/cmd/__init__.py b/monkey/common/cmd/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/common/cmd/aws_cmd_result.py b/monkey/common/cmd/aws_cmd_result.py new file mode 100644 index 000000000..5c9057a61 --- /dev/null +++ b/monkey/common/cmd/aws_cmd_result.py @@ -0,0 +1,20 @@ +from common.cmd.cmd_result import CmdResult + + +__author__ = 'itay.mizeretz' + + +class AwsCmdResult(CmdResult): + """ + Class representing an AWS command result + """ + + def __init__(self, command_info): + super(AwsCmdResult, self).__init__( + self.is_successful(command_info), command_info[u'ResponseCode'], command_info[u'StandardOutputContent'], + command_info[u'StandardErrorContent']) + self.command_info = command_info + + @staticmethod + def is_successful(command_info): + return command_info[u'Status'] == u'Success' diff --git a/monkey/common/cmd/aws_cmd_runner.py b/monkey/common/cmd/aws_cmd_runner.py new file mode 100644 index 000000000..0f5032b9d --- /dev/null +++ b/monkey/common/cmd/aws_cmd_runner.py @@ -0,0 +1,39 @@ +import time + +from common.cloud.aws_service import AwsService +from common.cmd.aws_cmd_result import AwsCmdResult + +__author__ = 'itay.mizeretz' + + +class AwsCmdRunner(object): + """ + Class for running a command on a remote AWS machine + """ + + def __init__(self, instance_id, region, is_powershell=False): + self.instance_id = instance_id + self.region = region + self.is_powershell = is_powershell + self.ssm = AwsService.get_client('ssm', region) + + def run_command(self, command, timeout): + command_id = self._send_command(command) + init_time = time.time() + curr_time = init_time + command_info = None + while curr_time - init_time < timeout: + command_info = self.ssm.get_command_invocation(CommandId=command_id, InstanceId=self.instance_id) + if AwsCmdResult.is_successful(command_info): + break + else: + time.sleep(0.5) + curr_time = time.time() + + return AwsCmdResult(command_info) + + def _send_command(self, command): + doc_name = "AWS-RunPowerShellScript" if self.is_powershell else "AWS-RunShellScript" + command_res = self.ssm.send_command(DocumentName=doc_name, Parameters={'commands': [command]}, + InstanceIds=[self.instance_id]) + return command_res['Command']['CommandId'] diff --git a/monkey/common/cmd/cmd_result.py b/monkey/common/cmd/cmd_result.py new file mode 100644 index 000000000..40eca2c85 --- /dev/null +++ b/monkey/common/cmd/cmd_result.py @@ -0,0 +1,12 @@ + + +class CmdResult(object): + """ + Class representing a command result + """ + + def __init__(self, is_success, status_code=None, stdout=None, stderr=None): + self.is_success = is_success + self.status_code = status_code + self.stdout = stdout + self.stderr = stderr diff --git a/monkey/common/cmd/cmd_runner.py b/monkey/common/cmd/cmd_runner.py new file mode 100644 index 000000000..cf4afa289 --- /dev/null +++ b/monkey/common/cmd/cmd_runner.py @@ -0,0 +1,22 @@ +from abc import abstractmethod + +__author__ = 'itay.mizeretz' + + +class CmdRunner(object): + """ + Interface for running a command on a remote machine + """ + + # Default command timeout in seconds + DEFAULT_TIMEOUT = 5 + + @abstractmethod + def run_command(self, command, timeout=DEFAULT_TIMEOUT): + """ + Runs the given command on the remote machine + :param command: The command to run + :param timeout: Timeout in seconds for command. + :return: Command result + """ + raise NotImplementedError() diff --git a/monkey/infection_monkey/system_info/aws_collector.py b/monkey/infection_monkey/system_info/aws_collector.py index 699339ae8..df90e5913 100644 --- a/monkey/infection_monkey/system_info/aws_collector.py +++ b/monkey/infection_monkey/system_info/aws_collector.py @@ -1,6 +1,6 @@ import logging -from common.cloud.aws import AWS +from common.cloud.aws_instance import AwsInstance __author__ = 'itay.mizeretz' @@ -15,7 +15,7 @@ class AwsCollector(object): @staticmethod def get_aws_info(): LOG.info("Collecting AWS info") - aws = AWS() + aws = AwsInstance() info = {} if aws.is_aws_instance(): LOG.info("Machine is an AWS instance") diff --git a/monkey/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py index a004a2540..d80157806 100644 --- a/monkey/monkey_island/cc/environment/aws.py +++ b/monkey/monkey_island/cc/environment/aws.py @@ -1,6 +1,6 @@ import cc.auth from cc.environment import Environment -from common.cloud.aws import AWS +from common.cloud.aws_instance import AwsInstance __author__ = 'itay.mizeretz' @@ -8,7 +8,7 @@ __author__ = 'itay.mizeretz' class AwsEnvironment(Environment): def __init__(self): super(AwsEnvironment, self).__init__() - self.aws_info = AWS() + self.aws_info = AwsInstance() self._instance_id = self._get_instance_id() self.region = self._get_region() diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 735de6584..d8a01e909 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -7,7 +7,7 @@ from botocore.exceptions import UnknownServiceError from cc.resources.exporter import Exporter from cc.services.config import ConfigService from cc.environment.environment import load_server_configuration_from_file -from common.cloud.aws import AWS +from common.cloud.aws_instance import AwsInstance logger = logging.getLogger(__name__) @@ -20,7 +20,7 @@ class AWSExporter(Exporter): @staticmethod def handle_report(report_json): - aws = AWS() + aws = AwsInstance() findings_list = [] issues_list = report_json['recommendations']['issues'] if not issues_list: diff --git a/monkey/monkey_island/cc/resources/remote_run.py b/monkey/monkey_island/cc/resources/remote_run.py new file mode 100644 index 000000000..6df5aee02 --- /dev/null +++ b/monkey/monkey_island/cc/resources/remote_run.py @@ -0,0 +1,20 @@ +import json +from flask import request, jsonify, make_response +import flask_restful + + +class RemoteRun(flask_restful.Resource): + def run_aws_monkey(self, request_body): + instance_id = request_body.get('instance_id') + region = request_body.get('region') + os = request_body.get('os') # TODO: consider getting this from instance + island_ip = request_body.get('island_ip') # TODO: Consider getting this another way. Not easy to determine target interface + + def post(self): + body = json.loads(request.data) + if body.get('type') == 'aws': + local_run = self.run_aws_monkey(body) + return jsonify(is_running=local_run[0], error_text=local_run[1]) + + # default action + return make_response({'error': 'Invalid action'}, 500) \ No newline at end of file