diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index 72d2bfc43..8c74bf145 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -11,7 +11,8 @@ STATUS_UNEXECUTED = u"Unexecuted" STATUS_POSITIVE = u"Positive" STATUS_INCONCLUSIVE = u"Inconclusive" STATUS_CONCLUSIVE = u"Conclusive" -TEST_STATUSES = (STATUS_CONCLUSIVE, STATUS_INCONCLUSIVE, STATUS_POSITIVE, STATUS_UNEXECUTED) +# Don't change order! +TEST_STATUSES = [STATUS_CONCLUSIVE, STATUS_INCONCLUSIVE, STATUS_POSITIVE, STATUS_UNEXECUTED] TEST_DATA_ENDPOINT_ELASTIC = u"unencrypted_data_endpoint_elastic" TEST_DATA_ENDPOINT_HTTP = u"unencrypted_data_endpoint_http" @@ -120,6 +121,31 @@ def populate_pillars_to_tests(): populate_pillars_to_tests() +DIRECTIVES_TO_TESTS = {} + + +def populate_directives_to_tests(): + for single_directive in DIRECTIVES: + DIRECTIVES_TO_TESTS[single_directive] = [] + for test, test_info in TESTS_MAP.items(): + DIRECTIVES_TO_TESTS[test_info[DIRECTIVE_KEY]].append(test) + + +populate_directives_to_tests() + +DIRECTIVES_TO_PILLARS = {} + + +def populate_directives_to_pillars(): + for directive, directive_tests in DIRECTIVES_TO_TESTS.items(): + directive_pillars = set() + for test in directive_tests: + for pillar in TESTS_MAP[test][PILLARS_KEY]: + directive_pillars.add(pillar) + DIRECTIVES_TO_PILLARS[directive] = directive_pillars + + +populate_directives_to_pillars() EVENT_TYPE_ISLAND = "island" EVENT_TYPE_MONKEY_NETWORK = "monkey_network" diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index e2a1a4722..e120b27ae 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -1,14 +1,13 @@ import httplib -import json + import flask_restful from flask import jsonify -from common.data.zero_trust_consts import TESTS_MAP, EXPLANATION_KEY, PILLARS_KEY, PILLARS, STATUS_CONCLUSIVE, \ - STATUS_INCONCLUSIVE, STATUS_POSITIVE, STATUS_UNEXECUTED, PILLARS_TO_TESTS from monkey_island.cc.auth import jwt_required -from monkey_island.cc.models.finding import Finding from monkey_island.cc.services.reporting.report import ReportService +from monkey_island.cc.services.reporting.zero_trust_report import get_all_findings, get_pillars_grades, \ + get_directives_status ZERO_TRUST_REPORT_TYPE = "zero_trust" GENERAL_REPORT_TYPE = "general" @@ -16,7 +15,7 @@ REPORT_TYPES = [GENERAL_REPORT_TYPE, ZERO_TRUST_REPORT_TYPE] REPORT_DATA_PILLARS = "pillars" REPORT_DATA_FINDINGS = "findings" -REPORT_DATA_RECOMMENDATION_STATUS = "recommendations" +REPORT_DATA_DIRECTIVES_STATUS = "directives" __author__ = ["itay.mizeretz", "shay.nehmad"] @@ -32,155 +31,7 @@ class Report(flask_restful.Resource): return jsonify(get_all_findings()) elif report_data == REPORT_DATA_PILLARS: return jsonify(get_pillars_grades()) - elif report_data == REPORT_DATA_RECOMMENDATION_STATUS: - return jsonify(get_recommendations_status()) + elif report_data == REPORT_DATA_DIRECTIVES_STATUS: + return jsonify(get_directives_status()) flask_restful.abort(httplib.NOT_FOUND) - - -def get_all_findings(): - all_findings = Finding.objects() - enriched_findings = [get_enriched_finding(f) for f in all_findings] - return enriched_findings - - -def get_events_as_dict(events): - return [json.loads(event.to_json()) for event in events] - - -def get_enriched_finding(finding): - test_info = TESTS_MAP[finding.test] - enriched_finding = { - # TODO add test explanation per status. - "test": test_info[EXPLANATION_KEY], - "pillars": test_info[PILLARS_KEY], - "status": finding.status, - "events": get_events_as_dict(finding.events) - } - return enriched_finding - - -def get_recommendations_status(): - network_recomms = [ - { - "Recommendation": "Do network segmentation.", - "Status": "Positive", - "Tests": [ - { - "Test": "Test B for segmentation", - "Status": "Positive" - }, - { - "Test": "Test A for segmentation", - "Status": "Positive" - }, - ] - }, - { - "Recommendation": "Analyze malicious network traffic.", - "Status": "Inconclusive", - "Tests": [ - { - "Test": "Use exploits.", - "Status": "Inconclusive" - }, - { - "Test": "Bruteforce passwords.", - "Status": "Inconclusive" - } - ] - }, - { - "Recommendation": "Data at trasnit should be...", - "Status": "Conclusive", - "Tests": [ - { - "Test": "Scan HTTP.", - "Status": "Conclusive" - }, - { - "Test": "Scan elastic.", - "Status": "Unexecuted" - } - ] - }, - ] - - device_recomms = [ - { - "Recommendation": "Install AV software.", - "Status": "Unexecuted", - "Tests": [ - { - "Test": "Search for active AV software processes", - "Status": "Unexecuted" - } - ] - }, - ] - - data_recommns = [ - { - "Recommendation": "Data at trasnit should be...", - "Status": "Conclusive", - "Tests": [ - { - "Test": "Scan HTTP.", - "Status": "Conclusive" - }, - { - "Test": "Scan elastic.", - "Status": "Unexecuted" - } - ] - }, - ] - - return [ - { - "pillar": "Networks", - "recommendationStatus": network_recomms - }, - { - "pillar": "Data", - "recommendationStatus": data_recommns - }, - { - "pillar": "Devices", - "recommendationStatus": device_recomms - }, - ] - - -def get_pillar_grade(pillar, all_findings): - pillar_grade = { - "Pillar": pillar, - STATUS_CONCLUSIVE: 0, - STATUS_INCONCLUSIVE: 0, - STATUS_POSITIVE: 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 - - -def get_pillars_grades(): - pillars_grades = [] - all_findings = Finding.objects() - for pillar in PILLARS: - pillars_grades.append(get_pillar_grade(pillar, all_findings)) - return pillars_grades diff --git a/monkey/monkey_island/cc/services/reporting/zero_trust_report.py b/monkey/monkey_island/cc/services/reporting/zero_trust_report.py new file mode 100644 index 000000000..09227f134 --- /dev/null +++ b/monkey/monkey_island/cc/services/reporting/zero_trust_report.py @@ -0,0 +1,114 @@ +import json +from common.data.zero_trust_consts import * +from monkey_island.cc.models.finding import Finding + + +def get_all_findings(): + all_findings = Finding.objects() + enriched_findings = [get_enriched_finding(f) for f in all_findings] + return enriched_findings + + +def get_events_as_dict(events): + return [json.loads(event.to_json()) for event in events] + + +def get_enriched_finding(finding): + test_info = TESTS_MAP[finding.test] + enriched_finding = { + # TODO add test explanation per status. + "test": test_info[EXPLANATION_KEY], + "pillars": test_info[PILLARS_KEY], + "status": finding.status, + "events": get_events_as_dict(finding.events) + } + return enriched_finding + + +def get_lcd_worst_status_for_test(all_findings_for_test): + current_status = STATUS_UNEXECUTED + for finding in all_findings_for_test: + if TEST_STATUSES.index(finding.status) < TEST_STATUSES.index(current_status): + current_status = finding.status + + return current_status + + +def get_tests_status(directive_tests): + results = [] + for test in directive_tests: + test_findings = Finding.objects(test=test) + results.append( + { + "test": test, + "status": get_lcd_worst_status_for_test(test_findings) + } + ) + return results + + +def get_directive_status(directive_tests): + worst_status = STATUS_UNEXECUTED + all_statuses = set() + for test in directive_tests: + all_statuses |= set(Finding.objects(test=test).distinct("status")) + + for status in all_statuses: + if TEST_STATUSES.index(status) < TEST_STATUSES.index(worst_status): + worst_status = status + + return worst_status + + +def get_directives_status(): + all_directive_statuses = {} + + # init with empty lists + for pillar in PILLARS: + all_directive_statuses[pillar] = [] + + for directive, directive_tests in DIRECTIVES_TO_TESTS.items(): + for pillar in DIRECTIVES_TO_PILLARS[directive]: + all_directive_statuses[pillar].append( + { + "directive": directive, + "tests": get_tests_status(directive_tests), + "status": get_directive_status(directive_tests) + } + ) + + return all_directive_statuses + + +def get_pillar_grade(pillar, all_findings): + pillar_grade = { + "pillar": pillar, + STATUS_CONCLUSIVE: 0, + STATUS_INCONCLUSIVE: 0, + STATUS_POSITIVE: 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 + + +def get_pillars_grades(): + pillars_grades = [] + all_findings = Finding.objects() + for pillar in PILLARS: + pillars_grades.append(get_pillar_grade(pillar, all_findings)) + return pillars_grades diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 630952961..1dfe0c40b 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -4,7 +4,7 @@ import AuthComponent from '../AuthComponent'; import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader"; import PillarGrades from "../report-components/zerotrust/PillarGrades"; import FindingsTable from "../report-components/zerotrust/FindingsTable"; -import {SinglePillarRecommendationsStatus} from "../report-components/zerotrust/SinglePillarRecommendationsStatus"; +import {SinglePillarDirectivesStatus} from "../report-components/zerotrust/SinglePillarDirectivesStatus"; class ZeroTrustReportPageComponent extends AuthComponent { @@ -45,13 +45,13 @@ class ZeroTrustReportPageComponent extends AuthComponent { ; - const recommendationsSection =

Recommendations Status

+ const directivesSection =

Directives status

{ - this.state.recommendations.map((recommendation) => - + Object.keys(this.state.directives).map((pillar) => + ) }
; @@ -61,7 +61,7 @@ class ZeroTrustReportPageComponent extends AuthComponent { content =
{pillarsSection} - {recommendationsSection} + {directivesSection} {findingSection}
; } @@ -84,8 +84,8 @@ class ZeroTrustReportPageComponent extends AuthComponent { PILLARS:
{JSON.stringify(this.state.pillars, undefined, 2)}

- recommendations: -
{JSON.stringify(this.state.recommendations, undefined, 2)}
+ DIRECTIVES: +
{JSON.stringify(this.state.directives, undefined, 2)}

FINDINGS:
{JSON.stringify(this.state.findings, undefined, 2)}
@@ -95,7 +95,7 @@ class ZeroTrustReportPageComponent extends AuthComponent { } stillLoadingDataFromServer() { - return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.recommendations === "undefined"; + return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.directives === "undefined"; } print() { @@ -111,11 +111,11 @@ class ZeroTrustReportPageComponent extends AuthComponent { findings: res }); }); - this.authFetch('/api/report/zero_trust/recommendations') + this.authFetch('/api/report/zero_trust/directives') .then(res => res.json()) .then(res => { this.setState({ - recommendations: res + directives: res }); }); this.authFetch('/api/report/zero_trust/pillars') diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js similarity index 52% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js index acd7bc322..f1e2a5d43 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js @@ -1,28 +1,29 @@ -import React, {Component} from "react"; +import React from "react"; import PagenatedTable from "../common/PagenatedTable"; import AuthComponent from "../../AuthComponent"; +const statusToIcon = { + "Positive": "fa-shield alert-success", + "Inconclusive": "fa-question alert-info", + "Conclusive": "fa-unlock-alt alert-danger", + "Unexecuted": "fa-toggle-off", +}; + const columns = [ { - Header: 'Recommendations status', + Header: 'Directives status', columns: [ - { Header: 'Recommendation', accessor: 'Recommendation', + { Header: 'Directive', accessor: 'directive', style: {'whiteSpace': 'unset'} // This enables word wrap }, - { Header: 'Status', id: "Status", + { Header: 'Status', id: 'status', accessor: x => { - const statusToIcon = { - "Positive": "fa-shield alert-success", - "Inconclusive": "fa-question alert-info", - "Conclusive": "fa-unlock-alt alert-danger", - "Unexecuted": "fa-toggle-off", - }; - return ; + return ; } }, - { Header: 'Tests', id:"Tests", + { Header: 'Tests', id: 'tests', accessor: x => { - return ; + return ; } } ] @@ -48,24 +49,24 @@ class TestsStatus extends AuthComponent { getTestsOfStatusIfAny(statusToFilter) { const filteredTests = this.props.tests.filter((test) => { - return (test.Status === statusToFilter); + return (test.status === statusToFilter); } ); if (filteredTests.length > 0) { const listItems = filteredTests.map((test) => { - return (
  • {test.Test}
  • ) + return (
  • {test.test}
  • ) }); - return
    {statusToFilter}
      {listItems}
    ; + return
    {statusToFilter}
      {listItems}
    ; } return
    ; } } -export class RecommendationsStatusTable extends AuthComponent { +export class DirectivesStatusTable extends AuthComponent { render() { - return ; + return ; } } -export default RecommendationsStatusTable; +export default DirectivesStatusTable; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js index c2dd335f3..5c0d0d903 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js @@ -7,7 +7,7 @@ const columns = [ Header: 'Pillar Grading', columns: [ { Header: 'Pillar', id: 'Pillar', accessor: x => { - return (); + return (); }}, { Header: 'Conclusive', accessor: 'Conclusive'}, { Header: 'Inconclusive', accessor: 'Inconclusive'}, diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js new file mode 100644 index 000000000..dad36d025 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js @@ -0,0 +1,22 @@ +import AuthComponent from "../../AuthComponent"; +import {PillarLabel} from "./PillarLabel"; +import DirectivesStatusTable from "./DirectivesStatusTable"; +import React, {Fragment} from "react"; + +export class SinglePillarDirectivesStatus extends AuthComponent { + directivesStatus; + + render() { + if (this.props.directivesStatus.length === 0) { + return null; + } + else { + return ( + +

    + +
    + ); + } + } +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js deleted file mode 100644 index 802b7a0a2..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js +++ /dev/null @@ -1,15 +0,0 @@ -import AuthComponent from "../../AuthComponent"; -import {PillarLabel} from "./PillarLabel"; -import RecommendationsStatusTable from "./RecommendationsStatusTable"; -import React from "react"; - -export class SinglePillarRecommendationsStatus extends AuthComponent { - render() { - return ( -
    -

    - -
    - ); - } -}