From 83ed12249ed88bbb1d6991cbdf1677035aee4e94 Mon Sep 17 00:00:00 2001 From: vkuchinov Date: Thu, 22 Aug 2019 10:13:10 +0300 Subject: [PATCH 01/20] VennDiagram component --- .../zerotrust/VennDiagram/ArcNode/index.js | 46 +++ .../VennDiagram/CircularNode/index.js | 57 +++ .../zerotrust/VennDiagram/Tooltip/index.js | 49 +++ .../zerotrust/VennDiagram/index.css | 14 + .../zerotrust/VennDiagram/index.js | 369 ++++++++++++++++++ .../zerotrust/VennDiagram/utility.js | 8 + 6 files changed, 543 insertions(+) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/ArcNode/index.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/CircularNode/index.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/Tooltip/index.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.css create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/utility.js diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/ArcNode/index.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/ArcNode/index.js new file mode 100644 index 000000000..b9861a55e --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/ArcNode/index.js @@ -0,0 +1,46 @@ +import React from 'react' +import PropTypes from 'prop-types'; +import * as d3 from 'd3' + +class ArcNode extends React.Component{ + + render() { + + let {prefix, ref, index, data} = this.props;; + + let arc = d3.arc().innerRadius(data.inner).outerRadius(data.outer).startAngle(0).endAngle(Math.PI * 2.0); + + return ( + + + + + + {data.label} + + + + + ) + + } + +} + +ArcNode.propTypes = { + + prefix: PropTypes.string, + index: PropTypes.number, + data: PropTypes.object + +} + +export default ArcNode; \ No newline at end of file diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/CircularNode/index.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/CircularNode/index.js new file mode 100644 index 000000000..a8caf5bb0 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/CircularNode/index.js @@ -0,0 +1,57 @@ +import React from 'react' +import PropTypes from 'prop-types'; + +class CircularNode extends React.Component{ + + render() { + + let {prefix, ref, index, data} = this.props; + + let tspans = data.label.split("|").map((d_, i_) =>{ + + let halfTextHeight = data.fontStyle.size * data.label.split("|").length / 2; + let key = 'vennDiagramCircularNode' + index + '_Tspan' + i_; + + return ( + + {d_} + + ) + + }) + + let translate = 'translate(' + data.cx + ',' + data.cy + ')'; + + return ( + + + + + {tspans} + + + + ) + + } + +} + +CircularNode.propTypes = { + + prefix: PropTypes.string, + index: PropTypes.number, + data: PropTypes.object + +} + +export default CircularNode; \ No newline at end of file diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/Tooltip/index.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/Tooltip/index.js new file mode 100644 index 000000000..579b36a5b --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/Tooltip/index.js @@ -0,0 +1,49 @@ +import React from 'react' +import PropTypes from 'prop-types'; + +class Tooltip extends React.Component{ + + render() { + + const {prefix, bcolor, top, left, display, html } = this.props; + + const style = { + + backgroundColor: bcolor, + border : '1px solid #FFFFFF', + borderRadius: '2px', + fontSize: 10, + padding: 8, + display, + opacity: 0.9, + position: 'fixed', + top, + left, + pointerEvents: 'none' + + }; + + return ( + +
+ {html.split('\n').map((i_, key_) => { return
{i_}
; })} +
+ + ); + + } + +} + +Tooltip.propTypes = { + + prefix: PropTypes.string, + bcolor: PropTypes.string, + top: PropTypes.number, + left: PropTypes.number, + display: PropTypes.string, + html: PropTypes.string + +} + +export default Tooltip; \ No newline at end of file diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.css b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.css new file mode 100644 index 000000000..6c7cd778e --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.css @@ -0,0 +1,14 @@ +@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/VennDiagram/index.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js new file mode 100644 index 000000000..b221d4159 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js @@ -0,0 +1,369 @@ +import React from 'react' +import PropTypes from 'prop-types'; +import Dimensions from 'react-dimensions' +import Tooltip from './Tooltip' +import CircularNode from './CircularNode' +import ArcNode from './ArcNode' +import { TypographicUtilities } from './utility.js' +import './index.css' + +/* + +TODO LIST + +UPDATED [21.08.2019] + +[-] SVG > ViewBox 0 0 512 512, so it would be responsive and scalable +[-] Add resize listener to ResponsiveVennDiagram wrapper +[-] I have noticed that you have PillarGrades at ZeroTrustReportPage, so how I can fetch the data out of it? + +UPDATED [20.08.2019] + +[x] I've seen that lots of D3 responsive examples are using 'wrapper' around the main component to + get parent container dimensions. And lister for resize. + + So, here it's Responsive(VennDiagram) + + If it doesn't work, I have another alternative: + + import React, { useRef, useEffect, useState, useLayoutEffect } from 'react' + + const ResponsiveVennDiagram = props => { + + const minWidth = 256; + const targetRef = useRef(); + const [dimensions, setDimensions] = useState({ width: 0, heigth: 0 }); + let movement_timer = null; + const RESET_TIMEOUT = 100; + + const checkForDimensionsUpdate = () => { + + if (targetRef.current) { + + setDimensions({ + + width: Math.min(targetRef.current.offsetWidth, targetRef.current.offsetHeight), + height: targetRef.current.offsetHeight + + }); + + } + }; + + useLayoutEffect(() => { checkForDimensionsUpdate(); }, []); + + window.addEventListener("resize", () => { clearInterval(movement_timer); movement_timer = setTimeout(checkForDimensionsUpdate, RESET_TIMEOUT); }); + + return ( + +
+ +
+ + ); + + }; + + ResponsiveVennDiagram.propTypes = { + + pillarsGrades: PropTypes.array + + } + + export default ResponsiveVennDiagram; + + While your diagram laout is squared, the VennDiaagram gets the min(width, height) + +[x] Colours have been updated. + +[x] String prototypes have been moved to '.utilities.js' + +[x] I have use the prefix 'vennDiagram' for all children elements to prevent naming conflicts with other components + DOM objects. + +[x] I have used PropTypes already a year ago on ThreeJS/ReactJS stack project. + +[!] External callback is still on my list, can you make a mockup for external function which have to get pillar variable? + +[!] Z-indices sorting on mouseover + Would be n my 21.08.2019 TODO list. With D3.JS it's an easy task, however would try do + make these z-soring without D3 framework. + +UPDATED [20.08.2019] + +[!] By now, there are three input props for VennDiagram component: + data, width and height. + +[-] Since layout has to be hardcoded, it's driven by this.layout object. + There're many ways of setting circles/arc positions, now I'm using the + stright-forward one. + + Usually, I am put all hardcoded params to external JSON file, i.e config.json. + Let me know if it's a good idea. + +[-] Could rearange z-indecies for nodes on hover, so highlighted node would have highest z-index. +[-] If you want callback on click even, please provide additional info what are you expecting to pass + through this. + +[!] I don't used to make lots of comments in my code, but treying to name everything in a way, + so third person could instantly get its job and concept. + + If it is not enoough just let me know. + +[!] I have tried to avoid using D3 so much, especially its mouse events, cause it creates a bunch + of listeners for every children DOM elements. I have tried to use raw SVG objects. + The ArcNode is the only component where I've to call d3.arc constrictor. + +[!] There are lots of discussion over different blogs an forums that ReactJS and D3.JS have a DOM + issue conflict, [could find lots of articels at medium.org about that, for example, + https://medium.com/@tibotiber/react-d3-js-balancing-performance-developer-experience-4da35f912484]. + + Since the current component has only a few DOM elements, I don't thing we would have any troubles, + but please keep in mind that I could tweak current version with react-faux-dom. + + Actually, by now, I'm using D3 only for math, for arc path calculations. + +[!] Don't mind about code spacings, it's just for me, the final code would be clear out of them. + +[-] On click, an EXTERNAL callback should be called with the pillar name as a parameter. That is to enable us to expand the click functionality in future without editing the internal implementation of the component. + +[-] planned, [x] done, [!] see comments + +@author Vladimir V KUCHINOV +@email helloworld@vkuchinov.co.uk + +*/ + +const VENN_MIN_WIDTH = 256; + +class ResponsiveVennDiagram extends React.Component { + + constructor(props) { super(props); } + render() { + + const {options, pillarsGrades} = this.props; + let childrenWidth = this.props.containerWidth, childrenHeight = this.props.containerHeight; + + if(childrenHeight === 0 || childrenHeight === NaN){ childrenHeight = childrenWidth; } + else{ childrenWidth = Math.min(childrenWidth, childrenHeight) } + + return ( + +
+ +
) + + } + +} + +ResponsiveVennDiagram.propTypes = { + + pillarsGrades: PropTypes.array + +} + +export default Dimensions()(ResponsiveVennDiagram); + +class VennDiagram extends React.Component{ + + constructor(props_){ + + super(props_); + + this.state = { tooltip: { top: 0, left: 0, display: 'none', html: '' } } + + this.colors = ['#777777', '#D9534F', '#F0AD4E', '#5CB85C']; + this.prefix = 'vennDiagram'; + this.suffices = ['', '|tests are|conclusive', '|tests were|inconclusive', '|tests|performed']; + this.fontStyles = [{size: Math.max(9, this.props.width / 32), color: 'white'}, {size: Math.max(6, this.props.width / 52), color: 'black'}]; + this.offset = this.props.width / 16; + + this.thirdWidth = this.props.width / 3; + this.sixthWidth = this.props.width / 6; + this.width2By7 = 2 * this.props.width / 7 + this.width1By11 = this.props.width / 11; + this.width1By28 = this.props.width / 28; + + this.toggle = false; + + this.layout = { + + Data: { cx: 0, cy: 0, r: this.thirdWidth - this.offset * 2, offset: {x: 0, y: 0} }, + People: { cx: -this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: this.width1By11, y: 0} }, + Networks: { cx: this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: -this.width1By11, y: 0} }, + Devices: { cx: 0, cy: this.width2By7, r: this.sixthWidth, offset: {x: 0, y: -this.width1By11} }, + Workloads : { cx: 0, cy: -this.width2By7, r: this.sixthWidth, offset: {x: 0, y: this.width1By11} }, + VisibilityAndAnalytics : { inner: this.thirdWidth - this.width1By28, outer: this.thirdWidth }, + AutomationAndOrchestration: { inner: this.thirdWidth - this.width1By28 * 2, outer: this.thirdWidth - this.width1By28 } + + }; + + } + + componentDidMount() { + + this.parseData(); + + } + + _onMouseMove(e) { + + if(!this.toggle){ + + let hidden = 'none'; + let html = ''; + let bcolor = '#DEDEDE'; + + e.stopPropagation(); + + document.querySelectorAll('circle, path').forEach((d_, i_) => { d_.setAttribute('opacity', 0.8)}); + + if(e.target.id.includes('Node')) { + + html = e.target.dataset.tooltip; + this.divElement.style.cursor = 'pointer'; + hidden = 'block'; e.target.setAttribute('opacity', 1.0); + bcolor = e.target.getAttribute('fill'); + + }else{ + + this.divElement.style.cursor = 'default'; + + } + + this.setState({target: e, tooltip: { target: e.target, bcolor: bcolor, top: e.clientY + 8, left: e.clientX + 8, display: hidden, html: html } }); + + } + } + + _onClick(e) { + + if(this.state.tooltip.target === e.target) { this.toggle = true; } else { this.toggle = false; } + + //variable to external callback + //e.target.parentNode.id) + + } + + relativeCoords (e) { + + let bounds = e.target.getBoundingClientRect(); + var x = e.clientX - bounds.left; + var y = e.clientY - bounds.top; + return {x: x, y: y}; + + } + + 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(d_); + let rule = 3; + + if(sum === 0){ rule = 0 } + else if(d_['Conclusive'] > 0){ rule = 1 } + else if(d_['Conclusive'] === 0 && d_['Inconclusive'] > 0) { rule = 2 } + + self.setLayoutElement(rule, key, html, d_); + data.push(this.layout[key]) + + }) + + this.setState({ data: data }); + this.render(); + + } + + buildTooltipHtmlContent(object_){ return Object.keys(object_).reduce((out_, key_) => out_ + TypographicUtilities.setTitle(key_) + ': ' + object_[key_] + '\n', ''); } + + setLayoutElement(rule_, key_, html_, d_){ + + if(key_ === 'Data'){ this.layout[key_].fontStyle = this.fontStyles[0]; } + else {this.layout[key_].fontStyle = this.fontStyles[1]; } + + this.layout[key_].hex = this.colors[rule_]; + this.layout[key_].label = d_.pillar + this.suffices[rule_]; + this.layout[key_].node = d_; + this.layout[key_].tooltip = html_; + + } + + render() { + + if(this.state.data === undefined) { return null; } + else { + + let { width, height } = this.props; + let translate = 'translate(' + width /2 + ',' + height/2 + ')'; + + 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)}> + + + {nodes} + + + +
+ + ) + + } + + } + +} + +VennDiagram.propTypes = { + + pillarsGrades: PropTypes.array, + width: PropTypes.number, + height: PropTypes.number + +} \ No newline at end of file diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/utility.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/utility.js new file mode 100644 index 000000000..c9816c721 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/utility.js @@ -0,0 +1,8 @@ +export class TypographicUtilities { + + static removeAmpersand(string_) { return string_.replace(' & ', 'And'); } + static removeBrokenBar(string_) { return string_.replace(/\|/g, ' '); } + static setTitle(string_) { return string_.charAt(0).toUpperCase() + string_.substr(1).toLowerCase(); } + +} + \ No newline at end of file From 2174f43a849874226cff98dc19adb9ab9b32a409 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 22 Aug 2019 13:33:40 +0300 Subject: [PATCH 02/20] Added d3 to package.json and now using the ResponsiveVennDiagram in Pillaroverview --- monkey/monkey_island/cc/ui/package-lock.json | 315 ++++++++++++++++-- monkey/monkey_island/cc/ui/package.json | 1 + .../zerotrust/PillarOverview.js | 10 +- 3 files changed, 286 insertions(+), 40 deletions(-) diff --git a/monkey/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json index 09ccebad9..f366d73bd 100644 --- a/monkey/monkey_island/cc/ui/package-lock.json +++ b/monkey/monkey_island/cc/ui/package-lock.json @@ -3087,8 +3087,7 @@ "commander": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" }, "commondir": { "version": "1.0.1", @@ -3657,6 +3656,270 @@ "es5-ext": "^0.10.9" } }, + "d3": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-5.11.0.tgz", + "integrity": "sha512-LXgMVUAEAzQh6WfEEOa8tJX4RA64ZJ6twC3CJ+Xzid+fXWLTZkkglagXav/eOoQgzQi5rzV0xC4Sfspd6hFDHA==", + "requires": { + "d3-array": "1", + "d3-axis": "1", + "d3-brush": "1", + "d3-chord": "1", + "d3-collection": "1", + "d3-color": "1", + "d3-contour": "1", + "d3-dispatch": "1", + "d3-drag": "1", + "d3-dsv": "1", + "d3-ease": "1", + "d3-fetch": "1", + "d3-force": "1", + "d3-format": "1", + "d3-geo": "1", + "d3-hierarchy": "1", + "d3-interpolate": "1", + "d3-path": "1", + "d3-polygon": "1", + "d3-quadtree": "1", + "d3-random": "1", + "d3-scale": "2", + "d3-scale-chromatic": "1", + "d3-selection": "1", + "d3-shape": "1", + "d3-time": "1", + "d3-time-format": "2", + "d3-timer": "1", + "d3-transition": "1", + "d3-voronoi": "1", + "d3-zoom": "1" + } + }, + "d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + }, + "d3-axis": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz", + "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==" + }, + "d3-brush": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.3.tgz", + "integrity": "sha512-v8bbYyCFKjyCzFk/tdWqXwDykY8YWqhXYjcYxfILIit085VZOpj4XJKOMccTsvWxgzSLMJQg5SiqHjslsipEDg==", + "requires": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, + "d3-chord": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz", + "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==", + "requires": { + "d3-array": "1", + "d3-path": "1" + } + }, + "d3-collection": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", + "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" + }, + "d3-color": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.3.0.tgz", + "integrity": "sha512-NHODMBlj59xPAwl2BDiO2Mog6V+PrGRtBfWKqKRrs9MCqlSkIEb0Z/SfY7jW29ReHTDC/j+vwXhnZcXI3+3fbg==" + }, + "d3-contour": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz", + "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==", + "requires": { + "d3-array": "^1.1.1" + } + }, + "d3-dispatch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.5.tgz", + "integrity": "sha512-vwKx+lAqB1UuCeklr6Jh1bvC4SZgbSqbkGBLClItFBIYH4vqDJCA7qfoy14lXmJdnBOdxndAMxjCbImJYW7e6g==" + }, + "d3-drag": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.4.tgz", + "integrity": "sha512-ICPurDETFAelF1CTHdIyiUM4PsyZLaM+7oIBhmyP+cuVjze5vDZ8V//LdOFjg0jGnFIZD/Sfmk0r95PSiu78rw==", + "requires": { + "d3-dispatch": "1", + "d3-selection": "1" + } + }, + "d3-dsv": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.1.1.tgz", + "integrity": "sha512-1EH1oRGSkeDUlDRbhsFytAXU6cAmXFzc52YUe6MRlPClmWb85MP1J5x+YJRzya4ynZWnbELdSAvATFW/MbxaXw==", + "requires": { + "commander": "2", + "iconv-lite": "0.4", + "rw": "1" + } + }, + "d3-ease": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.5.tgz", + "integrity": "sha512-Ct1O//ly5y5lFM9YTdu+ygq7LleSgSE4oj7vUt9tPLHUi8VCV7QoizGpdWRWAwCO9LdYzIrQDg97+hGVdsSGPQ==" + }, + "d3-fetch": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.1.2.tgz", + "integrity": "sha512-S2loaQCV/ZeyTyIF2oP8D1K9Z4QizUzW7cWeAOAS4U88qOt3Ucf6GsmgthuYSdyB2HyEm4CeGvkQxWsmInsIVA==", + "requires": { + "d3-dsv": "1" + } + }, + "d3-force": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", + "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", + "requires": { + "d3-collection": "1", + "d3-dispatch": "1", + "d3-quadtree": "1", + "d3-timer": "1" + } + }, + "d3-format": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.3.2.tgz", + "integrity": "sha512-Z18Dprj96ExragQ0DeGi+SYPQ7pPfRMtUXtsg/ChVIKNBCzjO8XYJvRTC1usblx52lqge56V5ect+frYTQc8WQ==" + }, + "d3-geo": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.6.tgz", + "integrity": "sha512-z0J8InXR9e9wcgNtmVnPTj0TU8nhYT6lD/ak9may2PdKqXIeHUr8UbFLoCtrPYNsjv6YaLvSDQVl578k6nm7GA==", + "requires": { + "d3-array": "1" + } + }, + "d3-hierarchy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.8.tgz", + "integrity": "sha512-L+GHMSZNwTpiq4rt9GEsNcpLa4M96lXMR8M/nMG9p5hBE0jy6C+3hWtyZMenPQdwla249iJy7Nx0uKt3n+u9+w==" + }, + "d3-interpolate": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.3.2.tgz", + "integrity": "sha512-NlNKGopqaz9qM1PXh9gBF1KSCVh+jSFErrSlD/4hybwoNX/gt1d8CDbDW+3i+5UOHhjC6s6nMvRxcuoMVNgL2w==", + "requires": { + "d3-color": "1" + } + }, + "d3-path": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.8.tgz", + "integrity": "sha512-J6EfUNwcMQ+aM5YPOB8ZbgAZu6wc82f/0WFxrxwV6Ll8wBwLaHLKCqQ5Imub02JriCVVdPjgI+6P3a4EWJCxAg==" + }, + "d3-polygon": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.5.tgz", + "integrity": "sha512-RHhh1ZUJZfhgoqzWWuRhzQJvO7LavchhitSTHGu9oj6uuLFzYZVeBzaWTQ2qSO6bz2w55RMoOCf0MsLCDB6e0w==" + }, + "d3-quadtree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.6.tgz", + "integrity": "sha512-NUgeo9G+ENQCQ1LsRr2qJg3MQ4DJvxcDNCiohdJGHt5gRhBW6orIB5m5FJ9kK3HNL8g9F4ERVoBzcEwQBfXWVA==" + }, + "d3-random": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz", + "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==" + }, + "d3-scale": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", + "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", + "requires": { + "d3-array": "^1.2.0", + "d3-collection": "1", + "d3-format": "1", + "d3-interpolate": "1", + "d3-time": "1", + "d3-time-format": "2" + } + }, + "d3-scale-chromatic": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz", + "integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==", + "requires": { + "d3-color": "1", + "d3-interpolate": "1" + } + }, + "d3-selection": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.0.tgz", + "integrity": "sha512-EYVwBxQGEjLCKF2pJ4+yrErskDnz5v403qvAid96cNdCMr8rmCYfY5RGzWz24mdIbxmDf6/4EAH+K9xperD5jg==" + }, + "d3-shape": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.5.tgz", + "integrity": "sha512-VKazVR3phgD+MUCldapHD7P9kcrvPcexeX/PkMJmkUov4JM8IxsSg1DvbYoYich9AtdTsa5nNk2++ImPiDiSxg==", + "requires": { + "d3-path": "1" + } + }, + "d3-time": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.11.tgz", + "integrity": "sha512-Z3wpvhPLW4vEScGeIMUckDW7+3hWKOQfAWg/U7PlWBnQmeKQ00gCUsTtWSYulrKNA7ta8hJ+xXc6MHrMuITwEw==" + }, + "d3-time-format": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.3.tgz", + "integrity": "sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA==", + "requires": { + "d3-time": "1" + } + }, + "d3-timer": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.9.tgz", + "integrity": "sha512-rT34J5HnQUHhcLvhSB9GjCkN0Ddd5Y8nCwDBG2u6wQEeYxT/Lf51fTFFkldeib/sE/J0clIe0pnCfs6g/lRbyg==" + }, + "d3-transition": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.2.0.tgz", + "integrity": "sha512-VJ7cmX/FPIPJYuaL2r1o1EMHLttvoIuZhhuAlRoOxDzogV8iQS6jYulDm3xEU3TqL80IZIhI551/ebmCMrkvhw==", + "requires": { + "d3-color": "1", + "d3-dispatch": "1", + "d3-ease": "1", + "d3-interpolate": "1", + "d3-selection": "^1.1.0", + "d3-timer": "1" + } + }, + "d3-voronoi": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz", + "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==" + }, + "d3-zoom": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz", + "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==", + "requires": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -8419,8 +8682,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -8441,14 +8703,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8463,20 +8723,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -8593,8 +8850,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -8606,7 +8862,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -8621,7 +8876,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -8629,14 +8883,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -8655,7 +8907,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -8736,8 +8987,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -8749,7 +8999,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -8835,8 +9084,7 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -8872,7 +9120,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -8892,7 +9139,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -8936,14 +9182,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -15091,6 +15335,11 @@ "aproba": "^1.1.1" } }, + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" + }, "rxjs": { "version": "6.3.3", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", @@ -19041,6 +19290,7 @@ "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -19209,6 +19459,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index 1a60ee27c..872a22bdc 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -70,6 +70,7 @@ "bootstrap": "3.4.1", "classnames": "^2.2.6", "core-js": "^2.5.7", + "d3": "^5.11.0", "downloadjs": "^1.4.7", "fetch": "^1.1.0", "file-saver": "^2.0.2", 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 index e2b16c91b..f772e0652 100644 --- 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 @@ -1,7 +1,7 @@ import React, {Component} from "react"; import PillarLabel from "./PillarLabel"; -import PaginatedTable from "../common/PaginatedTable"; import * as PropTypes from "prop-types"; +import ResponsiveVennDiagram from "./VennDiagram"; const columns = [ { @@ -20,13 +20,8 @@ const columns = [ class PillarOverview extends Component { render() { - const data = this.props.grades.map((grade) => { - const newGrade = JSON.parse(JSON.stringify(grade)); - newGrade.pillar = {name: grade.pillar, status: this.props.pillarsToStatuses[grade.pillar]}; - return newGrade; - }); return (
- +
); } } @@ -35,5 +30,4 @@ export default PillarOverview; PillarOverview.propTypes = { grades: PropTypes.array, - status: PropTypes.string, }; From 20e282f5fb998a7495d63ad5d9f8623df966c8f4 Mon Sep 17 00:00:00 2001 From: vkuchinov Date: Thu, 22 Aug 2019 14:50:07 +0300 Subject: [PATCH 03/20] Update index.js [x] Z-Indices sorting on hover [x] Resize (viewBox solution] [x] Rule correction, have excluded 'Unexecuted' parameter from the sum for Rule #1 Still on my today's TODO list: [-] Still looking for an elegant solution to scrolling glitch. Yes, the easiest way is to hide tooltip on scrolling, but that's not cool [-] Need some coding refining --- .../zerotrust/VennDiagram/index.js | 61 +++++++++++-------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js index b221d4159..6e61261e6 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js @@ -134,7 +134,7 @@ UPDATED [20.08.2019] */ -const VENN_MIN_WIDTH = 256; +const VENN_MIN_WIDTH = '300px'; class ResponsiveVennDiagram extends React.Component { @@ -149,8 +149,8 @@ class ResponsiveVennDiagram extends React.Component { return ( -
- +
+
) } @@ -173,17 +173,19 @@ class VennDiagram extends React.Component{ this.state = { tooltip: { top: 0, left: 0, display: 'none', html: '' } } + this.width = this.height = 512, this.zOrder = []; + this.colors = ['#777777', '#D9534F', '#F0AD4E', '#5CB85C']; this.prefix = 'vennDiagram'; this.suffices = ['', '|tests are|conclusive', '|tests were|inconclusive', '|tests|performed']; - this.fontStyles = [{size: Math.max(9, this.props.width / 32), color: 'white'}, {size: Math.max(6, this.props.width / 52), color: 'black'}]; - this.offset = this.props.width / 16; + this.fontStyles = [{size: Math.max(9, this.width / 32), color: 'white'}, {size: Math.max(6, this.width / 52), color: 'black'}]; + this.offset = this.width / 16; - this.thirdWidth = this.props.width / 3; - this.sixthWidth = this.props.width / 6; - this.width2By7 = 2 * this.props.width / 7 - this.width1By11 = this.props.width / 11; - this.width1By28 = this.props.width / 28; + this.thirdWidth = this.width / 3; + this.sixthWidth = this.width / 6; + this.width2By7 = 2 * this.width / 7 + this.width1By11 = this.width / 11; + this.width1By28 = this.width / 28; this.toggle = false; @@ -207,8 +209,10 @@ class VennDiagram extends React.Component{ } - _onMouseMove(e) { + _onMouseMove(e) { + let self = this; + if(!this.toggle){ let hidden = 'none'; @@ -218,17 +222,22 @@ class VennDiagram extends React.Component{ e.stopPropagation(); document.querySelectorAll('circle, path').forEach((d_, i_) => { d_.setAttribute('opacity', 0.8)}); - + if(e.target.id.includes('Node')) { html = e.target.dataset.tooltip; this.divElement.style.cursor = 'pointer'; - hidden = 'block'; e.target.setAttribute('opacity', 1.0); + hidden = 'block'; e.target.setAttribute('opacity', 0.95); bcolor = e.target.getAttribute('fill'); + //set highest z-index + e.target.parentNode.parentNode.appendChild(e.target.parentNode); }else{ this.divElement.style.cursor = 'default'; + + //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); }) } @@ -237,6 +246,13 @@ class VennDiagram extends React.Component{ } } + _onMouseLeave(e){ + + let hidden = 'none'; + + this.setState({target: null, tooltip: { target: null, bcolor: 'none', top: 0, left: 0, display: hidden, html: '' } }); + + } _onClick(e) { if(this.state.tooltip.target === e.target) { this.toggle = true; } else { this.toggle = false; } @@ -263,7 +279,7 @@ class VennDiagram extends React.Component{ this.props.pillarsGrades.forEach((d_, i_) => { - let params = omit('pillar', d_); + let params = omit('Unexpected', 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(d_); @@ -302,8 +318,9 @@ class VennDiagram extends React.Component{ if(this.state.data === undefined) { return null; } else { - let { width, height } = this.props; - let translate = 'translate(' + width /2 + ',' + height/2 + ')'; + //equivalent to center translate (width/2, height/2) + let viewPortParameters = (-this.width / 2) + ' ' + (-this.height / 2) + ' ' + this.width + ' ' + this.height; + let translate = 'translate(' + this.width /2 + ',' + this.height/2 + ')'; let nodes = Object.values(this.layout).map((d_, i_) =>{ @@ -343,11 +360,9 @@ class VennDiagram extends React.Component{ return ( -
this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)} onClick={this._onClick.bind(this)}> - - - {nodes} - +
this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)} onMouseLeave={this._onMouseLeave.bind(this)} onClick={this._onClick.bind(this)}> + + {nodes}
@@ -362,8 +377,6 @@ class VennDiagram extends React.Component{ VennDiagram.propTypes = { - pillarsGrades: PropTypes.array, - width: PropTypes.number, - height: PropTypes.number + pillarsGrades: PropTypes.array } \ No newline at end of file From 244be146bb3ceb6f733012a0ad4ab7dd936aa15b Mon Sep 17 00:00:00 2001 From: vkuchinov Date: Fri, 23 Aug 2019 02:41:00 +0300 Subject: [PATCH 04/20] Update VennDiagram/index.js [x] Scrolling issue. Since only window/document have 'scroll' event, the only option to fix tooltip issue on scrolling is just simply hide it. That works well if after scrolling the mouse pointer doesn't stay on any venn nodes. Otherwise, you have to move mouse for the tooltip. Theoretically, I could store hovered node coordinates and use them in _onScroll function to check if mouse is still on top one Venn nodes find window.pageYOffset difference. --- .../zerotrust/VennDiagram/index.js | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js index 6e61261e6..de28fc012 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js @@ -52,7 +52,7 @@ UPDATED [20.08.2019] useLayoutEffect(() => { checkForDimensionsUpdate(); }, []); - window.addEventListener("resize", () => { clearInterval(movement_timer); movement_timer = setTimeout(checkForDimensionsUpdate, RESET_TIMEOUT); }); + window.addEventListener('resize', () => { clearInterval(movement_timer); movement_timer = setTimeout(checkForDimensionsUpdate, RESET_TIMEOUT); }); return ( @@ -200,12 +200,15 @@ class VennDiagram extends React.Component{ AutomationAndOrchestration: { inner: this.thirdWidth - this.width1By28 * 2, outer: this.thirdWidth - this.width1By28 } }; + + this._onScroll = this._onScroll.bind(this); } componentDidMount() { this.parseData(); + window.addEventListener('scroll', this._onScroll); } @@ -218,9 +221,7 @@ class VennDiagram extends React.Component{ let hidden = 'none'; let html = ''; let bcolor = '#DEDEDE'; - - e.stopPropagation(); - + document.querySelectorAll('circle, path').forEach((d_, i_) => { d_.setAttribute('opacity', 0.8)}); if(e.target.id.includes('Node')) { @@ -229,6 +230,7 @@ class VennDiagram extends React.Component{ this.divElement.style.cursor = 'pointer'; hidden = 'block'; e.target.setAttribute('opacity', 0.95); bcolor = e.target.getAttribute('fill'); + //set highest z-index e.target.parentNode.parentNode.appendChild(e.target.parentNode); @@ -245,14 +247,13 @@ class VennDiagram extends React.Component{ } } + _onScroll(e){ + + this.divElement.style.cursor = 'default'; + this.setState({target: e, tooltip: { target: null, bcolor: 'none', top: 0, left: 0, display: 'none', html: '' } }); - _onMouseLeave(e){ - - let hidden = 'none'; - - this.setState({target: null, tooltip: { target: null, bcolor: 'none', top: 0, left: 0, display: hidden, html: '' } }); - } + _onClick(e) { if(this.state.tooltip.target === e.target) { this.toggle = true; } else { this.toggle = false; } @@ -360,7 +361,7 @@ class VennDiagram extends React.Component{ return ( -
this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)} onMouseLeave={this._onMouseLeave.bind(this)} onClick={this._onClick.bind(this)}> +
this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)} onClick={this._onClick.bind(this)} > {nodes} From fb01bface6b6341a2c7a327cca9dcd4634a149d6 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 25 Aug 2019 10:30:17 +0300 Subject: [PATCH 05/20] Extracted config utility function to new package - in future all config should move here --- monkey/monkey_island/cc/services/configuration/__init__.py | 0 monkey/monkey_island/cc/services/configuration/utils.py | 5 +++++ monkey/monkey_island/cc/services/reporting/report.py | 3 ++- 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 monkey/monkey_island/cc/services/configuration/__init__.py create mode 100644 monkey/monkey_island/cc/services/configuration/utils.py diff --git a/monkey/monkey_island/cc/services/configuration/__init__.py b/monkey/monkey_island/cc/services/configuration/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/services/configuration/utils.py b/monkey/monkey_island/cc/services/configuration/utils.py new file mode 100644 index 000000000..34d6a9bb5 --- /dev/null +++ b/monkey/monkey_island/cc/services/configuration/utils.py @@ -0,0 +1,5 @@ +from monkey_island.cc.services.config import ConfigService + + +def get_config_network_segments_as_subnet_groups(): + return [ConfigService.get_config_value(['basic_network', 'network_analysis', 'inaccessible_subnets'])] diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index 1d7ac162d..af3d2673b 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -13,6 +13,7 @@ from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey from monkey_island.cc.report_exporter_manager import ReportExporterManager from monkey_island.cc.services.config import ConfigService +from monkey_island.cc.services.configuration.utils import get_config_network_segments_as_subnet_groups from monkey_island.cc.services.edge import EdgeService from monkey_island.cc.services.node import NodeService from monkey_island.cc.utils import local_ip_addresses, get_subnets @@ -552,7 +553,7 @@ class ReportService: cross_segment_issues = [] # For now the feature is limited to 1 group. - subnet_groups = [ConfigService.get_config_value(['basic_network', 'network_analysis', 'inaccessible_subnets'])] + subnet_groups = get_config_network_segments_as_subnet_groups() for subnet_group in subnet_groups: cross_segment_issues += ReportService.get_cross_segment_issues_per_subnet_group(scans, subnet_group) From 6ec4e613cf32e8200c94d3b67b1e50d02fe3601d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 25 Aug 2019 11:31:31 +0300 Subject: [PATCH 06/20] Extracted segmentation utils function --- monkey/common/network/segmentation_utils.py | 19 +++++++++++++++ .../common/network/segmentation_utils_test.py | 19 +++++++++++++++ .../cc/services/reporting/report.py | 24 ++++--------------- 3 files changed, 42 insertions(+), 20 deletions(-) create mode 100644 monkey/common/network/segmentation_utils.py create mode 100644 monkey/common/network/segmentation_utils_test.py diff --git a/monkey/common/network/segmentation_utils.py b/monkey/common/network/segmentation_utils.py new file mode 100644 index 000000000..68122f398 --- /dev/null +++ b/monkey/common/network/segmentation_utils.py @@ -0,0 +1,19 @@ +from common.network.network_range import NetworkRange + + +def get_ip_in_src_and_not_in_dst(ip_addresses, source_subnet, target_subnet): + # type: (List[str], NetworkRange, NetworkRange) -> Union[str, None] + """ + Finds an IP address in ip_addresses which is in source_subnet but not in target_subnet. + :param ip_addresses: List of IP addresses to test. + :param source_subnet: Subnet to want an IP to not be in. + :param target_subnet: Subnet we want an IP to be in. + :return: The cross segment IP if in source but not in target, else None. + """ + for ip_address in ip_addresses: + if target_subnet.is_in_range(ip_address): + return None + for ip_address in ip_addresses: + if source_subnet.is_in_range(ip_address): + return ip_address + return None diff --git a/monkey/common/network/segmentation_utils_test.py b/monkey/common/network/segmentation_utils_test.py new file mode 100644 index 000000000..7ef3e4450 --- /dev/null +++ b/monkey/common/network/segmentation_utils_test.py @@ -0,0 +1,19 @@ +from common.network.network_range import * +from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst +from monkey_island.cc.testing.IslandTestCase import IslandTestCase + + +class TestSegmentationUtils(IslandTestCase): + def test_get_ip_in_src_and_not_in_dst(self): + self.fail_if_not_testing_env() + source = CidrRange("1.1.1.0/24") + target = CidrRange("2.2.2.0/24") + self.assertIsNone(get_ip_in_src_and_not_in_dst( + [text_type("2.2.2.2")], source, target + )) + self.assertIsNone(get_ip_in_src_and_not_in_dst( + [text_type("3.3.3.3"), text_type("4.4.4.4")], source, target + )) + self.assertIsNotNone(get_ip_in_src_and_not_in_dst( + [text_type("8.8.8.8"), text_type("1.1.1.1")], source, target + )) diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index af3d2673b..fdba3b549 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -9,6 +9,7 @@ from enum import Enum from six import text_type +from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey from monkey_island.cc.report_exporter_manager import ReportExporterManager @@ -424,23 +425,6 @@ class ReportService: return issues - @staticmethod - def get_ip_in_src_and_not_in_dst(ip_addresses, source_subnet, target_subnet): - """ - Finds an IP address in ip_addresses which is in source_subnet but not in target_subnet. - :param ip_addresses: List of IP addresses to test. - :param source_subnet: Subnet to want an IP to not be in. - :param target_subnet: Subnet we want an IP to be in. - :return: - """ - for ip_address in ip_addresses: - if target_subnet.is_in_range(ip_address): - return None - for ip_address in ip_addresses: - if source_subnet.is_in_range(ip_address): - return ip_address - return None - @staticmethod def get_cross_segment_issues_of_single_machine(source_subnet_range, target_subnet_range): """ @@ -503,9 +487,9 @@ class ReportService: target_ip = scan['data']['machine']['ip_addr'] if target_subnet_range.is_in_range(text_type(target_ip)): monkey = NodeService.get_monkey_by_guid(scan['monkey_guid']) - cross_segment_ip = ReportService.get_ip_in_src_and_not_in_dst(monkey['ip_addresses'], - source_subnet_range, - target_subnet_range) + cross_segment_ip = get_ip_in_src_and_not_in_dst(monkey['ip_addresses'], + source_subnet_range, + target_subnet_range) if cross_segment_ip is not None: cross_segment_issues.append( From 5c4797108ea2cb36540eacad651fdba98271233b Mon Sep 17 00:00:00 2001 From: vkuchinov Date: Sun, 25 Aug 2019 13:05:56 +0300 Subject: [PATCH 07/20] Rules update The rules are now set at this.rules array. While some of them have two conditions, i.e. Rule #2 shoud check if Conclusive is 0 and Inconclusive > 0, all rules has its own function (formula), which returns true or false. Eventually, I could shorten variable naming, for example, d_['Conclusive'] to something more prompt, but keeping this helps understand formulas even without referencing to upper comments. --- .../zerotrust/VennDiagram/index.js | 53 ++++++++++++++----- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js index de28fc012..6a0b41356 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js @@ -201,6 +201,34 @@ class VennDiagram extends React.Component{ }; + /* + + RULE #1: All scores have to be equal 0, except Unexecuted [U] which could be also a negative integer + sum(C, I, P, U) has to be <=0 + + RULE #2: Conclusive [C] has to be > 0, + sum(C) > 0 + + RULE #3: Inconclusive [I] has to be > 0 while Conclusive has to be 0, + sum(C, I) > 0 and C * I = 0, while C has to be 0 + + RULE #4: Positive [P] and Unexecuted have to be positive + sum(P, U) >= 2 and P * U = positive integer, while + if the P is bigger by 2 then negative U, first conditional + would be true. + + */ + + this.rules = [ + + { id: 'Rule #1', f: function(d_){ return d_['Conclusive'] + d_['Inconclusive'] + d_['Positive'] + d_['Unexecuted'] <= 0; } }, + { id: 'Rule #2', f: function(d_){ return d_['Conclusive'] > 0; } }, + { id: 'Rule #3', f: function(d_){ return d_['Conclusive'] === 0 && d_['Inconclusive'] > 0; } }, + { id: 'Rule #4', f: function(d_){ return d_['Positive'] + d_['Unexecuted'] >= 2 && d_['Positive'] * d_['Unexecuted'] > 0; } } + + ]; + + this._onScroll = this._onScroll.bind(this); } @@ -213,7 +241,7 @@ class VennDiagram extends React.Component{ } _onMouseMove(e) { - + let self = this; if(!this.toggle){ @@ -247,11 +275,12 @@ class VennDiagram extends React.Component{ } } + _onScroll(e){ - this.divElement.style.cursor = 'default'; - this.setState({target: e, tooltip: { target: null, bcolor: 'none', top: 0, left: 0, display: 'none', html: '' } }); - + this.divElement.style.cursor = 'default'; + this.setState({target: null, tooltip: { target: null, bcolor: 'none', top: 0, left: 0, display: 'none', html: '' } }); + } _onClick(e) { @@ -277,19 +306,17 @@ class VennDiagram extends React.Component{ let self = this; let data = []; const omit = (prop, { [prop]: _, ...rest }) => rest; - + this.props.pillarsGrades.forEach((d_, i_) => { - let params = omit('Unexpected', omit('pillar', d_)); + 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(d_); - let rule = 3; + let rule = null; - if(sum === 0){ rule = 0 } - else if(d_['Conclusive'] > 0){ rule = 1 } - else if(d_['Conclusive'] === 0 && d_['Inconclusive'] > 0) { rule = 2 } - + 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]) @@ -304,6 +331,8 @@ class VennDiagram extends React.Component{ setLayoutElement(rule_, key_, html_, d_){ + if(rule_ == null) { throw Error('The node scores are invalid'); } + if(key_ === 'Data'){ this.layout[key_].fontStyle = this.fontStyles[0]; } else {this.layout[key_].fontStyle = this.fontStyles[1]; } @@ -361,7 +390,7 @@ class VennDiagram extends React.Component{ return ( -
this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)} onClick={this._onClick.bind(this)} > +
this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)} onClick={this._onClick.bind(this)} > {nodes} From b9cb6551146f6981f77662295bac026532b70bf7 Mon Sep 17 00:00:00 2001 From: vkuchinov Date: Sun, 25 Aug 2019 17:32:21 +0300 Subject: [PATCH 08/20] Fixes Fixes for issues posted by Shay at VennDiagram component #412 [x] ResponsiveVennDiagram.js, VennDiagram.js [x] VennDiagram.css (rename) [x] ArcNode, CicularNode, Tooltip as .js and other minor issues --- .../components/pages/ZeroTrustReportPage.js | 203 ++++++++++-------- .../zerotrust/VennDiagram/ArcNode/index.js | 46 ---- .../zerotrust/venn-components/.DS_Store | Bin 0 -> 8196 bytes .../zerotrust/venn-components/ArcNode.js | 44 ++++ .../CircularNode.js} | 27 ++- .../venn-components/ResponsiveVennDiagram.js | 36 ++++ .../index.js => venn-components/Tooltip.js} | 0 .../utility.js => venn-components/Utility.js} | 0 .../VennDiagram.css} | 0 .../VennDiagram.js} | 184 +--------------- 10 files changed, 210 insertions(+), 330 deletions(-) mode change 100644 => 100755 monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js delete mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/ArcNode/index.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/.DS_Store create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js rename monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/{VennDiagram/CircularNode/index.js => venn-components/CircularNode.js} (61%) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ResponsiveVennDiagram.js rename monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/{VennDiagram/Tooltip/index.js => venn-components/Tooltip.js} (100%) rename monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/{VennDiagram/utility.js => venn-components/Utility.js} (100%) rename monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/{VennDiagram/index.css => venn-components/VennDiagram.css} (100%) rename monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/{VennDiagram/index.js => venn-components/VennDiagram.js} (57%) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js old mode 100644 new mode 100755 index 2fe43c42e..e80af2366 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -1,17 +1,64 @@ -import React, {Fragment} from 'react'; -import {Col, Grid, Row} from 'react-bootstrap'; +import React from 'react'; +import {Button, Col} from 'react-bootstrap'; import AuthComponent from '../AuthComponent'; import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader"; -import PillarsOverview from "../report-components/zerotrust/PillarOverview"; +import PillarGrades from "../report-components/zerotrust/PillarGrades"; +import PillarLabel from "../report-components/zerotrust/PillarLabel"; +import ResponsiveVennDiagram from "../report-components/zerotrust/venn-components/ResponsiveVennDiagram"; import FindingsTable from "../report-components/zerotrust/FindingsTable"; -import SinglePillarDirectivesStatus from "../report-components/zerotrust/SinglePillarDirectivesStatus"; -import MonkeysStillAliveWarning from "../report-components/common/MonkeysStillAliveWarning"; -import ReportLoader from "../report-components/common/ReportLoader"; -import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning"; -import SecurityIssuesGlance from "../report-components/common/SecurityIssuesGlance"; -import StatusesToPillarsSummary from "../report-components/zerotrust/StatusesToPillarsSummary"; -import PrintReportButton from "../report-components/common/PrintReportButton"; -import {extractExecutionStatusFromServerResponse} from "../report-components/common/ExecutionStatus"; +import {SinglePillarRecommendationsStatus} from "../report-components/zerotrust/SinglePillarRecommendationsStatus"; + +let mockup = [ + { + "Conclusive": 4, + "Inconclusive": 0, + "Positive": 1, + "Unexecuted": 2, + "pillar": "Data" + }, + { + "Conclusive": 0, + "Inconclusive": 5, + "Positive": 0, + "Unexecuted": 2, + "pillar": "People" + }, + { + "Conclusive": 0, + "Inconclusive": 0, + "Positive": 6, + "Unexecuted": 3, + "pillar": "Networks" + }, + { + "Conclusive": 2, + "Inconclusive": 0, + "Positive": 1, + "Unexecuted": 1, + "pillar": "Devices" + }, + { + "Conclusive": 0, + "Inconclusive": 0, + "Positive": 0, + "Unexecuted": 0, + "pillar": "Workloads" + }, + { + "Conclusive": 0, + "Inconclusive": 2, + "Positive": 0, + "Unexecuted": 0, + "pillar": "Visibility & Analytics" + }, + { + "Conclusive": 0, + "Inconclusive": 0, + "Positive": 0, + "Unexecuted": 0, + "pillar": "Automation & Orchestration" + } +]; class ZeroTrustReportPageComponent extends AuthComponent { @@ -20,30 +67,16 @@ class ZeroTrustReportPageComponent extends AuthComponent { this.state = { allMonkeysAreDead: false, - runStarted: true + runStarted: false }; } - componentDidMount() { - this.updateMonkeysRunning().then(res => this.getZeroTrustReportFromServer(res)); - } - - updateMonkeysRunning = () => { - return this.authFetch('/api') - .then(res => res.json()) - .then(res => { - this.setState(extractExecutionStatusFromServerResponse(res)); - return res; - }); - }; - render() { - let content; - if (this.state.runStarted) { - content = this.generateReportContent(); - } else { - content = ; - } + let res; + // Todo move to componentDidMount + this.getZeroTrustReportFromServer(res); + + const content = this.generateReportContent(); return ( @@ -59,75 +92,63 @@ class ZeroTrustReportPageComponent extends AuthComponent { let content; if (this.stillLoadingDataFromServer()) { - content = ; + content = "Still empty"; } else { - content =
- {this.generateOverviewSection()} - {this.generateDirectivesSection()} - {this.generateFindingsSection()} + const pillarsSection =
+

Pillars Overview

+ +
; + + const recommendationsSection =

Recommendations Status

+ { + this.state.recommendations.map((recommendation) => + + ) + } +
; + + const findingSection =

Findings

+
; + + content =
+ {pillarsSection} + {recommendationsSection} + {findingSection}
; } return ( - -
- {print();}} /> +
+
+

{content} +
+
{JSON.stringify(this.state.pillars, undefined, 2)}
+
+ +
{JSON.stringify(this.state.recommendations, undefined, 2)}
+
+
{JSON.stringify(this.state.findings, undefined, 2)}
-
- {print();}} /> -
- +
) } - generateFindingsSection() { - return (
-

Findings

- -
); - } - - generateDirectivesSection() { - return (
-

Directives

- { - Object.keys(this.state.directives).map((pillar) => - - ) - } -
); - } - - generateOverviewSection() { - return (
-

Overview

- - - - - - - - - - - - -
); - } - stillLoadingDataFromServer() { - return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.directives === "undefined"; + return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.recommendations === "undefined"; + } + + print() { + alert("unimplemented"); } getZeroTrustReportFromServer() { @@ -139,11 +160,11 @@ class ZeroTrustReportPageComponent extends AuthComponent { findings: res }); }); - this.authFetch('/api/report/zero_trust/directives') + this.authFetch('/api/report/zero_trust/recommendations') .then(res => res.json()) .then(res => { this.setState({ - directives: res + recommendations: res }); }); this.authFetch('/api/report/zero_trust/pillars') @@ -154,14 +175,6 @@ class ZeroTrustReportPageComponent extends AuthComponent { }); }); } - - anyIssuesFound() { - const severe = function(finding) { - return (finding.status === "Conclusive" || finding.status === "Inconclusive"); - }; - - return this.state.findings.some(severe); - } } export default ZeroTrustReportPageComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/ArcNode/index.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/ArcNode/index.js deleted file mode 100644 index b9861a55e..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/ArcNode/index.js +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types'; -import * as d3 from 'd3' - -class ArcNode extends React.Component{ - - render() { - - let {prefix, ref, index, data} = this.props;; - - let arc = d3.arc().innerRadius(data.inner).outerRadius(data.outer).startAngle(0).endAngle(Math.PI * 2.0); - - return ( - - - - - - {data.label} - - - - - ) - - } - -} - -ArcNode.propTypes = { - - prefix: PropTypes.string, - index: PropTypes.number, - data: PropTypes.object - -} - -export default ArcNode; \ No newline at end of file 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 0000000000000000000000000000000000000000..344923cf9c56b32713b678e0b337a23ff212fa20 GIT binary patch literal 8196 zcmeHMT}&KR6h3F6<<5+h0V@S;%NlBhbc-x#!M3E!0<~0{R(JU;2;F6-Fgnc8o!O<7 zVu(#NCdL{yG4bcc#wUsPMHB6dFUH2G@rR@uV|>sTAB{#6pFH>8ffOhY`k*$QOYS{) z?m1`fIp6(e?%n|aFqt>v0963MsB)=m)ZCzOJ+JFZBp4|siR2I9g9X#s8E-mkud@yZ zLIgqtLIgqtLIgqt{tpPyp3Mu}VBeS8uniFi5x6B0;O~bhRW1{OT#!<{I;asx0FwL& zAPV*A93Xt6flLH)K}z9D_mrmx3|uidVxV*1n4SEh z!W=LmW!Q!Ygb1uhfImJZkOr6j?Ca0(Zk9BkWT$P*^P8HkVWg;d)8-P!RHl}ykESNw znUtUL^IkHO8|GIhGX7*X=%-!RUg>AD{gyd1rf=KP%>_ zjcV$ML9rZj4L*yZzsnS%*%j5aVXo=SnbaDLiQ)PNO-*||5jxaXFwH=0G8g+?!x#|K1r7qDFkbproG}1t3@ZF~nWbNm{YWPFij z(jE)q4FrBqos!iLCy|yut_jkP-jKB2ht(th5oy0Il}z1wM`i7YJqYezjF0BJ0IcyI(!Xh_Cu)sXe52DEv{=L0<3H6ovf* z4qk#qxB?%;r^LN$@B{n=zrdduL53BAciV9X)?z(2;2w-&Ber52G4KGk<6(Rdd+;c+ zun+rj5QlJ>SZE3sK87CpSil)#JidKsh* + + + + {data.label} + + + + + ) + + } + +} + +ArcNode.propTypes = { + + data: PropTypes.object + +} + +export default ArcNode; \ No newline at end of file diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/CircularNode/index.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js similarity index 61% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/CircularNode/index.js rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js index a8caf5bb0..8d8df10bf 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/CircularNode/index.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js @@ -5,7 +5,7 @@ class CircularNode extends React.Component{ render() { - let {prefix, ref, index, data} = this.props; + let {prefix, index, data} = this.props; let tspans = data.label.split("|").map((d_, i_) =>{ @@ -21,23 +21,23 @@ class CircularNode extends React.Component{ }) let translate = 'translate(' + data.cx + ',' + data.cy + ')'; - + return ( - - - {tspans} - + /> + + {tspans} + ) @@ -48,7 +48,6 @@ class CircularNode extends React.Component{ CircularNode.propTypes = { - prefix: PropTypes.string, index: PropTypes.number, data: PropTypes.object 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..d20abf94a --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ResponsiveVennDiagram.js @@ -0,0 +1,36 @@ +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; + let childrenWidth = this.props.containerWidth, childrenHeight = this.props.containerHeight; + + if(childrenHeight === 0 || isNaN(childrenHeight)){ childrenHeight = childrenWidth; } + else{ childrenWidth = Math.min(childrenWidth, childrenHeight) } + + return ( + +
+ +
) + + } + +} + +ResponsiveVennDiagram.propTypes = { + + pillarsGrades: PropTypes.array + +} + +export default Dimensions()(ResponsiveVennDiagram); diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/Tooltip/index.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Tooltip.js similarity index 100% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/Tooltip/index.js rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Tooltip.js diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/utility.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Utility.js similarity index 100% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/utility.js rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Utility.js diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.css b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.css similarity index 100% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.css rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.css diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js similarity index 57% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js index 6a0b41356..0f154cdfe 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js @@ -1,169 +1,10 @@ import React from 'react' -import PropTypes from 'prop-types'; -import Dimensions from 'react-dimensions' +import PropTypes from 'prop-types' import Tooltip from './Tooltip' import CircularNode from './CircularNode' import ArcNode from './ArcNode' -import { TypographicUtilities } from './utility.js' -import './index.css' - -/* - -TODO LIST - -UPDATED [21.08.2019] - -[-] SVG > ViewBox 0 0 512 512, so it would be responsive and scalable -[-] Add resize listener to ResponsiveVennDiagram wrapper -[-] I have noticed that you have PillarGrades at ZeroTrustReportPage, so how I can fetch the data out of it? - -UPDATED [20.08.2019] - -[x] I've seen that lots of D3 responsive examples are using 'wrapper' around the main component to - get parent container dimensions. And lister for resize. - - So, here it's Responsive(VennDiagram) - - If it doesn't work, I have another alternative: - - import React, { useRef, useEffect, useState, useLayoutEffect } from 'react' - - const ResponsiveVennDiagram = props => { - - const minWidth = 256; - const targetRef = useRef(); - const [dimensions, setDimensions] = useState({ width: 0, heigth: 0 }); - let movement_timer = null; - const RESET_TIMEOUT = 100; - - const checkForDimensionsUpdate = () => { - - if (targetRef.current) { - - setDimensions({ - - width: Math.min(targetRef.current.offsetWidth, targetRef.current.offsetHeight), - height: targetRef.current.offsetHeight - - }); - - } - }; - - useLayoutEffect(() => { checkForDimensionsUpdate(); }, []); - - window.addEventListener('resize', () => { clearInterval(movement_timer); movement_timer = setTimeout(checkForDimensionsUpdate, RESET_TIMEOUT); }); - - return ( - -
- -
- - ); - - }; - - ResponsiveVennDiagram.propTypes = { - - pillarsGrades: PropTypes.array - - } - - export default ResponsiveVennDiagram; - - While your diagram laout is squared, the VennDiaagram gets the min(width, height) - -[x] Colours have been updated. - -[x] String prototypes have been moved to '.utilities.js' - -[x] I have use the prefix 'vennDiagram' for all children elements to prevent naming conflicts with other components - DOM objects. - -[x] I have used PropTypes already a year ago on ThreeJS/ReactJS stack project. - -[!] External callback is still on my list, can you make a mockup for external function which have to get pillar variable? - -[!] Z-indices sorting on mouseover - Would be n my 21.08.2019 TODO list. With D3.JS it's an easy task, however would try do - make these z-soring without D3 framework. - -UPDATED [20.08.2019] - -[!] By now, there are three input props for VennDiagram component: - data, width and height. - -[-] Since layout has to be hardcoded, it's driven by this.layout object. - There're many ways of setting circles/arc positions, now I'm using the - stright-forward one. - - Usually, I am put all hardcoded params to external JSON file, i.e config.json. - Let me know if it's a good idea. - -[-] Could rearange z-indecies for nodes on hover, so highlighted node would have highest z-index. -[-] If you want callback on click even, please provide additional info what are you expecting to pass - through this. - -[!] I don't used to make lots of comments in my code, but treying to name everything in a way, - so third person could instantly get its job and concept. - - If it is not enoough just let me know. - -[!] I have tried to avoid using D3 so much, especially its mouse events, cause it creates a bunch - of listeners for every children DOM elements. I have tried to use raw SVG objects. - The ArcNode is the only component where I've to call d3.arc constrictor. - -[!] There are lots of discussion over different blogs an forums that ReactJS and D3.JS have a DOM - issue conflict, [could find lots of articels at medium.org about that, for example, - https://medium.com/@tibotiber/react-d3-js-balancing-performance-developer-experience-4da35f912484]. - - Since the current component has only a few DOM elements, I don't thing we would have any troubles, - but please keep in mind that I could tweak current version with react-faux-dom. - - Actually, by now, I'm using D3 only for math, for arc path calculations. - -[!] Don't mind about code spacings, it's just for me, the final code would be clear out of them. - -[-] On click, an EXTERNAL callback should be called with the pillar name as a parameter. That is to enable us to expand the click functionality in future without editing the internal implementation of the component. - -[-] planned, [x] done, [!] see comments - -@author Vladimir V KUCHINOV -@email helloworld@vkuchinov.co.uk - -*/ - -const VENN_MIN_WIDTH = '300px'; - -class ResponsiveVennDiagram extends React.Component { - - constructor(props) { super(props); } - render() { - - const {options, pillarsGrades} = this.props; - let childrenWidth = this.props.containerWidth, childrenHeight = this.props.containerHeight; - - if(childrenHeight === 0 || childrenHeight === NaN){ childrenHeight = childrenWidth; } - else{ childrenWidth = Math.min(childrenWidth, childrenHeight) } - - return ( - -
- -
) - - } - -} - -ResponsiveVennDiagram.propTypes = { - - pillarsGrades: PropTypes.array - -} - -export default Dimensions()(ResponsiveVennDiagram); +import { TypographicUtilities } from './Utility.js' +import './VennDiagram.css' class VennDiagram extends React.Component{ @@ -292,15 +133,6 @@ class VennDiagram extends React.Component{ } - relativeCoords (e) { - - let bounds = e.target.getBoundingClientRect(); - var x = e.clientX - bounds.left; - var y = e.clientY - bounds.top; - return {x: x, y: y}; - - } - parseData(){ let self = this; @@ -318,7 +150,7 @@ class VennDiagram extends React.Component{ 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]) + data.push(this.layout[key]); }) @@ -361,7 +193,7 @@ class VennDiagram extends React.Component{ Date: Sun, 25 Aug 2019 18:07:49 +0300 Subject: [PATCH 09/20] Added a custom segmentation finding type --- .../cc/models/zero_trust/finding.py | 2 + .../models/zero_trust/segmentation_finding.py | 52 +++++++++++++++++++ .../zero_trust/test_segmentation_finding.py | 52 +++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py create mode 100644 monkey/monkey_island/cc/models/zero_trust/test_segmentation_finding.py diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py index 1869d6f18..5454ad9e1 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -32,6 +32,8 @@ class Finding(Document): test = StringField(required=True, choices=TESTS) status = StringField(required=True, choices=ORDERED_TEST_STATUSES) events = EmbeddedDocumentListField(document_type=Event) + # http://docs.mongoengine.org/guide/defining-documents.html#document-inheritance + meta = {'allow_inheritance': True} # LOGIC def get_test_explanation(self): diff --git a/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py b/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py new file mode 100644 index 000000000..428af72cb --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py @@ -0,0 +1,52 @@ +from mongoengine import StringField + +from common.data.zero_trust_consts import TEST_SEGMENTATION, STATUS_CONCLUSIVE, STATUS_POSITIVE +from monkey_island.cc.models.zero_trust.finding import Finding + + +def need_to_overwrite_status(saved_status, new_status): + return (saved_status == STATUS_POSITIVE) and (new_status == STATUS_CONCLUSIVE) + + +class SegmentationFinding(Finding): + """ + trying to add conclusive: + If the finding doesn't exist at all: create conclusive + else: + if positive, turn to conclusive + add event + + trying to add positive: + If the finding doesn't exist at all: create positive + else: add event + """ + first_subnet = StringField() + second_subnet = StringField() + + @staticmethod + def create_or_add_to_existing_finding(subnets, status, segmentation_event): + assert len(subnets) == 2 + + # Sort them so A -> B and B -> A segmentation findings will be the same one. + subnets.sort() + + existing_findings = SegmentationFinding.objects(first_subnet=subnets[0], second_subnet=subnets[1]) + + if len(existing_findings) == 0: + # No finding exists - create. + new_finding = SegmentationFinding( + first_subnet=subnets[0], + second_subnet=subnets[1], + test=TEST_SEGMENTATION, + status=status, + events=[segmentation_event] + ) + new_finding.save() + else: + # A finding exists (should be one). Add the event to it. + assert len(existing_findings) == 1 + existing_finding = existing_findings[0] + existing_finding.events.append(segmentation_event) + if need_to_overwrite_status(existing_finding.status, status): + existing_finding.status = status + existing_finding.save() diff --git a/monkey/monkey_island/cc/models/zero_trust/test_segmentation_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_segmentation_finding.py new file mode 100644 index 000000000..ad3ff9b97 --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/test_segmentation_finding.py @@ -0,0 +1,52 @@ +from common.data.zero_trust_consts import STATUS_CONCLUSIVE, EVENT_TYPE_MONKEY_NETWORK +from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.testing.IslandTestCase import IslandTestCase +from monkey_island.cc.models.zero_trust.segmentation_finding import SegmentationFinding + + +class TestSegmentationFinding(IslandTestCase): + def test_create_or_add_to_existing_finding(self): + self.fail_if_not_testing_env() + self.clean_finding_db() + + first_segment = "1.1.1.0/24" + second_segment = "2.2.2.0-2.2.2.254" + third_segment = "3.3.3.3" + event = Event.create_event("bla", "bla", EVENT_TYPE_MONKEY_NETWORK) + + SegmentationFinding.create_or_add_to_existing_finding( + subnets=[first_segment, second_segment], + status=STATUS_CONCLUSIVE, + segmentation_event=event + ) + + self.assertEquals(len(SegmentationFinding.objects()), 1) + self.assertEquals(len(SegmentationFinding.objects()[0].events), 1) + + SegmentationFinding.create_or_add_to_existing_finding( + # !!! REVERSE ORDER + subnets=[second_segment, first_segment], + status=STATUS_CONCLUSIVE, + segmentation_event=event + ) + + self.assertEquals(len(SegmentationFinding.objects()), 1) + self.assertEquals(len(SegmentationFinding.objects()[0].events), 2) + + SegmentationFinding.create_or_add_to_existing_finding( + # !!! REVERSE ORDER + subnets=[first_segment, third_segment], + status=STATUS_CONCLUSIVE, + segmentation_event=event + ) + + self.assertEquals(len(SegmentationFinding.objects()), 2) + + SegmentationFinding.create_or_add_to_existing_finding( + # !!! REVERSE ORDER + subnets=[second_segment, third_segment], + status=STATUS_CONCLUSIVE, + segmentation_event=event + ) + + self.assertEquals(len(SegmentationFinding.objects()), 3) From 470806f3bc51e90e0381fcf2d7d31c9015a3618b Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 25 Aug 2019 18:08:21 +0300 Subject: [PATCH 10/20] Added segmentation violation test --- monkey/common/network/segmentation_utils.py | 9 ++- .../cc/services/telemetry/processing/scan.py | 2 + .../zero_trust_tests/segmentation.py | 68 +++++++++++++++++++ 3 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py diff --git a/monkey/common/network/segmentation_utils.py b/monkey/common/network/segmentation_utils.py index 68122f398..97adbd203 100644 --- a/monkey/common/network/segmentation_utils.py +++ b/monkey/common/network/segmentation_utils.py @@ -10,9 +10,12 @@ def get_ip_in_src_and_not_in_dst(ip_addresses, source_subnet, target_subnet): :param target_subnet: Subnet we want an IP to be in. :return: The cross segment IP if in source but not in target, else None. """ - for ip_address in ip_addresses: - if target_subnet.is_in_range(ip_address): - return None + if get_ip_if_in_subnet(ip_addresses, target_subnet) is not None: + return None + return get_ip_if_in_subnet(ip_addresses, source_subnet) + + +def get_ip_if_in_subnet(ip_addresses, source_subnet): for ip_address in ip_addresses: if source_subnet.is_in_range(ip_address): return ip_address diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scan.py b/monkey/monkey_island/cc/services/telemetry/processing/scan.py index 3b532ff22..8ae386388 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/scan.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/scan.py @@ -3,11 +3,13 @@ import copy from monkey_island.cc.database import mongo from monkey_island.cc.services.telemetry.processing.utils import get_edge_by_scan_or_exploit_telemetry from monkey_island.cc.services.telemetry.zero_trust_tests.data_endpoints import test_open_data_endpoints +from monkey_island.cc.services.telemetry.zero_trust_tests.segmentation import test_segmentation_violation def process_scan_telemetry(telemetry_json): update_edges_and_nodes_based_on_scan_telemetry(telemetry_json) test_open_data_endpoints(telemetry_json) + test_segmentation_violation(telemetry_json) def update_edges_and_nodes_based_on_scan_telemetry(telemetry_json): diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py new file mode 100644 index 000000000..ca308aafd --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py @@ -0,0 +1,68 @@ +import itertools +from six import text_type + +from common.data.zero_trust_consts import STATUS_CONCLUSIVE, EVENT_TYPE_MONKEY_NETWORK +from common.network.network_range import NetworkRange +from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst, get_ip_if_in_subnet +from monkey_island.cc.models import Monkey +from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.models.zero_trust.segmentation_finding import SegmentationFinding +from monkey_island.cc.services.configuration.utils import get_config_network_segments_as_subnet_groups + +SEGMENTATION_VIOLATION_EVENT_TEXT = \ + "Segmentation violation! Monkey on '{hostname}', with the {source_ip} IP address (in segment {source_seg}) " \ + "managed to communicate cross segment to {target_ip} (in segment {target_seg})." + + +def is_segmentation_violation(current_monkey, target_ip, source_subnet, target_subnet): + if source_subnet == target_subnet: + return False + source_subnet_range = NetworkRange.get_range_obj(source_subnet) + target_subnet_range = NetworkRange.get_range_obj(target_subnet) + + if target_subnet_range.is_in_range(text_type(target_ip)): + cross_segment_ip = get_ip_in_src_and_not_in_dst( + current_monkey.ip_addresses, + source_subnet_range, + target_subnet_range) + + return cross_segment_ip is not None + + +def test_segmentation_violation(telemetry_json): + """ + + :param telemetry_json: A SCAN telemetry sent from a Monkey. + """ + # TODO - lower code duplication between this and report.py. + # TODO - single machine + current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) + target_ip = telemetry_json['data']['machine']['ip_addr'] + subnet_groups = get_config_network_segments_as_subnet_groups() + for subnet_group in subnet_groups: + subnet_pairs = itertools.product(subnet_group, subnet_group) + for subnet_pair in subnet_pairs: + source_subnet = subnet_pair[0] + target_subnet = subnet_pair[1] + if is_segmentation_violation(current_monkey, target_ip, source_subnet, target_subnet): + event = get_segmentation_violation_event(current_monkey, source_subnet, target_ip, target_subnet) + SegmentationFinding.create_or_add_to_existing_finding( + subnets=[source_subnet, target_subnet], + status=STATUS_CONCLUSIVE, + segmentation_event=event + ) + + +def get_segmentation_violation_event(current_monkey, source_subnet, target_ip, target_subnet): + return Event.create_event( + title="Segmentation event", + message=SEGMENTATION_VIOLATION_EVENT_TEXT.format( + hostname=current_monkey.hostname, + source_ip=get_ip_if_in_subnet(current_monkey.ip_addresses, NetworkRange.get_range_obj(source_subnet)), + source_seg=source_subnet, + target_ip=target_ip, + target_seg=target_subnet + ), + event_type=EVENT_TYPE_MONKEY_NETWORK + ) + From 223adb0f3327938d3f99bf7028232e1bd230355c Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 25 Aug 2019 18:14:15 +0300 Subject: [PATCH 11/20] Added state function, WIP --- .../monkey_island/cc/services/telemetry/processing/state.py | 5 +++++ .../cc/services/telemetry/zero_trust_tests/segmentation.py | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/state.py b/monkey/monkey_island/cc/services/telemetry/processing/state.py index ac8e32939..46176c9b9 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/state.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/state.py @@ -1,4 +1,6 @@ from monkey_island.cc.services.node import NodeService +from monkey_island.cc.services.telemetry.zero_trust_tests.segmentation import \ + test_positive_findings_for_unreached_segments def process_state_telemetry(telemetry_json): @@ -8,3 +10,6 @@ def process_state_telemetry(telemetry_json): NodeService.set_monkey_dead(monkey, True) else: NodeService.set_monkey_dead(monkey, False) + + if telemetry_json['data']['done']: + test_positive_findings_for_unreached_segments(telemetry_json) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py index ca308aafd..647db59fc 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py @@ -66,3 +66,7 @@ def get_segmentation_violation_event(current_monkey, source_subnet, target_ip, t event_type=EVENT_TYPE_MONKEY_NETWORK ) + +def test_positive_findings_for_unreached_segments(telemetry_json): + current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) + subnet_groups = get_config_network_segments_as_subnet_groups() From 0a044e2295fcdc09319c45b2cb4d978ac8423c30 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 25 Aug 2019 18:30:56 +0300 Subject: [PATCH 12/20] Reverted changed to ZT report page and fixed rule bug in diagram + whitespace. --- .../components/pages/ZeroTrustReportPage.js | 201 ++++++++---------- .../zerotrust/PillarOverview.js | 2 +- .../zerotrust/venn-components/VennDiagram.js | 152 +++++-------- 3 files changed, 152 insertions(+), 203 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index e80af2366..2fe43c42e 100755 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -1,64 +1,17 @@ -import React from 'react'; -import {Button, Col} from 'react-bootstrap'; +import React, {Fragment} from 'react'; +import {Col, Grid, Row} from 'react-bootstrap'; import AuthComponent from '../AuthComponent'; import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader"; -import PillarGrades from "../report-components/zerotrust/PillarGrades"; -import PillarLabel from "../report-components/zerotrust/PillarLabel"; -import ResponsiveVennDiagram from "../report-components/zerotrust/venn-components/ResponsiveVennDiagram"; +import PillarsOverview from "../report-components/zerotrust/PillarOverview"; import FindingsTable from "../report-components/zerotrust/FindingsTable"; -import {SinglePillarRecommendationsStatus} from "../report-components/zerotrust/SinglePillarRecommendationsStatus"; - -let mockup = [ - { - "Conclusive": 4, - "Inconclusive": 0, - "Positive": 1, - "Unexecuted": 2, - "pillar": "Data" - }, - { - "Conclusive": 0, - "Inconclusive": 5, - "Positive": 0, - "Unexecuted": 2, - "pillar": "People" - }, - { - "Conclusive": 0, - "Inconclusive": 0, - "Positive": 6, - "Unexecuted": 3, - "pillar": "Networks" - }, - { - "Conclusive": 2, - "Inconclusive": 0, - "Positive": 1, - "Unexecuted": 1, - "pillar": "Devices" - }, - { - "Conclusive": 0, - "Inconclusive": 0, - "Positive": 0, - "Unexecuted": 0, - "pillar": "Workloads" - }, - { - "Conclusive": 0, - "Inconclusive": 2, - "Positive": 0, - "Unexecuted": 0, - "pillar": "Visibility & Analytics" - }, - { - "Conclusive": 0, - "Inconclusive": 0, - "Positive": 0, - "Unexecuted": 0, - "pillar": "Automation & Orchestration" - } -]; +import SinglePillarDirectivesStatus from "../report-components/zerotrust/SinglePillarDirectivesStatus"; +import MonkeysStillAliveWarning from "../report-components/common/MonkeysStillAliveWarning"; +import ReportLoader from "../report-components/common/ReportLoader"; +import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning"; +import SecurityIssuesGlance from "../report-components/common/SecurityIssuesGlance"; +import StatusesToPillarsSummary from "../report-components/zerotrust/StatusesToPillarsSummary"; +import PrintReportButton from "../report-components/common/PrintReportButton"; +import {extractExecutionStatusFromServerResponse} from "../report-components/common/ExecutionStatus"; class ZeroTrustReportPageComponent extends AuthComponent { @@ -67,16 +20,30 @@ class ZeroTrustReportPageComponent extends AuthComponent { this.state = { allMonkeysAreDead: false, - runStarted: false + runStarted: true }; } - render() { - let res; - // Todo move to componentDidMount - this.getZeroTrustReportFromServer(res); + componentDidMount() { + this.updateMonkeysRunning().then(res => this.getZeroTrustReportFromServer(res)); + } - const content = this.generateReportContent(); + updateMonkeysRunning = () => { + return this.authFetch('/api') + .then(res => res.json()) + .then(res => { + this.setState(extractExecutionStatusFromServerResponse(res)); + return res; + }); + }; + + render() { + let content; + if (this.state.runStarted) { + content = this.generateReportContent(); + } else { + content = ; + } return ( @@ -92,63 +59,75 @@ class ZeroTrustReportPageComponent extends AuthComponent { let content; if (this.stillLoadingDataFromServer()) { - content = "Still empty"; + content = ; } else { - const pillarsSection =
-

Pillars Overview

- -
; - - const recommendationsSection =

Recommendations Status

- { - this.state.recommendations.map((recommendation) => - - ) - } -
; - - const findingSection =

Findings

-
; - - content =
- {pillarsSection} - {recommendationsSection} - {findingSection} + content =
+ {this.generateOverviewSection()} + {this.generateDirectivesSection()} + {this.generateFindingsSection()}
; } return ( -
-
- + +
+ {print();}} />

{content} -
-
{JSON.stringify(this.state.pillars, undefined, 2)}
-
- -
{JSON.stringify(this.state.recommendations, undefined, 2)}
-
-
{JSON.stringify(this.state.findings, undefined, 2)}
-
+
+ {print();}} /> +
+ ) } - stillLoadingDataFromServer() { - return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.recommendations === "undefined"; + generateFindingsSection() { + return (
+

Findings

+ +
); } - print() { - alert("unimplemented"); + generateDirectivesSection() { + return (
+

Directives

+ { + Object.keys(this.state.directives).map((pillar) => + + ) + } +
); + } + + generateOverviewSection() { + return (
+

Overview

+ + + + + + + + + + + + +
); + } + + stillLoadingDataFromServer() { + return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.directives === "undefined"; } getZeroTrustReportFromServer() { @@ -160,11 +139,11 @@ class ZeroTrustReportPageComponent extends AuthComponent { findings: res }); }); - this.authFetch('/api/report/zero_trust/recommendations') + this.authFetch('/api/report/zero_trust/directives') .then(res => res.json()) .then(res => { this.setState({ - recommendations: res + directives: res }); }); this.authFetch('/api/report/zero_trust/pillars') @@ -175,6 +154,14 @@ class ZeroTrustReportPageComponent extends AuthComponent { }); }); } + + anyIssuesFound() { + const severe = function(finding) { + return (finding.status === "Conclusive" || finding.status === "Inconclusive"); + }; + + return this.state.findings.some(severe); + } } export default ZeroTrustReportPageComponent; 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 index f772e0652..824885cad 100644 --- 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 @@ -1,7 +1,7 @@ import React, {Component} from "react"; import PillarLabel from "./PillarLabel"; import * as PropTypes from "prop-types"; -import ResponsiveVennDiagram from "./VennDiagram"; +import ResponsiveVennDiagram from "./venn-components/ResponsiveVennDiagram"; const columns = [ { 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 index 0f154cdfe..1e0be3b16 100644 --- 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 @@ -7,31 +7,29 @@ import { TypographicUtilities } from './Utility.js' import './VennDiagram.css' class VennDiagram extends React.Component{ - - constructor(props_){ - - super(props_); - - this.state = { tooltip: { top: 0, left: 0, display: 'none', html: '' } } - - this.width = this.height = 512, this.zOrder = []; - + constructor(props_){ + super(props_); + + this.state = { tooltip: { top: 0, left: 0, display: 'none', html: '' } }; + + this.width = this.height = 512; + this.zOrder = []; + this.colors = ['#777777', '#D9534F', '#F0AD4E', '#5CB85C']; this.prefix = 'vennDiagram'; this.suffices = ['', '|tests are|conclusive', '|tests were|inconclusive', '|tests|performed']; this.fontStyles = [{size: Math.max(9, this.width / 32), color: 'white'}, {size: Math.max(6, this.width / 52), color: 'black'}]; this.offset = this.width / 16; - - this.thirdWidth = this.width / 3; + + this.thirdWidth = this.width / 3; this.sixthWidth = this.width / 6; - this.width2By7 = 2 * this.width / 7 + this.width2By7 = 2 * this.width / 7; this.width1By11 = this.width / 11; this.width1By28 = this.width / 28; - + this.toggle = false; - + this.layout = { - Data: { cx: 0, cy: 0, r: this.thirdWidth - this.offset * 2, offset: {x: 0, y: 0} }, People: { cx: -this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: this.width1By11, y: 0} }, Networks: { cx: this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: -this.width1By11, y: 0} }, @@ -39,15 +37,14 @@ class VennDiagram extends React.Component{ Workloads : { cx: 0, cy: -this.width2By7, r: this.sixthWidth, offset: {x: 0, y: this.width1By11} }, VisibilityAndAnalytics : { inner: this.thirdWidth - this.width1By28, outer: this.thirdWidth }, AutomationAndOrchestration: { inner: this.thirdWidth - this.width1By28 * 2, outer: this.thirdWidth - this.width1By28 } - }; - + /* RULE #1: All scores have to be equal 0, except Unexecuted [U] which could be also a negative integer - sum(C, I, P, U) has to be <=0 + sum(C, I, P) has to be <=0 - RULE #2: Conclusive [C] has to be > 0, + RULE #2: Conclusive [C] has to be > 0, sum(C) > 0 RULE #3: Inconclusive [I] has to be > 0 while Conclusive has to be 0, @@ -62,185 +59,150 @@ class VennDiagram extends React.Component{ this.rules = [ - { id: 'Rule #1', f: function(d_){ return d_['Conclusive'] + d_['Inconclusive'] + d_['Positive'] + d_['Unexecuted'] <= 0; } }, + { id: 'Rule #1', f: function(d_){ return d_['Conclusive'] + d_['Inconclusive'] + d_['Positive'] === 0; } }, { id: 'Rule #2', f: function(d_){ return d_['Conclusive'] > 0; } }, { id: 'Rule #3', f: function(d_){ return d_['Conclusive'] === 0 && d_['Inconclusive'] > 0; } }, { id: 'Rule #4', f: function(d_){ return d_['Positive'] + d_['Unexecuted'] >= 2 && d_['Positive'] * d_['Unexecuted'] > 0; } } ]; - - - this._onScroll = this._onScroll.bind(this); - - } - - componentDidMount() { + this._onScroll = this._onScroll.bind(this); + } + + componentDidMount() { this.parseData(); window.addEventListener('scroll', this._onScroll); - } _onMouseMove(e) { - let self = this; - + if(!this.toggle){ - let hidden = 'none'; let html = ''; let bcolor = '#DEDEDE'; - + document.querySelectorAll('circle, path').forEach((d_, i_) => { d_.setAttribute('opacity', 0.8)}); - - if(e.target.id.includes('Node')) { + + if(e.target.id.includes('Node')) { html = e.target.dataset.tooltip; this.divElement.style.cursor = 'pointer'; - hidden = 'block'; e.target.setAttribute('opacity', 0.95); + hidden = 'block'; e.target.setAttribute('opacity', 0.95); bcolor = e.target.getAttribute('fill'); - - //set highest z-index + + // Set highest z-index e.target.parentNode.parentNode.appendChild(e.target.parentNode); }else{ this.divElement.style.cursor = 'default'; - - //return z indices to default + + // 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); }) } this.setState({target: e, tooltip: { target: e.target, bcolor: bcolor, top: e.clientY + 8, left: e.clientX + 8, display: hidden, html: html } }); - } } _onScroll(e){ - this.divElement.style.cursor = 'default'; this.setState({target: null, tooltip: { target: null, bcolor: 'none', top: 0, left: 0, display: 'none', html: '' } }); - } _onClick(e) { + this.toggle = this.state.tooltip.target === e.target; - if(this.state.tooltip.target === e.target) { this.toggle = true; } else { this.toggle = false; } - //variable to external callback //e.target.parentNode.id) - } 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 sum = Object.keys(params).reduce((sum_, key_) => sum_ + parseFloat(params[key_]||0), 0); let key = TypographicUtilities.removeAmpersand(d_.pillar); let html = self.buildTooltipHtmlContent(d_); 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({ data: data }); - this.render(); + }); + + this.setState({ data: data }); + this.render(); } - + buildTooltipHtmlContent(object_){ return Object.keys(object_).reduce((out_, key_) => out_ + TypographicUtilities.setTitle(key_) + ': ' + object_[key_] + '\n', ''); } setLayoutElement(rule_, key_, html_, d_){ - + console.log(rule_, key_, html_, d_); if(rule_ == null) { throw Error('The node scores are invalid'); } - + if(key_ === 'Data'){ this.layout[key_].fontStyle = this.fontStyles[0]; } else {this.layout[key_].fontStyle = this.fontStyles[1]; } - - this.layout[key_].hex = this.colors[rule_]; - this.layout[key_].label = d_.pillar + this.suffices[rule_]; + + this.layout[key_].hex = this.colors[rule_]; + this.layout[key_].label = d_.pillar + this.suffices[rule_]; 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) + else { + // equivalent to center translate (width/2, height/2) let viewPortParameters = (-this.width / 2) + ' ' + (-this.height / 2) + ' ' + this.width + ' ' + this.height; let translate = 'translate(' + this.width /2 + ',' + this.height/2 + ')'; - let nodes = Object.values(this.layout).map((d_, i_) =>{ - - if(d_.hasOwnProperty('cx')){ - + let nodes = Object.values(this.layout).map((d_, i_) => { + if(d_.hasOwnProperty('cx')) { return ( - ); - - }else{ - + } else { d_.label = TypographicUtilities.removeBrokenBar(d_.label); - + return ( - ); - } - }); - - return ( + return (
this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)} onClick={this._onClick.bind(this)} > - + {nodes} - - -
- + + +
) - } - } - } VennDiagram.propTypes = { - pillarsGrades: PropTypes.array - -} +}; -export default VennDiagram; \ No newline at end of file +export default VennDiagram; From 79fabb1ac1e3018dda1942913f5d582bfa8b646d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 25 Aug 2019 18:49:57 +0300 Subject: [PATCH 13/20] Whitespace fixes --- .../zerotrust/venn-components/VennDiagram.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) 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 index 1e0be3b16..f238b7f63 100644 --- 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 @@ -85,7 +85,6 @@ class VennDiagram extends React.Component{ document.querySelectorAll('circle, path').forEach((d_, i_) => { d_.setAttribute('opacity', 0.8)}); if(e.target.id.includes('Node')) { - html = e.target.dataset.tooltip; this.divElement.style.cursor = 'pointer'; hidden = 'block'; e.target.setAttribute('opacity', 0.95); @@ -93,14 +92,11 @@ class VennDiagram extends React.Component{ // Set highest z-index e.target.parentNode.parentNode.appendChild(e.target.parentNode); - - }else{ - + } else { this.divElement.style.cursor = 'default'; // 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); }) - } this.setState({target: e, tooltip: { target: e.target, bcolor: bcolor, top: e.clientY + 8, left: e.clientX + 8, display: hidden, html: html } }); @@ -145,8 +141,7 @@ class VennDiagram extends React.Component{ buildTooltipHtmlContent(object_){ return Object.keys(object_).reduce((out_, key_) => out_ + TypographicUtilities.setTitle(key_) + ': ' + object_[key_] + '\n', ''); } - setLayoutElement(rule_, key_, html_, d_){ - console.log(rule_, key_, html_, d_); + setLayoutElement(rule_, key_, html_, d_) { if(rule_ == null) { throw Error('The node scores are invalid'); } if(key_ === 'Data'){ this.layout[key_].fontStyle = this.fontStyles[0]; } From f0d43e033e7eff3933036daa76e3e1f21219af4b Mon Sep 17 00:00:00 2001 From: vkuchinov Date: Mon, 26 Aug 2019 08:12:51 +0300 Subject: [PATCH 14/20] Update ArcNode.js HAve returned missing labels --- .../report-components/zerotrust/venn-components/ArcNode.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 index 1306ecc54..506ec0b7d 100644 --- 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 @@ -9,13 +9,14 @@ class ArcNode extends React.Component{ 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.label} From 9367b6ce8e2357857cbabfaccd785c873324a05b Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 26 Aug 2019 09:20:59 +0300 Subject: [PATCH 15/20] Fixed arcnode text bug + formatting --- .../zerotrust/venn-components/ArcNode.js | 58 ++- .../zerotrust/venn-components/CircularNode.js | 81 ++-- .../venn-components/ResponsiveVennDiagram.js | 43 +- .../zerotrust/venn-components/Tooltip.js | 74 ++-- .../zerotrust/venn-components/Utility.js | 17 +- .../zerotrust/venn-components/VennDiagram.css | 17 +- .../zerotrust/venn-components/VennDiagram.js | 390 ++++++++++-------- 7 files changed, 358 insertions(+), 322 deletions(-) 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 index 1306ecc54..95d86db2f 100644 --- 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 @@ -2,43 +2,37 @@ import React from 'react' import PropTypes from 'prop-types'; import * as d3 from 'd3' -class ArcNode extends React.Component{ - - render() { +class ArcNode extends React.Component { + render() { + let {prefix, index, data} = this.props; - 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; - let arc = d3.arc().innerRadius(data.inner).outerRadius(data.outer).startAngle(0).endAngle(Math.PI * 2.0); - - return ( - - - + - - - {data.label} - - - + id={prefix + 'Node_' + index} + className={'arcNode'} + data-tooltip={data.tooltip} + d={arc()} + fill={data.hex} - ) - - } - + /> + + + {data.label} + + + + ); + } } ArcNode.propTypes = { + data: PropTypes.object +}; - data: PropTypes.object - -} - -export default ArcNode; \ No newline at end of file +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 index 8d8df10bf..43ef529f3 100644 --- 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 @@ -1,56 +1,43 @@ import React from 'react' import PropTypes from 'prop-types'; -class CircularNode extends React.Component{ - - render() { +class CircularNode extends React.Component { + render() { + let {prefix, index, data} = this.props; - let {prefix, index, data} = this.props; + let tspans = data.label.split("|").map((d_, i_) => { + let halfTextHeight = data.fontStyle.size * data.label.split("|").length / 2; + let key = 'vennDiagramCircularNode' + index + '_Tspan' + i_; - let tspans = data.label.split("|").map((d_, i_) =>{ - - let halfTextHeight = data.fontStyle.size * data.label.split("|").length / 2; - let key = 'vennDiagramCircularNode' + index + '_Tspan' + i_; - - return ( - - {d_} - - ) - - }) - - let translate = 'translate(' + data.cx + ',' + data.cy + ')'; - - return ( - - - - - {tspans} - - - - ) - - } - + return ( + {d_} + ); + }); + + let translate = 'translate(' + data.cx + ',' + data.cy + ')'; + + return ( + + + + {tspans} + + + ); + } } CircularNode.propTypes = { - - index: PropTypes.number, - data: PropTypes.object - -} + index: PropTypes.number, + data: PropTypes.object +}; -export default CircularNode; \ No newline at end of file +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 index d20abf94a..8cf096d69 100644 --- 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 @@ -6,31 +6,30 @@ import VennDiagram from './VennDiagram' const VENN_MIN_WIDTH = '300px'; class ResponsiveVennDiagram extends React.Component { - - constructor(props) { super(props); } - - render() { - - const {pillarsGrades} = this.props; - let childrenWidth = this.props.containerWidth, childrenHeight = this.props.containerHeight; - - if(childrenHeight === 0 || isNaN(childrenHeight)){ childrenHeight = childrenWidth; } - else{ childrenWidth = Math.min(childrenWidth, childrenHeight) } - - return ( - -
- -
) - + constructor(props) { + super(props); + } + + render() { + const {pillarsGrades} = this.props; + let childrenWidth = this.props.containerWidth, childrenHeight = this.props.containerHeight; + + if (childrenHeight === 0 || isNaN(childrenHeight)) { + childrenHeight = childrenWidth; + } else { + childrenWidth = Math.min(childrenWidth, childrenHeight) + } + + return ( +
+ +
+ ); } - } ResponsiveVennDiagram.propTypes = { - - pillarsGrades: PropTypes.array - -} + pillarsGrades: PropTypes.array +}; export default Dimensions()(ResponsiveVennDiagram); diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Tooltip.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Tooltip.js index 579b36a5b..e103af3c3 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Tooltip.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Tooltip.js @@ -1,49 +1,43 @@ import React from 'react' import PropTypes from 'prop-types'; -class Tooltip extends React.Component{ +class Tooltip extends React.Component { - render() { - - const {prefix, bcolor, top, left, display, html } = this.props; + render() { + const {prefix, bcolor, top, left, display, html} = this.props; - const style = { - - backgroundColor: bcolor, - border : '1px solid #FFFFFF', - borderRadius: '2px', - fontSize: 10, - padding: 8, - display, - opacity: 0.9, - position: 'fixed', - top, - left, - pointerEvents: 'none' - - }; + const style = { + backgroundColor: bcolor, + border: '1px solid #FFFFFF', + borderRadius: '2px', + fontSize: 10, + padding: 8, + display, + opacity: 0.9, + position: 'fixed', + top, + left, + pointerEvents: 'none' + }; - return ( - -
- {html.split('\n').map((i_, key_) => { return
{i_}
; })} -
- - ); - - } - + return ( + +
+ {html.split('\n').map((i_, key_) => { + return
{i_}
; + })} +
+ ); + } } Tooltip.propTypes = { - - prefix: PropTypes.string, - bcolor: PropTypes.string, - top: PropTypes.number, - left: PropTypes.number, - display: PropTypes.string, - html: PropTypes.string - -} - -export default Tooltip; \ No newline at end of file + prefix: PropTypes.string, + bcolor: PropTypes.string, + top: PropTypes.number, + left: PropTypes.number, + display: PropTypes.string, + html: PropTypes.string +}; + +export default Tooltip; 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 index c9816c721..230e277f6 100644 --- 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 @@ -1,8 +1,15 @@ export class TypographicUtilities { - - static removeAmpersand(string_) { return string_.replace(' & ', 'And'); } - static removeBrokenBar(string_) { return string_.replace(/\|/g, ' '); } - static setTitle(string_) { return string_.charAt(0).toUpperCase() + string_.substr(1).toLowerCase(); } + + static removeAmpersand(string_) { + return string_.replace(' & ', 'And'); + } + + static removeBrokenBar(string_) { + return string_.replace(/\|/g, ' '); + } + + static setTitle(string_) { + return string_.charAt(0).toUpperCase() + string_.substr(1).toLowerCase(); + } } - \ No newline at end of file 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 index 6c7cd778e..dd4883125 100644 --- 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 @@ -1,14 +1,17 @@ @import url('https://fonts.googleapis.com/css?family=Noto+Sans&display=swap'); -body { margin: 0; font-family: "Noto Sans", sans-serif; } +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 */ + -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 index f238b7f63..2b36266c0 100644 --- 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 @@ -3,201 +3,253 @@ import PropTypes from 'prop-types' import Tooltip from './Tooltip' import CircularNode from './CircularNode' import ArcNode from './ArcNode' -import { TypographicUtilities } from './Utility.js' +import {TypographicUtilities} from './Utility.js' import './VennDiagram.css' -class VennDiagram extends React.Component{ - constructor(props_){ - super(props_); +class VennDiagram extends React.Component { + constructor(props_) { + super(props_); - this.state = { tooltip: { top: 0, left: 0, display: 'none', html: '' } }; + this.state = {tooltip: {top: 0, left: 0, display: 'none', html: ''}}; - this.width = this.height = 512; - this.zOrder = []; + this.width = this.height = 512; + this.zOrder = []; - this.colors = ['#777777', '#D9534F', '#F0AD4E', '#5CB85C']; - this.prefix = 'vennDiagram'; - this.suffices = ['', '|tests are|conclusive', '|tests were|inconclusive', '|tests|performed']; - this.fontStyles = [{size: Math.max(9, this.width / 32), color: 'white'}, {size: Math.max(6, this.width / 52), color: 'black'}]; - this.offset = this.width / 16; + this.colors = ['#777777', '#D9534F', '#F0AD4E', '#5CB85C']; + this.prefix = 'vennDiagram'; + this.suffices = ['', '|tests are|conclusive', '|tests were|inconclusive', '|tests|performed']; + this.fontStyles = [{size: Math.max(9, this.width / 32), color: 'white'}, { + size: Math.max(6, this.width / 52), + color: 'black' + }]; + this.offset = this.width / 16; - this.thirdWidth = this.width / 3; - this.sixthWidth = this.width / 6; - this.width2By7 = 2 * this.width / 7; - this.width1By11 = this.width / 11; - this.width1By28 = this.width / 28; + this.thirdWidth = this.width / 3; + this.sixthWidth = this.width / 6; + this.width2By7 = 2 * this.width / 7; + this.width1By11 = this.width / 11; + this.width1By28 = this.width / 28; - this.toggle = false; + this.toggle = false; - this.layout = { - Data: { cx: 0, cy: 0, r: this.thirdWidth - this.offset * 2, offset: {x: 0, y: 0} }, - People: { cx: -this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: this.width1By11, y: 0} }, - Networks: { cx: this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: -this.width1By11, y: 0} }, - Devices: { cx: 0, cy: this.width2By7, r: this.sixthWidth, offset: {x: 0, y: -this.width1By11} }, - Workloads : { cx: 0, cy: -this.width2By7, r: this.sixthWidth, offset: {x: 0, y: this.width1By11} }, - VisibilityAndAnalytics : { inner: this.thirdWidth - this.width1By28, outer: this.thirdWidth }, - AutomationAndOrchestration: { inner: this.thirdWidth - this.width1By28 * 2, outer: this.thirdWidth - this.width1By28 } - }; + this.layout = { + Data: {cx: 0, cy: 0, r: this.thirdWidth - this.offset * 2, offset: {x: 0, y: 0}}, + People: {cx: -this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: this.width1By11, y: 0}}, + Networks: {cx: this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: -this.width1By11, y: 0}}, + Devices: {cx: 0, cy: this.width2By7, r: this.sixthWidth, offset: {x: 0, y: -this.width1By11}}, + Workloads: {cx: 0, cy: -this.width2By7, r: this.sixthWidth, offset: {x: 0, y: this.width1By11}}, + VisibilityAndAnalytics: {inner: this.thirdWidth - this.width1By28, outer: this.thirdWidth}, + AutomationAndOrchestration: { + inner: this.thirdWidth - this.width1By28 * 2, + outer: this.thirdWidth - this.width1By28 + } + }; - /* + /* - 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 #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: Conclusive [C] has to be > 0, - sum(C) > 0 + RULE #2: Conclusive [C] has to be > 0, + sum(C) > 0 - RULE #3: Inconclusive [I] has to be > 0 while Conclusive has to be 0, - sum(C, I) > 0 and C * I = 0, while C has to be 0 + RULE #3: Inconclusive [I] has to be > 0 while Conclusive has to be 0, + sum(C, I) > 0 and C * I = 0, while C has to be 0 - RULE #4: Positive [P] and Unexecuted have to be positive - sum(P, U) >= 2 and P * U = positive integer, while - if the P is bigger by 2 then negative U, first conditional - would be true. + RULE #4: Positive [P] and Unexecuted have to be positive + sum(P, U) >= 2 and P * U = positive integer, while + if the P is bigger by 2 then negative U, first conditional + would be true. - */ + */ - this.rules = [ + this.rules = [ - { id: 'Rule #1', f: function(d_){ return d_['Conclusive'] + d_['Inconclusive'] + d_['Positive'] === 0; } }, - { id: 'Rule #2', f: function(d_){ return d_['Conclusive'] > 0; } }, - { id: 'Rule #3', f: function(d_){ return d_['Conclusive'] === 0 && d_['Inconclusive'] > 0; } }, - { id: 'Rule #4', f: function(d_){ return d_['Positive'] + d_['Unexecuted'] >= 2 && d_['Positive'] * d_['Unexecuted'] > 0; } } - - ]; - - this._onScroll = this._onScroll.bind(this); - } - - componentDidMount() { - this.parseData(); - window.addEventListener('scroll', this._onScroll); - } - - _onMouseMove(e) { - let self = this; - - if(!this.toggle){ - let hidden = 'none'; - let html = ''; - let bcolor = '#DEDEDE'; - - document.querySelectorAll('circle, path').forEach((d_, i_) => { d_.setAttribute('opacity', 0.8)}); - - if(e.target.id.includes('Node')) { - html = e.target.dataset.tooltip; - this.divElement.style.cursor = 'pointer'; - hidden = 'block'; e.target.setAttribute('opacity', 0.95); - bcolor = e.target.getAttribute('fill'); - - // Set highest z-index - e.target.parentNode.parentNode.appendChild(e.target.parentNode); - } else { - this.divElement.style.cursor = 'default'; - - // 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); }) - } - - this.setState({target: e, tooltip: { target: e.target, bcolor: bcolor, top: e.clientY + 8, left: e.clientX + 8, display: hidden, html: html } }); + { + id: 'Rule #1', f: function (d_) { + return d_['Conclusive'] + d_['Inconclusive'] + d_['Positive'] === 0; } - } + }, + { + id: 'Rule #2', f: function (d_) { + return d_['Conclusive'] > 0; + } + }, + { + id: 'Rule #3', f: function (d_) { + return d_['Conclusive'] === 0 && d_['Inconclusive'] > 0; + } + }, + { + id: 'Rule #4', f: function (d_) { + return d_['Positive'] + d_['Unexecuted'] >= 2 && d_['Positive'] * d_['Unexecuted'] > 0; + } + } - _onScroll(e){ + ]; + + this._onScroll = this._onScroll.bind(this); + } + + componentDidMount() { + this.parseData(); + window.addEventListener('scroll', this._onScroll); + } + + _onMouseMove(e) { + let self = this; + + if (!this.toggle) { + let hidden = 'none'; + let html = ''; + let bcolor = '#DEDEDE'; + + document.querySelectorAll('circle, path').forEach((d_, i_) => { + d_.setAttribute('opacity', "0.8"); + }); + + if (e.target.id.includes('Node')) { + html = e.target.dataset.tooltip; + this.divElement.style.cursor = 'pointer'; + hidden = 'block'; + e.target.setAttribute('opacity', 0.95); + bcolor = e.target.getAttribute('fill'); + + // Set highest z-index + e.target.parentNode.parentNode.appendChild(e.target.parentNode); + } else { this.divElement.style.cursor = 'default'; - this.setState({target: null, tooltip: { target: null, bcolor: 'none', top: 0, left: 0, display: 'none', html: '' } }); - } - _onClick(e) { - this.toggle = this.state.tooltip.target === e.target; + // 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); + }) + } - //variable to external callback - //e.target.parentNode.id) - } - - 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(d_); - 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({ data: data }); - this.render(); - } - - buildTooltipHtmlContent(object_){ return Object.keys(object_).reduce((out_, key_) => out_ + TypographicUtilities.setTitle(key_) + ': ' + object_[key_] + '\n', ''); } - - setLayoutElement(rule_, key_, html_, d_) { - if(rule_ == null) { throw Error('The node scores are invalid'); } - - if(key_ === 'Data'){ this.layout[key_].fontStyle = this.fontStyles[0]; } - else {this.layout[key_].fontStyle = this.fontStyles[1]; } - - this.layout[key_].hex = this.colors[rule_]; - this.layout[key_].label = d_.pillar + this.suffices[rule_]; - 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 translate = 'translate(' + this.width /2 + ',' + this.height/2 + ')'; - - 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)} > - - {nodes} - - -
- ) + this.setState({ + target: e, + tooltip: { + target: e.target, + bcolor: bcolor, + top: e.clientY + 8, + left: e.clientX + 8, + display: hidden, + html: html } + }); } + } + + _onScroll(e) { + this.divElement.style.cursor = 'default'; + this.setState({target: null, tooltip: {target: null, bcolor: 'none', top: 0, left: 0, display: 'none', html: ''}}); + } + + _onClick(e) { + this.toggle = this.state.tooltip.target === e.target; + + //variable to external callback + //e.target.parentNode.id) + } + + 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(d_); + 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({data: data}); + this.render(); + } + + buildTooltipHtmlContent(object_) { + return Object.keys(object_).reduce((out_, key_) => out_ + TypographicUtilities.setTitle(key_) + ': ' + object_[key_] + '\n', ''); + } + + setLayoutElement(rule_, key_, html_, d_) { + if (rule_ == null) { + throw Error('The node scores are invalid'); + } + + if (key_ === 'Data') { + this.layout[key_].fontStyle = this.fontStyles[0]; + } else { + this.layout[key_].fontStyle = this.fontStyles[1]; + } + + this.layout[key_].hex = this.colors[rule_]; + this.layout[key_].label = d_.pillar + this.suffices[rule_]; + 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 translate = 'translate(' + this.width / 2 + ',' + this.height / 2 + ')'; + + 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)}> + + {nodes} + + +
+ ) + } + } } VennDiagram.propTypes = { - pillarsGrades: PropTypes.array + pillarsGrades: PropTypes.array }; export default VennDiagram; From e4a03ce3e0b83df1d8c550d2d4391a5532a54430 Mon Sep 17 00:00:00 2001 From: vkuchinov Date: Mon, 26 Aug 2019 09:59:36 +0300 Subject: [PATCH 16/20] Removing rudimentary variables from ResponsiveVennDiagram and VennDiagram [x] childrenWidth/childrenHeight were removed, have beed used previously for non-responsive SVG [x] this.ZOrder array was removed, since there is another way of soring z-indices [x] translate was removed, since now it's the part of viewPortParameters string ((-this.width / 2) + ' ' + (-this.height / 2)) --- .../zerotrust/venn-components/ResponsiveVennDiagram.js | 7 ------- .../zerotrust/venn-components/VennDiagram.js | 2 -- 2 files changed, 9 deletions(-) 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 index 8cf096d69..4b2069f06 100644 --- 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 @@ -12,13 +12,6 @@ class ResponsiveVennDiagram extends React.Component { render() { const {pillarsGrades} = this.props; - let childrenWidth = this.props.containerWidth, childrenHeight = this.props.containerHeight; - - if (childrenHeight === 0 || isNaN(childrenHeight)) { - childrenHeight = childrenWidth; - } else { - childrenWidth = Math.min(childrenWidth, childrenHeight) - } return (
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 index 2b36266c0..25e57c2dd 100644 --- 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 @@ -13,7 +13,6 @@ class VennDiagram extends React.Component { this.state = {tooltip: {top: 0, left: 0, display: 'none', html: ''}}; this.width = this.height = 512; - this.zOrder = []; this.colors = ['#777777', '#D9534F', '#F0AD4E', '#5CB85C']; this.prefix = 'vennDiagram'; @@ -208,7 +207,6 @@ class VennDiagram extends React.Component { } else { // equivalent to center translate (width/2, height/2) let viewPortParameters = (-this.width / 2) + ' ' + (-this.height / 2) + ' ' + this.width + ' ' + this.height; - let translate = 'translate(' + this.width / 2 + ',' + this.height / 2 + ')'; let nodes = Object.values(this.layout).map((d_, i_) => { if (d_.hasOwnProperty('cx')) { From f865c4b4b94638769b4e1aa6d29bee770bf0700d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 26 Aug 2019 14:08:18 +0300 Subject: [PATCH 17/20] Added sent telemetry logging. --- monkey/infection_monkey/telemetry/base_telem.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/telemetry/base_telem.py b/monkey/infection_monkey/telemetry/base_telem.py index c232ab975..31d7332bd 100644 --- a/monkey/infection_monkey/telemetry/base_telem.py +++ b/monkey/infection_monkey/telemetry/base_telem.py @@ -1,7 +1,11 @@ import abc +import json +import logging from infection_monkey.control import ControlClient +logger = logging.getLogger(__name__) + __author__ = 'itay.mizeretz' @@ -19,7 +23,9 @@ class BaseTelem(object): """ Sends telemetry to island """ - ControlClient.send_telemetry(self.telem_category, self.get_data()) + data = self.get_data() + logger.debug("Sending {} telemetry. Data: {}".format(self.telem_category, json.dumps(data))) + ControlClient.send_telemetry(self.telem_category, data) @abc.abstractproperty def telem_category(self): From fbb82f412b376626f3c43690a4d8a5f70268c89d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 26 Aug 2019 14:08:45 +0300 Subject: [PATCH 18/20] Fixed copy-pasta bug about state telemetry. --- monkey/infection_monkey/monkey.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 692e278fb..3cd20d9c2 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -113,7 +113,7 @@ class InfectionMonkey(object): if monkey_tunnel: monkey_tunnel.start() - StateTelem(False).send() + StateTelem(is_done=False).send() TunnelTelem().send() if WormConfiguration.collect_system_info: @@ -225,7 +225,7 @@ class InfectionMonkey(object): InfectionMonkey.close_tunnel() firewall.close() else: - StateTelem(False).send() # Signal the server (before closing the tunnel) + StateTelem(is_done=True).send() # Signal the server (before closing the tunnel) InfectionMonkey.close_tunnel() firewall.close() if WormConfiguration.send_log_to_server: From a9ba3273dddc4191c83413624010e392b8b33386 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 26 Aug 2019 14:23:14 +0300 Subject: [PATCH 19/20] Added positive segmentation findings --- .../zero_trust_tests/segmentation.py | 50 ++++++++++++++---- .../test_segmentation_zt_tests.py | 51 +++++++++++++++++++ 2 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py index 647db59fc..bb447d992 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py @@ -1,7 +1,8 @@ import itertools from six import text_type -from common.data.zero_trust_consts import STATUS_CONCLUSIVE, EVENT_TYPE_MONKEY_NETWORK +from common.data.zero_trust_consts import STATUS_CONCLUSIVE, EVENT_TYPE_MONKEY_NETWORK, STATUS_POSITIVE, \ + EVENT_TYPE_ISLAND from common.network.network_range import NetworkRange from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst, get_ip_if_in_subnet from monkey_island.cc.models import Monkey @@ -29,15 +30,11 @@ def is_segmentation_violation(current_monkey, target_ip, source_subnet, target_s return cross_segment_ip is not None -def test_segmentation_violation(telemetry_json): - """ - - :param telemetry_json: A SCAN telemetry sent from a Monkey. - """ +def test_segmentation_violation(scan_telemetry_json): # TODO - lower code duplication between this and report.py. # TODO - single machine - current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) - target_ip = telemetry_json['data']['machine']['ip_addr'] + current_monkey = Monkey.get_single_monkey_by_guid(scan_telemetry_json['monkey_guid']) + target_ip = scan_telemetry_json['data']['machine']['ip_addr'] subnet_groups = get_config_network_segments_as_subnet_groups() for subnet_group in subnet_groups: subnet_pairs = itertools.product(subnet_group, subnet_group) @@ -67,6 +64,37 @@ def get_segmentation_violation_event(current_monkey, source_subnet, target_ip, t ) -def test_positive_findings_for_unreached_segments(telemetry_json): - current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) - subnet_groups = get_config_network_segments_as_subnet_groups() +def test_positive_findings_for_unreached_segments(state_telemetry_json): + flat_all_subnets = [item for sublist in get_config_network_segments_as_subnet_groups() for item in sublist] + current_monkey = Monkey.get_single_monkey_by_guid(state_telemetry_json['monkey_guid']) + create_or_add_findings_for_all_pairs(flat_all_subnets, current_monkey) + + +def create_or_add_findings_for_all_pairs(all_subnets, current_monkey): + # Filter the subnets that this monkey is part of. + this_monkey_subnets = [] + for subnet in all_subnets: + if get_ip_if_in_subnet(current_monkey.ip_addresses, NetworkRange.get_range_obj(subnet)) is not None: + this_monkey_subnets.append(subnet) + + # Get all the other subnets. + other_subnets = list(set(all_subnets) - set(this_monkey_subnets)) + + # Calculate the cartesian product - (this monkey subnets X other subnets). These pairs are the pairs that the monkey + # should have tested. + all_subnets_pairs_for_this_monkey = itertools.product(this_monkey_subnets, other_subnets) + + for subnet_pair in all_subnets_pairs_for_this_monkey: + SegmentationFinding.create_or_add_to_existing_finding( + subnets=list(subnet_pair), + status=STATUS_POSITIVE, + segmentation_event=Event.create_event( + "Segmentation test done", + message="Monkey on {hostname} is done attempting cross-segment communications from `{src_seg}` " + "segments to `{dst_seg}` segments.".format( + hostname=current_monkey.hostname, + src_seg=subnet_pair[0], + dst_seg=subnet_pair[1]), + event_type=EVENT_TYPE_ISLAND + ) + ) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py new file mode 100644 index 000000000..f345d4482 --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py @@ -0,0 +1,51 @@ +import uuid + +from common.data.zero_trust_consts import TEST_SEGMENTATION, STATUS_POSITIVE, STATUS_CONCLUSIVE, \ + EVENT_TYPE_MONKEY_NETWORK +from monkey_island.cc.models import Monkey +from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.models.zero_trust.segmentation_finding import SegmentationFinding +from monkey_island.cc.services.telemetry.zero_trust_tests.segmentation import create_or_add_findings_for_all_pairs +from monkey_island.cc.testing.IslandTestCase import IslandTestCase + +FIRST_SUBNET = "1.1.1.1" +SECOND_SUBNET = "2.2.2.0/24" +THIRD_SUBNET = "3.3.3.3-3.3.3.200" + + +class TestSegmentationTests(IslandTestCase): + def test_create_findings_for_all_done_pairs(self): + self.fail_if_not_testing_env() + self.clean_finding_db() + + all_subnets = [FIRST_SUBNET, SECOND_SUBNET, THIRD_SUBNET] + + monkey = Monkey( + guid=str(uuid.uuid4()), + ip_addresses=[FIRST_SUBNET]) + + # no findings + self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION)), 0) + + # This is like the monkey is done and sent done telem + create_or_add_findings_for_all_pairs(all_subnets, monkey) + + # There are 2 subnets in which the monkey is NOT + self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION, status=STATUS_POSITIVE)), 2) + + # This is a monkey from 2nd subnet communicated with 1st subnet. + SegmentationFinding.create_or_add_to_existing_finding( + [FIRST_SUBNET, SECOND_SUBNET], + STATUS_CONCLUSIVE, + Event.create_event(title="sdf", message="asd", event_type=EVENT_TYPE_MONKEY_NETWORK) + ) + + print("Printing all segmentation findings") + all_findings = Finding.objects(test=TEST_SEGMENTATION) + for f in all_findings: + print(f.to_json()) + + self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION, status=STATUS_POSITIVE)), 1) + self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION, status=STATUS_CONCLUSIVE)), 1) + self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION)), 2) From 05a1b2d235e2454ec93a58ed3507688746362e0c Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 26 Aug 2019 15:24:55 +0300 Subject: [PATCH 20/20] Remove type hint as it doesn't work well with python2. :cry: --- monkey/common/network/segmentation_utils.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/monkey/common/network/segmentation_utils.py b/monkey/common/network/segmentation_utils.py index 97adbd203..6569d636b 100644 --- a/monkey/common/network/segmentation_utils.py +++ b/monkey/common/network/segmentation_utils.py @@ -1,14 +1,10 @@ -from common.network.network_range import NetworkRange - - def get_ip_in_src_and_not_in_dst(ip_addresses, source_subnet, target_subnet): - # type: (List[str], NetworkRange, NetworkRange) -> Union[str, None] """ Finds an IP address in ip_addresses which is in source_subnet but not in target_subnet. - :param ip_addresses: List of IP addresses to test. - :param source_subnet: Subnet to want an IP to not be in. - :param target_subnet: Subnet we want an IP to be in. - :return: The cross segment IP if in source but not in target, else None. + :param ip_addresses: List[str]: List of IP addresses to test. + :param source_subnet: NetworkRange: Subnet to want an IP to not be in. + :param target_subnet: NetworkRange: Subnet we want an IP to be in. + :return: The cross segment IP if in source but not in target, else None. Union[str, None] """ if get_ip_if_in_subnet(ip_addresses, target_subnet) is not None: return None