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:
Shay Nehmad 2019-08-13 11:54:50 +03:00
parent ae88764dc8
commit 6cd7af6eaa
8 changed files with 203 additions and 204 deletions

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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;

View File

@ -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'},

View File

@ -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>
);
}
}
}

View File

@ -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>
);
}
}