-
- Domain related recommendations
-
+ {/* Checks if there are any domain issues. If there are more then one: render the title. Otherwise,
+ * don't render it (since the issues themselves will be empty. */}
+ {Object.keys(this.state.report.recommendations.domain_issues).length !== 0 ?
+
Domain related recommendations
: null }
{this.generateIssues(this.state.report.recommendations.domain_issues)}
-
- Machine related Recommendations
-
+ {/* Checks if there are any issues. If there are more then one: render the title. Otherwise,
+ * don't render it (since the issues themselves will be empty. */}
+ {Object.keys(this.state.report.recommendations.issues).length !== 0 ?
+
Machine related recommendations
: null }
{this.generateIssues(this.state.report.recommendations.issues)}
-
);
}
+
generateReportGlanceSection() {
let exploitPercentage =
(100 * this.state.report.glance.exploited.length) / this.state.report.glance.scanned.length;
@@ -458,6 +437,9 @@ class ReportPageComponent extends AuthComponent {
- Credential Map
+ Credentials Map
This map visualizes possible attack paths through the network using credential compromise. Paths represent lateral movement opportunities by attackers.
@@ -495,6 +477,21 @@ class ReportPageComponent extends AuthComponent {
);
}
+ generateAttackSection() {
+ return (
+
+ ATT&CK report
+
+
+ This report shows information about ATT&CK techniques used by Infection Monkey.
+
+
+
+
)
+ }
+
generateReportFooter() {
return (
+ {
+ this.state.isLoadingAws ?
+
+
+
+
+
+ : null
+ }
+ {
+ this.state.isOnAws ?
+
+ OR
+
+ :
+ null
+ }
+ {
+ this.state.isOnAws ?
+
+
+
+ :
+ null
+ }
+
+ {
+ this.state.isErrorWhileCollectingAwsMachines ?
+
+
+
+ Error while collecting AWS machine data. Error message: {this.state.awsMachineCollectionErrorMsg}
+ Are you sure you've set the correct role on your Island AWS machine?
+ Not sure what this is? Read the documentation!
+
+
+ :
+ this.renderAwsMachinesDiv()
+ }
+
+
Go ahead and monitor the ongoing infection in the Infection Map view.
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/TelemetryPage.js b/monkey/monkey_island/cc/ui/src/components/pages/TelemetryPage.js
index a23dd1d36..120344eea 100644
--- a/monkey/monkey_island/cc/ui/src/components/pages/TelemetryPage.js
+++ b/monkey/monkey_island/cc/ui/src/components/pages/TelemetryPage.js
@@ -11,7 +11,7 @@ const renderTime = (val) => val.split('.')[0];
const columns = [
{ title: 'Time', prop: 'timestamp', render: renderTime},
{ title: 'Monkey', prop: 'monkey'},
- { title: 'Type', prop: 'telem_type'},
+ { title: 'Type', prop: 'telem_catagory'},
{ title: 'Details', prop: 'data', render: renderJson, width: '40%' }
];
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js
new file mode 100755
index 000000000..a0b92d9bd
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js
@@ -0,0 +1,134 @@
+import React, {Fragment} from 'react';
+import {Col} from 'react-bootstrap';
+import AuthComponent from '../AuthComponent';
+import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader";
+import ReportLoader from "../report-components/common/ReportLoader";
+import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning";
+import PrintReportButton from "../report-components/common/PrintReportButton";
+import {extractExecutionStatusFromServerResponse} from "../report-components/common/ExecutionStatus";
+import SummarySection from "../report-components/zerotrust/SummarySection";
+import FindingsSection from "../report-components/zerotrust/FindingsSection";
+import PrinciplesSection from "../report-components/zerotrust/PrinciplesSection";
+
+class ZeroTrustReportPageComponent extends AuthComponent {
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ allMonkeysAreDead: false,
+ runStarted: true
+ };
+ }
+
+ componentDidMount() {
+ this.updatePageState();
+ const refreshInterval = setInterval(this.updatePageState, 8000);
+ this.setState(
+ {refreshDataIntervalHandler: refreshInterval}
+ )
+ }
+
+ componentWillUnmount() {
+ clearInterval(this.state.refreshDataIntervalHandler);
+ }
+
+ updateMonkeysRunning = () => {
+ return this.authFetch('/api')
+ .then(res => res.json())
+ .then(res => {
+ this.setState(extractExecutionStatusFromServerResponse(res));
+ return res;
+ });
+ };
+
+ updatePageState = () => {
+ this.updateMonkeysRunning().then(res => this.getZeroTrustReportFromServer(res));
+ };
+
+ render() {
+ let content;
+ if (this.state.runStarted) {
+ content = this.generateReportContent();
+ } else {
+ content = ;
+ }
+
+ return (
+
+
5. Zero Trust Report
+
+ {content}
+
+
+ );
+ }
+
+ generateReportContent() {
+ let content;
+
+ if (this.stillLoadingDataFromServer()) {
+ content =
;
+ } else {
+ content = ;
+ }
+
+ return (
+
+
+
+
+
+ {content}
+
+
+
+ )
+ }
+
+ stillLoadingDataFromServer() {
+ return typeof this.state.findings === "undefined"
+ || typeof this.state.pillars === "undefined"
+ || typeof this.state.principles === "undefined";
+ }
+
+ getZeroTrustReportFromServer() {
+ let res;
+ this.authFetch('/api/report/zero_trust/findings')
+ .then(res => res.json())
+ .then(res => {
+ this.setState({
+ findings: res
+ });
+ });
+ this.authFetch('/api/report/zero_trust/principles')
+ .then(res => res.json())
+ .then(res => {
+ this.setState({
+ principles: res
+ });
+ });
+ this.authFetch('/api/report/zero_trust/pillars')
+ .then(res => res.json())
+ .then(res => {
+ this.setState({
+ pillars: res
+ });
+ });
+ }
+}
+
+export default ZeroTrustReportPageComponent;
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/ExecutionStatus.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/ExecutionStatus.js
new file mode 100644
index 000000000..840e570d7
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/ExecutionStatus.js
@@ -0,0 +1,6 @@
+export function extractExecutionStatusFromServerResponse(res) {
+ return {
+ allMonkeysAreDead: (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done']),
+ runStarted: res['completed_steps']['run_monkey']
+ };
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/MonkeysStillAliveWarning.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/MonkeysStillAliveWarning.js
new file mode 100644
index 000000000..7b72570fa
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/MonkeysStillAliveWarning.js
@@ -0,0 +1,21 @@
+import React, {Component} from "react";
+import * as PropTypes from "prop-types";
+
+export default class MonkeysStillAliveWarning extends Component {
+ render() {
+ return
+ {
+ this.props.allMonkeysAreDead ?
+ ''
+ :
+ (
+
+ Some monkeys are still running. To get the best report it's best to wait for all of them to finish
+ running.
+
)
+ }
+
+ }
+}
+
+MonkeysStillAliveWarning.propTypes = {allMonkeysAreDead: PropTypes.bool};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/MustRunMonkeyWarning.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/MustRunMonkeyWarning.js
new file mode 100644
index 000000000..f1d23e302
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/MustRunMonkeyWarning.js
@@ -0,0 +1,11 @@
+import React, {Component} from "react";
+import {NavLink} from "react-router-dom";
+
+export default class MustRunMonkeyWarning extends Component {
+ render() {
+ return
+
+ You have to run a monkey before generating a report!
+
+ }
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/PaginatedTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/PaginatedTable.js
new file mode 100644
index 000000000..5bc6183fd
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/PaginatedTable.js
@@ -0,0 +1,36 @@
+import React, {Component} from "react";
+import ReactTable from "react-table";
+import * as PropTypes from "prop-types";
+
+class PaginatedTable extends Component {
+ render() {
+ if (this.props.data.length > 0) {
+ let defaultPageSize = this.props.data.length > this.props.pageSize ? this.props.pageSize : this.props.data.length;
+ let showPagination = this.props.data.length > this.props.pageSize;
+
+ return (
+
+
+
+ );
+ }
+ else {
+ return (
+
+ );
+ }
+ }
+}
+
+export default PaginatedTable;
+
+PaginatedTable.propTypes = {
+ data: PropTypes.array,
+ columns: PropTypes.array,
+ pageSize: PropTypes.number,
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/PrintReportButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/PrintReportButton.js
new file mode 100644
index 000000000..1a692bd68
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/PrintReportButton.js
@@ -0,0 +1,14 @@
+import React, {Component} from "react";
+import {Button} from "react-bootstrap";
+import * as PropTypes from "prop-types";
+
+export default class PrintReportButton extends Component {
+ render() {
+ return
+
+
+ }
+}
+
+PrintReportButton.propTypes = {onClick: PropTypes.func};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportHeader.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportHeader.js
new file mode 100644
index 000000000..44d470f7e
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportHeader.js
@@ -0,0 +1,45 @@
+import React, {Component} from "react";
+import {Col} from "react-bootstrap";
+import * as PropTypes from "prop-types";
+
+let monkeyLogoImage = require('../../../images/monkey-icon.svg');
+
+export const ReportTypes = {
+ zeroTrust: "Zero Trust",
+ security: "Security",
+ null: ""
+};
+
+export class ReportHeader extends Component {
+ report_type;
+
+ render() {
+ return
+ }
+}
+
+export default ReportHeader;
+
+ReportHeader.propTypes = {
+ report_type: PropTypes.string,
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportLoader.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportLoader.js
new file mode 100644
index 000000000..e389f7532
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportLoader.js
@@ -0,0 +1,28 @@
+import {css} from "@emotion/core";
+import React, {Component} from "react";
+import {GridLoader} from "react-spinners";
+import * as PropTypes from "prop-types";
+
+const loading_css_override = css`
+ display: block;
+ margin-right: auto;
+ margin-left: auto;
+`;
+
+
+export default class ReportLoader extends Component {
+ render() {
+ return
+
Generating Report...
+
+
+ }
+}
+
+ReportLoader.propTypes = {loading: PropTypes.bool};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/SecurityIssuesGlance.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/SecurityIssuesGlance.js
new file mode 100644
index 000000000..41a45edad
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/SecurityIssuesGlance.js
@@ -0,0 +1,22 @@
+import React, {Component, Fragment} from "react";
+import * as PropTypes from "prop-types";
+
+export default class SecurityIssuesGlance extends Component {
+ render() {
+ return
+ {
+ this.props.issuesFound ?
+ (
+
+ Critical security issues were detected!
+
) :
+ (
+
+ No critical security issues were detected.
+
)
+ }
+
+ }
+}
+
+SecurityIssuesGlance.propTypes = {issuesFound: PropTypes.bool};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/AttackReport.js
new file mode 100644
index 000000000..13f9cd92e
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/AttackReport.js
@@ -0,0 +1,200 @@
+import React from 'react';
+import {Col} from 'react-bootstrap';
+import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
+import {edgeGroupToColor, options} from 'components/map/MapOptions';
+import '../../../styles/Collapse.scss';
+import AuthComponent from '../../AuthComponent';
+import {ScanStatus} from "../../attack/techniques/Helpers";
+import Collapse from '@kunukn/react-collapse';
+
+import T1210 from '../../attack/techniques/T1210';
+import T1197 from '../../attack/techniques/T1197';
+import T1110 from '../../attack/techniques/T1110';
+import T1075 from "../../attack/techniques/T1075";
+import T1003 from "../../attack/techniques/T1003";
+import T1059 from "../../attack/techniques/T1059";
+import T1086 from "../../attack/techniques/T1086";
+import T1082 from "../../attack/techniques/T1082";
+import T1145 from "../../attack/techniques/T1145";
+import T1105 from "../../attack/techniques/T1105";
+import T1107 from "../../attack/techniques/T1107";
+import T1065 from "../../attack/techniques/T1065";
+import T1035 from "../../attack/techniques/T1035";
+import T1129 from "../../attack/techniques/T1129";
+import T1106 from "../../attack/techniques/T1106";
+import T1188 from "../../attack/techniques/T1188";
+import T1090 from "../../attack/techniques/T1090";
+import T1041 from "../../attack/techniques/T1041";
+import T1222 from "../../attack/techniques/T1222";
+import T1005 from "../../attack/techniques/T1005";
+import T1018 from "../../attack/techniques/T1018";
+import T1016 from "../../attack/techniques/T1016";
+import T1021 from "../../attack/techniques/T1021";
+import T1064 from "../../attack/techniques/T1064";
+import {extractExecutionStatusFromServerResponse} from "../common/ExecutionStatus";
+
+const tech_components = {
+ 'T1210': T1210,
+ 'T1197': T1197,
+ 'T1110': T1110,
+ 'T1075': T1075,
+ 'T1003': T1003,
+ 'T1059': T1059,
+ 'T1086': T1086,
+ 'T1082': T1082,
+ 'T1145': T1145,
+ 'T1065': T1065,
+ 'T1105': T1105,
+ 'T1035': T1035,
+ 'T1129': T1129,
+ 'T1106': T1106,
+ 'T1107': T1107,
+ 'T1188': T1188,
+ 'T1090': T1090,
+ 'T1041': T1041,
+ 'T1222': T1222,
+ 'T1005': T1005,
+ 'T1018': T1018,
+ 'T1016': T1016,
+ 'T1021': T1021,
+ 'T1064': T1064
+};
+
+const classNames = require('classnames');
+
+class AttackReportPageComponent extends AuthComponent {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ report: false,
+ allMonkeysAreDead: false,
+ runStarted: true,
+ collapseOpen: ''
+ };
+ }
+
+ componentDidMount() {
+ this.updateMonkeysRunning().then(res => this.getReportFromServer(res));
+ }
+
+ updateMonkeysRunning = () => {
+ return this.authFetch('/api')
+ .then(res => res.json())
+ .then(res => {
+ this.setState(extractExecutionStatusFromServerResponse(res));
+ return res;
+ });
+ };
+
+ getReportFromServer(res) {
+ if (res['completed_steps']['run_monkey']) {
+ this.authFetch('/api/attack/report')
+ .then(res => res.json())
+ .then(res => {
+ this.setState({
+ report: res
+ });
+ });
+ }
+ }
+
+ onToggle = technique =>
+ this.setState(state => ({ collapseOpen: state.collapseOpen === technique ? null : technique }));
+
+ getComponentClass(tech_id){
+ switch (this.state.report[tech_id].status) {
+ case ScanStatus.SCANNED:
+ return 'collapse-info';
+ case ScanStatus.USED:
+ return 'collapse-danger';
+ default:
+ return 'collapse-default';
+ }
+ }
+
+ getTechniqueCollapse(tech_id){
+ return (
+
+
+ {
+ this.setState({ tech_id: collapseState });
+ }}
+ onInit={({ collapseState }) => {
+ this.setState({ tech_id: collapseState });
+ }}
+ render={collapseState => this.createTechniqueContent(collapseState, tech_id)}/>
+
+ );
+ }
+
+ createTechniqueContent(collapseState, technique) {
+ const TechniqueComponent = tech_components[technique];
+ return (
+
+
+
+ );
+ }
+
+ renderLegend() {
+ return( )
+ }
+
+ generateReportContent(){
+ let content = [];
+ Object.keys(this.state.report).forEach((tech_id) => {
+ content.push(this.getTechniqueCollapse(tech_id))
+ });
+ return (
+
+ {this.renderLegend()}
+
+
+ )
+ }
+
+ render() {
+ let content;
+ if (! this.state.runStarted)
+ {
+ content =
+
+
+ You have to run a monkey before generating a report!
+
;
+ } else if (this.state.report === false){
+ content = (Generating Report...
);
+ } else if (Object.keys(this.state.report).length === 0) {
+ if (this.state.runStarted) {
+ content = (No techniques were scanned
);
+ }
+ } else {
+ content = this.generateReportContent();
+ }
+ return ( {content}
);
+ }
+}
+
+export default AttackReportPageComponent;
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/BreachedServers.js
similarity index 79%
rename from monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js
rename to monkey/monkey_island/cc/ui/src/components/report-components/security/BreachedServers.js
index d23a14c38..16f445ce9 100644
--- a/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/BreachedServers.js
@@ -5,12 +5,17 @@ let renderArray = function(val) {
return ;
};
+let renderIpAddresses = function (val) {
+ return {renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}
;
+};
+
const columns = [
{
Header: 'Breached Servers',
columns: [
{Header: 'Machine', accessor: 'label'},
- {Header: 'IP Addresses', id: 'ip_addresses', accessor: x => renderArray(x.ip_addresses)},
+ {Header: 'IP Addresses', id: 'ip_addresses',
+ accessor: x => renderIpAddresses(x)},
{Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)}
]
}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/CollapsibleWell.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/CollapsibleWell.js
similarity index 100%
rename from monkey/monkey_island/cc/ui/src/components/report-components/CollapsibleWell.js
rename to monkey/monkey_island/cc/ui/src/components/report-components/security/CollapsibleWell.js
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreach.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreach.js
new file mode 100644
index 000000000..ea39e3c45
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreach.js
@@ -0,0 +1,82 @@
+import React from 'react';
+import ReactTable from 'react-table'
+
+let renderArray = function(val) {
+ return {val.map(x => {x})};
+};
+
+let renderIpAddresses = function (val) {
+ return {renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")} ;
+};
+
+let renderMachine = function (data) {
+ return {data.label} ( {renderIpAddresses(data)} )
+};
+
+let renderPbaResults = function (results) {
+ let pbaClass = "";
+ if (results[1]){
+ pbaClass="pba-success"
+ } else {
+ pbaClass="pba-danger"
+ }
+ return {results[0]}
+};
+
+const subColumns = [
+ {id: 'pba_name', Header: "Name", accessor: x => x.name, style: { 'whiteSpace': 'unset' }, width: 160},
+ {id: 'pba_output', Header: "Output", accessor: x => renderPbaResults(x.result), style: { 'whiteSpace': 'unset' }}
+];
+
+let renderDetails = function (data) {
+ let defaultPageSize = data.length > pageSize ? pageSize : data.length;
+ let showPagination = data.length > pageSize;
+ return
+};
+
+const columns = [
+ {
+ Header: 'Post breach actions',
+ columns: [
+ {id: 'pba_machine', Header:'Machine', accessor: x => renderMachine(x)}
+ ]
+ }
+];
+
+const pageSize = 10;
+
+class PostBreachComponent extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+
+ render() {
+ let pbaMachines = this.props.data.filter(function(value, index, arr){
+ return ( value.pba_results !== "None" && value.pba_results.length > 0);
+ });
+ let defaultPageSize = pbaMachines.length > pageSize ? pageSize : pbaMachines.length;
+ let showPagination = pbaMachines > pageSize;
+ return (
+
+ {
+ return renderDetails(row.original.pba_results);
+ }}
+ />
+
+
+ );
+ }
+}
+
+export default PostBreachComponent;
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/ScannedServers.js
similarity index 80%
rename from monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js
rename to monkey/monkey_island/cc/ui/src/components/report-components/security/ScannedServers.js
index 9b62bbdc5..57418e415 100644
--- a/monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/ScannedServers.js
@@ -5,12 +5,17 @@ let renderArray = function(val) {
return ;
};
+let renderIpAddresses = function (val) {
+ return {renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}
;
+};
+
const columns = [
{
Header: 'Scanned Servers',
columns: [
{ Header: 'Machine', accessor: 'label'},
- { Header: 'IP Addresses', id: 'ip_addresses', accessor: x => renderArray(x.ip_addresses)},
+ { Header: 'IP Addresses', id: 'ip_addresses',
+ accessor: x => renderIpAddresses(x)},
{ Header: 'Accessible From', id: 'accessible_from_nodes', accessor: x => renderArray(x.accessible_from_nodes)},
{ Header: 'Services', id: 'services', accessor: x => renderArray(x.services)}
]
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/StolenPasswords.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/StolenPasswords.js
similarity index 100%
rename from monkey/monkey_island/cc/ui/src/components/report-components/StolenPasswords.js
rename to monkey/monkey_island/cc/ui/src/components/report-components/security/StolenPasswords.js
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/StrongUsers.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/StrongUsers.js
similarity index 100%
rename from monkey/monkey_island/cc/ui/src/components/report-components/StrongUsers.js
rename to monkey/monkey_island/cc/ui/src/components/report-components/security/StrongUsers.js
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js
new file mode 100644
index 000000000..761ff94a9
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js
@@ -0,0 +1,43 @@
+import React, {Component, Fragment} from "react";
+import EventsModal from "./EventsModal";
+import {Badge, Button} from "react-bootstrap";
+import * as PropTypes from "prop-types";
+
+export default class EventsButton extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ isShow: false
+ }
+ }
+
+ hide = () => {
+ this.setState({isShow: false});
+ };
+
+ show = () => {
+ this.setState({isShow: true});
+ };
+
+ render() {
+ return
+
+
+
+
+ ;
+ }
+
+ createEventsAmountBadge() {
+ const eventsAmountBadgeContent = this.props.events.length > 9 ? "9+" : this.props.events.length;
+ return {eventsAmountBadgeContent};
+ }
+}
+
+EventsButton.propTypes = {
+ events: PropTypes.array,
+ exportFilename: PropTypes.string,
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js
new file mode 100644
index 000000000..a7f2fe41c
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js
@@ -0,0 +1,51 @@
+import React, {Component} from "react";
+import {Badge, Modal} from "react-bootstrap";
+import EventsTimeline from "./EventsTimeline";
+import * as PropTypes from "prop-types";
+import saveJsonToFile from "../../utils/SaveJsonToFile";
+import EventsModalButtons from "./EventsModalButtons";
+import Pluralize from 'pluralize'
+import {statusToLabelType} from "./StatusLabel";
+
+export default class EventsModal extends Component {
+ constructor(props) {
+ super(props);
+ }
+
+ render() {
+ return (
+
+
this.props.hideCallback()}>
+
+
+
Events
+
+
+
+ There {Pluralize('is', this.props.events.length)} {
{this.props.events.length}
} {Pluralize('event', this.props.events.length)} associated with this finding.
+
+ {this.props.events.length > 5 ? this.renderButtons() : null}
+
+ {this.renderButtons()}
+
+
+
+ );
+ }
+
+ renderButtons() {
+ return this.props.hideCallback()}
+ onClickExport={() => {
+ const dataToSave = this.props.events;
+ const filename = this.props.exportFilename;
+ saveJsonToFile(dataToSave, filename);
+ }}/>;
+ }
+}
+
+EventsModal.propTypes = {
+ showEvents: PropTypes.bool,
+ events: PropTypes.array,
+ hideCallback: PropTypes.func,
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModalButtons.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModalButtons.js
new file mode 100644
index 000000000..962c54893
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModalButtons.js
@@ -0,0 +1,20 @@
+import React, {Component} from "react";
+import ExportEventsButton from "./ExportEventsButton";
+import * as PropTypes from "prop-types";
+
+export default class EventsModalButtons extends Component {
+ render() {
+ return
+
+
+
+ }
+}
+
+EventsModalButtons.propTypes = {
+ onClickClose: PropTypes.func,
+ onClickExport: PropTypes.func
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js
new file mode 100644
index 000000000..b7fb90811
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js
@@ -0,0 +1,36 @@
+import React, {Component} from "react";
+import {Timeline, TimelineEvent} from "react-event-timeline";
+import * as PropTypes from "prop-types";
+
+let monkeyLocalIcon = require('../../../images/zerotrust/im-alert-machine-icon.svg');
+let monkeyNetworkIcon = require('../../../images/zerotrust/im-alert-network-icon.svg');
+
+const eventTypeToIcon = {
+ "monkey_local": monkeyLocalIcon,
+ "monkey_network": monkeyNetworkIcon,
+};
+
+export default class EventsTimeline extends Component {
+ render() {
+ return (
+
+
+ {
+ this.props.events.map((event, index) => {
+ const event_time = new Date(event.timestamp['$date']).toString();
+ return (}>
+ {event.message}
+ )
+ })
+ }
+
+
+ );
+ }
+}
+
+EventsTimeline.propTypes = {events: PropTypes.array};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ExportEventsButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ExportEventsButton.js
new file mode 100644
index 000000000..bb6fc6c45
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ExportEventsButton.js
@@ -0,0 +1,15 @@
+import React, {Component} from "react";
+import {Button} from "react-bootstrap";
+import * as PropTypes from "prop-types";
+
+export default class ExportEventsButton extends Component {
+ render() {
+ return
+ }
+}
+
+ExportEventsButton.propTypes = {onClick: PropTypes.func};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js
new file mode 100644
index 000000000..95b9d0389
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js
@@ -0,0 +1,63 @@
+import React, {Component, Fragment} from "react";
+import PillarLabel from "./PillarLabel";
+import EventsButton from "./EventsButton";
+import ZeroTrustPillars, {ZeroTrustStatuses} from "./ZeroTrustPillars";
+import {FindingsTable} from "./FindingsTable";
+
+
+class FindingsSection extends Component {
+ mapFindingsByStatus() {
+ const statusToFindings = {};
+ for (const key in ZeroTrustStatuses) {
+ statusToFindings[ZeroTrustStatuses[key]] = [];
+ }
+
+ this.props.findings.map((finding) => {
+ // Deep copy
+ const newFinding = JSON.parse(JSON.stringify(finding));
+ newFinding.pillars = newFinding.pillars.map((pillar) => {
+ return {name: pillar, status: this.props.pillarsToStatuses[pillar]}
+ });
+ statusToFindings[newFinding.status].push(newFinding);
+ });
+ return statusToFindings;
+ }
+
+ render() {
+ const findingsByStatus = this.mapFindingsByStatus();
+ return (
+
+
Findings
+
+ Deep-dive into the details of each test, and see the explicit events and exact timestamps in which things
+ happened in your network. This will enable you to match up with your SOC logs and alerts and to gain deeper
+ insight as to what exactly happened during this test.
+
+
+
+
+
+
+ );
+ }
+
+ getFilteredFindings(statusToFilter) {
+ const findings = this.props.findings.map((finding) => {
+ // Deep copy
+ const newFinding = JSON.parse(JSON.stringify(finding));
+ if (newFinding.status === statusToFilter) {
+ newFinding.pillars = newFinding.pillars.map((pillar) => {
+ return {name: pillar, status: this.props.pillarsToStatuses[pillar]}
+ });
+ return newFinding;
+ }
+ });
+ // Filter nulls out of the list
+ return findings.filter(function (el) {
+ return el != null;
+ });
+ }
+}
+
+
+export default FindingsSection;
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js
new file mode 100644
index 000000000..acff1df89
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js
@@ -0,0 +1,53 @@
+import React, {Component, Fragment} from "react";
+import StatusLabel from "./StatusLabel";
+import PaginatedTable from "../common/PaginatedTable";
+import * as PropTypes from "prop-types";
+import PillarLabel from "./PillarLabel";
+import EventsButton from "./EventsButton";
+
+const EVENTS_COLUMN_MAX_WIDTH = 160;
+const PILLARS_COLUMN_MAX_WIDTH = 200;
+const columns = [
+ {
+ columns: [
+ {
+ Header: 'Finding', accessor: 'test',
+ style: {'whiteSpace': 'unset'} // This enables word wrap
+ },
+
+ {
+ Header: 'Events', id: "events",
+ accessor: x => {
+ return ;
+ },
+ maxWidth: EVENTS_COLUMN_MAX_WIDTH,
+ },
+
+ {
+ Header: 'Pillars', id: "pillars",
+ accessor: x => {
+ const pillars = x.pillars;
+ const pillarLabels = pillars.map((pillar) =>
+
+ );
+ return {pillarLabels}
;
+ },
+ maxWidth: PILLARS_COLUMN_MAX_WIDTH,
+ style: {'whiteSpace': 'unset'}
+ },
+ ]
+ }
+];
+
+
+export class FindingsTable extends Component {
+ render() {
+ return
+ {
+ } tests' findings
+
+
+ }
+}
+
+FindingsTable.propTypes = {data: PropTypes.array, status: PropTypes.string};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js
new file mode 100644
index 000000000..51c5ca380
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js
@@ -0,0 +1,25 @@
+import React, {Component} from "react";
+import {statusToLabelType} from "./StatusLabel";
+import * as PropTypes from "prop-types";
+
+const pillarToIcon = {
+ "Data": "fa fa-database",
+ "People": "fa fa-user",
+ "Networks": "fa fa-wifi",
+ "Workloads": "fa fa-cloud",
+ "Devices": "fa fa-laptop",
+ "Visibility & Analytics": "fa fa-eye-slash",
+ "Automation & Orchestration": "fa fa-cogs",
+};
+
+export default class PillarLabel extends Component {
+ render() {
+ const className = "label " + statusToLabelType[this.props.status];
+ return {this.props.pillar}
+ }
+}
+
+PillarLabel.propTypes = {
+ status: PropTypes.string,
+ pillar: PropTypes.string,
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js
new file mode 100644
index 000000000..7cefcab61
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js
@@ -0,0 +1,17 @@
+import React, {Component} from "react";
+import * as PropTypes from "prop-types";
+import ResponsiveVennDiagram from "./venn-components/ResponsiveVennDiagram";
+
+class PillarOverview extends Component {
+ render() {
+ return (
+
+
);
+ }
+}
+
+export default PillarOverview;
+
+PillarOverview.propTypes = {
+ grades: PropTypes.array,
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesSection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesSection.js
new file mode 100644
index 000000000..bb957d42d
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesSection.js
@@ -0,0 +1,31 @@
+import React, {Component} from "react";
+import SinglePillarPrinciplesStatus from "./SinglePillarPrinciplesStatus";
+import * as PropTypes from "prop-types";
+
+export default class PrinciplesSection extends Component {
+ render() {
+ return
+
Test Results
+
+ The
+ Zero Trust eXtended (ZTX) framework
+ is composed of 7 pillars. Each pillar is built of
+ several guiding principles tested by the Infection Monkey.
+
+ {
+ Object.keys(this.props.principles).map((pillar) =>
+
+ )
+ }
+
+ }
+}
+
+PrinciplesSection.propTypes = {
+ principles: PropTypes.object,
+ pillarsToStatuses: PropTypes.object
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesStatusTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesStatusTable.js
new file mode 100644
index 000000000..b50ee0c28
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesStatusTable.js
@@ -0,0 +1,71 @@
+import React, {Fragment} from "react";
+import PaginatedTable from "../common/PaginatedTable";
+import AuthComponent from "../../AuthComponent";
+import StatusLabel from "./StatusLabel";
+import * as PropTypes from "prop-types";
+import {ZeroTrustStatuses} from "./ZeroTrustPillars";
+
+
+const MAX_WIDTH_STATUS_COLUMN = 80;
+const columns = [
+ {
+ columns: [
+ { Header: 'Status', id: 'status',
+ accessor: x => {
+ return ;
+ },
+ maxWidth: MAX_WIDTH_STATUS_COLUMN
+ },
+ { Header: 'Zero Trust Principle', accessor: 'principle',
+ style: {'whiteSpace': 'unset'} // This enables word wrap
+ },
+ { Header: 'Monkey Tests', id: 'tests',
+ style: {'whiteSpace': 'unset'}, // This enables word wrap
+ accessor: x => {
+ return ;
+ }
+ }
+ ]
+ }
+];
+
+class TestsStatus extends AuthComponent {
+ render() {
+ return (
+
+ {this.getFilteredTestsByStatusIfAny(ZeroTrustStatuses.failed)}
+ {this.getFilteredTestsByStatusIfAny(ZeroTrustStatuses.verify)}
+ {this.getFilteredTestsByStatusIfAny(ZeroTrustStatuses.passed)}
+ {this.getFilteredTestsByStatusIfAny(ZeroTrustStatuses.unexecuted)}
+
+ );
+ }
+
+ getFilteredTestsByStatusIfAny(statusToFilter) {
+ const filteredTests = this.props.tests.filter((test) => {
+ return (test.status === statusToFilter);
+ }
+ );
+
+ if (filteredTests.length > 0) {
+ const listItems = filteredTests.map((test) => {
+ return ({test.test})
+ });
+ return
+
+
+ ;
+ }
+ return ;
+ }
+}
+
+export class PrinciplesStatusTable extends AuthComponent {
+ render() {
+ return ;
+ }
+}
+
+export default PrinciplesStatusTable;
+
+PrinciplesStatusTable.propTypes = {principlesStatus: PropTypes.array};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js
new file mode 100644
index 000000000..5ef75f2b4
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js
@@ -0,0 +1,60 @@
+import React, {Component} from "react";
+import StatusLabel from "./StatusLabel";
+import {ZeroTrustStatuses} from "./ZeroTrustPillars";
+import {NavLink} from "react-router-dom";
+import {Panel} from "react-bootstrap";
+
+
+class ZeroTrustReportLegend extends Component {
+ render() {
+ const legendContent = this.getLegendContent();
+
+ return (
+
+
+
+ Legend
+
+
+
+
+ {legendContent}
+
+
+
+ );
+ }
+
+ getLegendContent() {
+ return
+
+ -
+
+
+
+ {"\t"}At least one of the tests related to this component failed. This means that the Infection Monkey detected an unmet Zero Trust requirement.
+
+ -
+
+
+
+ {"\t"}At least one of the tests’ results related to this component requires further manual verification.
+
+ -
+
+
+
+ {"\t"}All Tests related to this pillar passed. No violation of a Zero Trust guiding principle was detected.
+
+ -
+
+
+
+ {"\t"}This status means the test wasn't executed.To activate more tests, refer to the Monkey configuration page.
+
+
+
;
+ }
+}
+
+export default ZeroTrustReportLegend;
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarPrinciplesStatus.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarPrinciplesStatus.js
new file mode 100644
index 000000000..8e4512ac7
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarPrinciplesStatus.js
@@ -0,0 +1,37 @@
+import AuthComponent from "../../AuthComponent";
+import PillarLabel from "./PillarLabel";
+import PrinciplesStatusTable from "./PrinciplesStatusTable";
+import React from "react";
+import * as PropTypes from "prop-types";
+import {Panel} from "react-bootstrap";
+
+export default class SinglePillarPrinciplesStatus extends AuthComponent {
+ render() {
+ if (this.props.principlesStatus.length === 0) {
+ return null;
+ }
+ else {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+ }
+}
+
+SinglePillarPrinciplesStatus.propTypes = {
+ principlesStatus: PropTypes.array,
+ pillar: PropTypes.string,
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js
new file mode 100644
index 000000000..028ca7d89
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js
@@ -0,0 +1,37 @@
+import React, {Component} from "react";
+import * as PropTypes from "prop-types";
+
+const statusToIcon = {
+ "Passed": "fa-check",
+ "Verify": "fa-exclamation-triangle",
+ "Failed": "fa-bomb",
+ "Unexecuted": "fa-question",
+};
+
+export const statusToLabelType = {
+ "Passed": "label-success",
+ "Verify": "label-warning",
+ "Failed": "label-danger",
+ "Unexecuted": "label-default",
+};
+
+export default class StatusLabel extends Component {
+ render() {
+ let text = "";
+ if (this.props.showText) {
+ text = " " + this.props.status;
+ }
+
+ return (
+
+ {text}
+
+ );
+ }
+}
+
+StatusLabel.propTypes = {
+ status: PropTypes.string,
+ showText: PropTypes.bool,
+ size: PropTypes.string
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js
new file mode 100644
index 000000000..d34a484b9
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js
@@ -0,0 +1,37 @@
+import React, {Component, Fragment} from "react";
+import PillarLabel from "./PillarLabel";
+import StatusLabel from "./StatusLabel";
+import * as PropTypes from "prop-types";
+import {ZeroTrustStatuses} from "./ZeroTrustPillars";
+
+export default class StatusesToPillarsSummary extends Component {
+ render() {
+ return (
+ {this.getStatusSummary(ZeroTrustStatuses.failed)}
+ {this.getStatusSummary(ZeroTrustStatuses.verify)}
+ {this.getStatusSummary(ZeroTrustStatuses.passed)}
+ {this.getStatusSummary(ZeroTrustStatuses.unexecuted)}
+
);
+ }
+
+ getStatusSummary(status) {
+ if (this.props.statusesToPillars[status].length > 0) {
+ return
+
+
+
+
+ {
+ this.props.statusesToPillars[status].map((pillar) => {
+ return
+ })
+ }
+
+
+ }
+ }
+}
+
+StatusesToPillarsSummary.propTypes = {
+ statusesToPillars: PropTypes.object
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js
new file mode 100644
index 000000000..e4012bf50
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js
@@ -0,0 +1,40 @@
+import React, {Component} from "react";
+import {Col, Grid, Row} from "react-bootstrap";
+import MonkeysStillAliveWarning from "../common/MonkeysStillAliveWarning";
+import PillarsOverview from "./PillarOverview";
+import ZeroTrustReportLegend from "./ReportLegend";
+import * as PropTypes from "prop-types";
+
+export default class SummarySection extends Component {
+ render() {
+ return
+ }
+}
+
+SummarySection.propTypes = {
+ allMonkeysAreDead: PropTypes.bool,
+ pillars: PropTypes.object
+};
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js
new file mode 100644
index 000000000..dd2a55865
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js
@@ -0,0 +1,18 @@
+export const ZeroTrustPillars = {
+ data: "Data",
+ people: "People",
+ network: "Networks",
+ workload: "Workload",
+ devices: "Devices",
+ visibility: "Visibility & Analytics",
+ automation: "Automation & Orchestration"
+};
+
+export const ZeroTrustStatuses = {
+ failed: "Failed",
+ verify: "Verify",
+ passed: "Passed",
+ unexecuted: "Unexecuted"
+};
+
+export default ZeroTrustPillars;
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/.DS_Store b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/.DS_Store
new file mode 100644
index 000000000..344923cf9
Binary files /dev/null and b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/.DS_Store differ
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js
new file mode 100644
index 000000000..aee1fb7f2
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js
@@ -0,0 +1,65 @@
+import React from 'react'
+import PropTypes from 'prop-types';
+import {Popover, OverlayTrigger} from 'react-bootstrap';
+import * as d3 from 'd3'
+
+class ArcNode extends React.Component {
+ render() {
+ let {prefix, index, data} = this.props;
+
+ let arc = d3.arc().innerRadius(data.inner).outerRadius(data.outer).startAngle(0).endAngle(Math.PI * 2.0);
+ let id = prefix + 'Node_' + index;
+
+ return (
+
+ {data.tooltip}} rootClose>
+
+
+
+
+ {data.icon + '\u2000'}
+ {data.label}
+
+
+
+ );
+ }
+
+
+ handleClick(e_) {
+ this.props.disableHover(this.refs.overlay);
+ }
+
+ handleOver(e_) {
+ if (this.props.hover) {
+ this.refs.overlay.show();
+ }
+ }
+
+ handleOut(e_) {
+ if (this.props.hover) {
+ this.refs.overlay.hide();
+ }
+ }
+}
+
+ArcNode.propTypes = {
+ data: PropTypes.object
+};
+
+export default ArcNode;
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js
new file mode 100644
index 000000000..5c84d95a5
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js
@@ -0,0 +1,64 @@
+import React from 'react'
+import PillarLabel from "../PillarLabel";
+import {Popover, OverlayTrigger} from 'react-bootstrap';
+import PropTypes from 'prop-types';
+
+class CircularNode extends React.Component {
+ render() {
+ let {prefix, index, data} = this.props;
+
+ let translate = 'translate(' + data.cx + ',' + data.cy + ')';
+ return (
+
+ {data.tooltip}} rootClose>
+
+
+
+
+
+
+ );
+ }
+
+
+ handleClick(e_) {
+ this.props.disableHover(this.refs.overlay);
+ }
+
+ handleOver(e_) {
+ if (this.props.hover) {
+ this.refs.overlay.show();
+ }
+ }
+
+ handleOut(e_) {
+ if (this.props.hover) {
+ this.refs.overlay.hide();
+ }
+ }
+
+}
+
+CircularNode.propTypes = {
+ index: PropTypes.number,
+ data: PropTypes.object
+};
+
+export default CircularNode;
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ResponsiveVennDiagram.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ResponsiveVennDiagram.js
new file mode 100644
index 000000000..4b2069f06
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ResponsiveVennDiagram.js
@@ -0,0 +1,28 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import Dimensions from 'react-dimensions'
+import VennDiagram from './VennDiagram'
+
+const VENN_MIN_WIDTH = '300px';
+
+class ResponsiveVennDiagram extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+
+ render() {
+ const {pillarsGrades} = this.props;
+
+ return (
+
+
+
+ );
+ }
+}
+
+ResponsiveVennDiagram.propTypes = {
+ pillarsGrades: PropTypes.array
+};
+
+export default Dimensions()(ResponsiveVennDiagram);
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Utility.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Utility.js
new file mode 100644
index 000000000..fa9309506
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Utility.js
@@ -0,0 +1,9 @@
+export class TypographicUtilities {
+ static removeAmpersand(string_) {
+ return string_.replace(' & ', 'And');
+ }
+
+ static removeBrokenBar(string_) {
+ return string_.replace(/\|/g, ' ');
+ }
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.css b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.css
new file mode 100644
index 000000000..dd4883125
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.css
@@ -0,0 +1,17 @@
+@import url('https://fonts.googleapis.com/css?family=Noto+Sans&display=swap');
+
+body {
+ margin: 0;
+ font-family: "Noto Sans", sans-serif;
+}
+
+svg {
+
+ -webkit-touch-callout: none; /* iOS Safari */
+ -webkit-user-select: none; /* Safari */
+ -khtml-user-select: none; /* Konqueror HTML */
+ -moz-user-select: none; /* Firefox */
+ -ms-user-select: none; /* Internet Explorer/Edge */
+ user-select: none; /* Non-prefixed version, currently supported by Chrome and Opera */
+
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js
new file mode 100644
index 000000000..70304daad
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js
@@ -0,0 +1,280 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import CircularNode from './CircularNode'
+import ArcNode from './ArcNode'
+import {TypographicUtilities} from './Utility.js'
+import './VennDiagram.css'
+import {ZeroTrustStatuses} from "../ZeroTrustPillars";
+
+class VennDiagram extends React.Component {
+ constructor(props_) {
+ super(props_);
+
+ this.state = {hover: true, currentPopover: undefined};
+ this._disableHover = this._disableHover.bind(this);
+
+ this.width = this.height = 512;
+
+ this.prefix = 'vennDiagram';
+ this.fontStyles = [{size: Math.max(9, this.width / 28), color: 'white'}, {
+ size: Math.max(6, this.width / 38),
+ color: 'white'
+ }, {size: Math.max(6, this.width / 48), color: 'white'}];
+ this.offset = this.width / 16;
+
+ this.thirdWidth = this.width / 3;
+ this.width11By2 = this.width / 5.5;
+ this.width2By7 = 2 * this.width / 7;
+ this.width1By11 = this.width / 11;
+ this.width1By28 = this.width / 28;
+ this.arcNodesGap = 4;
+
+ this.layout = {
+ Data: {cx: 0, cy: 0, r: this.width11By2, offset: {x: 0, y: 0}, popover: 'top'},
+ People: {
+ cx: -this.width2By7,
+ cy: 0,
+ r: this.width11By2,
+ offset: {x: this.width1By11 + this.fontStyles[1].size / 5 * 3, y: 0},
+ popover: 'right'
+ },
+ Networks: {
+ cx: this.width2By7,
+ cy: 0,
+ r: this.width11By2,
+ offset: {x: -this.width1By11 - this.fontStyles[1].size / 5 * 3, y: 0},
+ popover: 'left'
+ },
+ Devices: {
+ cx: 0,
+ cy: this.width2By7,
+ r: this.width11By2,
+ offset: {x: 0, y: -this.width1By11 + this.fontStyles[1].size / 6 * 3},
+ popover: 'top'
+ },
+ Workloads: {
+ cx: 0,
+ cy: -this.width2By7,
+ r: this.width11By2,
+ offset: {x: 0, y: this.width1By11},
+ popover: 'bottom'
+ },
+ VisibilityAndAnalytics: {
+ inner: this.thirdWidth - this.width1By28,
+ outer: this.thirdWidth,
+ icon: '\uf070',
+ popover: 'right'
+ },
+ AutomationAndOrchestration: {
+ inner: this.thirdWidth - this.width1By28 * 2 - this.arcNodesGap,
+ outer: this.thirdWidth - this.width1By28 - this.arcNodesGap,
+ icon: '\uf085',
+ popover: 'right'
+ }
+ };
+
+ /*
+
+ RULE #1: All scores have to be equal 0, except Unexecuted [U] which could be also a negative integer
+ sum(C, I, P) has to be <=0
+
+ RULE #2: Failed [C] has to be > 0,
+ sum(C) > 0
+
+ RULE #3: Verify [I] has to be > 0 while Failed has to be 0,
+ sum(C, I) > 0 and C * I = 0, while C has to be 0
+
+ RULE #4: By process of elimination, passed.
+ if the P is bigger by 2 then negative U, first conditional
+ would be true.
+ */
+
+ this.rules = [
+
+ {
+ id: 'Rule #1', status: ZeroTrustStatuses.unexecuted, hex: '#777777', f: function (d_) {
+ return d_[ZeroTrustStatuses.failed] + d_[ZeroTrustStatuses.verify] + d_[ZeroTrustStatuses.passed] === 0;
+ }
+ },
+ {
+ id: 'Rule #2', status: ZeroTrustStatuses.failed, hex: '#D9534F', f: function (d_) {
+ return d_[ZeroTrustStatuses.failed] > 0;
+ }
+ },
+ {
+ id: 'Rule #3', status: ZeroTrustStatuses.verify, hex: '#F0AD4E', f: function (d_) {
+ return d_[ZeroTrustStatuses.failed] === 0 && d_[ZeroTrustStatuses.verify] > 0;
+ }
+ },
+ {
+ id: 'Rule #4', status: ZeroTrustStatuses.passed, hex: '#5CB85C', f: function (d_) {
+ return d_[ZeroTrustStatuses.passed] > 0;
+ }
+ }
+
+ ];
+
+ }
+
+ componentDidMount() {
+ this.parseData();
+ if (this.state.currentPopover !== undefined) {
+ this.state.currentPopover.show();
+ }
+ }
+
+ _disableHover(ref_) {
+ this.setState({hover: false, currentPopover: ref_, data: this.state.data});
+ }
+
+ _onMouseMove(e) {
+
+ let self = this;
+
+ let hidden = 'none';
+ let html = '';
+ let bcolor = '#DEDEDE';
+
+ if (this.state.currentPopover !== undefined) {
+ this.state.currentPopover.show();
+ }
+
+ document.querySelectorAll('circle, path').forEach((d_, i_) => {
+ d_.setAttribute('opacity', "0.8");
+ });
+
+ if (e.target.id.includes('Node')) {
+
+ e.target.setAttribute('opacity', 0.95);
+
+ // Set highest z-index
+ e.target.parentNode.parentNode.appendChild(e.target.parentNode);
+
+ } else {
+
+ // Return z indices to default
+ Object.keys(this.layout).forEach(function (d_, i_) {
+ document.querySelector('#' + self.prefix).appendChild(document.querySelector('#' + self.prefix + 'Node_' + i_).parentNode);
+ })
+ }
+
+ }
+
+ _onClick(e) {
+
+ if (!e.target.id.includes('Node')) {
+
+ this.state.currentPopover.hide();
+ this.setState({hover: true, currentPopover: undefined, data: this.state.data});
+ }
+ }
+
+ parseData() {
+
+ let self = this;
+ let data = [];
+ const omit = (prop, {[prop]: _, ...rest}) => rest;
+
+ this.props.pillarsGrades.forEach((d_, i_) => {
+
+ let params = omit('pillar', d_);
+ let sum = Object.keys(params).reduce((sum_, key_) => sum_ + parseFloat(params[key_] || 0), 0);
+ let key = TypographicUtilities.removeAmpersand(d_.pillar);
+ let html = self.buildTooltipHtmlContent(params);
+ let rule = null;
+
+ for (let j = 0; j < self.rules.length; j++) {
+ if (self.rules[j].f(d_)) {
+ rule = j;
+ break;
+ }
+ }
+
+ self.setLayoutElement(rule, key, html, d_);
+ data.push(this.layout[key]);
+
+ });
+
+ this.setState({hover: true, activePopover: undefined, data: data});
+ this.render();
+ }
+
+ buildTooltipHtmlContent(object_) {
+
+ return Object.keys(object_).map((key_, i_) => {
+ return ({key_}: {object_[key_]}
)
+ })
+ }
+
+ setLayoutElement(rule_, key_, html_, d_) {
+
+ if (rule_ === null) {
+ console.log(Error('The node scores are invalid, please check the data or the rules set.'));
+ }
+
+ if (key_ === 'Data') {
+ this.layout[key_].fontStyle = this.fontStyles[0];
+ } else if (this.layout[key_].hasOwnProperty('cx')) {
+ this.layout[key_].fontStyle = this.fontStyles[1];
+ } else {
+ this.layout[key_].fontStyle = this.fontStyles[2];
+ }
+
+ this.layout[key_].hex = this.rules[rule_].hex;
+ this.layout[key_].status = this.rules[rule_].status;
+ this.layout[key_].label = d_.pillar;
+ this.layout[key_].node = d_;
+ this.layout[key_].tooltip = html_;
+ }
+
+ render() {
+ if (this.state.data === undefined) {
+ return null;
+ } else {
+ // equivalent to center translate (width/2, height/2)
+ let viewPortParameters = (-this.width / 2) + ' ' + (-this.height / 2) + ' ' + this.width + ' ' + this.height;
+ let nodes = Object.values(this.layout).map((d_, i_) => {
+ if (d_.hasOwnProperty('cx')) {
+ return (
+
+ );
+ } else {
+ d_.label = TypographicUtilities.removeBrokenBar(d_.label);
+ return (
+
+ );
+ }
+ });
+
+ return (
+ this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)}
+ onClick={this._onClick.bind(this)}>
+
+
+ )
+ }
+ }
+}
+
+VennDiagram.propTypes = {
+ pillarsGrades: PropTypes.array
+};
+
+export default VennDiagram;
diff --git a/monkey/monkey_island/cc/ui/src/components/run-monkey/AwsRunTable.js b/monkey/monkey_island/cc/ui/src/components/run-monkey/AwsRunTable.js
new file mode 100644
index 000000000..6a8fe9416
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/run-monkey/AwsRunTable.js
@@ -0,0 +1,109 @@
+import React from 'react';
+import ReactTable from 'react-table'
+import checkboxHOC from "react-table/lib/hoc/selectTable";
+
+const CheckboxTable = checkboxHOC(ReactTable);
+
+const columns = [
+ {
+ Header: 'Machines',
+ columns: [
+ { Header: 'Machine', accessor: 'name'},
+ { Header: 'Instance ID', accessor: 'instance_id'},
+ { Header: 'IP Address', accessor: 'ip_address'},
+ { Header: 'OS', accessor: 'os'}
+ ]
+ }
+];
+
+const pageSize = 10;
+
+class AwsRunTableComponent extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ selection: [],
+ selectAll: false,
+ result: {}
+ }
+ }
+
+ toggleSelection = (key, shift, row) => {
+ // start off with the existing state
+ let selection = [...this.state.selection];
+ const keyIndex = selection.indexOf(key);
+ // check to see if the key exists
+ if (keyIndex >= 0) {
+ // it does exist so we will remove it using destructing
+ selection = [
+ ...selection.slice(0, keyIndex),
+ ...selection.slice(keyIndex + 1)
+ ];
+ } else {
+ // it does not exist so add it
+ selection.push(key);
+ }
+ // update the state
+ this.setState({ selection });
+ };
+
+ isSelected = key => {
+ return this.state.selection.includes(key);
+ };
+
+ toggleAll = () => {
+ const selectAll = !this.state.selectAll;
+ const selection = [];
+ if (selectAll) {
+ // we need to get at the internals of ReactTable
+ const wrappedInstance = this.checkboxTable.getWrappedInstance();
+ // the 'sortedData' property contains the currently accessible records based on the filter and sort
+ const currentRecords = wrappedInstance.getResolvedState().sortedData;
+ // we just push all the IDs onto the selection array
+ currentRecords.forEach(item => {
+ selection.push(item._original.instance_id);
+ });
+ }
+ this.setState({ selectAll, selection });
+ };
+
+ getTrProps = (s, r) => {
+ let color = "inherit";
+ if (r) {
+ let instId = r.original.instance_id;
+ if (this.isSelected(instId)) {
+ color = "#ffed9f";
+ } else if (this.state.result.hasOwnProperty(instId)) {
+ color = this.state.result[instId] ? "#00f01b" : '#f00000'
+ }
+ }
+
+ return {
+ style: {backgroundColor: color}
+ };
+ };
+
+ render() {
+ return (
+
+ (this.checkboxTable = r)}
+ keyField="instance_id"
+ columns={columns}
+ data={this.props.data}
+ showPagination={true}
+ defaultPageSize={pageSize}
+ className="-highlight"
+ selectType="checkbox"
+ toggleSelection={this.toggleSelection}
+ isSelected={this.isSelected}
+ toggleAll={this.toggleAll}
+ selectAll={this.state.selectAll}
+ getTrProps={this.getTrProps}
+ />
+
+ );
+ }
+}
+
+export default AwsRunTableComponent;
diff --git a/monkey/monkey_island/cc/ui/src/components/side-menu/VersionComponent.js b/monkey/monkey_island/cc/ui/src/components/side-menu/VersionComponent.js
new file mode 100644
index 000000000..1246b5b94
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/side-menu/VersionComponent.js
@@ -0,0 +1,46 @@
+import React from 'react';
+import {Icon} from 'react-fa';
+
+class VersionComponent extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ currentVersion: undefined,
+ newerVersion: undefined,
+ downloadLink: undefined
+ }
+ }
+
+ componentDidMount() {
+ fetch('/api/version-update') // This is not authenticated on purpose
+ .then(res => res.json())
+ .then(res => {
+ this.setState({
+ currentVersion: res['current_version'],
+ newerVersion: res['newer_version'],
+ downloadLink: res['download_link'],
+ });
+ });
+ }
+
+ render() {
+ return (
+
+ Infection Monkey Version: {this.state.currentVersion}
+ {
+ this.state.newerVersion ?
+
+ :
+ undefined
+ }
+
+ );
+ }
+}
+
+
+export default VersionComponent;
diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/Checkbox.js b/monkey/monkey_island/cc/ui/src/components/ui-components/Checkbox.js
new file mode 100644
index 000000000..74204973a
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/ui-components/Checkbox.js
@@ -0,0 +1,73 @@
+import '../../styles/Checkbox.scss'
+import React from 'react';
+
+class CheckboxComponent extends React.PureComponent {
+
+ componentDidUpdate(prevProps) {
+ if (this.props.checked !== prevProps.checked) {
+ this.setState({checked: this.props.checked});
+ }
+ }
+
+ /*
+ Parent component can pass a name and a changeHandler (function) for this component in props.
+ changeHandler(name, checked) function will be called with these parameters:
+ this.props.name (the name of this component) and
+ this.state.checked (boolean indicating if this component is checked or not)
+ */
+ constructor(props) {
+ super(props);
+ this.state = {
+ checked: this.props.checked,
+ necessary: this.props.necessary,
+ isAnimating: false
+ };
+ this.toggleChecked = this.toggleChecked.bind(this);
+ this.stopAnimation = this.stopAnimation.bind(this);
+ this.composeStateClasses = this.composeStateClasses.bind(this);
+ }
+
+ //Toggles component.
+ toggleChecked() {
+ if (this.state.isAnimating) {return false;}
+ this.setState({
+ checked: !this.state.checked,
+ isAnimating: true,
+ }, () => { this.props.changeHandler ? this.props.changeHandler(this.props.name, this.state.checked) : null});
+ }
+
+ // Stops ping animation on checkbox after click
+ stopAnimation() {
+ this.setState({ isAnimating: false })
+ }
+
+ // Creates class string for component
+ composeStateClasses(core) {
+ let result = core;
+ if (this.state.necessary){
+ return result + ' blocked'
+ }
+ if (this.state.checked) { result += ' is-checked'; }
+ else { result += ' is-unchecked' }
+
+ if (this.state.isAnimating) { result += ' do-ping'; }
+ return result;
+ }
+
+ render() {
+ const cl = this.composeStateClasses('ui-checkbox-btn');
+ return (
+
+
+
+
+
+ )
+ }
+}
+
+export default CheckboxComponent;
diff --git a/monkey/monkey_island/cc/ui/src/components/utils/SaveJsonToFile.js b/monkey/monkey_island/cc/ui/src/components/utils/SaveJsonToFile.js
new file mode 100644
index 000000000..6ad124457
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/utils/SaveJsonToFile.js
@@ -0,0 +1,7 @@
+import FileSaver from "file-saver";
+
+export default function saveJsonToFile(dataToSave, filename) {
+ const content = JSON.stringify(dataToSave, null, 2);
+ const blob = new Blob([content], {type: "text/plain;charset=utf-8"});
+ FileSaver.saveAs(blob, filename + ".json");
+}
diff --git a/monkey/monkey_island/cc/ui/src/images/guardicore-logo.png b/monkey/monkey_island/cc/ui/src/images/guardicore-logo.png
index d5f02d006..31075a586 100644
Binary files a/monkey/monkey_island/cc/ui/src/images/guardicore-logo.png and b/monkey/monkey_island/cc/ui/src/images/guardicore-logo.png differ
diff --git a/monkey/monkey_island/cc/ui/src/images/notification-logo-512x512.png b/monkey/monkey_island/cc/ui/src/images/notification-logo-512x512.png
new file mode 100644
index 000000000..387581858
Binary files /dev/null and b/monkey/monkey_island/cc/ui/src/images/notification-logo-512x512.png differ
diff --git a/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-machine-icon.svg b/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-machine-icon.svg
new file mode 100644
index 000000000..507541be4
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-machine-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-network-icon.svg b/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-network-icon.svg
new file mode 100644
index 000000000..50dcc6726
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-network-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/monkey/monkey_island/cc/ui/src/index.js b/monkey/monkey_island/cc/ui/src/index.js
index 3b4138107..329e94dfe 100644
--- a/monkey/monkey_island/cc/ui/src/index.js
+++ b/monkey/monkey_island/cc/ui/src/index.js
@@ -1,6 +1,7 @@
import 'core-js/fn/object/assign';
import React from 'react';
import ReactDOM from 'react-dom';
+import 'babel-polyfill';
import App from './components/Main';
import Bootstrap from 'bootstrap/dist/css/bootstrap.css'; // eslint-disable-line no-unused-vars
diff --git a/monkey/monkey_island/cc/ui/src/server_config/PasswordConfig.js b/monkey/monkey_island/cc/ui/src/server_config/PasswordConfig.js
new file mode 100644
index 000000000..359b21bfb
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/server_config/PasswordConfig.js
@@ -0,0 +1,9 @@
+import BaseConfig from './BaseConfig';
+
+class PasswordConfig extends BaseConfig{
+ isAuthEnabled() {
+ return true;
+ }
+}
+
+export default PasswordConfig;
diff --git a/monkey/monkey_island/cc/ui/src/server_config/ServerConfig.js b/monkey/monkey_island/cc/ui/src/server_config/ServerConfig.js
index a291bd6e6..c71cc4d94 100644
--- a/monkey/monkey_island/cc/ui/src/server_config/ServerConfig.js
+++ b/monkey/monkey_island/cc/ui/src/server_config/ServerConfig.js
@@ -1,12 +1,14 @@
import StandardConfig from './StandardConfig';
import AwsConfig from './AwsConfig';
+import PasswordConfig from "./PasswordConfig";
const SERVER_CONFIG_JSON = require('../../../server_config.json');
const CONFIG_DICT =
{
'standard': StandardConfig,
- 'aws': AwsConfig
+ 'aws': AwsConfig,
+ 'password': PasswordConfig
};
export const SERVER_CONFIG = new CONFIG_DICT[SERVER_CONFIG_JSON['server_config']]();
diff --git a/monkey/monkey_island/cc/ui/src/services/AuthService.js b/monkey/monkey_island/cc/ui/src/services/AuthService.js
index c5a474ebf..9c62bde63 100644
--- a/monkey/monkey_island/cc/ui/src/services/AuthService.js
+++ b/monkey/monkey_island/cc/ui/src/services/AuthService.js
@@ -1,25 +1,32 @@
+import { SHA3 } from 'sha3';
import decode from 'jwt-decode';
-import {SERVER_CONFIG} from '../server_config/ServerConfig';
export default class AuthService {
- AUTH_ENABLED = SERVER_CONFIG.isAuthEnabled();
+ // SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()'
+ NO_AUTH_CREDS =
+ "55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062" +
+ "8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557";
login = (username, password) => {
- if (this.AUTH_ENABLED) {
- return this._login(username, password);
- } else {
- return {result: true};
- }
+ return this._login(username, this.hashSha3(password));
};
authFetch = (url, options) => {
- if (this.AUTH_ENABLED) {
- return this._authFetch(url, options);
- } else {
- return fetch(url, options);
+ return this._authFetch(url, options);
+ };
+
+ jwtHeader = () => {
+ if (this._loggedIn()) {
+ return 'JWT ' + this._getToken();
}
};
+ hashSha3(text) {
+ let hash = new SHA3(512);
+ hash.update(text);
+ return this._toHexStr(hash.digest());
+ }
+
_login = (username, password) => {
return this._authFetch('/api/auth', {
method: 'POST',
@@ -36,7 +43,6 @@ export default class AuthService {
this._removeToken();
return {result: false};
}
-
})
};
@@ -46,7 +52,7 @@ export default class AuthService {
'Content-Type': 'application/json'
};
- if (this.loggedIn()) {
+ if (this._loggedIn()) {
headers['Authorization'] = 'JWT ' + this._getToken();
}
@@ -67,20 +73,26 @@ export default class AuthService {
});
};
- loggedIn() {
- if (!this.AUTH_ENABLED) {
- return true;
+ async loggedIn() {
+ let token = this._getToken();
+ if ((token === null) || (this._isTokenExpired(token))) {
+ await this.attemptNoAuthLogin();
}
+ return this._loggedIn();
+ }
+ attemptNoAuthLogin() {
+ return this._login(this.NO_AUTH_CREDS, this.NO_AUTH_CREDS);
+ }
+
+ _loggedIn() {
const token = this._getToken();
return ((token !== null) && !this._isTokenExpired(token));
}
- logout() {
- if (this.AUTH_ENABLED) {
- this._removeToken();
- }
- }
+ logout = () => {
+ this._removeToken();
+ };
_isTokenExpired(token) {
try {
@@ -103,4 +115,9 @@ export default class AuthService {
return localStorage.getItem('jwt')
}
+ _toHexStr(byteArr) {
+ return byteArr.reduce((acc, x) => (acc + ('0' + x.toString(0x10)).slice(-2)), '');
+ }
+
+
}
diff --git a/monkey/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css
index 7f487694c..109f1c147 100644
--- a/monkey/monkey_island/cc/ui/src/styles/App.css
+++ b/monkey/monkey_island/cc/ui/src/styles/App.css
@@ -138,12 +138,11 @@ body {
padding-left: 40px;
}
}
+
.main .page-header {
margin-top: 0;
}
-
-
.index img {
margin: 40px auto;
border-radius: 4px;
@@ -164,6 +163,18 @@ body {
* Configuration Page
*/
+.linux-pba-file-info, .windows-pba-file-info {
+ display: none
+}
+
+.filepond--root li {
+ overflow: visible;
+}
+
+.filepond--root * {
+ font-size: 1.04em;
+}
+
.rjsf .form-group .form-group {
margin-left: 2em;
}
@@ -172,6 +183,13 @@ body {
display: none;
}
+.nav-tabs > li > a {
+ height: 63px
+}
+
+.nav > li > a:focus {
+ background-color: transparent !important;
+}
/*
* Run Monkey Page
*/
@@ -410,6 +428,14 @@ body {
top: 30%;
}
+.pba-danger {
+ background-color: #ffc7af;
+}
+
+.pba-success {
+ background-color: #afd2a2;
+}
+
/* Print report styling */
@media print {
@@ -491,4 +517,71 @@ body {
.label-danger {
background-color: #d9534f !important;
}
+
+}
+
+/* Attack pages */
+.attack-matrix .messages {
+ margin-bottom: 30px;
+}
+
+.attack-matrix {
+ margin-bottom: 20px;
+}
+
+.attack-report .btn-collapse span:nth-of-type(2){
+ flex: 0;
+}
+
+.icon-info {
+ color: #ade3eb !important;
+}
+
+.icon-warning {
+ color: #f0ad4e !important;
+}
+
+.icon-danger {
+ color: #d9acac !important;
+}
+
+.icon-default {
+ color: #e0ddde !important;
+}
+
+.status-success {
+ color: #24b716 !important;
+}
+
+.status-warning {
+ color: #b1a91c !important;
+}
+
+.status-danger {
+ color: #d91016 !important;
+}
+
+.status-default {
+ color: #575556 !important;
+}
+
+.attack-legend {
+ text-align: center;
+ margin-bottom: 20px;
+}
+
+.version-text {
+ font-size: 0.9em;
+ position: absolute;
+ bottom: 5px;
+ left: 0;
+ right: 0;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.attack-report.footer-text{
+ text-align: right;
+ font-size: 0.8em;
+ margin-top: 20px;
}
diff --git a/monkey/monkey_island/cc/ui/src/styles/Checkbox.scss b/monkey/monkey_island/cc/ui/src/styles/Checkbox.scss
new file mode 100644
index 000000000..3bf0281f6
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/styles/Checkbox.scss
@@ -0,0 +1,105 @@
+// colors
+$light-grey: #EAF4F4;
+$medium-grey: #7B9EA8;
+$dark-green: #007d02;
+$green: #44CF6C;
+$black: #000000;
+
+.ui-checkbox-btn {
+ position: relative;
+ display: inline-block;
+ background-color: rgba(red, .6);
+ text-align: center;
+ width: 100%;
+ height: 100%;
+
+ input { display: none; }
+
+ .icon,
+ .text {
+ display: inline-block;
+ color: inherit;
+ }
+
+ .text {
+ padding-top: 4px;
+ font-size: 14px;
+ }
+
+ // color states
+ &.is-unchecked {
+ background-color: transparent;
+ color: $black;
+ fill: $black;
+ }
+
+ &.blocked {
+ background-color: $dark-green;
+ color: $light-grey;
+ fill: $light-grey;
+ }
+
+ &.is-checked {
+ background-color: $green;
+ color: white;
+ fill: white;
+ }
+}
+
+.icon {
+ position: relative;
+ display: inline-block;
+
+ svg {
+ position: absolute;
+ top: 0; right: 0; bottom: 0; left: 0;
+ margin: auto;
+ width: 16px;
+ height: auto;
+ fill: inherit;
+ }
+
+ .is-checked & {
+ color: white;
+ fill: white;
+ }
+}
+
+// ping animation magic
+.ui-btn-ping {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 100%;
+ transform: translate3d(-50%, -50%, 0); // center center by default
+
+ // set the square
+ &:before {
+ content: '';
+ transform: scale(0, 0); // center center by default
+ transition-property: background-color transform;
+ transition-timing-function: cubic-bezier(0.0, 0.0, 0.2, 1);
+ display: block;
+ padding-bottom: 100%;
+ border-radius: 50%;
+ background-color: rgba(white, .84);;
+ }
+
+ .do-ping &:before {
+ transform: scale(2.5, 2.5);
+ transition-duration: .35s;
+ background-color: rgba(white, .08);
+ }
+}
+
+.icon-checked{
+ color:$green
+}
+
+.icon-mandatory{
+ color:$dark-green
+}
+
+.icon-unchecked{
+ color:$black;
+}
diff --git a/monkey/monkey_island/cc/ui/src/styles/Collapse.scss b/monkey/monkey_island/cc/ui/src/styles/Collapse.scss
new file mode 100644
index 000000000..e2d7d334a
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/styles/Collapse.scss
@@ -0,0 +1,96 @@
+$transition: 500ms cubic-bezier(0.4, 0.1, 0.1, 0.5);
+
+$danger-color: #d9acac;
+$info-color: #ade3eb;
+$default-color: #e0ddde;
+
+.collapse-item button {
+ font-size: inherit;
+ margin: 0;
+ padding: 1rem;
+ background: transparent;
+ border: 1px solid #ccc;
+ box-shadow: none;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+
+.collapse-item button span:first-child{
+ text-align:left;
+}
+
+.collapse-item button {
+ width: 100%;
+ box-shadow: 0 2px 6px #ccc;
+ border: none;
+ transition: background-color $transition;
+ display: flex;
+ font-family: inherit;
+ > span {
+ display: inline-block;
+ flex: 4;
+ text-align: right;
+
+ &:nth-child(2) {
+ flex: 3;
+ }
+ }
+}
+
+.collapse-danger {
+ background-color: $danger-color !important;
+}
+
+.collapse-info {
+ background-color: $info-color !important;
+}
+
+.collapse-default {
+ background-color: $default-color !important;
+}
+
+.collapse-item {
+ padding: 0.5rem;
+ &--active {
+ .btn-collapse {
+ background-color: #f7f7f7;
+ }
+ }
+}
+
+.collapse-item .collapse-comp {
+ padding: 0 7px 7px 7px;
+ border: 2px solid rgb(232, 228, 228);
+ border-top: 0;
+ display:block !important;
+ transition: height $transition;
+ overflow: hidden;
+}
+
+.collapse-item .content {
+ padding: 2rem 0;
+ transition: transform $transition;
+ will-change: transform;
+ $offset: 10px;
+
+ &.collapsing {
+ transform: translateY(-$offset);
+ }
+ &.collapse-comp {
+ transform: translateY(-$offset);
+ }
+ &.expanding {
+ transform: translateX(0px);
+ }
+ &.expanded {
+ transform: translateX(0px);
+ }
+}
+
+.collapse-item .text {
+ margin-bottom: 1rem;
+}
+
+.collapse-item .state {
+ display: inline-block;
+ min-width: 6em;
+}
diff --git a/monkey/monkey_island/cc/ui/src/styles/Tooltip.scss b/monkey/monkey_island/cc/ui/src/styles/Tooltip.scss
new file mode 100644
index 000000000..7d2ff9d35
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/styles/Tooltip.scss
@@ -0,0 +1,8 @@
+$background: #000000;
+$font: #fff;
+
+.react-tooltip-lite {
+ background: $background;
+ color: $font;
+ max-width: 400px !important;
+}
diff --git a/monkey/monkey_island/cc/ui/webpack.config.js b/monkey/monkey_island/cc/ui/webpack.config.js
index b6f7d2dfa..7c56ccff2 100644
--- a/monkey/monkey_island/cc/ui/webpack.config.js
+++ b/monkey/monkey_island/cc/ui/webpack.config.js
@@ -18,6 +18,14 @@ module.exports = {
'css-loader'
]
},
+ {
+ test: /\.scss$/,
+ use: [
+ 'style-loader',
+ 'css-loader',
+ 'sass-loader'
+ ]
+ },
{
test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: {
diff --git a/monkey/monkey_island/cc/utils.py b/monkey/monkey_island/cc/utils.py
index 9c49eba2c..cf59ae7df 100644
--- a/monkey/monkey_island/cc/utils.py
+++ b/monkey/monkey_island/cc/utils.py
@@ -6,6 +6,7 @@ import array
import struct
import ipaddress
from netifaces import interfaces, ifaddresses, AF_INET
+from ring import lru
__author__ = 'Barak'
@@ -46,9 +47,13 @@ else:
# name of interface is (namestr[i:i+16].split('\0', 1)[0]
finally:
return result
-# End of local ips function
+# The local IP addresses list should not change often. Therefore, we can cache the result and never call this function
+# more than once. This stopgap measure is here since this function is called a lot of times during the report
+# generation.
+# This means that if the interfaces of the Island machine change, the Island process needs to be restarted.
+@lru(maxsize=1)
def local_ip_addresses():
ip_list = []
for interface in interfaces():
@@ -57,6 +62,11 @@ def local_ip_addresses():
return ip_list
+# The subnets list should not change often. Therefore, we can cache the result and never call this function
+# more than once. This stopgap measure is here since this function is called a lot of times during the report
+# generation.
+# This means that if the interfaces or subnets of the Island machine change, the Island process needs to be restarted.
+@lru(maxsize=1)
def get_subnets():
subnets = []
for interface in interfaces():
diff --git a/monkey/monkey_island/deb-package/DEBIAN/postinst b/monkey/monkey_island/deb-package/DEBIAN/postinst
index b55f791b8..8efc19bbb 100644
--- a/monkey/monkey_island/deb-package/DEBIAN/postinst
+++ b/monkey/monkey_island/deb-package/DEBIAN/postinst
@@ -9,25 +9,22 @@ pip2 install virtualenv --no-index --find-links file://$INSTALLATION_FOLDER
virtualenv -p python2.7 ${PYTHON_FOLDER}
# install pip requirements
-${PYTHON_FOLDER}/bin/python -m pip install -r $MONKEY_FOLDER/monkey_island/pip_requirements.txt --no-index --find-links file://$INSTALLATION_FOLDER
+${PYTHON_FOLDER}/bin/python -m pip install -r $MONKEY_FOLDER/monkey_island/requirements.txt --no-index --find-links file://$INSTALLATION_FOLDER
# remove installation folder and unnecessary files
rm -rf ${INSTALLATION_FOLDER}
-rm -f ${MONKEY_FOLDER}/monkey_island/pip_requirements.txt
+rm -f ${MONKEY_FOLDER}/monkey_island/requirements.txt
-cp ${MONKEY_FOLDER}/monkey_island/ubuntu/* /etc/init/
if [ -d "/etc/systemd/network" ]; then
- cp ${MONKEY_FOLDER}/monkey_island/ubuntu/systemd/*.service /lib/systemd/system/
- chmod +x ${MONKEY_FOLDER}/monkey_island/ubuntu/systemd/start_server.sh
+ cp ${MONKEY_FOLDER}/monkey_island/service/systemd/*.service /lib/systemd/system/
+ chmod +x ${MONKEY_FOLDER}/monkey_island/service/systemd/start_server.sh
systemctl daemon-reload
- systemctl enable monkey-mongo
systemctl enable monkey-island
fi
${MONKEY_FOLDER}/monkey_island/create_certificate.sh
service monkey-island start
-service monkey-mongo start
echo Monkey Island installation ended
diff --git a/monkey/monkey_island/deb-package/DEBIAN/prerm b/monkey/monkey_island/deb-package/DEBIAN/prerm
index 69070adaf..0449e0b3f 100644
--- a/monkey/monkey_island/deb-package/DEBIAN/prerm
+++ b/monkey/monkey_island/deb-package/DEBIAN/prerm
@@ -1,12 +1,9 @@
#!/bin/sh
service monkey-island stop || true
-service monkey-mongo stop || true
rm -f /etc/init/monkey-island.conf
-rm -f /etc/init/monkey-mongo.conf
[ -f "/lib/systemd/system/monkey-island.service" ] && rm -f /lib/systemd/system/monkey-island.service
-[ -f "/lib/systemd/system/monkey-mongo.service" ] && rm -f /lib/systemd/system/monkey-mongo.service
rm -r -f /var/monkey
diff --git a/monkey/monkey_island/deb-package/DEBIAN_MONGO/postinst b/monkey/monkey_island/deb-package/DEBIAN_MONGO/postinst
new file mode 100644
index 000000000..76e57caa3
--- /dev/null
+++ b/monkey/monkey_island/deb-package/DEBIAN_MONGO/postinst
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+MONKEY_FOLDER=/var/monkey
+INSTALLATION_FOLDER=/var/monkey/monkey_island/installation
+PYTHON_FOLDER=/var/monkey/monkey_island/bin/python
+
+# Prepare python virtualenv
+pip2 install virtualenv --no-index --find-links file://$INSTALLATION_FOLDER
+virtualenv -p python2.7 ${PYTHON_FOLDER}
+
+# install pip requirements
+${PYTHON_FOLDER}/bin/python -m pip install -r $MONKEY_FOLDER/monkey_island/requirements.txt --no-index --find-links file://$INSTALLATION_FOLDER
+
+# remove installation folder and unnecessary files
+rm -rf ${INSTALLATION_FOLDER}
+rm -f ${MONKEY_FOLDER}/monkey_island/requirements.txt
+
+${MONKEY_FOLDER}/monkey_island/install_mongo.sh ${MONKEY_FOLDER}/monkey_island/bin/mongodb
+
+if [ -d "/etc/systemd/network" ]; then
+ cp ${MONKEY_FOLDER}/monkey_island/service/systemd/*.service /lib/systemd/system/
+ chmod +x ${MONKEY_FOLDER}/monkey_island/service/systemd/start_server.sh
+ systemctl daemon-reload
+ systemctl enable monkey-mongo
+ systemctl enable monkey-island
+fi
+
+${MONKEY_FOLDER}/monkey_island/create_certificate.sh
+
+service monkey-island start
+service monkey-mongo start
+
+echo Monkey Island installation ended
+
+exit 0
\ No newline at end of file
diff --git a/monkey/monkey_island/deb-package/DEBIAN_MONGO/prerm b/monkey/monkey_island/deb-package/DEBIAN_MONGO/prerm
new file mode 100644
index 000000000..dabdbd2fa
--- /dev/null
+++ b/monkey/monkey_island/deb-package/DEBIAN_MONGO/prerm
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+service monkey-island stop || true
+service monkey-mongo stop || true
+
+[ -f "/lib/systemd/system/monkey-island.service" ] && rm -f /lib/systemd/system/monkey-island.service
+[ -f "/lib/systemd/system/monkey-mongo.service" ] && rm -f /lib/systemd/system/monkey-mongo.service
+
+rm -r -f /var/monkey
+
+exit 0
\ No newline at end of file
diff --git a/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt b/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt
deleted file mode 100644
index 446414ecf..000000000
--- a/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-python-dateutil
-tornado
-werkzeug
-jinja2
-markupsafe
-itsdangerous
-click
-flask
-Flask-Pymongo
-Flask-Restful
-Flask-JWT
-jsonschema
-netifaces
-ipaddress
-enum34
-PyCrypto
-virtualenv
\ No newline at end of file
diff --git a/monkey/monkey_island/deb-package/service/systemd/monkey-island.service b/monkey/monkey_island/deb-package/service/systemd/monkey-island.service
new file mode 100644
index 000000000..e1dea878a
--- /dev/null
+++ b/monkey/monkey_island/deb-package/service/systemd/monkey-island.service
@@ -0,0 +1,10 @@
+[Unit]
+Description=Monkey Island Service
+After=network.target
+
+[Service]
+Type=simple
+ExecStart=/var/monkey/monkey_island/service/systemd/start_server.sh
+
+[Install]
+WantedBy=multi-user.target
\ No newline at end of file
diff --git a/monkey/monkey_island/linux/ubuntu/systemd/start_server.sh b/monkey/monkey_island/deb-package/service/systemd/start_server.sh
similarity index 100%
rename from monkey/monkey_island/linux/ubuntu/systemd/start_server.sh
rename to monkey/monkey_island/deb-package/service/systemd/start_server.sh
diff --git a/monkey/monkey_island/linux/ubuntu/systemd/monkey-island.service b/monkey/monkey_island/deb-package/service_mongo/systemd/monkey-island.service
similarity index 56%
rename from monkey/monkey_island/linux/ubuntu/systemd/monkey-island.service
rename to monkey/monkey_island/deb-package/service_mongo/systemd/monkey-island.service
index d66de2377..aa609f068 100644
--- a/monkey/monkey_island/linux/ubuntu/systemd/monkey-island.service
+++ b/monkey/monkey_island/deb-package/service_mongo/systemd/monkey-island.service
@@ -5,7 +5,7 @@ After=network.target
[Service]
Type=simple
-ExecStart=/var/monkey/monkey_island/ubuntu/systemd/start_server.sh
+ExecStart=/var/monkey/monkey_island/service/systemd/start_server.sh
[Install]
WantedBy=multi-user.target
\ No newline at end of file
diff --git a/monkey/monkey_island/linux/ubuntu/systemd/monkey-mongo.service b/monkey/monkey_island/deb-package/service_mongo/systemd/monkey-mongo.service
similarity index 100%
rename from monkey/monkey_island/linux/ubuntu/systemd/monkey-mongo.service
rename to monkey/monkey_island/deb-package/service_mongo/systemd/monkey-mongo.service
diff --git a/monkey/monkey_island/linux/clear_db.sh b/monkey/monkey_island/linux/clear_db.sh
deleted file mode 100644
index 7ec819cd5..000000000
--- a/monkey/monkey_island/linux/clear_db.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/bash
-
-service monkey-mongo stop
-cd /var/monkey/monkey_island
-rm -rf ./db/*
-service monkey-mongo start
diff --git a/monkey/monkey_island/linux/create_certificate.sh b/monkey/monkey_island/linux/create_certificate.sh
index 477440a6f..0aae17558 100644
--- a/monkey/monkey_island/linux/create_certificate.sh
+++ b/monkey/monkey_island/linux/create_certificate.sh
@@ -1,6 +1,6 @@
#!/bin/bash
cd /var/monkey/monkey_island
-openssl genrsa -out cc/server.key 1024
+openssl genrsa -out cc/server.key 2048
openssl req -new -key cc/server.key -out cc/server.csr -subj "/C=GB/ST=London/L=London/O=Global Security/OU=Monkey Department/CN=monkey.com"
openssl x509 -req -days 366 -in cc/server.csr -signkey cc/server.key -out cc/server.crt
diff --git a/monkey/monkey_island/linux/install_mongo.sh b/monkey/monkey_island/linux/install_mongo.sh
new file mode 100755
index 000000000..2395454b6
--- /dev/null
+++ b/monkey/monkey_island/linux/install_mongo.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+export os_version_monkey=$(cat /etc/issue)
+MONGODB_DIR=$1 # If using deb, this should be: /var/monkey/monkey_island/bin/mongodb
+
+if [[ ${os_version_monkey} == "Ubuntu 16.04"* ]] ;
+then
+ echo Detected Ubuntu 16.04
+ export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-3.6.12.tgz"
+elif [[ ${os_version_monkey} == "Ubuntu 18.04"* ]] ;
+then
+ echo Detected Ubuntu 18.04
+ export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1804-4.0.8.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-3.6.12.tgz"
+elif [[ ${os_version_monkey} == "Debian GNU/Linux 9"* ]] ;
+then
+ echo Detected Debian 9
+ export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-debian92-3.6.12.tgz"
+else
+ echo Unsupported OS
+ exit -1
+fi
+
+TEMP_MONGO=$(mktemp -d)
+pushd ${TEMP_MONGO}
+wget ${tgz_url} -O mongodb.tgz
+tar -xf mongodb.tgz
+popd
+
+mkdir -p ${MONGODB_DIR}/bin
+cp ${TEMP_MONGO}/mongodb-*/bin/mongod ${MONGODB_DIR}/bin/mongod
+cp ${TEMP_MONGO}/mongodb-*/LICENSE-Community.txt ${MONGODB_DIR}/
+chmod a+x ${MONGODB_DIR}/bin/mongod
+rm -r ${TEMP_MONGO}
+
+exit 0
\ No newline at end of file
diff --git a/monkey/monkey_island/linux/run.sh b/monkey/monkey_island/linux/run.sh
index 6770e2922..978e02fe5 100644
--- a/monkey/monkey_island/linux/run.sh
+++ b/monkey/monkey_island/linux/run.sh
@@ -1,5 +1,4 @@
#!/bin/bash
cd /var/monkey
-/var/monkey/monkey_island/bin/mongodb/bin/mongod --quiet --dbpath /var/monkey/monkey_island/db &
-/var/monkey/monkey_island/bin/python/bin/python monkey_island/cc/main.py
\ No newline at end of file
+/var/monkey/monkey_island/bin/python/bin/python monkey_island.py
\ No newline at end of file
diff --git a/monkey/monkey_island/linux/ubuntu/monkey-island.conf b/monkey/monkey_island/linux/ubuntu/monkey-island.conf
deleted file mode 100644
index 1ded4d94a..000000000
--- a/monkey/monkey_island/linux/ubuntu/monkey-island.conf
+++ /dev/null
@@ -1,18 +0,0 @@
-description "Monkey Island Service"
-
-start on runlevel [2345]
-stop on runlevel [!2345]
-
-respawn
-respawn limit unlimited
-
-script
- chdir /var/monkey
- exec python monkey_island/cc/main.py
-end script
-
-post-stop script
- if [ -n $UPSTART_EVENTS ]; then
- exec sleep 2
- fi
-end script
\ No newline at end of file
diff --git a/monkey/monkey_island/linux/ubuntu/monkey-mongo.conf b/monkey/monkey_island/linux/ubuntu/monkey-mongo.conf
deleted file mode 100644
index cd148d877..000000000
--- a/monkey/monkey_island/linux/ubuntu/monkey-mongo.conf
+++ /dev/null
@@ -1,18 +0,0 @@
-description "Monkey Island Mongo Service"
-
-start on runlevel [2345]
-stop on runlevel [!2345]
-
-respawn
-respawn limit unlimited
-
-script
- chdir /var/monkey/monkey_island/
- exec /var/monkey/monkey_island/bin/mongodb/bin/mongod --dbpath db
-end script
-
-post-stop script
- if [ -n $UPSTART_EVENTS ]; then
- exec sleep 3
- fi
-end script
\ No newline at end of file
diff --git a/monkey/monkey_island/monkey_island.ico b/monkey/monkey_island/monkey_island.ico
new file mode 100644
index 000000000..0a9976256
Binary files /dev/null and b/monkey/monkey_island/monkey_island.ico differ
diff --git a/monkey/monkey_island/monkey_island.spec b/monkey/monkey_island/monkey_island.spec
new file mode 100644
index 000000000..342df5ab3
--- /dev/null
+++ b/monkey/monkey_island/monkey_island.spec
@@ -0,0 +1,93 @@
+# -*- mode: python -*-
+import os
+import platform
+
+
+__author__ = 'itay.mizeretz'
+
+block_cipher = None
+
+
+def main():
+ a = Analysis(['cc/main.py'],
+ pathex=['..'],
+ hiddenimports=get_hidden_imports(),
+ hookspath=None,
+ runtime_hooks=None,
+ binaries=None,
+ datas=None,
+ excludes=None,
+ win_no_prefer_redirects=None,
+ win_private_assemblies=None,
+ cipher=block_cipher
+ )
+
+ a.binaries += get_binaries()
+ a.datas = process_datas(a.datas)
+
+ pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
+ exe = EXE(pyz,
+ a.scripts,
+ a.binaries,
+ a.zipfiles,
+ a.datas,
+ name=get_monkey_filename(),
+ debug=False,
+ strip=get_exe_strip(),
+ upx=True,
+ console=True,
+ icon=get_exe_icon())
+
+
+def is_windows():
+ return platform.system().find("Windows") >= 0
+
+
+def is_32_bit():
+ return platform.architecture()[0] == "32bit"
+
+
+def process_datas(orig_datas):
+ datas = orig_datas
+ if is_windows():
+ datas = [i for i in datas if i[0].find('Include') < 0]
+ return datas
+
+
+def get_binaries():
+ binaries = get_windows_only_binaries() if is_windows() else get_linux_only_binaries()
+ return binaries
+
+
+def get_windows_only_binaries():
+ binaries = []
+ binaries += get_msvcr()
+ return binaries
+
+
+def get_linux_only_binaries():
+ binaries = []
+ return binaries
+
+
+def get_hidden_imports():
+ return ['_cffi_backend', 'queue'] if is_windows() else ['_cffi_backend']
+
+
+def get_msvcr():
+ return [('msvcr100.dll', os.environ['WINDIR'] + '\\system32\\msvcr100.dll', 'BINARY')]
+
+
+def get_monkey_filename():
+ return 'monkey_island.exe' if is_windows() else 'monkey_island'
+
+
+def get_exe_strip():
+ return not is_windows()
+
+
+def get_exe_icon():
+ return 'monkey_island.ico' if is_windows() else None
+
+
+main() # We don't check if __main__ because this isn't the main script.
diff --git a/monkey/monkey_island/readme.txt b/monkey/monkey_island/readme.txt
index 82deb43b6..956892e23 100644
--- a/monkey/monkey_island/readme.txt
+++ b/monkey/monkey_island/readme.txt
@@ -1,18 +1,28 @@
+To get development versions of Monkey Island and Monkey look into deployment scripts folder.
+If you only want to run the software from source you may refer to the instructions below.
+
How to set up the Monkey Island server:
---------------- On Windows ----------------:
0. Exclude the folder you are planning to install the Monkey in from your AV software, as it might block or delete files from the installation.
1. Create folder "bin" under monkey_island
-2. Place portable version of Python 2.7
- 2.1. Download and install from: https://www.python.org/download/releases/2.7/
- 2.2. Install the required python libraries using "python -m pip install -r monkey_island\requirements.txt"
- 2.3. Copy contents from installation path (Usually C:\Python27) to monkey_island\bin\Python27
- 2.4. Copy Python27.dll from System32 folder (Usually C:\Windows\System32 or C:\Python27) to monkey_island\bin\Python27
- 2.5. (Optional) You may uninstall Python27 if you like.
-3. Place portable version of mongodb
- 3.1. Download from: https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-latest.zip
- 3.2. Extract contents from bin folder to monkey_island\bin\mongodb.
- 3.3. Create monkey_island\db folder.
+2. Place portable version of Python 2.7.15
+ 2.1. Download and install from: https://www.python.org/downloads/release/python-2715/
+ 2.2. Install virtualenv using "python -m pip install virtualenv"
+ 2.3. Create a virtualenv using "python -m virtualenv --always-copy \Python27" Where is the path to the bin folder created on step 1.
+ 2.4. Run "python -m virtualenv --relocatable \Python27"
+ 2.5. Install the required python libraries using "\Python27\Scripts\python -m pip install -r monkey_island\requirements.txt"
+ 2.6. Copy DLLs from installation path (Usually C:\Python27\DLLs) to \Python27\DLLs
+ 2.7. (Optional) You may uninstall Python27 if you like.
+3. Setup mongodb (Use one of the following two options):
+ 3.a Place portable version of mongodb
+ 3.a.1. Download from: https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-latest.zip
+ 3.a.2. Extract contents from bin folder to monkey_island\bin\mongodb.
+ 3.a.3. Create monkey_island\db folder.
+ OR
+ 3.b. Use already running instance of mongodb
+ 3.b.1. Run 'set MONKEY_MONGO_URL="mongodb://:27017/monkeyisland"'. Replace '' with address of mongo server
+
4. Place portable version of OpenSSL
4.1. Download from: https://indy.fulgan.com/SSL/Archive/openssl-1.0.2l-i386-win32.zip
4.2. Extract content from bin folder to monkey_island\bin\openssl
@@ -53,13 +63,13 @@ How to run:
monkey-windows-32.exe - monkey binary for windows 32bit
monkey-windows-64.exe - monkey binary for windows 64bi
-4. Download MongoDB and extract it to /var/monkey_island/bin/mongodb
- for debian64 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-debian81-latest.tgz
- for ubuntu64 16.10 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-latest.tgz
- find more at - https://www.mongodb.org/downloads#production
- untar.gz with: tar -zxvf filename.tar.gz -C /var/monkey_island/bin/mongodb
- (make sure the content of the mongo folder is in this directory, meaning this path exists:
- /var/monkey_island/bin/mongodb/bin)
+4. Setup MongoDB (Use one of the two following options):
+ 4.a. Download MongoDB and extract it to /var/monkey_island/bin/mongodb
+ 4.a.1. Run '/var/monkey_island/linux/install_mongo.sh /var/monkey_island/bin/mongodb'
+ This will download and extract the relevant mongoDB for your OS.
+ OR
+ 4.b. Use already running instance of mongodb
+ 4.b.1. Run 'set MONKEY_MONGO_URL="mongodb://:27017/monkeyisland"'. Replace '' with address of mongo server
5. install OpenSSL
sudo apt-get install openssl
diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt
index 29c364c9f..ee66bb797 100644
--- a/monkey/monkey_island/requirements.txt
+++ b/monkey/monkey_island/requirements.txt
@@ -1,5 +1,6 @@
+bson
python-dateutil
-tornado
+tornado==5.1.1
werkzeug
jinja2
markupsafe
@@ -9,8 +10,20 @@ flask
Flask-Pymongo
Flask-Restful
Flask-JWT
-jsonschema
+jsonschema==2.6.0
netifaces
ipaddress
enum34
-PyCrypto
+pycryptodome
+boto3
+botocore
+PyInstaller
+awscli
+cffi
+virtualenv
+wheel
+mongoengine
+mongomock
+requests
+dpath
+ring
diff --git a/monkey/monkey_island/scripts/island_password_hasher.py b/monkey/monkey_island/scripts/island_password_hasher.py
new file mode 100644
index 000000000..159e0d098
--- /dev/null
+++ b/monkey/monkey_island/scripts/island_password_hasher.py
@@ -0,0 +1,23 @@
+"""
+Utility script for running a string through SHA3_512 hash.
+Used for Monkey Island password hash, see
+https://github.com/guardicore/monkey/wiki/Enabling-Monkey-Island-Password-Protection
+for more details.
+"""
+
+import argparse
+from Crypto.Hash import SHA3_512
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("string_to_sha", help="The string to do sha for")
+ args = parser.parse_args()
+
+ h = SHA3_512.new()
+ h.update(args.string_to_sha)
+ print(h.hexdigest())
+
+
+if __name__ == '__main__':
+ main()
diff --git a/monkey/monkey_island/windows/build_pyinstaller.bat b/monkey/monkey_island/windows/build_pyinstaller.bat
new file mode 100644
index 000000000..58ec101d0
--- /dev/null
+++ b/monkey/monkey_island/windows/build_pyinstaller.bat
@@ -0,0 +1,5 @@
+REM - Builds Monkey Island Server EXE using pyinstaller -
+bin\Python27\Scripts\pyinstaller.exe -F --log-level=DEBUG --clean --upx-dir=.\bin monkey_island.spec
+move /Y dist\monkey_island.exe monkey_island.exe
+rmdir /S /Q build
+rmdir /S /Q dist
\ No newline at end of file
diff --git a/monkey/monkey_island/windows/run_cc.bat b/monkey/monkey_island/windows/run_cc.bat
index e86b5a145..c1a2fd88e 100644
--- a/monkey/monkey_island/windows/run_cc.bat
+++ b/monkey/monkey_island/windows/run_cc.bat
@@ -1,4 +1,5 @@
+REM - Runs Monkey Island Server using python -
@title C^&C Server
@pushd ..
-@monkey_island\bin\Python27\python monkey_island.py
+@monkey_island\bin\Python27\Scripts\python monkey_island.py
@popd
\ No newline at end of file
diff --git a/monkey/monkey_island/windows/run_cc_exe.bat b/monkey/monkey_island/windows/run_cc_exe.bat
new file mode 100644
index 000000000..a8b2cb14b
--- /dev/null
+++ b/monkey/monkey_island/windows/run_cc_exe.bat
@@ -0,0 +1,5 @@
+REM - Runs Monkey Island Server using built pyinstaller EXE -
+@title C^&C Server
+@pushd ..
+@monkey_island\monkey_island.exe
+@popd
\ No newline at end of file
diff --git a/monkey/monkey_island/windows/run_mongodb.bat b/monkey/monkey_island/windows/run_mongodb.bat
index ca33c22d7..106b5f00a 100644
--- a/monkey/monkey_island/windows/run_mongodb.bat
+++ b/monkey/monkey_island/windows/run_mongodb.bat
@@ -1,2 +1,3 @@
+REM - Runs MongoDB Server -
@title MongoDB
-@bin\mongodb\mongod.exe --dbpath db
\ No newline at end of file
+@bin\mongodb\mongod.exe --dbpath db --bind_ip 127.0.0.1
\ No newline at end of file
diff --git a/monkey/monkey_island/windows/run_server.bat b/monkey/monkey_island/windows/run_server.bat
index a15fbcc04..ab2ad274c 100644
--- a/monkey/monkey_island/windows/run_server.bat
+++ b/monkey/monkey_island/windows/run_server.bat
@@ -1,4 +1,5 @@
+REM - Runs MongoDB Server & Monkey Island Server using built pyinstaller EXE -
if not exist db mkdir db
start windows\run_mongodb.bat
-start windows\run_cc.bat
+start windows\run_cc_exe.bat
start https://localhost:5000
\ No newline at end of file
diff --git a/monkey/monkey_island/windows/run_server_py.bat b/monkey/monkey_island/windows/run_server_py.bat
new file mode 100644
index 000000000..07a587f49
--- /dev/null
+++ b/monkey/monkey_island/windows/run_server_py.bat
@@ -0,0 +1,5 @@
+REM - Runs MongoDB Server & Monkey Island Server using python -
+if not exist db mkdir db
+start windows\run_mongodb.bat
+start windows\run_cc.bat
+start https://localhost:5000
\ No newline at end of file