forked from p15670423/monkey
Moved report generation logic to service, and now all report data is automatic
also renamed fields to lowercase and renamed "recommendation" to "directive".
This commit is contained in:
parent
ae88764dc8
commit
6cd7af6eaa
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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 {
|
|||
<PillarGrades pillars={this.state.pillars}/>
|
||||
</div>;
|
||||
|
||||
const recommendationsSection = <div><h2>Recommendations Status</h2>
|
||||
const directivesSection = <div><h2>Directives status</h2>
|
||||
{
|
||||
this.state.recommendations.map((recommendation) =>
|
||||
<SinglePillarRecommendationsStatus
|
||||
key={recommendation.pillar}
|
||||
pillar={recommendation.pillar}
|
||||
recommendationStatus={recommendation.recommendationStatus}/>
|
||||
Object.keys(this.state.directives).map((pillar) =>
|
||||
<SinglePillarDirectivesStatus
|
||||
key={pillar}
|
||||
pillar={pillar}
|
||||
directivesStatus={this.state.directives[pillar]}/>
|
||||
)
|
||||
}
|
||||
</div>;
|
||||
|
@ -61,7 +61,7 @@ class ZeroTrustReportPageComponent extends AuthComponent {
|
|||
|
||||
content = <div>
|
||||
{pillarsSection}
|
||||
{recommendationsSection}
|
||||
{directivesSection}
|
||||
{findingSection}
|
||||
</div>;
|
||||
}
|
||||
|
@ -84,8 +84,8 @@ class ZeroTrustReportPageComponent extends AuthComponent {
|
|||
PILLARS:
|
||||
<pre>{JSON.stringify(this.state.pillars, undefined, 2)}</pre>
|
||||
<br/>
|
||||
recommendations:
|
||||
<pre>{JSON.stringify(this.state.recommendations, undefined, 2)}</pre>
|
||||
DIRECTIVES:
|
||||
<pre>{JSON.stringify(this.state.directives, undefined, 2)}</pre>
|
||||
<br/>
|
||||
FINDINGS:
|
||||
<pre>{JSON.stringify(this.state.findings, undefined, 2)}</pre>
|
||||
|
@ -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')
|
||||
|
|
|
@ -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 <i className={"fa " + statusToIcon[x.Status] + " fa-2x"} />;
|
||||
return <i className={"fa " + statusToIcon[x.status] + " fa-3x"} />;
|
||||
}
|
||||
},
|
||||
{ Header: 'Tests', id:"Tests",
|
||||
{ Header: 'Tests', id: 'tests',
|
||||
accessor: x => {
|
||||
return <TestsStatus tests={x.Tests} />;
|
||||
return <TestsStatus tests={x.tests} />;
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -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 (<li key={test.Test}>{test.Test}</li>)
|
||||
return (<li key={test.test}>{test.test}</li>)
|
||||
});
|
||||
return <div>{statusToFilter}<ul>{listItems}</ul></div>;
|
||||
return <div><i className={"fa " + statusToIcon[statusToFilter]}/> {statusToFilter}<ul>{listItems}</ul></div>;
|
||||
}
|
||||
return <div/>;
|
||||
}
|
||||
}
|
||||
|
||||
export class RecommendationsStatusTable extends AuthComponent {
|
||||
export class DirectivesStatusTable extends AuthComponent {
|
||||
render() {
|
||||
return <PagenatedTable data={this.props.recommendationStatus} columns={columns} pageSize={5}/>;
|
||||
return <PagenatedTable data={this.props.directivesStatus} columns={columns} pageSize={5}/>;
|
||||
}
|
||||
}
|
||||
|
||||
export default RecommendationsStatusTable;
|
||||
export default DirectivesStatusTable;
|
|
@ -7,7 +7,7 @@ const columns = [
|
|||
Header: 'Pillar Grading',
|
||||
columns: [
|
||||
{ Header: 'Pillar', id: 'Pillar', accessor: x => {
|
||||
return (<PillarLabel pillar={x.Pillar} />);
|
||||
return (<PillarLabel pillar={x.pillar} />);
|
||||
}},
|
||||
{ Header: 'Conclusive', accessor: 'Conclusive'},
|
||||
{ Header: 'Inconclusive', accessor: 'Inconclusive'},
|
||||
|
|
|
@ -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 (
|
||||
<Fragment>
|
||||
<h3><PillarLabel pillar={this.props.pillar}/></h3>
|
||||
<DirectivesStatusTable directivesStatus={this.props.directivesStatus}/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 (
|
||||
<div>
|
||||
<h3><PillarLabel pillar={this.props.pillar}/></h3>
|
||||
<RecommendationsStatusTable recommendationStatus={this.props.recommendationStatus}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue