forked from p15670423/monkey
Merge pull request #645 from guardicore/feature/zt_performance_fixes
ZeroTrust performance fixes
This commit is contained in:
commit
1f79c16318
|
@ -85,5 +85,8 @@ MonkeyZoo/*
|
||||||
# Exported monkey telemetries
|
# Exported monkey telemetries
|
||||||
/monkey/telem_sample/
|
/monkey/telem_sample/
|
||||||
|
|
||||||
|
# Profiling logs
|
||||||
|
profiler_logs/
|
||||||
|
|
||||||
# vim swap files
|
# vim swap files
|
||||||
*.swp
|
*.swp
|
||||||
|
|
|
@ -10,7 +10,10 @@ from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIs
|
||||||
from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHandler
|
from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHandler
|
||||||
from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest
|
from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest
|
||||||
from envs.monkey_zoo.blackbox.tests.performance.map_generation import MapGenerationTest
|
from envs.monkey_zoo.blackbox.tests.performance.map_generation import MapGenerationTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.map_generation_from_telemetries import MapGenerationFromTelemetryTest
|
||||||
from envs.monkey_zoo.blackbox.tests.performance.report_generation import ReportGenerationTest
|
from envs.monkey_zoo.blackbox.tests.performance.report_generation import ReportGenerationTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.report_generation_from_telemetries import \
|
||||||
|
ReportGenerationFromTelemetryTest
|
||||||
from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test import TelemetryPerformanceTest
|
from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test import TelemetryPerformanceTest
|
||||||
from envs.monkey_zoo.blackbox.utils import gcp_machine_handlers
|
from envs.monkey_zoo.blackbox.utils import gcp_machine_handlers
|
||||||
|
|
||||||
|
@ -146,5 +149,11 @@ class TestMonkeyBlackbox(object):
|
||||||
"PERFORMANCE.conf",
|
"PERFORMANCE.conf",
|
||||||
timeout_in_seconds=10*60)
|
timeout_in_seconds=10*60)
|
||||||
|
|
||||||
|
def test_report_generation_from_fake_telemetries(self, island_client):
|
||||||
|
ReportGenerationFromTelemetryTest(island_client).run()
|
||||||
|
|
||||||
|
def test_map_generation_from_fake_telemetries(self, island_client):
|
||||||
|
MapGenerationFromTelemetryTest(island_client).run()
|
||||||
|
|
||||||
def test_telem_performance(self, island_client):
|
def test_telem_performance(self, island_client):
|
||||||
TelemetryPerformanceTest(island_client).test_telemetry_performance()
|
TelemetryPerformanceTest(island_client).test_telemetry_performance()
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.performance_test import PerformanceTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test_workflow import \
|
||||||
|
TelemetryPerformanceTestWorkflow
|
||||||
|
|
||||||
|
MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2)
|
||||||
|
MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5)
|
||||||
|
|
||||||
|
MAP_RESOURCES = [
|
||||||
|
"api/netmap",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class MapGenerationFromTelemetryTest(PerformanceTest):
|
||||||
|
|
||||||
|
TEST_NAME = "Map generation from fake telemetries test"
|
||||||
|
|
||||||
|
def __init__(self, island_client, break_on_timeout=False):
|
||||||
|
self.island_client = island_client
|
||||||
|
performance_config = PerformanceTestConfig(max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME,
|
||||||
|
max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME,
|
||||||
|
endpoints_to_test=MAP_RESOURCES,
|
||||||
|
break_on_timeout=break_on_timeout)
|
||||||
|
self.performance_test_workflow = TelemetryPerformanceTestWorkflow(MapGenerationFromTelemetryTest.TEST_NAME,
|
||||||
|
self.island_client,
|
||||||
|
performance_config)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.performance_test_workflow.run()
|
|
@ -25,6 +25,8 @@ class PerformanceTestWorkflow(BasicTest):
|
||||||
self.exploitation_test.wait_for_monkey_process_to_finish()
|
self.exploitation_test.wait_for_monkey_process_to_finish()
|
||||||
performance_test = EndpointPerformanceTest(self.name, self.performance_config, self.island_client)
|
performance_test = EndpointPerformanceTest(self.name, self.performance_config, self.island_client)
|
||||||
try:
|
try:
|
||||||
|
if not self.island_client.is_all_monkeys_dead():
|
||||||
|
raise RuntimeError("Can't test report times since not all Monkeys have died.")
|
||||||
assert performance_test.run()
|
assert performance_test.run()
|
||||||
finally:
|
finally:
|
||||||
self.exploitation_test.parse_logs()
|
self.exploitation_test.parse_logs()
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.performance_test import PerformanceTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test_workflow import \
|
||||||
|
TelemetryPerformanceTestWorkflow
|
||||||
|
|
||||||
|
MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2)
|
||||||
|
MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5)
|
||||||
|
|
||||||
|
REPORT_RESOURCES = [
|
||||||
|
"api/report/security",
|
||||||
|
"api/attack/report",
|
||||||
|
"api/report/zero_trust/findings",
|
||||||
|
"api/report/zero_trust/principles",
|
||||||
|
"api/report/zero_trust/pillars"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ReportGenerationFromTelemetryTest(PerformanceTest):
|
||||||
|
|
||||||
|
TEST_NAME = "Map generation from fake telemetries test"
|
||||||
|
|
||||||
|
def __init__(self, island_client, break_on_timeout=False):
|
||||||
|
self.island_client = island_client
|
||||||
|
performance_config = PerformanceTestConfig(max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME,
|
||||||
|
max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME,
|
||||||
|
endpoints_to_test=REPORT_RESOURCES,
|
||||||
|
break_on_timeout=break_on_timeout)
|
||||||
|
self.performance_test_workflow = TelemetryPerformanceTestWorkflow(ReportGenerationFromTelemetryTest.TEST_NAME,
|
||||||
|
self.island_client,
|
||||||
|
performance_config)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.performance_test_workflow.run()
|
|
@ -0,0 +1,20 @@
|
||||||
|
from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.endpoint_performance_test import EndpointPerformanceTest
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
|
||||||
|
from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test import TelemetryPerformanceTest
|
||||||
|
|
||||||
|
|
||||||
|
class TelemetryPerformanceTestWorkflow(BasicTest):
|
||||||
|
|
||||||
|
def __init__(self, name, island_client, performance_config: PerformanceTestConfig):
|
||||||
|
self.name = name
|
||||||
|
self.island_client = island_client
|
||||||
|
self.performance_config = performance_config
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
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:
|
||||||
|
self.island_client.reset_env()
|
|
@ -32,6 +32,7 @@ from monkey_island.cc.resources.pba_file_upload import FileUpload
|
||||||
from monkey_island.cc.resources.attack.attack_config import AttackConfiguration
|
from monkey_island.cc.resources.attack.attack_config import AttackConfiguration
|
||||||
from monkey_island.cc.resources.attack.attack_report import AttackReport
|
from monkey_island.cc.resources.attack.attack_report import AttackReport
|
||||||
from monkey_island.cc.resources.bootloader import Bootloader
|
from monkey_island.cc.resources.bootloader import Bootloader
|
||||||
|
from monkey_island.cc.resources.zero_trust.finding_event import ZeroTrustFindingEvent
|
||||||
from monkey_island.cc.services.database import Database
|
from monkey_island.cc.services.database import Database
|
||||||
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
|
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
|
||||||
from monkey_island.cc.services.representations import output_json
|
from monkey_island.cc.services.representations import output_json
|
||||||
|
@ -107,6 +108,7 @@ def init_api_resources(api):
|
||||||
Report,
|
Report,
|
||||||
'/api/report/<string:report_type>',
|
'/api/report/<string:report_type>',
|
||||||
'/api/report/<string:report_type>/<string:report_data>')
|
'/api/report/<string:report_type>/<string:report_data>')
|
||||||
|
api.add_resource(ZeroTrustFindingEvent, '/api/zero-trust/finding-event/<string:finding_id>')
|
||||||
|
|
||||||
api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/')
|
api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/')
|
||||||
api.add_resource(Log, '/api/log', '/api/log/')
|
api.add_resource(Log, '/api/log', '/api/log/')
|
||||||
|
|
|
@ -12,7 +12,7 @@ class AggregateFinding(Finding):
|
||||||
:raises: Assertion error if this is used when there's more then one finding which fits the query - this is not
|
: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.
|
when this function should be used.
|
||||||
"""
|
"""
|
||||||
existing_findings = Finding.objects(test=test, status=status)
|
existing_findings = Finding.objects(test=test, status=status).exclude('events')
|
||||||
assert (len(existing_findings) < 2), "More than one finding exists for {}:{}".format(test, status)
|
assert (len(existing_findings) < 2), "More than one finding exists for {}:{}".format(test, status)
|
||||||
|
|
||||||
if len(existing_findings) == 0:
|
if len(existing_findings) == 0:
|
||||||
|
@ -21,7 +21,6 @@ class AggregateFinding(Finding):
|
||||||
# Now we know for sure this is the only one
|
# Now we know for sure this is the only one
|
||||||
orig_finding = existing_findings[0]
|
orig_finding = existing_findings[0]
|
||||||
orig_finding.add_events(events)
|
orig_finding.add_events(events)
|
||||||
orig_finding.save()
|
|
||||||
|
|
||||||
|
|
||||||
def add_malicious_activity_to_timeline(events):
|
def add_malicious_activity_to_timeline(events):
|
||||||
|
|
|
@ -23,7 +23,9 @@ class Event(EmbeddedDocument):
|
||||||
|
|
||||||
# LOGIC
|
# LOGIC
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_event(title, message, event_type, timestamp=datetime.now()):
|
def create_event(title, message, event_type, timestamp=None):
|
||||||
|
if not timestamp:
|
||||||
|
timestamp = datetime.now()
|
||||||
event = Event(
|
event = Event(
|
||||||
timestamp=timestamp,
|
timestamp=timestamp,
|
||||||
title=title,
|
title=title,
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
"""
|
"""
|
||||||
Define a Document Schema for Zero Trust findings.
|
Define a Document Schema for Zero Trust findings.
|
||||||
"""
|
"""
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from mongoengine import Document, StringField, EmbeddedDocumentListField
|
from mongoengine import Document, StringField, EmbeddedDocumentListField
|
||||||
|
|
||||||
|
@ -55,6 +56,5 @@ class Finding(Document):
|
||||||
|
|
||||||
return finding
|
return finding
|
||||||
|
|
||||||
def add_events(self, events):
|
def add_events(self, events: List) -> None:
|
||||||
# type: (list) -> None
|
self.update(push_all__events=events)
|
||||||
self.events.extend(events)
|
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
import unittest
|
||||||
|
from packaging import version
|
||||||
|
|
||||||
|
import mongomock
|
||||||
|
|
||||||
import common.data.zero_trust_consts as zero_trust_consts
|
import common.data.zero_trust_consts as zero_trust_consts
|
||||||
from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding
|
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.event import Event
|
||||||
|
@ -6,6 +11,9 @@ from monkey_island.cc.testing.IslandTestCase import IslandTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestAggregateFinding(IslandTestCase):
|
class TestAggregateFinding(IslandTestCase):
|
||||||
|
|
||||||
|
@unittest.skipIf(version.parse(mongomock.__version__) <= version.parse("3.19.0"),
|
||||||
|
"mongomock version doesn't support this test")
|
||||||
def test_create_or_add_to_existing(self):
|
def test_create_or_add_to_existing(self):
|
||||||
self.fail_if_not_testing_env()
|
self.fail_if_not_testing_env()
|
||||||
self.clean_finding_db()
|
self.clean_finding_db()
|
||||||
|
@ -25,6 +33,8 @@ class TestAggregateFinding(IslandTestCase):
|
||||||
self.assertEqual(len(Finding.objects(test=test, status=status)), 1)
|
self.assertEqual(len(Finding.objects(test=test, status=status)), 1)
|
||||||
self.assertEqual(len(Finding.objects(test=test, status=status)[0].events), 2)
|
self.assertEqual(len(Finding.objects(test=test, status=status)[0].events), 2)
|
||||||
|
|
||||||
|
@unittest.skipIf(version.parse(mongomock.__version__) <= version.parse("3.19.0"),
|
||||||
|
"mongomock version doesn't support this test")
|
||||||
def test_create_or_add_to_existing_2_tests_already_exist(self):
|
def test_create_or_add_to_existing_2_tests_already_exist(self):
|
||||||
self.fail_if_not_testing_env()
|
self.fail_if_not_testing_env()
|
||||||
self.clean_finding_db()
|
self.clean_finding_db()
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import flask_restful
|
||||||
|
import json
|
||||||
|
|
||||||
|
from monkey_island.cc.auth import jwt_required
|
||||||
|
from monkey_island.cc.services.reporting.zero_trust_service import ZeroTrustService
|
||||||
|
|
||||||
|
|
||||||
|
class ZeroTrustFindingEvent(flask_restful.Resource):
|
||||||
|
|
||||||
|
@jwt_required()
|
||||||
|
def get(self, finding_id: str):
|
||||||
|
return {'events_json': json.dumps(ZeroTrustService.get_events_by_finding(finding_id), default=str)}
|
|
@ -1,6 +1,7 @@
|
||||||
import common.data.zero_trust_consts as zero_trust_consts
|
import common.data.zero_trust_consts as zero_trust_consts
|
||||||
from monkey_island.cc.models.zero_trust.finding import Finding
|
from monkey_island.cc.models.zero_trust.finding import Finding
|
||||||
from monkey_island.cc.services.reporting.zero_trust_service import ZeroTrustService
|
from monkey_island.cc.services.reporting.zero_trust_service import ZeroTrustService
|
||||||
|
import monkey_island.cc.services.reporting.zero_trust_service
|
||||||
from monkey_island.cc.testing.IslandTestCase import IslandTestCase
|
from monkey_island.cc.testing.IslandTestCase import IslandTestCase
|
||||||
|
|
||||||
EXPECTED_DICT = {
|
EXPECTED_DICT = {
|
||||||
|
@ -316,6 +317,12 @@ class TestZeroTrustService(IslandTestCase):
|
||||||
|
|
||||||
self.assertEqual(ZeroTrustService.get_pillars_to_statuses(), expected)
|
self.assertEqual(ZeroTrustService.get_pillars_to_statuses(), expected)
|
||||||
|
|
||||||
|
def test_get_events_without_overlap(self):
|
||||||
|
monkey_island.cc.services.reporting.zero_trust_service.EVENT_FETCH_CNT = 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):
|
def compare_lists_no_order(s, t):
|
||||||
t = list(t) # make a mutable copy
|
t = list(t) # make a mutable copy
|
||||||
|
|
|
@ -1,21 +1,26 @@
|
||||||
import json
|
from typing import List
|
||||||
|
|
||||||
import common.data.zero_trust_consts as zero_trust_consts
|
import common.data.zero_trust_consts as zero_trust_consts
|
||||||
|
from bson.objectid import ObjectId
|
||||||
|
|
||||||
from monkey_island.cc.models.zero_trust.finding import Finding
|
from monkey_island.cc.models.zero_trust.finding import Finding
|
||||||
|
|
||||||
|
# How many events of a single finding to return to UI.
|
||||||
|
# 50 will return 50 latest and 50 oldest events from a finding
|
||||||
|
EVENT_FETCH_CNT = 50
|
||||||
|
|
||||||
|
|
||||||
class ZeroTrustService(object):
|
class ZeroTrustService(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_pillars_grades():
|
def get_pillars_grades():
|
||||||
pillars_grades = []
|
pillars_grades = []
|
||||||
|
all_findings = Finding.objects().exclude('events')
|
||||||
for pillar in zero_trust_consts.PILLARS:
|
for pillar in zero_trust_consts.PILLARS:
|
||||||
pillars_grades.append(ZeroTrustService.__get_pillar_grade(pillar))
|
pillars_grades.append(ZeroTrustService.__get_pillar_grade(pillar, all_findings))
|
||||||
return pillars_grades
|
return pillars_grades
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __get_pillar_grade(pillar):
|
def __get_pillar_grade(pillar, all_findings):
|
||||||
all_findings = Finding.objects()
|
|
||||||
pillar_grade = {
|
pillar_grade = {
|
||||||
"pillar": pillar,
|
"pillar": pillar,
|
||||||
zero_trust_consts.STATUS_FAILED: 0,
|
zero_trust_consts.STATUS_FAILED: 0,
|
||||||
|
@ -65,7 +70,7 @@ class ZeroTrustService(object):
|
||||||
worst_status = zero_trust_consts.STATUS_UNEXECUTED
|
worst_status = zero_trust_consts.STATUS_UNEXECUTED
|
||||||
all_statuses = set()
|
all_statuses = set()
|
||||||
for test in principle_tests:
|
for test in principle_tests:
|
||||||
all_statuses |= set(Finding.objects(test=test).distinct("status"))
|
all_statuses |= set(Finding.objects(test=test).exclude('events').distinct("status"))
|
||||||
|
|
||||||
for status in all_statuses:
|
for status in all_statuses:
|
||||||
if zero_trust_consts.ORDERED_TEST_STATUSES.index(status) \
|
if zero_trust_consts.ORDERED_TEST_STATUSES.index(status) \
|
||||||
|
@ -78,7 +83,7 @@ class ZeroTrustService(object):
|
||||||
def __get_tests_status(principle_tests):
|
def __get_tests_status(principle_tests):
|
||||||
results = []
|
results = []
|
||||||
for test in principle_tests:
|
for test in principle_tests:
|
||||||
test_findings = Finding.objects(test=test)
|
test_findings = Finding.objects(test=test).exclude('events')
|
||||||
results.append(
|
results.append(
|
||||||
{
|
{
|
||||||
"test": zero_trust_consts.TESTS_MAP[test][zero_trust_consts.TEST_EXPLANATION_KEY],
|
"test": zero_trust_consts.TESTS_MAP[test][zero_trust_consts.TEST_EXPLANATION_KEY],
|
||||||
|
@ -104,25 +109,42 @@ class ZeroTrustService(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_all_findings():
|
def get_all_findings():
|
||||||
all_findings = Finding.objects()
|
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'])
|
||||||
|
|
||||||
enriched_findings = [ZeroTrustService.__get_enriched_finding(f) for f in all_findings]
|
enriched_findings = [ZeroTrustService.__get_enriched_finding(f) for f in all_findings]
|
||||||
return enriched_findings
|
return enriched_findings
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __get_enriched_finding(finding):
|
def _get_events_without_overlap(event_count: int, events: List[object]) -> List[object]:
|
||||||
test_info = zero_trust_consts.TESTS_MAP[finding.test]
|
overlap_count = event_count - EVENT_FETCH_CNT
|
||||||
enriched_finding = {
|
if overlap_count >= EVENT_FETCH_CNT:
|
||||||
"test": test_info[zero_trust_consts.FINDING_EXPLANATION_BY_STATUS_KEY][finding.status],
|
return events
|
||||||
"test_key": finding.test,
|
elif overlap_count <= 0:
|
||||||
"pillars": test_info[zero_trust_consts.PILLARS_KEY],
|
return []
|
||||||
"status": finding.status,
|
else:
|
||||||
"events": ZeroTrustService.__get_events_as_dict(finding.events)
|
return events[-1 * overlap_count:]
|
||||||
}
|
|
||||||
return enriched_finding
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __get_events_as_dict(events):
|
def __get_enriched_finding(finding):
|
||||||
return [json.loads(event.to_json()) for event in events]
|
test_info = zero_trust_consts.TESTS_MAP[finding['test']]
|
||||||
|
enriched_finding = {
|
||||||
|
'finding_id': str(finding['_id']),
|
||||||
|
'test': test_info[zero_trust_consts.FINDING_EXPLANATION_BY_STATUS_KEY][finding['status']],
|
||||||
|
'test_key': finding['test'],
|
||||||
|
'pillars': test_info[zero_trust_consts.PILLARS_KEY],
|
||||||
|
'status': finding['status'],
|
||||||
|
'latest_events': finding['latest_events'],
|
||||||
|
'oldest_events': finding['oldest_events'],
|
||||||
|
'event_count': finding['event_count']
|
||||||
|
}
|
||||||
|
return enriched_finding
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_statuses_to_pillars():
|
def get_statuses_to_pillars():
|
||||||
|
@ -147,8 +169,17 @@ class ZeroTrustService(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __get_status_of_single_pillar(pillar):
|
def __get_status_of_single_pillar(pillar):
|
||||||
grade = ZeroTrustService.__get_pillar_grade(pillar)
|
all_findings = Finding.objects().exclude('events')
|
||||||
|
grade = ZeroTrustService.__get_pillar_grade(pillar, all_findings)
|
||||||
for status in zero_trust_consts.ORDERED_TEST_STATUSES:
|
for status in zero_trust_consts.ORDERED_TEST_STATUSES:
|
||||||
if grade[status] > 0:
|
if grade[status] > 0:
|
||||||
return status
|
return status
|
||||||
return zero_trust_consts.STATUS_UNEXECUTED
|
return zero_trust_consts.STATUS_UNEXECUTED
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_events_by_finding(finding_id: str) -> List[object]:
|
||||||
|
pipeline = [{'$match': {'_id': ObjectId(finding_id)}},
|
||||||
|
{'$unwind': '$events'},
|
||||||
|
{'$project': {'events': '$events'}},
|
||||||
|
{'$replaceRoot': {'newRoot': '$events'}}]
|
||||||
|
return list(Finding.objects.aggregate(*pipeline))
|
||||||
|
|
|
@ -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`.
|
|
@ -0,0 +1,32 @@
|
||||||
|
from cProfile import Profile
|
||||||
|
import os
|
||||||
|
import pstats
|
||||||
|
|
||||||
|
PROFILER_LOG_DIR = "./profiler_logs/"
|
||||||
|
|
||||||
|
|
||||||
|
def profile(sort_args=['cumulative'], print_args=[100]):
|
||||||
|
|
||||||
|
def decorator(fn):
|
||||||
|
def inner(*args, **kwargs):
|
||||||
|
result = None
|
||||||
|
try:
|
||||||
|
profiler = Profile()
|
||||||
|
result = profiler.runcall(fn, *args, **kwargs)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
os.mkdir(PROFILER_LOG_DIR)
|
||||||
|
except os.error:
|
||||||
|
pass
|
||||||
|
filename = PROFILER_LOG_DIR + _get_filename_for_function(fn)
|
||||||
|
with open(filename, 'w') as stream:
|
||||||
|
stats = pstats.Stats(profiler, stream=stream)
|
||||||
|
stats.strip_dirs().sort_stats(*sort_args).print_stats(*print_args)
|
||||||
|
return result
|
||||||
|
return inner
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def _get_filename_for_function(fn):
|
||||||
|
function_name = fn.__module__ + "." + fn.__name__
|
||||||
|
return function_name.replace(".", "_")
|
|
@ -24,7 +24,12 @@ export default class EventsButton extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <Fragment>
|
return <Fragment>
|
||||||
<EventsModal events={this.props.events} showEvents={this.state.isShow} hideCallback={this.hide}
|
<EventsModal finding_id={this.props.finding_id}
|
||||||
|
latest_events={this.props.latest_events}
|
||||||
|
oldest_events={this.props.oldest_events}
|
||||||
|
event_count={this.props.event_count}
|
||||||
|
showEvents={this.state.isShow}
|
||||||
|
hideCallback={this.hide}
|
||||||
exportFilename={this.props.exportFilename}/>
|
exportFilename={this.props.exportFilename}/>
|
||||||
<div className="text-center" style={{'display': 'grid'}}>
|
<div className="text-center" style={{'display': 'grid'}}>
|
||||||
<Button className="btn btn-primary btn-lg" onClick={this.show}>
|
<Button className="btn btn-primary btn-lg" onClick={this.show}>
|
||||||
|
@ -35,12 +40,14 @@ export default class EventsButton extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
createEventsAmountBadge() {
|
createEventsAmountBadge() {
|
||||||
const eventsAmountBadgeContent = this.props.events.length > 9 ? '9+' : this.props.events.length;
|
const eventsAmountBadgeContent = this.props.event_count > 9 ? '9+' : this.props.event_count;
|
||||||
return <Badge>{eventsAmountBadgeContent}</Badge>;
|
return <Badge>{eventsAmountBadgeContent}</Badge>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EventsButton.propTypes = {
|
EventsButton.propTypes = {
|
||||||
events: PropTypes.array,
|
latest_events: PropTypes.array,
|
||||||
|
oldest_events: PropTypes.array,
|
||||||
|
event_count: PropTypes.number,
|
||||||
exportFilename: PropTypes.string
|
exportFilename: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
import React, {Component} from 'react';
|
import React from 'react';
|
||||||
import {Modal} from 'react-bootstrap';
|
import {Modal} from 'react-bootstrap';
|
||||||
import EventsTimeline from './EventsTimeline';
|
import EventsTimeline from './EventsTimeline';
|
||||||
import * as PropTypes from 'prop-types';
|
import * as PropTypes from 'prop-types';
|
||||||
import saveJsonToFile from '../../utils/SaveJsonToFile';
|
import saveJsonToFile from '../../utils/SaveJsonToFile';
|
||||||
import EventsModalButtons from './EventsModalButtons';
|
import EventsModalButtons from './EventsModalButtons';
|
||||||
import Pluralize from 'pluralize'
|
import AuthComponent from '../../AuthComponent';
|
||||||
|
import Pluralize from 'pluralize';
|
||||||
|
import SkippedEventsTimeline from './SkippedEventsTimeline';
|
||||||
|
|
||||||
export default class EventsModal extends Component {
|
const FINDING_EVENTS_URL = '/api/zero-trust/finding-event/';
|
||||||
|
|
||||||
|
|
||||||
|
export default class EventsModal extends AuthComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
|
@ -21,12 +26,20 @@ export default class EventsModal extends Component {
|
||||||
</h3>
|
</h3>
|
||||||
<hr/>
|
<hr/>
|
||||||
<p>
|
<p>
|
||||||
There {Pluralize('is', this.props.events.length)} {<div
|
There {Pluralize('is', this.props.event_count)} {<div
|
||||||
className={'label label-primary'}>{this.props.events.length}</div>} {Pluralize('event', this.props.events.length)} associated
|
className={'label label-primary'}>{this.props.event_count}</div>}
|
||||||
|
{Pluralize('event', this.props.event_count)} associated
|
||||||
with this finding.
|
with this finding.
|
||||||
|
{<div className={'label label-primary'}>
|
||||||
|
{this.props.latest_events.length + this.props.oldest_events.length}
|
||||||
|
</div>} {Pluralize('is', this.props.event_count)} displayed below.
|
||||||
|
All events can be exported to json.
|
||||||
</p>
|
</p>
|
||||||
{this.props.events.length > 5 ? this.renderButtons() : null}
|
{this.props.event_count > 5 ? this.renderButtons() : null}
|
||||||
<EventsTimeline events={this.props.events}/>
|
<EventsTimeline events={this.props.oldest_events}/>
|
||||||
|
{this.props.event_count > this.props.latest_events.length+this.props.oldest_events.length ?
|
||||||
|
this.renderSkippedEventsTimeline() : null}
|
||||||
|
<EventsTimeline events={this.props.latest_events}/>
|
||||||
{this.renderButtons()}
|
{this.renderButtons()}
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -34,13 +47,24 @@ export default class EventsModal extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderSkippedEventsTimeline(){
|
||||||
|
return <div className={'skipped-events-timeline'}>
|
||||||
|
<SkippedEventsTimeline
|
||||||
|
skipped_count={this.props.event_count -
|
||||||
|
this.props.latest_events.length + this.props.oldest_events.length}/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
renderButtons() {
|
renderButtons() {
|
||||||
return <EventsModalButtons
|
return <EventsModalButtons
|
||||||
onClickClose={() => this.props.hideCallback()}
|
onClickClose={() => this.props.hideCallback()}
|
||||||
onClickExport={() => {
|
onClickExport={() => {
|
||||||
const dataToSave = this.props.events;
|
let full_url = FINDING_EVENTS_URL + this.props.finding_id;
|
||||||
const filename = this.props.exportFilename;
|
this.authFetch(full_url).then(res => res.json()).then(res => {
|
||||||
saveJsonToFile(dataToSave, filename);
|
const dataToSave = res.events_json;
|
||||||
|
const filename = this.props.exportFilename;
|
||||||
|
saveJsonToFile(dataToSave, filename);
|
||||||
|
});
|
||||||
}}/>;
|
}}/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ export default class EventsTimeline extends Component {
|
||||||
<Timeline style={{fontSize: '100%'}}>
|
<Timeline style={{fontSize: '100%'}}>
|
||||||
{
|
{
|
||||||
this.props.events.map((event, index) => {
|
this.props.events.map((event, index) => {
|
||||||
const event_time = new Date(event.timestamp['$date']).toString();
|
const event_time = new Date(event.timestamp).toString();
|
||||||
return (<TimelineEvent
|
return (<TimelineEvent
|
||||||
key={index}
|
key={index}
|
||||||
createdAt={event_time}
|
createdAt={event_time}
|
||||||
|
|
|
@ -18,7 +18,11 @@ const columns = [
|
||||||
{
|
{
|
||||||
Header: 'Events', id: 'events',
|
Header: 'Events', id: 'events',
|
||||||
accessor: x => {
|
accessor: x => {
|
||||||
return <EventsButton events={x.events} exportFilename={'Events_' + x.test_key}/>;
|
return <EventsButton finding_id={x.finding_id}
|
||||||
|
latest_events={x.latest_events}
|
||||||
|
oldest_events={x.oldest_events}
|
||||||
|
event_count={x.event_count}
|
||||||
|
exportFilename={'Events_' + x.test_key}/>;
|
||||||
},
|
},
|
||||||
maxWidth: EVENTS_COLUMN_MAX_WIDTH
|
maxWidth: EVENTS_COLUMN_MAX_WIDTH
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import React, {Component} from 'react';
|
||||||
|
import {Timeline, TimelineEvent} from 'react-event-timeline';
|
||||||
|
import { faArrowsAltV } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import * as PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
|
||||||
|
export default class SkippedEventsTimeline extends Component {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Timeline style={{fontSize: '100%'}}>
|
||||||
|
<TimelineEvent
|
||||||
|
bubbleStyle={{border: '2px solid #ffcc00'}}
|
||||||
|
title='Events in between are not displayed, but can be exported to JSON.'
|
||||||
|
icon={<FontAwesomeIcon className={'timeline-event-icon'} icon={faArrowsAltV}/>} >
|
||||||
|
{this.props.skipped_count} events not displayed.
|
||||||
|
</TimelineEvent>
|
||||||
|
</Timeline>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SkippedEventsTimeline.propTypes = {skipped_count: PropTypes.number};
|
|
@ -2,7 +2,7 @@
|
||||||
margin-bottom: 2em !important;
|
margin-bottom: 2em !important;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 1000000;
|
z-index: 1000;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
font-size: large;
|
font-size: large;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,6 @@ elif [[ ${os_version_monkey} == "Ubuntu 18.04"* ]]; then
|
||||||
elif [[ ${os_version_monkey} == "Ubuntu 19.10"* ]]; then
|
elif [[ ${os_version_monkey} == "Ubuntu 19.10"* ]]; then
|
||||||
echo Detected Ubuntu 19.10
|
echo Detected Ubuntu 19.10
|
||||||
export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1804-4.2.3.tgz"
|
export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1804-4.2.3.tgz"
|
||||||
elif [[ ${os_version_monkey} == "Debian GNU/Linux 8"* ]]; then
|
|
||||||
echo Detected Debian 8
|
|
||||||
export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-debian81-4.0.16.tgz"
|
|
||||||
elif [[ ${os_version_monkey} == "Debian GNU/Linux 9"* ]]; then
|
elif [[ ${os_version_monkey} == "Debian GNU/Linux 9"* ]]; then
|
||||||
echo Detected Debian 9
|
echo Detected Debian 9
|
||||||
export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-debian92-4.2.3.tgz"
|
export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-debian92-4.2.3.tgz"
|
||||||
|
|
Loading…
Reference in New Issue