From 7f3ee6952758e4a95ea3c42a36f46e3a062b726e Mon Sep 17 00:00:00 2001
From: "maor.rayzin" <maorrayzin@guardicore.com>
Date: Mon, 31 Dec 2018 14:51:07 +0200
Subject: [PATCH] - Created the exporter_init file, in there the exporter
 manager singleton is created and   populated with the relevant exporters (the
 aws exporter in this case) - changed the report file to use the new exporter
 manager singleton - changed the finding structure in the aws_exporter.py,
 divided it to creation functions   and cleaned the code.

---
 monkey/monkey_island/cc/exporter_init.py      |  17 +
 monkey/monkey_island/cc/main.py               |   3 +
 .../cc/report_exporter_manager.py             |  32 +
 .../cc/resources/aws_exporter.py              | 613 ++++++------------
 monkey/monkey_island/cc/services/report.py    |   9 +-
 .../monkey_island/report_exporter_manager.py  |  40 --
 6 files changed, 261 insertions(+), 453 deletions(-)
 create mode 100644 monkey/monkey_island/cc/exporter_init.py
 create mode 100644 monkey/monkey_island/cc/report_exporter_manager.py
 delete mode 100644 monkey/monkey_island/report_exporter_manager.py

diff --git a/monkey/monkey_island/cc/exporter_init.py b/monkey/monkey_island/cc/exporter_init.py
new file mode 100644
index 000000000..c2285772e
--- /dev/null
+++ b/monkey/monkey_island/cc/exporter_init.py
@@ -0,0 +1,17 @@
+from cc.environment.environment import load_env_from_file, AWS
+from cc.report_exporter_manager import ReportExporterManager
+from cc.resources.aws_exporter import AWSExporter
+
+
+def populate_exporter_list():
+
+    manager = ReportExporterManager()
+    if is_aws_exporter_required():
+        manager.add_exporter_to_list(AWSExporter)
+
+
+def is_aws_exporter_required():
+    if str(load_env_from_file()) == AWS:
+        return True
+    else:
+        return False
diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py
index 86015b5d4..a6ded6628 100644
--- a/monkey/monkey_island/cc/main.py
+++ b/monkey/monkey_island/cc/main.py
@@ -34,6 +34,8 @@ def main():
         logger.info('Waiting for MongoDB server')
         time.sleep(1)
 
+
+
     app = init_app(mongo_url)
     if env.is_debug():
         app.run(host='0.0.0.0', debug=True, ssl_context=('monkey_island/cc/server.crt', 'monkey_island/cc/server.key'))
@@ -44,6 +46,7 @@ def main():
         http_server.listen(env.get_island_port())
         logger.info(
             'Monkey Island Server is running on https://{}:{}'.format(local_ip_addresses()[0], env.get_island_port()))
+
         IOLoop.instance().start()
 
 
diff --git a/monkey/monkey_island/cc/report_exporter_manager.py b/monkey/monkey_island/cc/report_exporter_manager.py
new file mode 100644
index 000000000..210f28966
--- /dev/null
+++ b/monkey/monkey_island/cc/report_exporter_manager.py
@@ -0,0 +1,32 @@
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class Singleton(type):
+    _instances = {}
+
+    def __call__(cls, *args, **kwargs):
+        if cls not in cls._instances:
+            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
+        return cls._instances[cls]
+
+
+class ReportExporterManager(object):
+    __metaclass__ = Singleton
+
+    def __init__(self):
+        self._exporters_set = set()
+
+    def get_exporters_list(self):
+        return self._exporters_set
+
+    def add_exporter_to_list(self, exporter):
+        self._exporters_set.add(exporter)
+
+    def export(self, report):
+        try:
+            for exporter in self._exporters_set:
+                exporter().handle_report(report)
+        except Exception as e:
+            logger.exception('Failed to export report')
diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py
index 735de6584..8890342dd 100644
--- a/monkey/monkey_island/cc/resources/aws_exporter.py
+++ b/monkey/monkey_island/cc/resources/aws_exporter.py
@@ -87,6 +87,7 @@ class AWSExporter(Exporter):
             "ProductArn": product_arn,
             "GeneratorId": issue['type'],
             "AwsAccountId": account_id,
+            "RecordState": "ACTIVE",
             "Types": [
                 "Software and Configuration Checks/Vulnerabilities/CVE"
             ],
@@ -120,488 +121,288 @@ class AWSExporter(Exporter):
             return False
 
     @staticmethod
-    def _handle_tunnel_issue(issue, instance_arn):
-        finding = \
-            {"Severity": {
-                "Product": 5,
-                "Normalized": 100
-            }, "RecordState": "ACTIVE",
-                "Title": "Weak segmentation - Machines were able to communicate over unused ports.",
-                "Description": "Use micro-segmentation policies to disable communication other than the required.",
-                "Remediation": {
-                    "Recommendation": {
-                        "Text": "Machines are not locked down at port level. Network tunnel was set up from {0} to {1}"
-                            .format(issue['machine'], issue['dest'])
-                    }
-                }}
-
-        if 'aws_instance_id' in issue:
-            finding["Resources"] = [{
+    def _get_finding_resource(instance_id, instance_arn):
+        if instance_id:
+            return [{
                 "Type": "AwsEc2Instance",
-                "Id": instance_arn.format(instance_id=issue['aws_instance_id'])
+                "Id": instance_arn.format(instance_id=instance_id)
             }]
         else:
-            finding["Resources"] = [{'Type': 'Other'}]
+            return [{'Type': 'Other'}]
+
+    @staticmethod
+    def _build_generic_finding(severity, title, description, recommendation, instance_arn, instance_id=None):
+        finding = {
+            "Severity": {
+                "Product": severity,
+                "Normalized": 100
+            },
+            'Resource': AWSExporter._get_finding_resource(instance_id, instance_arn),
+            "Title": title,
+            "Description": description,
+            "Remediation": {
+                "Recommendation": {
+                    "Text": recommendation
+                }
+            }}
 
         return finding
 
+    @staticmethod
+    def _handle_tunnel_issue(issue, instance_arn):
+
+        return AWSExporter._build_generic_finding(
+            severity=5,
+            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']),
+            instance_arn=instance_arn,
+            instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+        )
+
     @staticmethod
     def _handle_sambacry_issue(issue, instance_arn):
-        finding = \
-            {"Severity": {
-                "Product": 10,
-                "Normalized": 100
-            }, "RecordState": "ACTIVE", "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']), "Remediation": {
-                "Recommendation": {
-                    "Text": "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'])
-                }
-            }}
 
-        if 'aws_instance_id' in issue:
-            finding["Resources"] = [{
-                "Type": "AwsEc2Instance",
-                "Id": instance_arn.format(instance_id=issue['aws_instance_id'])
-            }]
-        else:
-            finding["Resources"] = [{'Type': 'Other'}]
-
-        return finding
+        return AWSExporter._build_generic_finding(
+            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']),
+            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']),
+            instance_arn=instance_arn,
+            instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+        )
 
     @staticmethod
     def _handle_smb_pth_issue(issue, instance_arn):
-        finding = \
-            {"Severity": {
-                "Product": 5,
-                "Normalized": 100
-            }, "RecordState": "ACTIVE",
-                "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']), "Remediation": {
-                "Recommendation": {
-                    "Text": "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'])
-                }
-            }}
 
-        if 'aws_instance_id' in issue:
-            finding["Resources"] = [{
-                "Type": "AwsEc2Instance",
-                "Id": instance_arn.format(instance_id=issue['aws_instance_id'])
-            }]
-        else:
-            finding["Resources"] = [{'Type': 'Other'}]
-
-        return finding
+        return AWSExporter._build_generic_finding(
+            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']),
+            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']),
+            instance_arn=instance_arn,
+            instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+        )
 
     @staticmethod
     def _handle_ssh_issue(issue, instance_arn):
-        finding = \
-            {"Severity": {
-                "Product": 1,
-                "Normalized": 100
-            }, "RecordState": "ACTIVE",
-                "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']), "Remediation": {
-                "Recommendation": {
-                    "Text": "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'])
-                }
-            }}
 
-        if 'aws_instance_id' in issue:
-            finding["Resources"] = [{
-                "Type": "AwsEc2Instance",
-                "Id": instance_arn.format(instance_id=issue['aws_instance_id'])
-            }]
-        else:
-            finding["Resources"] = [{'Type': 'Other'}]
-
-        return finding
+        return AWSExporter._build_generic_finding(
+            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']),
+            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']),
+            instance_arn=instance_arn,
+            instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+        )
 
     @staticmethod
     def _handle_ssh_key_issue(issue, instance_arn):
-        finding = \
-            {"Severity": {
-                "Product": 1,
-                "Normalized": 100
-            }, "RecordState": "ACTIVE",
-                "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']),
-                "Remediation": {
-                    "Recommendation": {
-                        "Text": "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'])
-                    }
-                }}
 
-        if 'aws_instance_id' in issue:
-            finding["Resources"] = [{
-                "Type": "AwsEc2Instance",
-                "Id": instance_arn.format(instance_id=issue['aws_instance_id'])
-            }]
-        else:
-            finding["Resources"] = [{'Type': 'Other'}]
-        return finding
+        return AWSExporter._build_generic_finding(
+            severity=1,
+            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']),
+            instance_arn=instance_arn,
+            instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+        )
 
     @staticmethod
     def _handle_elastic_issue(issue, instance_arn):
-        finding = \
-            {"Severity": {
-                "Product": 10,
-                "Normalized": 100
-            }, "RecordState": "ACTIVE", "Title": "Elasticsearch servers are vulnerable to CVE-2015-1427",
-                "Description": "Update your Elastic Search server to version 1.4.3 and up.", "Remediation": {
-                "Recommendation": {
-                    "Text": "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'])
-                }
-            }}
 
-        if 'aws_instance_id' in issue:
-            finding["Resources"] = [{
-                "Type": "AwsEc2Instance",
-                "Id": instance_arn.format(instance_id=issue['aws_instance_id'])
-            }]
-        else:
-            finding["Resources"] = [{'Type': 'Other'}]
-
-        return finding
+        return AWSExporter._build_generic_finding(
+            severity=10,
+            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']),
+            instance_arn=instance_arn,
+            instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+        )
 
     @staticmethod
     def _handle_island_cross_segment_issue(issue, instance_arn):
-        finding = \
-            {"Severity": {
-                "Product": 1,
-                "Normalized": 100
-            }, "RecordState": "ACTIVE",
-                "Title": "Weak segmentation - Machines from different segments are able to communicate.",
-                "Description": "Segment your network and make sure there is no communication between machines from different segments.",
-                "Remediation": {
-                    "Recommendation": {
-                        "Text": "The network can probably be segmented. A monkey instance on \
+
+        return AWSExporter._build_generic_finding(
+            severity=1,
+            title="Weak segmentation - Machines from different segments are able to communicate.",
+            description="Segment your network and make sure there is no communication between machines from different segments.",
+            recommendation="The network can probably be segmented. A monkey instance on \
                         {0} in the networks {1} \
                         could directly access the Monkey Island server in the networks {2}.".format(issue['machine'],
                                                                                                     issue['networks'],
-                                                                                                    issue['server_networks'])
-                    }
-                }}
-
-        if 'aws_instance_id' in issue:
-            finding["Resources"] = [{
-                "Type": "AwsEc2Instance",
-                "Id": instance_arn.format(instance_id=issue['aws_instance_id'])
-            }]
-        else:
-            finding["Resources"] = [{'Type': 'Other'}]
-
-        return finding
+                                                                                                    issue['server_networks']),
+            instance_arn=instance_arn,
+            instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+        )
 
     @staticmethod
     def _handle_shared_passwords_issue(issue, instance_arn):
-        finding = \
-            {"Severity": {
-                "Product": 1,
-                "Normalized": 100
-            }, "RecordState": "ACTIVE", "Title": "Multiple users have the same password",
-                "Description": "Some users are sharing passwords, this should be fixed by changing passwords.",
-                "Remediation": {
-                    "Recommendation": {
-                        "Text": "These users are sharing access password: {0}.".format(issue['shared_with'])
-                    }
-                }}
 
-        if 'aws_instance_id' in issue:
-            finding["Resources"] = [{
-                "Type": "AwsEc2Instance",
-                "Id": instance_arn.format(instance_id=issue['aws_instance_id'])
-            }]
-        else:
-            finding["Resources"] = [{'Type': 'Other'}]
-
-        return finding
+        return AWSExporter._build_generic_finding(
+            severity=1,
+            title="Multiple users have the same password",
+            description="Some users are sharing passwords, this should be fixed by changing passwords.",
+            recommendation="These users are sharing access password: {0}.".format(issue['shared_with']),
+            instance_arn=instance_arn,
+            instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+        )
 
     @staticmethod
     def _handle_shellshock_issue(issue, instance_arn):
-        finding = \
-            {"Severity": {
-                "Product": 10,
-                "Normalized": 100
-            }, "RecordState": "ACTIVE", "Title": "Machines are vulnerable to 'Shellshock'",
-                "Description": "Update your Bash to a ShellShock-patched version.", "Remediation": {
-                "Recommendation": {
-                    "Text": "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'])
-                }
-            }}
 
-        if 'aws_instance_id' in issue:
-            finding["Resources"] = [{
-                "Type": "AwsEc2Instance",
-                "Id": instance_arn.format(instance_id=issue['aws_instance_id'])
-            }]
-        else:
-            finding["Resources"] = [{'Type': 'Other'}]
-
-        return finding
+        return AWSExporter._build_generic_finding(
+            severity=10,
+            title="Machines are vulnerable to 'Shellshock'",
+            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']),
+            instance_arn=instance_arn,
+            instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+        )
 
     @staticmethod
     def _handle_smb_password_issue(issue, instance_arn):
-        finding = \
-            {"Severity": {
-                "Product": 1,
-                "Normalized": 100
-            }, "RecordState": "ACTIVE",
-                "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']), "Remediation": {
-                "Recommendation": {
-                    "Text": "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'])
-                }
-            }}
 
-        if 'aws_instance_id' in issue:
-            finding["Resources"] = [{
-                "Type": "AwsEc2Instance",
-                "Id": instance_arn.format(instance_id=issue['aws_instance_id'])
-            }]
-        else:
-            finding["Resources"] = [{'Type': 'Other'}]
-
-        return finding
+        return AWSExporter._build_generic_finding(
+            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']),
+            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']),
+            instance_arn=instance_arn,
+            instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+        )
 
     @staticmethod
     def _handle_wmi_password_issue(issue, instance_arn):
-        finding = \
-            {"Severity": {
-                "Product": 1,
-                "Normalized": 100
-            }, "RecordState": "ACTIVE",
-                "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.",
-                "Remediation": {
-                    "Recommendation": {
-                        "Text": "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'])
-                    }
-                }}
 
-        if 'aws_instance_id' in issue:
-            finding["Resources"] = [{
-                "Type": "AwsEc2Instance",
-                "Id": instance_arn.format(instance_id=issue['aws_instance_id'])
-            }]
-        else:
-            finding["Resources"] = [{'Type': 'Other'}]
-
-        return finding
+        return AWSExporter._build_generic_finding(
+            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.",
+            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']),
+            instance_arn=instance_arn,
+            instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+        )
 
     @staticmethod
     def _handle_wmi_pth_issue(issue, instance_arn):
-        finding = \
-            {"Severity": {
-                "Product": 1,
-                "Normalized": 100
-            }, "RecordState": "ACTIVE",
-                "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']), "Remediation": {
-                "Recommendation": {
-                    "Text": "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'])
-                }
-            }}
 
-        if 'aws_instance_id' in issue:
-            finding["Resources"] = [{
-                "Type": "AwsEc2Instance",
-                "Id": instance_arn.format(instance_id=issue['aws_instance_id'])
-            }]
-        else:
-            finding["Resources"] = [{'Type': 'Other'}]
-
-        return finding
+        return AWSExporter._build_generic_finding(
+            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']),
+            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']),
+            instance_arn=instance_arn,
+            instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+        )
 
     @staticmethod
     def _handle_rdp_issue(issue, instance_arn):
-        finding = \
-            {"Severity": {
-                "Product": 1,
-                "Normalized": 100
-            }, "RecordState": "ACTIVE",
-                "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']), "Remediation": {
-                "Recommendation": {
-                    "Text": "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'])
-                }
-            }}
 
-        if 'aws_instance_id' in issue:
-            finding["Resources"] = [{
-                "Type": "AwsEc2Instance",
-                "Id": instance_arn.format(instance_id=issue['aws_instance_id'])
-            }]
-        else:
-            finding["Resources"] = [{'Type': 'Other'}]
-
-        return finding
+        return AWSExporter._build_generic_finding(
+            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']),
+            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']),
+            instance_arn=instance_arn,
+            instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+        )
 
     @staticmethod
     def _handle_shared_passwords_domain_issue(issue, instance_arn):
-        finding = \
-            {"Severity": {
-                "Product": 1,
-                "Normalized": 100
-            }, "RecordState": "ACTIVE", "Title": "Multiple users have the same password.",
-                "Description": "Some domain users are sharing passwords, this should be fixed by changing passwords.",
-                "Remediation": {
-                    "Recommendation": {
-                        "Text": "These users are sharing access password: {shared_with}.".format(
-                            shared_with=issue['shared_with'])
-                    }
-                }}
 
-        if 'aws_instance_id' in issue:
-            finding["Resources"] = [{
-                "Type": "AwsEc2Instance",
-                "Id": instance_arn.format(instance_id=issue['aws_instance_id'])
-            }]
-        else:
-            finding["Resources"] = [{'Type': 'Other'}]
-
-        return finding
+        return AWSExporter._build_generic_finding(
+            severity=1,
+            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']),
+            instance_arn=instance_arn,
+            instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+        )
 
     @staticmethod
     def _handle_shared_admins_domain_issue(issue, instance_arn):
-        finding = \
-            {"Severity": {
-                "Product": 1,
-                "Normalized": 100
-            }, "RecordState": "ACTIVE",
-                "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.",
-                "Remediation": {
-                    "Recommendation": {
-                        "Text": "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'])
-                    }
-                }}
 
-        if 'aws_instance_id' in issue:
-            finding["Resources"] = [{
-                "Type": "AwsEc2Instance",
-                "Id": instance_arn.format(instance_id=issue['aws_instance_id'])
-            }]
-        else:
-            finding["Resources"] = [{'Type': 'Other'}]
-
-        return finding
+        return AWSExporter._build_generic_finding(
+            severity=1,
+            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']),
+            instance_arn=instance_arn,
+            instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+        )
 
     @staticmethod
     def _handle_strong_users_on_crit_issue(issue, instance_arn):
-        finding = \
-            {"Severity": {
-                "Product": 1,
-                "Normalized": 100
-            }, "RecordState": "ACTIVE",
-                "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.",
-                "Remediation": {
-                    "Recommendation": {
-                        "Text": "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'])
-                    }
-                }}
 
-        if 'aws_instance_id' in issue:
-            finding["Resources"] = [{
-                "Type": "AwsEc2Instance",
-                "Id": instance_arn.format(instance_id=issue['aws_instance_id'])
-            }]
-        else:
-            finding["Resources"] = [{'Type': 'Other'}]
-
-        return finding
+        return AWSExporter._build_generic_finding(
+            severity=1,
+            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']),
+            instance_arn=instance_arn,
+            instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+        )
 
     @staticmethod
     def _handle_struts2_issue(issue, instance_arn):
-        finding = \
-            {"Severity": {
-                "Product": 10,
-                "Normalized": 100
-            }, "RecordState": "ACTIVE", "Title": "Struts2 servers are vulnerable to remote code execution.",
-                "Description": "Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.", "Remediation": {
-                "Recommendation": {
-                    "Text": "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'])
-                }
-            }}
 
-        if 'aws_instance_id' in issue:
-            finding["Resources"] = [{
-                "Type": "AwsEc2Instance",
-                "Id": instance_arn.format(instance_id=issue['aws_instance_id'])
-            }]
-        else:
-            finding["Resources"] = [{'Type': 'Other'}]
-
-        return finding
+        return AWSExporter._build_generic_finding(
+            severity=10,
+            title="Struts2 servers are vulnerable to remote code execution.",
+            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']),
+            instance_arn=instance_arn,
+            instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+        )
 
     @staticmethod
     def _handle_weblogic_issue(issue, instance_arn):
-        finding = \
-            {"Severity": {
-                "Product": 10,
-                "Normalized": 100
-            }, "RecordState": "ACTIVE", "Title": "Oracle WebLogic servers are vulnerable to remote code execution.",
-                "Description": "Install Oracle critical patch updates. Or update to the latest version. " \
-                               "Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.",
-                "Remediation": {
-                    "Recommendation": {
-                        "Text": "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'])
-                    }
-                }}
 
-        if 'aws_instance_id' in issue:
-            finding["Resources"] = [{
-                "Type": "AwsEc2Instance",
-                "Id": instance_arn.format(instance_id=issue['aws_instance_id'])
-            }]
-        else:
-            finding["Resources"] = [{'Type': 'Other'}]
-
-        return finding
+        return AWSExporter._build_generic_finding(
+            severity=10,
+            title="Oracle WebLogic servers are vulnerable to remote code execution.",
+            description="Install Oracle critical patch updates. Or update to the latest version. " \
+                        "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']),
+            instance_arn=instance_arn,
+            instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
+        )
 
     @staticmethod
     def _handle_hadoop_issue(issue, instance_arn):
-        finding = \
-            {"Severity": {
-                "Product": 10,
-                "Normalized": 100
-            }, "RecordState": "ACTIVE", "Title": "Hadoop/Yarn servers are vulnerable to remote code execution.",
-                "Description": "Run Hadoop in secure mode, add Kerberos authentication.", "Remediation": {
-                "Recommendation": {
-                    "Text": "The Hadoop server at {machine} ({ip_address}) is vulnerable to remote code execution attack."
-                            " The attack was made possible due to default Hadoop/Yarn configuration being insecure."
-                }
-            }}
 
-        if 'aws_instance_id' in issue:
-            finding["Resources"] = [{
-                "Type": "AwsEc2Instance",
-                "Id": instance_arn.format(instance_id=issue['aws_instance_id'])
-            }]
-        else:
-            finding["Resources"] = [{'Type': 'Other'}]
-
-        return finding
+        return AWSExporter._build_generic_finding(
+            severity=10,
+            title="Hadoop/Yarn servers are vulnerable to remote code execution.",
+            description="Run Hadoop in secure mode, add Kerberos authentication.",
+            recommendation="The Hadoop server at {machine} ({ip_address}) is vulnerable to remote code execution attack."
+                           "The attack was made possible due to default Hadoop/Yarn configuration being insecure.",
+            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/services/report.py b/monkey/monkey_island/cc/services/report.py
index 8f72e1b17..8861e8d85 100644
--- a/monkey/monkey_island/cc/services/report.py
+++ b/monkey/monkey_island/cc/services/report.py
@@ -8,7 +8,7 @@ from enum import Enum
 from six import text_type
 
 from cc.database import mongo
-from cc.resources.aws_exporter import AWSExporter
+from cc.report_exporter_manager import ReportExporterManager
 from cc.services.config import ConfigService
 from cc.services.edge import EdgeService
 from cc.services.node import NodeService
@@ -723,7 +723,7 @@ class ReportService:
                         'latest_monkey_modifytime': monkey_latest_modify_time
                     }
             }
-        ReportService.export_to_exporters(report)
+        ReportExporterManager().export(report)
         mongo.db.report.drop()
         mongo.db.report.insert_one(report)
 
@@ -755,8 +755,3 @@ class ReportService:
         return mongo.db.edge.count(
             {'exploits': {'$elemMatch': {'exploiter': exploit_type, 'result': True}}},
             limit=1) > 0
-
-    @staticmethod
-    def export_to_exporters(report):
-        for exporter in ReportService.get_active_exporters():
-            exporter.handle_report(report)
diff --git a/monkey/monkey_island/report_exporter_manager.py b/monkey/monkey_island/report_exporter_manager.py
deleted file mode 100644
index 7e9afc8a9..000000000
--- a/monkey/monkey_island/report_exporter_manager.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from cc.environment.environment import load_env_from_file, AWS
-from cc.resources.aws_exporter import AWSExporter
-import logging
-
-logger = logging.getLogger(__name__)
-
-
-class Borg:
-    _shared_state = {}
-
-    def __init__(self):
-        self.__dict__ = self._shared_state
-
-
-class ReportExporterManager(Borg):
-    def __init__(self):
-        Borg.__init__(self)
-        self._exporters_list = []
-        self._init_exporters()
-
-    def get_exporters_list(self):
-        return self._exporters_list
-
-    def _init_exporters(self):
-        self._init_aws_exporter()
-
-    def _init_aws_exporter(self):
-        if str(load_env_from_file()) == AWS:
-            self._exporters_list.append(AWSExporter)
-
-    def export(self):
-        try:
-            for exporter in self._exporters_list:
-                exporter().handle_report()
-        except Exception as e:
-            logger.exception('Failed to export report')
-
-if __name__ == '__main__':
-    print ReportExporterManager().get_exporters_list()
-    print ReportExporterManager().get_exporters_list()