Merge branch 'zt_performance_fixes' into security_performance_fixes

# Conflicts:
#	envs/monkey_zoo/blackbox/test_blackbox.py
#	monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js
This commit is contained in:
VakarisZ 2020-05-12 17:44:24 +03:00
commit 3d97cb3b61
25 changed files with 170 additions and 97 deletions

5
.gitignore vendored
View File

@ -83,7 +83,10 @@ MonkeyZoo/*
!MonkeyZoo/MonkeyZooDocs.pdf
# Exported monkey telemetries
/monkey/test_telems/
/monkey/telem_sample/
# Profiling logs
profiler_logs/
# vim swap files
*.swp

View File

@ -1,2 +1,2 @@
logs/
/blackbox/tests/performance/test_telems/*
/blackbox/tests/performance/telem_sample

View File

@ -24,13 +24,14 @@ To run telemetry performance test follow these steps:
1. Enable "Export monkey telemetries" in Configuration -> Internal -> Tests if you don't have
exported telemetries already.
2. Run monkey and wait until infection is done.
3. All telemetries are gathered in `monkey/test_telems`
3. All telemetries are gathered in `monkey/telem_sample`
2. Run telemetry performance test.
1. Move directory `monkey/test_telems` to `envs/monkey_zoo/blackbox/tests/performance/test_telems`
2. (Optional) Use `envs/monkey_zoo/blackbox/tests/performance/utils/telem_parser.py` to multiply
telemetries gathered.
1. Run `telem_parser.py` scrip with working directory set to `monkey\envs\monkey_zoo\blackbox`
1. Run `telem_parser.py` script with working directory set to `monkey\envs\monkey_zoo\blackbox`
2. Pass integer to indicate the multiplier. For example running `telem_parser.py 4` will replicate
telemetries 4 times.
3. If you're using pycharm check "Emulate terminal in output console" on debug/run configuraion.
3. Run blackbox tests, telemetry performance test will run as part of it.
3. Performance test will run as part of BlackBox tests or you can run it separately by adding
`-k 'test_telem_performance'` option.

View File

@ -1,4 +1,5 @@
import json
import logging
from time import sleep

View File

@ -1,8 +1,8 @@
import os
import logging
from time import sleep
import pytest
from time import sleep
from envs.monkey_zoo.blackbox.analyzers.communication_analyzer import CommunicationAnalyzer
from envs.monkey_zoo.blackbox.island_client.island_config_parser import IslandConfigParser
@ -29,12 +29,11 @@ LOGGER = logging.getLogger(__name__)
@pytest.fixture(autouse=True, scope='session')
def GCPHandler(request):
GCPHandler = gcp_machine_handlers.GCPHandler()
#GCPHandler.start_machines(" ".join(GCP_TEST_MACHINE_LIST))
#wait_machine_bootup()
GCPHandler.start_machines(" ".join(GCP_TEST_MACHINE_LIST))
wait_machine_bootup()
def fin():
#GCPHandler.stop_machines(" ".join(GCP_TEST_MACHINE_LIST))
pass
GCPHandler.stop_machines(" ".join(GCP_TEST_MACHINE_LIST))
request.addfinalizer(fin)
@ -52,7 +51,7 @@ def wait_machine_bootup():
@pytest.fixture(scope='class')
def island_client(island):
island_client_object = MonkeyIslandClient(island)
# island_client_object.reset_env()
island_client_object.reset_env()
yield island_client_object

View File

@ -0,0 +1,47 @@
import json
import logging
from os import listdir, path
from typing import List, Dict
from tqdm import tqdm
TELEM_DIR_PATH = './tests/performance/telem_sample'
MAX_SAME_TYPE_TELEM_FILES = 10000
LOGGER = logging.getLogger(__name__)
class SampleFileParser:
@staticmethod
def save_teletries_to_files(telems: List[Dict]):
for telem in (tqdm(telems, desc="Telemetries saved to files", position=3)):
SampleFileParser.save_telemetry_to_file(telem)
@staticmethod
def save_telemetry_to_file(telem: Dict):
telem_filename = telem['name'] + telem['method']
for i in range(MAX_SAME_TYPE_TELEM_FILES):
if not path.exists(path.join(TELEM_DIR_PATH, (str(i) + telem_filename))):
telem_filename = str(i) + telem_filename
break
with open(path.join(TELEM_DIR_PATH, telem_filename), 'w') as file:
file.write(json.dumps(telem))
@staticmethod
def read_telem_files() -> List[str]:
telems = []
try:
file_paths = [path.join(TELEM_DIR_PATH, f) for f in listdir(TELEM_DIR_PATH)
if path.isfile(path.join(TELEM_DIR_PATH, f))]
except FileNotFoundError:
raise FileNotFoundError("Telemetries to send not found. "
"Refer to readme to figure out how to generate telemetries and where to put them.")
for file_path in file_paths:
with open(file_path, 'r') as telem_file:
telem_string = "".join(telem_file.readlines()).replace("\n", "")
telems.append(telem_string)
return telems
@staticmethod
def get_all_telemetries() -> List[Dict]:
return [json.loads(t) for t in SampleFileParser.read_telem_files()]

View File

@ -0,0 +1,25 @@
from typing import List
class FakeIpGenerator:
def __init__(self):
self.fake_ip_parts = [1, 1, 1, 1]
def generate_fake_ips_for_real_ips(self, real_ips: List[str]) -> List[str]:
fake_ips = []
for i in range(len(real_ips)):
fake_ips.append('.'.join(str(part) for part in self.fake_ip_parts))
self.increment_ip()
return fake_ips
def increment_ip(self):
self.fake_ip_parts[3] += 1
self.try_fix_ip_range()
def try_fix_ip_range(self):
for i in range(len(self.fake_ip_parts)):
if self.fake_ip_parts[i] > 256:
if i-1 < 0:
raise Exception("Fake IP's out of range.")
self.fake_ip_parts[i-1] += 1
self.fake_ip_parts[i] = 1

View File

@ -1,6 +1,7 @@
import random
from envs.monkey_zoo.blackbox.tests.performance.utils.fake_ip_generator import FakeIpGenerator
from envs.monkey_zoo.blackbox.tests.performance.\
telem_sample_parsing.sample_multiplier.fake_ip_generator import FakeIpGenerator
class FakeMonkey:

View File

@ -2,35 +2,37 @@ import copy
import json
import logging
import sys
from os import listdir, path
from typing import List, Dict
from tqdm import tqdm
from envs.monkey_zoo.blackbox.tests.performance.utils.fake_ip_generator import FakeIpGenerator
from envs.monkey_zoo.blackbox.tests.performance.utils.fake_monkey import FakeMonkey
from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_file_parser import SampleFileParser
from envs.monkey_zoo.blackbox.tests.performance.\
telem_sample_parsing.sample_multiplier.fake_ip_generator import FakeIpGenerator
from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_monkey import FakeMonkey
TELEM_DIR_PATH = './tests/performance/test_telems'
TELEM_DIR_PATH = './tests/performance/telemetry_sample'
LOGGER = logging.getLogger(__name__)
class TelemParser:
class SampleMultiplier:
def __init__(self, multiplier: int):
self.multiplier = multiplier
self.fake_ip_generator = FakeIpGenerator()
def multiply_telems(self):
telems = TelemParser.get_all_telemetries()
telems = SampleFileParser.get_all_telemetries()
telem_contents = [json.loads(telem['content']) for telem in telems]
monkeys = self.get_monkeys_from_telems(telem_contents)
for i in tqdm(range(self.multiplier), desc="Batch of fabricated telemetries", position=1):
for monkey in monkeys:
monkey.change_fake_data()
fake_telem_batch = copy.deepcopy(telems)
TelemParser.fabricate_monkeys_in_telems(fake_telem_batch, monkeys)
TelemParser.offset_telem_times(iteration=i, telems=fake_telem_batch)
TelemParser.save_teletries_to_files(fake_telem_batch)
SampleMultiplier.fabricate_monkeys_in_telems(fake_telem_batch, monkeys)
SampleMultiplier.offset_telem_times(iteration=i, telems=fake_telem_batch)
SampleFileParser.save_teletries_to_files(fake_telem_batch)
LOGGER.info("")
@staticmethod
def fabricate_monkeys_in_telems(telems: List[Dict], monkeys: List[FakeMonkey]):
@ -38,7 +40,8 @@ class TelemParser:
for monkey in monkeys:
if monkey.on_island:
continue
if (monkey.original_guid in telem['content'] or monkey.original_guid in telem['endpoint']) and not monkey.on_island:
if (monkey.original_guid in telem['content'] or monkey.original_guid in telem['endpoint']) \
and not monkey.on_island:
telem['content'] = telem['content'].replace(monkey.original_guid, monkey.fake_guid)
telem['endpoint'] = telem['endpoint'].replace(monkey.original_guid, monkey.fake_guid)
for i in range(len(monkey.original_ips)):
@ -49,39 +52,11 @@ class TelemParser:
for telem in telems:
telem['time']['$date'] += iteration * 1000
@staticmethod
def save_teletries_to_files(telems: List[Dict]):
for telem in (tqdm(telems, desc="Telemetries saved to files", position=3)):
TelemParser.save_telemetry_to_file(telem)
@staticmethod
def save_telemetry_to_file(telem: Dict):
telem_filename = telem['name'] + telem['method']
for i in range(10000):
if not path.exists(path.join(TELEM_DIR_PATH, (str(i) + telem_filename))):
telem_filename = str(i) + telem_filename
break
with open(path.join(TELEM_DIR_PATH, telem_filename), 'w') as file:
file.write(json.dumps(telem))
@staticmethod
def read_telem_files() -> List[str]:
telems = []
file_paths = [path.join(TELEM_DIR_PATH, f) for f in listdir(TELEM_DIR_PATH)
if path.isfile(path.join(TELEM_DIR_PATH, f))]
for file_path in file_paths:
with open(file_path, 'r') as telem_file:
telems.append(telem_file.readline())
return telems
@staticmethod
def get_all_telemetries() -> List[Dict]:
return [json.loads(t) for t in TelemParser.read_telem_files()]
def get_monkeys_from_telems(self, telems: List[Dict]):
island_ips = TelemParser.get_island_ips_from_telems(telems)
island_ips = SampleMultiplier.get_island_ips_from_telems(telems)
monkeys = []
for telem in [telem for telem in telems if 'telem_category' in telem and telem['telem_category'] == 'system_info']:
for telem in [telem for telem in telems
if 'telem_category' in telem and telem['telem_category'] == 'system_info']:
if 'network_info' not in telem['data']:
continue
guid = telem['monkey_guid']
@ -111,4 +86,4 @@ class TelemParser:
if __name__ == "__main__":
TelemParser(multiplier=int(sys.argv[1])).multiply_telems()
SampleMultiplier(multiplier=int(sys.argv[1])).multiply_telems()

View File

@ -0,0 +1,19 @@
from unittest import TestCase
from envs.monkey_zoo.blackbox.tests.performance.\
telem_sample_parsing.sample_multiplier.fake_ip_generator import FakeIpGenerator
class TestFakeIpGenerator(TestCase):
def test_fake_ip_generation(self):
fake_ip_gen = FakeIpGenerator()
self.assertListEqual([1, 1, 1, 1], fake_ip_gen.fake_ip_parts)
for i in range(256):
fake_ip_gen.generate_fake_ips_for_real_ips(['1.1.1.1'])
self.assertListEqual(['1.1.2.1'], fake_ip_gen.generate_fake_ips_for_real_ips(['1.1.1.1']))
fake_ip_gen.fake_ip_parts = [256, 256, 255, 256]
self.assertListEqual(['256.256.255.256', '256.256.256.1'],
fake_ip_gen.generate_fake_ips_for_real_ips(['1.1.1.1', '1.1.1.2']))
fake_ip_gen.fake_ip_parts = [256, 256, 256, 256]
self.assertRaises(Exception, fake_ip_gen.generate_fake_ips_for_real_ips(['1.1.1.1']))

View File

@ -8,7 +8,7 @@ from envs.monkey_zoo.blackbox.analyzers.performance_analyzer import PerformanceA
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
from envs.monkey_zoo.blackbox.island_client.supported_request_method import SupportedRequestMethod
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
from envs.monkey_zoo.blackbox.tests.performance.utils.telem_parser import TelemParser
from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_file_parser import SampleFileParser
LOGGER = logging.getLogger(__name__)
@ -24,11 +24,10 @@ class TelemetryPerformanceTest:
def test_telemetry_performance(self):
LOGGER.info("Starting telemetry performance test.")
try:
all_telemetries = TelemParser.get_all_telemetries()
all_telemetries = SampleFileParser.get_all_telemetries()
except FileNotFoundError:
LOGGER.error("Telemetries to send not found. Refer to readme to figure out how to generate telemetries "
"and where to put them.")
return False
raise FileNotFoundError("Telemetries to send not found. "
"Refer to readme to figure out how to generate telemetries and where to put them.")
LOGGER.info("Telemetries imported successfully.")
all_telemetries.sort(key=lambda telem: telem['time']['$date'])
telemetry_parse_times = {}

View File

@ -13,9 +13,8 @@ class TelemetryPerformanceTestWorkflow(BasicTest):
def run(self):
try:
# TelemetryPerformanceTest(island_client=self.island_client).test_telemetry_performance()
TelemetryPerformanceTest(island_client=self.island_client).test_telemetry_performance()
performance_test = EndpointPerformanceTest(self.name, self.performance_config, self.island_client)
assert performance_test.run()
finally:
pass
# self.island_client.reset_env()
self.island_client.reset_env()

View File

@ -1,11 +0,0 @@
class FakeIpGenerator:
def __init__(self):
self.fake_ip_parts = [1, 1, 1, 1]
def generate_fake_ips_for_real_ips(self, real_ips):
self.fake_ip_parts[2] += 1
fake_ips = []
for i in range(len(real_ips)):
fake_ips.append('.'.join(str(part) for part in self.fake_ip_parts))
self.fake_ip_parts[3] += 1
return fake_ips

View File

@ -20,8 +20,7 @@ class AggregateFinding(Finding):
else:
# Now we know for sure this is the only one
orig_finding = existing_findings[0]
orig_finding.update(push_all__events=events)
orig_finding.save()
orig_finding.add_events(events)
def add_malicious_activity_to_timeline(events):

View File

@ -2,6 +2,7 @@
"""
Define a Document Schema for Zero Trust findings.
"""
from typing import List
from mongoengine import Document, StringField, EmbeddedDocumentListField
@ -54,3 +55,6 @@ class Finding(Document):
finding.save()
return finding
def add_events(self, events: List) -> None:
self.update(push_all__events=events)

View File

@ -6,7 +6,6 @@ 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
from monkey_island.cc.testing.profiler_decorator import profile
ZERO_TRUST_REPORT_TYPE = "zero_trust"
SECURITY_REPORT_TYPE = "security"
@ -22,7 +21,6 @@ __author__ = ["itay.mizeretz", "shay.nehmad"]
class Report(flask_restful.Resource):
@jwt_required()
@profile()
def get(self, report_type=SECURITY_REPORT_TYPE, report_data=None):
if report_type == SECURITY_REPORT_TYPE:
return ReportService.get_report()

View File

@ -1,3 +1,4 @@
import logging
from functools import wraps
from os import mkdir, path
import shutil
@ -8,10 +9,14 @@ from flask import request
from monkey_island.cc.models.test_telem import TestTelem
from monkey_island.cc.services.config import ConfigService
TEST_TELEM_DIR = "./test_telems"
TELEM_SAMPLE_DIR = "./telem_sample"
MAX_SAME_CATEGORY_TELEMS = 10000
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class TestTelemStore:
@staticmethod
@ -31,14 +36,17 @@ class TestTelemStore:
@staticmethod
def export_test_telems():
logger.info(f"Exporting all telemetries to {TELEM_SAMPLE_DIR}")
try:
mkdir(TEST_TELEM_DIR)
mkdir(TELEM_SAMPLE_DIR)
except FileExistsError:
shutil.rmtree(TEST_TELEM_DIR)
mkdir(TEST_TELEM_DIR)
logger.info("Deleting all previous telemetries.")
shutil.rmtree(TELEM_SAMPLE_DIR)
mkdir(TELEM_SAMPLE_DIR)
for test_telem in TestTelem.objects():
with open(TestTelemStore.get_unique_file_path_for_test_telem(TEST_TELEM_DIR, test_telem), 'w') as file:
file.write(test_telem.to_json())
with open(TestTelemStore.get_unique_file_path_for_test_telem(TELEM_SAMPLE_DIR, test_telem), 'w') as file:
file.write(test_telem.to_json(indent=2))
logger.info("Telemetries exported!")
@staticmethod
def get_unique_file_path_for_test_telem(target_dir: str, test_telem: TestTelem):

View File

@ -3,12 +3,10 @@ import json
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.services.reporting.zero_trust_service import ZeroTrustService
from monkey_island.cc.testing.profiler_decorator import profile
class ZeroTrustFindingEvent(flask_restful.Resource):
@jwt_required()
@profile()
def get(self, finding_id: str):
return {'events_json': json.dumps(ZeroTrustService.get_events_by_finding(finding_id), default=str)}

View File

@ -319,9 +319,9 @@ class TestZeroTrustService(IslandTestCase):
def test_get_events_without_overlap(self):
monkey_island.cc.services.reporting.zero_trust_service.EVENT_FETCH_CNT = 5
self.assertListEqual([], ZeroTrustService._ZeroTrustService__get_events_without_overlap(5, [1, 2, 3]))
self.assertListEqual([3], ZeroTrustService._ZeroTrustService__get_events_without_overlap(6, [1, 2, 3]))
self.assertListEqual([1, 2, 3, 4, 5], ZeroTrustService._ZeroTrustService__get_events_without_overlap(10, [1, 2, 3, 4, 5]))
self.assertListEqual([], ZeroTrustService._get_events_without_overlap(5, [1, 2, 3]))
self.assertListEqual([3], ZeroTrustService._get_events_without_overlap(6, [1, 2, 3]))
self.assertListEqual([1, 2, 3, 4, 5], ZeroTrustService._get_events_without_overlap(10, [1, 2, 3, 4, 5]))
def compare_lists_no_order(s, t):

View File

@ -109,28 +109,27 @@ class ZeroTrustService(object):
@staticmethod
def get_all_findings():
pipeline = [{'$match': {}},
{'$addFields': {'oldest_events': {'$slice': ['$events', EVENT_FETCH_CNT]},
pipeline = [{'$addFields': {'oldest_events': {'$slice': ['$events', EVENT_FETCH_CNT]},
'latest_events': {'$slice': ['$events', -1*EVENT_FETCH_CNT]},
'event_count': {'$size': '$events'}}},
{'$unset': ['events']}]
all_findings = list(Finding.objects.aggregate(*pipeline))
for finding in all_findings:
finding['latest_events'] = ZeroTrustService.__get_events_without_overlap(finding['event_count'],
finding['latest_events'])
finding['latest_events'] = ZeroTrustService._get_events_without_overlap(finding['event_count'],
finding['latest_events'])
enriched_findings = [ZeroTrustService.__get_enriched_finding(f) for f in all_findings]
return enriched_findings
@staticmethod
def __get_events_without_overlap(event_count: int, events: List[object]) -> List[object]:
def _get_events_without_overlap(event_count: int, events: List[object]) -> List[object]:
overlap_count = event_count - EVENT_FETCH_CNT
if overlap_count >= EVENT_FETCH_CNT:
return events
elif overlap_count <= 0:
return []
else:
return events[ -overlap_count :]
return events[-1 * overlap_count:]
@staticmethod
def __get_enriched_finding(finding):

View File

@ -0,0 +1,9 @@
# Profiling island
To profile specific methods on island a `@profile(sort_args=['cumulative'], print_args=[100])`
decorator can be used.
Use it as any other decorator. After decorated method is used, a file will appear in a
directory provided in `profiler_decorator.py`. Filename describes the path of
the method that was profiled. For example if method `monkey_island/cc/resources/netmap.get`
was profiled, then the results of this profiling will appear in
`monkey_island_cc_resources_netmap_get`.

View File

@ -1,4 +1,4 @@
import React, {Component} from 'react';
import React from 'react';
import {Modal} from 'react-bootstrap';
import EventsTimeline from './EventsTimeline';
import * as PropTypes from 'prop-types';
@ -6,7 +6,7 @@ import saveJsonToFile from '../../utils/SaveJsonToFile';
import EventsModalButtons from './EventsModalButtons';
import AuthComponent from '../../AuthComponent';
import Pluralize from 'pluralize';
import SkippedEventsTimeline from "./SkippedEventsTimeline";
import SkippedEventsTimeline from './SkippedEventsTimeline';
const FINDING_EVENTS_URL = '/api/zero-trust/finding-event/';

View File

@ -23,4 +23,4 @@ requests
dpath
ring
stix2
tcdm
tqdm