From 5eaed088d6fd2f0f7be0528bbf3c714a015e4a7f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 21 Aug 2020 11:30:54 +0300 Subject: [PATCH 01/17] Basic structure of run monkey page step by step wizard --- .../cc/ui/src/components/AuthComponent.js | 6 ++ .../cc/ui/src/components/Main.js | 2 +- .../pages/RunMonkeyPage/CommandTypes.js | 0 .../pages/RunMonkeyPage/InterfaceSelection.js | 29 ++++++++++ .../RunMonkeyPage/LocalManualRunOptions.js | 42 ++++++++++++++ .../pages/RunMonkeyPage/ManualRunOptions.js | 58 +++++++++++++++++++ .../{ => RunMonkeyPage}/RunMonkeyPage.js | 20 ++----- .../inline-selection/BackButton.js | 15 +++++ .../inline-selection/CommandSection.js | 19 ++++++ .../inline-selection/InlineSelection.js | 28 +++++++++ .../inline-selection/NextSelectionButton.js | 17 ++++++ .../monkey_island/cc/ui/src/styles/Main.scss | 1 + .../styles/components/InlineSelection.scss | 8 +++ 13 files changed, 229 insertions(+), 16 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/CommandTypes.js create mode 100644 monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/InterfaceSelection.js create mode 100644 monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/LocalManualRunOptions.js create mode 100644 monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/ManualRunOptions.js rename monkey/monkey_island/cc/ui/src/components/pages/{ => RunMonkeyPage}/RunMonkeyPage.js (96%) create mode 100644 monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/BackButton.js create mode 100644 monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/CommandSection.js create mode 100644 monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js create mode 100644 monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/NextSelectionButton.js create mode 100644 monkey/monkey_island/cc/ui/src/styles/components/InlineSelection.scss diff --git a/monkey/monkey_island/cc/ui/src/components/AuthComponent.js b/monkey/monkey_island/cc/ui/src/components/AuthComponent.js index 9eb02a397..eb8427670 100644 --- a/monkey/monkey_island/cc/ui/src/components/AuthComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/AuthComponent.js @@ -10,4 +10,10 @@ class AuthComponent extends React.Component { } } +export function authFetch(){ + const auth = new AuthService(); + const authFetch = auth.authFetch; + const jwtHeader = auth.jwtHeader(); +} + export default AuthComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 7ef373f05..47116c5da 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -4,7 +4,7 @@ import {Container} from 'react-bootstrap'; import RunServerPage from 'components/pages/RunServerPage'; import ConfigurePage from 'components/pages/ConfigurePage'; -import RunMonkeyPage from 'components/pages/RunMonkeyPage'; +import RunMonkeyPage from 'components/pages/RunMonkeyPage/RunMonkeyPage'; import MapPage from 'components/pages/MapPage'; import TelemetryPage from 'components/pages/TelemetryPage'; import StartOverPage from 'components/pages/StartOverPage'; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/CommandTypes.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/CommandTypes.js new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/InterfaceSelection.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/InterfaceSelection.js new file mode 100644 index 000000000..542edb568 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/InterfaceSelection.js @@ -0,0 +1,29 @@ +import React, {useEffect, useState} from 'react'; +import NextSelectionButton from '../../ui-components/inline-selection/NextSelectionButton'; +import InlineSelection from '../../ui-components/inline-selection/InlineSelection'; +import CommandSection from '../../ui-components/inline-selection/CommandSection'; +import LocalManualRunOptions from './LocalManualRunOptions'; + +function InterfaceSelection(props) { + return InlineSelection(getContents, props, LocalManualRunOptions) +} + +const getContents = (props) => { + const ips = props.ips.map((ip) => +
{ip}
+ ); + return (
{ips}
); +} + +const setCommandAsContent = (props) => { + let commandComponent = () => InlineSelection(CommandSection, + { + commands: win64commands, + setComponent: props.setComponent + }, + LocalManualRunOptions + ); + props.setComponent(commandComponent, props); +} + +export default InterfaceSelection; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/LocalManualRunOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/LocalManualRunOptions.js new file mode 100644 index 000000000..89275067a --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/LocalManualRunOptions.js @@ -0,0 +1,42 @@ +import React, {useEffect} from 'react'; +import NextSelectionButton from '../../ui-components/inline-selection/NextSelectionButton'; +import InlineSelection from '../../ui-components/inline-selection/InlineSelection'; +import CommandSection from '../../ui-components/inline-selection/CommandSection'; +import ManualRunOptions from './ManualRunOptions'; +import InterfaceSelection from './InterfaceSelection'; + +const LocalManualRunOptions = (props) => { + return InlineSelection(getContents, props, ManualRunOptions) +} + +const win64commands = [{name: "CMD", command: "monkey.exe m0nk3y -s 192.168.56.1"}] + +const getContents = (props) => { + return ( + <> + { + props.setComponent(InterfaceSelection('Windows64')) + }}/> + { + }}/> + { + }}/> + { + }}/> + + ) +} + +const setCommandAsContent = (props) => { + let commandComponent = () => InlineSelection(CommandSection, + { + commands: win64commands, + setComponent: props.setComponent + }, + LocalManualRunOptions + ); + props.setComponent(commandComponent, props); +} + +export default LocalManualRunOptions; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/ManualRunOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/ManualRunOptions.js new file mode 100644 index 000000000..0b84084ab --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/ManualRunOptions.js @@ -0,0 +1,58 @@ +import React, {useEffect, useState} from 'react'; +import NextSelectionButton from '../../ui-components/inline-selection/NextSelectionButton'; +import LocalManualRunOptions from './LocalManualRunOptions'; +import AuthComponent from '../../AuthComponent'; + +function ManualRunOptions(props) { + + const [currentContent, setCurrentContent] = useState(loadingContents()); + const [ips, setIps] = useState([]); + const [initialized, setInitialized] = useState(false); + + const authComponent = new AuthComponent({}) + + useEffect(() => { + if (initialized === false) { + authComponent.authFetch('/api') + .then(res => res.json()) + .then(res => { + setIps([res['ip_addresses']]); + setInitialized(true); + }); + } + }) + + useEffect(() => { + setCurrentContent(getDefaultContents()); + }, [initialized]) + + function setComponent(component, props) { + if (component === undefined) { + setCurrentContent(getDefaultContents()) + } else { + setCurrentContent(component({...props})) + } + } + + function loadingContents() { + return (
Loading
) + } + + function getDefaultContents() { + return ( +
+ { + setComponent(LocalManualRunOptions, {ips: ips, setComponent: setComponent}) + }}/> + { + }}/> + { + }}/> +
+ ); + } + + return currentContent; +} + +export default ManualRunOptions; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage.js similarity index 96% rename from monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js rename to monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage.js index 48a11f008..19650fc19 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage.js @@ -12,10 +12,11 @@ import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle'; import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons/faExclamationTriangle'; import {Link} from 'react-router-dom'; -import AuthComponent from '../AuthComponent'; -import AwsRunTable from '../run-monkey/AwsRunTable'; +import AuthComponent from '../../AuthComponent'; +import AwsRunTable from '../../run-monkey/AwsRunTable'; -import MissingBinariesModal from '../ui-components/MissingBinariesModal'; +import MissingBinariesModal from '../../ui-components/MissingBinariesModal'; +import ManualRunOptions from './ManualRunOptions'; const loading_css_override = css` display: block; @@ -329,22 +330,11 @@ class RunMonkeyPageComponent extends AuthComponent { showModal={this.state.showModal} onClose={this.closeModal} errorDetails={this.state.errorDetails}/> - { - // TODO: implement button functionality - /* - - */ - }

OR

+

+ ) +} + +backButton.propTypes = { + onClick: PropTypes.func +} diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/CommandSection.js b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/CommandSection.js new file mode 100644 index 000000000..ec4a53f0e --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/CommandSection.js @@ -0,0 +1,19 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + + +export default function CommandSection(props){ + return ( +

+ {props.commands[0].name} + {props.commands[0].command} +
+ ) +} + +CommandSection.propTypes = { + commands: PropTypes.arrayOf(PropTypes.exact({ + name: PropTypes.string, + command: PropTypes.string + })) +} diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js new file mode 100644 index 000000000..97bb82d99 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js @@ -0,0 +1,28 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import BackButton from './BackButton'; +import ManualRunOptions from '../../pages/RunMonkeyPage/ManualRunOptions'; + + +export default function InlineSelection(WrappedComponent, props, previousComponent){ + return ( +
+ {previousComponent === undefined ? '' : + {setPreviousComponent(props, previousComponent)}}/>} + +
+ ) +} + +function setPreviousComponent(props, previousComponent) { + if(previousComponent === ManualRunOptions){ + return props.setComponent() + } else { + return props.setComponent(previousComponent) + } +} + +InlineSelection.propTypes = { + setComponent: PropTypes.func, + ips: PropTypes.arrayOf(PropTypes.string) +} diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/NextSelectionButton.js b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/NextSelectionButton.js new file mode 100644 index 000000000..6f6ac4cb7 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/NextSelectionButton.js @@ -0,0 +1,17 @@ +import {Button} from 'react-bootstrap'; +import React from 'react'; +import PropTypes from 'prop-types'; + +export default function nextSelectionButton(props) { + return ( + + ) +} + +nextSelectionButton.propTypes = { + text: PropTypes.string, + onButtonClick: PropTypes.func +} diff --git a/monkey/monkey_island/cc/ui/src/styles/Main.scss b/monkey/monkey_island/cc/ui/src/styles/Main.scss index 6b6096a3f..2fb672693 100644 --- a/monkey/monkey_island/cc/ui/src/styles/Main.scss +++ b/monkey/monkey_island/cc/ui/src/styles/Main.scss @@ -11,6 +11,7 @@ @import 'components/InfoPane'; @import 'components/PreviewPane'; @import 'components/AdvancedMultiSelect'; +@import 'components/InlineSelection'; // Define custom elements after bootstrap import diff --git a/monkey/monkey_island/cc/ui/src/styles/components/InlineSelection.scss b/monkey/monkey_island/cc/ui/src/styles/components/InlineSelection.scss new file mode 100644 index 000000000..c264344de --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/styles/components/InlineSelection.scss @@ -0,0 +1,8 @@ +.inline-selection-component.container { + display: flex; + justify-content: center; +} + +.inline-selection-component.container .selection-button{ + margin: 0 5px 0 5px; +} From 522644238b574fcd9cbdd33c84b2cf1da242e11e Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 24 Aug 2020 14:22:01 +0300 Subject: [PATCH 02/17] Added command display component and tabs to it --- .../cc/ui/src/components/Main.js | 7 +- .../pages/RunMonkeyPage/CommandDisplay.js | 52 ++ .../RunMonkeyPage/LocalManualRunOptions.js | 69 ++- .../pages/RunMonkeyPage/ManualRunOptions.js | 4 +- .../components/pages/RunMonkeyPage/OsTypes.js | 6 + .../pages/RunMonkeyPage/RunMonkeyPage.js | 172 ++----- .../pages/RunMonkeyPage/RunMonkeyPage2.js | 472 ++++++++++++++++++ .../{CommandTypes.js => RunOptions.js} | 0 .../commands/local_linux_curl.js | 13 + .../commands/local_linux_wget.js | 10 + .../commands/local_windows_cmd.js | 10 + .../commands/local_windows_powershell.js | 10 + .../ui-components/DropdownSelect.js | 62 +++ .../ui/src/components/ui-components/Emoji.js | 12 + .../inline-selection/InlineSelection.js | 2 +- 15 files changed, 729 insertions(+), 172 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/CommandDisplay.js create mode 100644 monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/OsTypes.js create mode 100644 monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage2.js rename monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/{CommandTypes.js => RunOptions.js} (100%) create mode 100644 monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_linux_curl.js create mode 100644 monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_linux_wget.js create mode 100644 monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_cmd.js create mode 100644 monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_powershell.js create mode 100644 monkey/monkey_island/cc/ui/src/components/ui-components/DropdownSelect.js create mode 100644 monkey/monkey_island/cc/ui/src/components/ui-components/Emoji.js diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 47116c5da..37eb97f3f 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -5,6 +5,7 @@ import {Container} from 'react-bootstrap'; import RunServerPage from 'components/pages/RunServerPage'; import ConfigurePage from 'components/pages/ConfigurePage'; import RunMonkeyPage from 'components/pages/RunMonkeyPage/RunMonkeyPage'; +import RunMonkeyPage2 from 'components/pages/RunMonkeyPage/RunMonkeyPage2'; import MapPage from 'components/pages/MapPage'; import TelemetryPage from 'components/pages/TelemetryPage'; import StartOverPage from 'components/pages/StartOverPage'; @@ -29,7 +30,7 @@ const reportZeroTrustRoute = '/report/zeroTrust'; class AppComponent extends AuthComponent { updateStatus = () => { - if (this.state.isLoggedIn === false){ + if (this.state.isLoggedIn === false) { return } this.auth.loggedIn() @@ -148,6 +149,10 @@ class AppComponent extends AuthComponent { )} + {this.renderRoute('/run-monkey2', + )} {this.renderRoute('/infection/map', + {props.commands.map(command => { + return ( + + {command.name} + ); + })} + ); + } + + function getCommandByName(name, commands) { + commands.forEach((command) => { + + }) + } + + return ( + <> + {renderNav()} + +
+ + + + {props.commands[0].command} +
+
+ + ) +} + +commandDisplay.propTypes = { + commands: PropTypes.arrayOf(PropTypes.exact({ + name: PropTypes.string, + command: PropTypes.string + })) +} diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/LocalManualRunOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/LocalManualRunOptions.js index 89275067a..9680cfbad 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/LocalManualRunOptions.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/LocalManualRunOptions.js @@ -1,42 +1,57 @@ -import React, {useEffect} from 'react'; -import NextSelectionButton from '../../ui-components/inline-selection/NextSelectionButton'; +import React, {useEffect, useState} from 'react'; import InlineSelection from '../../ui-components/inline-selection/InlineSelection'; -import CommandSection from '../../ui-components/inline-selection/CommandSection'; import ManualRunOptions from './ManualRunOptions'; -import InterfaceSelection from './InterfaceSelection'; +import DropdownSelect from '../../ui-components/DropdownSelect'; +import {OS_TYPES} from './OsTypes'; +import GenerateLocalWindowsCmd from './commands/local_windows_cmd'; +import GenerateLocalWindowsPowershell from './commands/local_windows_powershell'; +import GenerateLocalLinuxWget from './commands/local_linux_wget'; +import GenerateLocalLinuxCurl from './commands/local_linux_curl'; +import CommandDisplay from './CommandDisplay'; + const LocalManualRunOptions = (props) => { return InlineSelection(getContents, props, ManualRunOptions) } -const win64commands = [{name: "CMD", command: "monkey.exe m0nk3y -s 192.168.56.1"}] - const getContents = (props) => { + + const osTypes = { + [OS_TYPES.WINDOWS_64]: 'Windows 64bit', + [OS_TYPES.WINDOWS_32]: 'Windows 32bit', + [OS_TYPES.LINUX_64]: 'Linux 64bit', + [OS_TYPES.LINUX_32]: 'Linux 32bit' + } + + const [osType, setOsType] = useState(OS_TYPES.WINDOWS_64); + const [selectedIp, setSelectedIp] = useState(props.ips[0]); + const [commands, setCommands] = useState(generateCommands()); + + useEffect(() => { + setCommands(generateCommands()); + }, [osType, selectedIp]) + + function setIp(index) { + setSelectedIp(props.ips[index]); + } + + function generateCommands() { + if(osType === OS_TYPES.WINDOWS_64 || osType === OS_TYPES.WINDOWS_32) { + return [{name: 'CMD', command: GenerateLocalWindowsCmd(selectedIp, osType)}, + {name: 'Powershell', command: GenerateLocalWindowsPowershell(selectedIp, osType)}] + } else { + return [{name: 'CURL', command: GenerateLocalLinuxCurl(selectedIp, osType)}, + {name: 'WGET', command: GenerateLocalLinuxWget(selectedIp, osType)}] + } + } + return ( <> - { - props.setComponent(InterfaceSelection('Windows64')) - }}/> - { - }}/> - { - }}/> - { - }}/> + + + ) } -const setCommandAsContent = (props) => { - let commandComponent = () => InlineSelection(CommandSection, - { - commands: win64commands, - setComponent: props.setComponent - }, - LocalManualRunOptions - ); - props.setComponent(commandComponent, props); -} - export default LocalManualRunOptions; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/ManualRunOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/ManualRunOptions.js index 0b84084ab..6874fc2c8 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/ManualRunOptions.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/ManualRunOptions.js @@ -2,6 +2,7 @@ import React, {useEffect, useState} from 'react'; import NextSelectionButton from '../../ui-components/inline-selection/NextSelectionButton'; import LocalManualRunOptions from './LocalManualRunOptions'; import AuthComponent from '../../AuthComponent'; +import BackButton from '../../ui-components/inline-selection/BackButton'; function ManualRunOptions(props) { @@ -16,7 +17,7 @@ function ManualRunOptions(props) { authComponent.authFetch('/api') .then(res => res.json()) .then(res => { - setIps([res['ip_addresses']]); + setIps([res['ip_addresses']][0]); setInitialized(true); }); } @@ -48,6 +49,7 @@ function ManualRunOptions(props) { }}/> { }}/> + ); } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/OsTypes.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/OsTypes.js new file mode 100644 index 000000000..b24c9b302 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/OsTypes.js @@ -0,0 +1,6 @@ +export const OS_TYPES = { + WINDOWS_32: 'win32', + WINDOWS_64: 'win64', + LINUX_32: 'linux32', + LINUX_64: 'linux64' +} diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage.js index 19650fc19..1a01ac913 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage.js @@ -1,11 +1,9 @@ import React from 'react'; import {css} from '@emotion/core'; import {Button, Col, Card, Nav, Collapse, Row} from 'react-bootstrap'; -import CopyToClipboard from 'react-copy-to-clipboard'; import GridLoader from 'react-spinners/GridLoader'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; -import {faClipboard} from '@fortawesome/free-solid-svg-icons/faClipboard'; import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck'; import {faSync} from '@fortawesome/free-solid-svg-icons/faSync'; import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle'; @@ -17,6 +15,7 @@ import AwsRunTable from '../../run-monkey/AwsRunTable'; import MissingBinariesModal from '../../ui-components/MissingBinariesModal'; import ManualRunOptions from './ManualRunOptions'; +import Emoji from '../../ui-components/Emoji'; const loading_css_override = css` display: block; @@ -29,12 +28,9 @@ class RunMonkeyPageComponent extends AuthComponent { constructor(props) { super(props); this.state = { - ips: [], runningOnIslandState: 'not_running', runningOnClientState: 'not_running', awsClicked: false, - selectedIp: '0.0.0.0', - selectedOs: 'windows-32', showManual: false, showAws: false, isOnAws: false, @@ -52,13 +48,6 @@ class RunMonkeyPageComponent extends AuthComponent { } componentDidMount() { - this.authFetch('/api') - .then(res => res.json()) - .then(res => this.setState({ - ips: res['ip_addresses'], - selectedIp: res['ip_addresses'][0] - })); - this.authFetch('/api/local-monkey') .then(res => res.json()) .then(res => { @@ -113,17 +102,7 @@ class RunMonkeyPageComponent extends AuthComponent { }); } - static generateLinuxCmd(ip, is32Bit) { - let bitText = is32Bit ? '32' : '64'; - return `wget --no-check-certificate https://${ip}:5000/api/monkey/download/monkey-linux-${bitText}; chmod +x monkey-linux-${bitText}; ./monkey-linux-${bitText} m0nk3y -s ${ip}:5000` - } - - static generateWindowsCmd(ip, is32Bit) { - let bitText = is32Bit ? '32' : '64'; - return `powershell [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}; (New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/monkey-windows-${bitText}.exe','.\\monkey.exe'); ;Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s ${ip}:5000';`; - } - - runLocalMonkey = () => { + runIslandMonkey = () => { this.authFetch('/api/local-monkey', { method: 'POST', @@ -154,41 +133,6 @@ class RunMonkeyPageComponent extends AuthComponent { }); }; - generateCmdDiv() { - let isLinux = (this.state.selectedOs.split('-')[0] === 'linux'); - let is32Bit = (this.state.selectedOs.split('-')[1] === '32'); - let cmdText = ''; - if (isLinux) { - cmdText = RunMonkeyPageComponent.generateLinuxCmd(this.state.selectedIp, is32Bit); - } else { - cmdText = RunMonkeyPageComponent.generateWindowsCmd(this.state.selectedIp, is32Bit); - } - return ( - -
- - - - {cmdText} -
-
- ) - } - - setSelectedOs = (key) => { - this.setState({ - selectedOs: key - }); - }; - - setSelectedIp = (key) => { - this.setState({ - selectedIp: key - }); - }; - static renderIconByState(state) { if (state === 'running') { return () @@ -274,14 +218,6 @@ class RunMonkeyPageComponent extends AuthComponent { rel="noopener noreferrer" target="_blank">Read the documentation!

- { - this.state.ips.length > 1 ? - - :
- } { return ( - -

1. Run Monkey

-

- Go ahead and run the monkey! - (Or configure the monkey to fine tune its behavior) -

+ <>

- OR

-

- -
-

- Choose the operating system where you want to run the monkey: -

- - - - - +

+ Go ahead and monitor the ongoing infection in the Infection Map view. +

+ + ) + } - {this.state.ips.length > 1 ? -
- - -

- Choose the interface to communicate with: -

- -
- - - - - -
- :
- } -

- Copy the following command to your machine and run it with Administrator or root privileges. -

- {this.generateCmdDiv()} -
- + disableManualOptions = () => { + this.setState({showManual: false}) + } + + render() { + return ( + +

1. Run Monkey

+

+ Go ahead and run the monkey! + (Or configure the monkey to fine tune its behavior) +

+ {this.state.showManual ? + : + this.renderIslandVsManual()} { this.state.isLoadingAws ?
@@ -459,10 +351,6 @@ class RunMonkeyPageComponent extends AuthComponent { } - -

- Go ahead and monitor the ongoing infection in the Infection Map view. -

); } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage2.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage2.js new file mode 100644 index 000000000..8edd0e44e --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage2.js @@ -0,0 +1,472 @@ +import React from 'react'; +import {css} from '@emotion/core'; +import {Button, Col, Card, Nav, Collapse, Row} from 'react-bootstrap'; +import CopyToClipboard from 'react-copy-to-clipboard'; +import GridLoader from 'react-spinners/GridLoader'; + +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {faClipboard} from '@fortawesome/free-solid-svg-icons/faClipboard'; +import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck'; +import {faSync} from '@fortawesome/free-solid-svg-icons/faSync'; +import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle'; +import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons/faExclamationTriangle'; + +import {Link} from 'react-router-dom'; +import AuthComponent from '../../AuthComponent'; +import AwsRunTable from '../../run-monkey/AwsRunTable'; + +import MissingBinariesModal from '../../ui-components/MissingBinariesModal'; +import ManualRunOptions from './ManualRunOptions'; +import Emoji from '../../ui-components/Emoji'; + +const loading_css_override = css` + display: block; + margin-right: auto; + margin-left: auto; +`; + +class RunMonkeyPageComponent2 extends AuthComponent { + + constructor(props) { + super(props); + this.state = { + ips: [], + runningOnIslandState: 'not_running', + runningOnClientState: 'not_running', + awsClicked: false, + selectedIp: '0.0.0.0', + selectedOs: 'windows-32', + showManual: false, + showAws: false, + isOnAws: false, + awsUpdateClicked: false, + awsUpdateFailed: false, + awsMachines: [], + isLoadingAws: true, + isErrorWhileCollectingAwsMachines: false, + awsMachineCollectionErrorMsg: '', + showModal: false, + errorDetails: '' + }; + + this.closeModal = this.closeModal.bind(this); + } + + componentDidMount() { + this.authFetch('/api') + .then(res => res.json()) + .then(res => this.setState({ + ips: res['ip_addresses'], + selectedIp: res['ip_addresses'][0] + })); + + this.authFetch('/api/local-monkey') + .then(res => res.json()) + .then(res => { + if (res['is_running']) { + this.setState({runningOnIslandState: 'running'}); + } else { + this.setState({runningOnIslandState: 'not_running'}); + } + }); + + this.fetchAwsInfo(); + this.fetchConfig(); + + this.authFetch('/api/client-monkey') + .then(res => res.json()) + .then(res => { + if (res['is_running']) { + this.setState({runningOnClientState: 'running'}); + } else { + this.setState({runningOnClientState: 'not_running'}); + } + }); + + this.props.onStatusChange(); + } + + fetchAwsInfo() { + return this.authFetch('/api/remote-monkey?action=list_aws') + .then(res => res.json()) + .then(res => { + let is_aws = res['is_aws']; + if (is_aws) { + // On AWS! + // Checks if there was an error while collecting the aws machines. + let is_error_while_collecting_aws_machines = (res['error'] != null); + if (is_error_while_collecting_aws_machines) { + // There was an error. Finish loading, and display error message. + this.setState({ + isOnAws: true, + isErrorWhileCollectingAwsMachines: true, + awsMachineCollectionErrorMsg: res['error'], + isLoadingAws: false + }); + } else { + // No error! Finish loading and display machines for user + this.setState({isOnAws: true, awsMachines: res['instances'], isLoadingAws: false}); + } + } else { + // Not on AWS. Finish loading and don't display the AWS div. + this.setState({isOnAws: false, isLoadingAws: false}); + } + }); + } + + static generateLinuxCmd(ip, is32Bit) { + let bitText = is32Bit ? '32' : '64'; + return `wget --no-check-certificate https://${ip}:5000/api/monkey/download/monkey-linux-${bitText}; chmod +x monkey-linux-${bitText}; ./monkey-linux-${bitText} m0nk3y -s ${ip}:5000` + } + + static generateWindowsCmd(ip, is32Bit) { + let bitText = is32Bit ? '32' : '64'; + return `powershell [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}; (New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/monkey-windows-${bitText}.exe','.\\monkey.exe'); ;Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s ${ip}:5000';`; + } + + runLocalMonkey = () => { + this.authFetch('/api/local-monkey', + { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({action: 'run'}) + }) + .then(res => res.json()) + .then(res => { + if (res['is_running']) { + this.setState({ + runningOnIslandState: 'installing' + }); + } else { + /* If Monkey binaries are missing, change the state accordingly */ + if (res['error_text'].startsWith('Copy file failed')) { + this.setState({ + showModal: true, + errorDetails: res['error_text'] + } + ); + } + this.setState({ + runningOnIslandState: 'not_running' + }); + } + + this.props.onStatusChange(); + }); + }; + + generateCmdDiv() { + let isLinux = (this.state.selectedOs.split('-')[0] === 'linux'); + let is32Bit = (this.state.selectedOs.split('-')[1] === '32'); + let cmdText = ''; + if (isLinux) { + cmdText = RunMonkeyPageComponent2.generateLinuxCmd(this.state.selectedIp, is32Bit); + } else { + cmdText = RunMonkeyPageComponent2.generateWindowsCmd(this.state.selectedIp, is32Bit); + } + return ( + +
+ + + + {cmdText} +
+
+ ) + } + + setSelectedOs = (key) => { + this.setState({ + selectedOs: key + }); + }; + + setSelectedIp = (key) => { + this.setState({ + selectedIp: key + }); + }; + + static renderIconByState(state) { + if (state === 'running') { + return () + } else if (state === 'installing') { + return () + } else { + return ''; + } + } + + toggleManual = () => { + this.setState({ + showManual: !this.state.showManual + }); + }; + + toggleAws = () => { + this.setState({ + showAws: !this.state.showAws + }); + }; + + runOnAws = () => { + this.setState({ + awsClicked: true + }); + + let instances = this.awsTable.state.selection.map(x => this.instanceIdToInstance(x)); + + this.authFetch('/api/remote-monkey', + { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({type: 'aws', instances: instances, island_ip: this.state.selectedIp}) + }).then(res => res.json()) + .then(res => { + let result = res['result']; + + // update existing state, not run-over + let prevRes = this.awsTable.state.result; + for (let key in result) { + if (result.hasOwnProperty(key)) { + prevRes[key] = result[key]; + } + } + this.awsTable.setState({ + result: prevRes, + selection: [], + selectAll: false + }); + + this.setState({ + awsClicked: false + }); + }); + }; + + fetchConfig() { + return this.authFetch('/api/configuration/island') + .then(res => res.json()) + .then(res => { + return res.configuration; + }) + } + + instanceIdToInstance = (instance_id) => { + let instance = this.state.awsMachines.find( + function (inst) { + return inst['instance_id'] === instance_id; + }); + return {'instance_id': instance_id, 'os': instance['os']} + + }; + + renderAwsMachinesDiv() { + return ( +
+
+

+ + Not sure what this is? Not seeing your AWS EC2 instances? Read the documentation! +

+
+ { + this.state.ips.length > 1 ? + + :
+ } + + (this.awsTable = r)} + /> +
+ +
+
+ ) + } + + closeModal = () => { + this.setState({ + showModal: false + }) + }; + + render() { + return ( + +

1. Run Monkey

+

+ Go ahead and run the monkey! + (Or configure the monkey to fine tune its behavior) +

+

+ + +

+

+ OR +

+ +

+ +

+ +
+

+ Choose the operating system where you want to run the monkey: +

+ + + + + + + {this.state.ips.length > 1 ? +
+ + +

+ Choose the interface to communicate with: +

+ +
+ + + + + +
+ :
+ } +

+ Copy the following command to your machine and run it with Administrator or root privileges. +

+ {this.generateCmdDiv()} +
+ + { + this.state.isLoadingAws ? +
+
+ +
+
+ : null + } + { + this.state.isOnAws ? +

+ OR +

+ : + null + } + { + this.state.isOnAws ? +

+ +

+ : + null + } + + { + this.state.isErrorWhileCollectingAwsMachines ? +
+

+ + Error while collecting AWS machine data. Error + message: {this.state.awsMachineCollectionErrorMsg}
+ Are you sure you've set the correct role on your Island AWS machine?
+ Not sure what this is? Read + the documentation! +

+
+ : + this.renderAwsMachinesDiv() + } + +
+ +

+ Go ahead and monitor the ongoing infection in the Infection Map view. +

+ + ); + } +} + +export default RunMonkeyPageComponent2; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/CommandTypes.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js similarity index 100% rename from monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/CommandTypes.js rename to monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_linux_curl.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_linux_curl.js new file mode 100644 index 000000000..fb0171bfd --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_linux_curl.js @@ -0,0 +1,13 @@ +import {OS_TYPES} from '../OsTypes'; + + +export default function generateLocalLinuxCurl(ip, osType) { + let bitText = osType === OS_TYPES.LINUX_32 ? '32' : '64'; + return `curl https://${ip}:5000/api/monkey/download/monkey-linux-${bitText} -k + -o monkey-linux-${bitText}; + chmod +x monkey-linux-${bitText}; + ./monkey-linux-${bitText} m0nk3y -s ${ip}:5000\`;`; + } + + + diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_linux_wget.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_linux_wget.js new file mode 100644 index 000000000..766822ee1 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_linux_wget.js @@ -0,0 +1,10 @@ +import {OS_TYPES} from '../OsTypes'; + + +export default function generateLocalLinuxWget(ip, osType) { + let bitText = osType === OS_TYPES.LINUX_32 ? '32' : '64'; + return `wget --no-check-certificate https://${ip}:5000/api/monkey/download/ + monkey-linux-${bitText}; + chmod +x monkey-linux-${bitText}; + ./monkey-linux-${bitText} m0nk3y -s ${ip}:5000`; + } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_cmd.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_cmd.js new file mode 100644 index 000000000..74afbe512 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_cmd.js @@ -0,0 +1,10 @@ +import {OS_TYPES} from '../OsTypes'; + + +export default function generateLocalWindowsCmd(ip, osType) { + let bitText = osType === OS_TYPES.WINDOWS_32 ? '32' : '64'; + return `powershell [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}; + (New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/ + monkey-windows-${bitText}.exe','.\\monkey.exe'); + ;Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s ${ip}:5000';`; +} diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_powershell.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_powershell.js new file mode 100644 index 000000000..1ebd1f4ac --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_powershell.js @@ -0,0 +1,10 @@ +import {OS_TYPES} from '../OsTypes'; + + +export default function generateLocalWindowsPowershell(ip, osType) { + let bitText = osType === OS_TYPES.WINDOWS_32 ? '32' : '64'; + return `[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}; + (New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/ + monkey-windows-${bitText}.exe','.\\monkey.exe'); + ;Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s ${ip}:5000';`; +} diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/DropdownSelect.js b/monkey/monkey_island/cc/ui/src/components/ui-components/DropdownSelect.js new file mode 100644 index 000000000..d57f14fe4 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/DropdownSelect.js @@ -0,0 +1,62 @@ +import React, {useState} from 'react'; +import {Dropdown} from 'react-bootstrap'; +import PropTypes from 'prop-types'; + +export default function DropdownSelect(props) { + const [selectedOption, setSelectedOption] = useState(props.defaultKey); + + function generateDropdownItems(data) { + if (Array.isArray(data)) { + return generateDropdownItemsFromArray(data); + } else if (typeof data === 'object') { + return generateDropdownItemsFromObject(data); + } else { + throw "Component can only generate dropdown intems from lists and objects." + } + } + + function generateDropdownItemsFromArray(data) { + const dropdownItems = []; + for (let i = 0; i < data.length; i++) { + dropdownItems.push(generateDropdownItem(i, data[i])); + } + return dropdownItems; + } + + function generateDropdownItemsFromObject(data) { + const dropdownItems = []; + for (let [key, value] of Object.entries(data)) { + dropdownItems.push(generateDropdownItem(key, value)); + } + return dropdownItems; + } + + function generateDropdownItem(key, value) { + return ( + { setSelectedOption(key); + props.onClick(key)}} + active={(key === selectedOption)}> + {value} + ); + } + + return ( + <> + + + {props.options[selectedOption]} + + + + {generateDropdownItems(props.options)} + + + + ) +} + +DropdownSelect.propTypes = { + options: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), + defaultKey: PropTypes.oneOfType([PropTypes.string,PropTypes.number]), + onClick: PropTypes.func +} diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/Emoji.js b/monkey/monkey_island/cc/ui/src/components/ui-components/Emoji.js new file mode 100644 index 000000000..1773efbd2 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/Emoji.js @@ -0,0 +1,12 @@ +import React from 'react'; +const Emoji = props => ( + + {props.symbol} + +); +export default Emoji; diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js index 97bb82d99..2a6dbc897 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js @@ -18,7 +18,7 @@ function setPreviousComponent(props, previousComponent) { if(previousComponent === ManualRunOptions){ return props.setComponent() } else { - return props.setComponent(previousComponent) + return props.setComponent(previousComponent, props) } } From 0d047b28e39eece43138f71501d88626207ebde4 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 25 Aug 2020 11:30:12 +0300 Subject: [PATCH 03/17] More work and styling of monkey run page components --- .../pages/RunMonkeyPage/CommandDisplay.js | 39 ++++++++++++------- .../RunMonkeyPage/LocalManualRunOptions.js | 8 ++-- .../pages/RunMonkeyPage/ManualRunOptions.js | 28 ++++++++----- .../ui-components/DropdownSelect.js | 5 ++- .../inline-selection/BackButton.js | 15 +++++-- .../inline-selection/InlineSelection.js | 5 ++- .../inline-selection/NextSelectionButton.js | 23 ++++++++--- .../monkey_island/cc/ui/src/styles/Main.scss | 4 +- .../styles/components/InlineSelection.scss | 8 ---- .../inline-selection/BackButton.scss | 20 ++++++++++ .../inline-selection/InlineSelection.scss | 12 ++++++ .../inline-selection/NextSelectionButton.scss | 34 ++++++++++++++++ 12 files changed, 152 insertions(+), 49 deletions(-) delete mode 100644 monkey/monkey_island/cc/ui/src/styles/components/InlineSelection.scss create mode 100644 monkey/monkey_island/cc/ui/src/styles/components/inline-selection/BackButton.scss create mode 100644 monkey/monkey_island/cc/ui/src/styles/components/inline-selection/InlineSelection.scss create mode 100644 monkey/monkey_island/cc/ui/src/styles/components/inline-selection/NextSelectionButton.scss diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/CommandDisplay.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/CommandDisplay.js index bf0852c59..b237e25d2 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/CommandDisplay.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/CommandDisplay.js @@ -2,42 +2,53 @@ import {Button, Card, Nav} from 'react-bootstrap'; import CopyToClipboard from 'react-copy-to-clipboard'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {faClipboard} from '@fortawesome/free-solid-svg-icons/faClipboard'; -import React, {useState} from 'react'; +import React, {useEffect, useState} from 'react'; import PropTypes from 'prop-types'; export default function commandDisplay(props) { - const [selectedVariant, setSelectedVariant] = useState(props.commands[0].name); + const [selectedCommand, setSelectedCommand] = useState(props.commands[0]); + + function setSelectedCommandByName(type){ + setSelectedCommand(getCommandByName(props.commands, type)); + } + + function getCommandByName(commands, type){ + return commands.find((command) => {return command.type === type}); + } + + useEffect(() => { + let sameTypeCommand = getCommandByName(props.commands, selectedCommand.type); + if( sameTypeCommand !== undefined){ + setSelectedCommand(sameTypeCommand); + } else { + setSelectedCommand(props.commands[0]); + } + }, [props.commands]); function renderNav() { return ( -
-
- ) - } - - closeModal = () => { - this.setState({ - showModal: false - }) - }; - - renderIslandVsManual = () => { - return ( - <> -

- - -

-

- OR -

-

- -

-

- Go ahead and monitor the ongoing infection in the Infection Map view. -

- - ) - } - - disableManualOptions = () => { - this.setState({showManual: false}) - } - render() { return ( (Or configure the monkey to fine tune its behavior)

- {this.state.showManual ? - : - this.renderIslandVsManual()} - { - this.state.isLoadingAws ? -
-
- -
-
- : null - } - { - this.state.isOnAws ? -

- OR -

- : - null - } - { - this.state.isOnAws ? -

- -

- : - null - } - - { - this.state.isErrorWhileCollectingAwsMachines ? -
-

- - Error while collecting AWS machine data. Error - message: {this.state.awsMachineCollectionErrorMsg}
- Are you sure you've set the correct role on your Island AWS machine?
- Not sure what this is? Read - the documentation! -

-
- : - this.renderAwsMachinesDiv() - } - -
+ ); } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage2.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage2.js deleted file mode 100644 index 8edd0e44e..000000000 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage2.js +++ /dev/null @@ -1,472 +0,0 @@ -import React from 'react'; -import {css} from '@emotion/core'; -import {Button, Col, Card, Nav, Collapse, Row} from 'react-bootstrap'; -import CopyToClipboard from 'react-copy-to-clipboard'; -import GridLoader from 'react-spinners/GridLoader'; - -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; -import {faClipboard} from '@fortawesome/free-solid-svg-icons/faClipboard'; -import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck'; -import {faSync} from '@fortawesome/free-solid-svg-icons/faSync'; -import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle'; -import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons/faExclamationTriangle'; - -import {Link} from 'react-router-dom'; -import AuthComponent from '../../AuthComponent'; -import AwsRunTable from '../../run-monkey/AwsRunTable'; - -import MissingBinariesModal from '../../ui-components/MissingBinariesModal'; -import ManualRunOptions from './ManualRunOptions'; -import Emoji from '../../ui-components/Emoji'; - -const loading_css_override = css` - display: block; - margin-right: auto; - margin-left: auto; -`; - -class RunMonkeyPageComponent2 extends AuthComponent { - - constructor(props) { - super(props); - this.state = { - ips: [], - runningOnIslandState: 'not_running', - runningOnClientState: 'not_running', - awsClicked: false, - selectedIp: '0.0.0.0', - selectedOs: 'windows-32', - showManual: false, - showAws: false, - isOnAws: false, - awsUpdateClicked: false, - awsUpdateFailed: false, - awsMachines: [], - isLoadingAws: true, - isErrorWhileCollectingAwsMachines: false, - awsMachineCollectionErrorMsg: '', - showModal: false, - errorDetails: '' - }; - - this.closeModal = this.closeModal.bind(this); - } - - componentDidMount() { - this.authFetch('/api') - .then(res => res.json()) - .then(res => this.setState({ - ips: res['ip_addresses'], - selectedIp: res['ip_addresses'][0] - })); - - this.authFetch('/api/local-monkey') - .then(res => res.json()) - .then(res => { - if (res['is_running']) { - this.setState({runningOnIslandState: 'running'}); - } else { - this.setState({runningOnIslandState: 'not_running'}); - } - }); - - this.fetchAwsInfo(); - this.fetchConfig(); - - this.authFetch('/api/client-monkey') - .then(res => res.json()) - .then(res => { - if (res['is_running']) { - this.setState({runningOnClientState: 'running'}); - } else { - this.setState({runningOnClientState: 'not_running'}); - } - }); - - this.props.onStatusChange(); - } - - fetchAwsInfo() { - return this.authFetch('/api/remote-monkey?action=list_aws') - .then(res => res.json()) - .then(res => { - let is_aws = res['is_aws']; - if (is_aws) { - // On AWS! - // Checks if there was an error while collecting the aws machines. - let is_error_while_collecting_aws_machines = (res['error'] != null); - if (is_error_while_collecting_aws_machines) { - // There was an error. Finish loading, and display error message. - this.setState({ - isOnAws: true, - isErrorWhileCollectingAwsMachines: true, - awsMachineCollectionErrorMsg: res['error'], - isLoadingAws: false - }); - } else { - // No error! Finish loading and display machines for user - this.setState({isOnAws: true, awsMachines: res['instances'], isLoadingAws: false}); - } - } else { - // Not on AWS. Finish loading and don't display the AWS div. - this.setState({isOnAws: false, isLoadingAws: false}); - } - }); - } - - static generateLinuxCmd(ip, is32Bit) { - let bitText = is32Bit ? '32' : '64'; - return `wget --no-check-certificate https://${ip}:5000/api/monkey/download/monkey-linux-${bitText}; chmod +x monkey-linux-${bitText}; ./monkey-linux-${bitText} m0nk3y -s ${ip}:5000` - } - - static generateWindowsCmd(ip, is32Bit) { - let bitText = is32Bit ? '32' : '64'; - return `powershell [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}; (New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/monkey-windows-${bitText}.exe','.\\monkey.exe'); ;Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s ${ip}:5000';`; - } - - runLocalMonkey = () => { - this.authFetch('/api/local-monkey', - { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({action: 'run'}) - }) - .then(res => res.json()) - .then(res => { - if (res['is_running']) { - this.setState({ - runningOnIslandState: 'installing' - }); - } else { - /* If Monkey binaries are missing, change the state accordingly */ - if (res['error_text'].startsWith('Copy file failed')) { - this.setState({ - showModal: true, - errorDetails: res['error_text'] - } - ); - } - this.setState({ - runningOnIslandState: 'not_running' - }); - } - - this.props.onStatusChange(); - }); - }; - - generateCmdDiv() { - let isLinux = (this.state.selectedOs.split('-')[0] === 'linux'); - let is32Bit = (this.state.selectedOs.split('-')[1] === '32'); - let cmdText = ''; - if (isLinux) { - cmdText = RunMonkeyPageComponent2.generateLinuxCmd(this.state.selectedIp, is32Bit); - } else { - cmdText = RunMonkeyPageComponent2.generateWindowsCmd(this.state.selectedIp, is32Bit); - } - return ( - -
- - - - {cmdText} -
-
- ) - } - - setSelectedOs = (key) => { - this.setState({ - selectedOs: key - }); - }; - - setSelectedIp = (key) => { - this.setState({ - selectedIp: key - }); - }; - - static renderIconByState(state) { - if (state === 'running') { - return () - } else if (state === 'installing') { - return () - } else { - return ''; - } - } - - toggleManual = () => { - this.setState({ - showManual: !this.state.showManual - }); - }; - - toggleAws = () => { - this.setState({ - showAws: !this.state.showAws - }); - }; - - runOnAws = () => { - this.setState({ - awsClicked: true - }); - - let instances = this.awsTable.state.selection.map(x => this.instanceIdToInstance(x)); - - this.authFetch('/api/remote-monkey', - { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({type: 'aws', instances: instances, island_ip: this.state.selectedIp}) - }).then(res => res.json()) - .then(res => { - let result = res['result']; - - // update existing state, not run-over - let prevRes = this.awsTable.state.result; - for (let key in result) { - if (result.hasOwnProperty(key)) { - prevRes[key] = result[key]; - } - } - this.awsTable.setState({ - result: prevRes, - selection: [], - selectAll: false - }); - - this.setState({ - awsClicked: false - }); - }); - }; - - fetchConfig() { - return this.authFetch('/api/configuration/island') - .then(res => res.json()) - .then(res => { - return res.configuration; - }) - } - - instanceIdToInstance = (instance_id) => { - let instance = this.state.awsMachines.find( - function (inst) { - return inst['instance_id'] === instance_id; - }); - return {'instance_id': instance_id, 'os': instance['os']} - - }; - - renderAwsMachinesDiv() { - return ( -
-
-

- - Not sure what this is? Not seeing your AWS EC2 instances? Read the documentation! -

-
- { - this.state.ips.length > 1 ? - - :
- } - - (this.awsTable = r)} - /> -
- -
-
- ) - } - - closeModal = () => { - this.setState({ - showModal: false - }) - }; - - render() { - return ( - -

1. Run Monkey

-

- Go ahead and run the monkey! - (Or configure the monkey to fine tune its behavior) -

-

- - -

-

- OR -

- -

- -

- -
-

- Choose the operating system where you want to run the monkey: -

- - - - - - - {this.state.ips.length > 1 ? -
- - -

- Choose the interface to communicate with: -

- -
- - - - - -
- :
- } -

- Copy the following command to your machine and run it with Administrator or root privileges. -

- {this.generateCmdDiv()} -
- - { - this.state.isLoadingAws ? -
-
- -
-
- : null - } - { - this.state.isOnAws ? -

- OR -

- : - null - } - { - this.state.isOnAws ? -

- -

- : - null - } - - { - this.state.isErrorWhileCollectingAwsMachines ? -
-

- - Error while collecting AWS machine data. Error - message: {this.state.awsMachineCollectionErrorMsg}
- Are you sure you've set the correct role on your Island AWS machine?
- Not sure what this is? Read - the documentation! -

-
- : - this.renderAwsMachinesDiv() - } - -
- -

- Go ahead and monitor the ongoing infection in the Infection Map view. -

- - ); - } -} - -export default RunMonkeyPageComponent2; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOnIslandButton.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOnIslandButton.js new file mode 100644 index 000000000..23de0cd21 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOnIslandButton.js @@ -0,0 +1,113 @@ +import React from 'react'; +import {Button, Col, Row} from 'react-bootstrap'; + +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck'; +import {faSync} from '@fortawesome/free-solid-svg-icons/faSync'; +import AuthComponent from '../../AuthComponent'; + +import MissingBinariesModal from '../../ui-components/MissingBinariesModal'; +import '../../../styles/components/RunOnIslandButton.scss'; + + +class RunOnIslandButton extends AuthComponent { + + constructor(props) { + super(props); + this.state = { + runningOnIslandState: 'not_running', + showModal: false, + errorDetails: '' + }; + + this.closeModal = this.closeModal.bind(this); + } + + componentDidMount() { + this.authFetch('/api/local-monkey') + .then(res => res.json()) + .then(res => { + if (res['is_running']) { + this.setState({runningOnIslandState: 'running'}); + } else { + this.setState({runningOnIslandState: 'not_running'}); + } + }); + } + + runIslandMonkey = () => { + this.setState({runningOnIslandState: 'installing'}, this.sendRunMonkeyRequest) + + }; + + sendRunMonkeyRequest = () => { + this.authFetch('/api/local-monkey', + { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({action: 'run'}) + }) + .then(res => res.json()) + .then(res => { + if (res['is_running']) { + this.setState({ + runningOnIslandState: 'running' + }); + } else { + /* If Monkey binaries are missing, change the state accordingly */ + if (res['error_text'].startsWith('Copy file failed')) { + this.setState({ + showModal: true, + errorDetails: res['error_text'] + } + ); + } + this.setState({ + runningOnIslandState: 'not_running' + }); + } + }); + } + + closeModal = () => { + this.setState({ + showModal: false + }) + }; + + getMonkeyRunStateIcon = () => { + if (this.state.runningOnIslandState === 'running') { + return () + } else if (this.state.runningOnIslandState === 'installing') { + return () + } else { + return ''; + } + } + + render() { + let description = this.props.description !== undefined ? (

{this.props.description}

) : '' + let icon = this.props.icon !== undefined ? () : '' + return ( + + + + + + + ); + } +} + +export default RunOnIslandButton; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/ManualRunOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js similarity index 66% rename from monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/ManualRunOptions.js rename to monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js index 3dbb97f60..a9fe1f8cf 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/ManualRunOptions.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js @@ -3,13 +3,12 @@ import NextSelectionButton from '../../ui-components/inline-selection/NextSelect import LocalManualRunOptions from './LocalManualRunOptions'; import AuthComponent from '../../AuthComponent'; import {faLaptopCode} from '@fortawesome/free-solid-svg-icons/faLaptopCode'; -import {faNetworkWired} from '@fortawesome/free-solid-svg-icons/faNetworkWired'; -import {faCogs} from '@fortawesome/free-solid-svg-icons/faCogs'; -import {Container} from 'react-bootstrap'; import InlineSelection from '../../ui-components/inline-selection/InlineSelection'; import {cloneDeep} from 'lodash'; +import {faExpandArrowsAlt} from '@fortawesome/free-solid-svg-icons'; +import RunOnIslandButton from './RunOnIslandButton'; -function ManualRunOptions(props) { +function RunOptions(props) { const [currentContent, setCurrentContent] = useState(loadingContents()); const [ips, setIps] = useState([]); @@ -45,30 +44,23 @@ function ManualRunOptions(props) { } function getDefaultContents() { - const newProps = cloneDeep({...props, onBackButtonClick: props.disableManualOptions}); + const newProps = cloneDeep({...props}); return InlineSelection(defaultContents, newProps); } function defaultContents() { return ( <> - + { setComponent(LocalManualRunOptions, {ips: ips, setComponent: setComponent}) }}/> - { - }}/> - { - }}/> ); } @@ -76,4 +68,4 @@ function ManualRunOptions(props) { return currentContent; } -export default ManualRunOptions; +export default RunOptions; diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js index c7a3d4169..d428c68ee 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import BackButton from './BackButton'; -import ManualRunOptions from '../../pages/RunMonkeyPage/ManualRunOptions'; +import ManualRunOptions from '../../pages/RunMonkeyPage/RunOptions'; import {Col, Row, Container} from 'react-bootstrap'; diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/NextSelectionButton.js b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/NextSelectionButton.js index 433fd076e..7a7c47087 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/NextSelectionButton.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/NextSelectionButton.js @@ -13,7 +13,7 @@ export default function nextSelectionButton(props) { @@ -23,7 +23,8 @@ export default function nextSelectionButton(props) { } nextSelectionButton.propTypes = { - text: PropTypes.string, + title: PropTypes.string, icon: FontAwesomeIcon, + description: PropTypes.string, onButtonClick: PropTypes.func } diff --git a/monkey/monkey_island/cc/ui/src/styles/Main.scss b/monkey/monkey_island/cc/ui/src/styles/Main.scss index 9ac2d680e..707308031 100644 --- a/monkey/monkey_island/cc/ui/src/styles/Main.scss +++ b/monkey/monkey_island/cc/ui/src/styles/Main.scss @@ -15,6 +15,7 @@ @import 'components/inline-selection/NextSelectionButton'; @import 'components/inline-selection/BackButton'; @import 'components/inline-selection/CommandDisplay'; +@import 'components/Icons'; // Define custom elements after bootstrap import diff --git a/monkey/monkey_island/cc/ui/src/styles/components/Icons.scss b/monkey/monkey_island/cc/ui/src/styles/components/Icons.scss new file mode 100644 index 000000000..ed75d8a9d --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/styles/components/Icons.scss @@ -0,0 +1,13 @@ +.spinning-icon { + animation: spin-animation 0.5s infinite; + display: inline-block; +} + +@keyframes spin-animation { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(359deg); + } +} diff --git a/monkey/monkey_island/cc/ui/src/styles/components/RunOnIslandButton.scss b/monkey/monkey_island/cc/ui/src/styles/components/RunOnIslandButton.scss new file mode 100644 index 000000000..e13c0e8a4 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/styles/components/RunOnIslandButton.scss @@ -0,0 +1,7 @@ +.monkey-on-island-run-state-icon { + display: inline-block; + position: absolute; + right: 23px; + top: 28%; + font-size: 1.1em; +} From 38b1cfacaa4a81a162400c922273298c5c6aa175 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 18 Sep 2020 15:35:34 +0300 Subject: [PATCH 08/17] Removed not working "Back" button from run monkey page, also removed unnecessary code --- monkey/monkey_island/cc/ui/src/components/Main.js | 5 ----- .../pages/RunMonkeyPage/LocalManualRunOptions.js | 12 ++++++++---- .../inline-selection/InlineSelection.js | 12 ------------ 3 files changed, 8 insertions(+), 21 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 37eb97f3f..7df443e6e 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -5,7 +5,6 @@ import {Container} from 'react-bootstrap'; import RunServerPage from 'components/pages/RunServerPage'; import ConfigurePage from 'components/pages/ConfigurePage'; import RunMonkeyPage from 'components/pages/RunMonkeyPage/RunMonkeyPage'; -import RunMonkeyPage2 from 'components/pages/RunMonkeyPage/RunMonkeyPage2'; import MapPage from 'components/pages/MapPage'; import TelemetryPage from 'components/pages/TelemetryPage'; import StartOverPage from 'components/pages/StartOverPage'; @@ -149,10 +148,6 @@ class AppComponent extends AuthComponent { )} - {this.renderRoute('/run-monkey2', - )} {this.renderRoute('/infection/map', { - return InlineSelection(getContents, props) + return InlineSelection(getContents, { + ...props, + onBackButtonClick: () => {props.setComponent()} + }) } const getContents = (props) => { @@ -35,12 +39,12 @@ const getContents = (props) => { } function generateCommands() { - if(osType === OS_TYPES.WINDOWS_64 || osType === OS_TYPES.WINDOWS_32) { + if (osType === OS_TYPES.WINDOWS_64 || osType === OS_TYPES.WINDOWS_32) { return [{type: 'CMD', command: GenerateLocalWindowsCmd(selectedIp, osType)}, - {type: 'Powershell', command: GenerateLocalWindowsPowershell(selectedIp, osType)}] + {type: 'Powershell', command: GenerateLocalWindowsPowershell(selectedIp, osType)}] } else { return [{type: 'CURL', command: GenerateLocalLinuxCurl(selectedIp, osType)}, - {type: 'WGET', command: GenerateLocalLinuxWget(selectedIp, osType)}] + {type: 'WGET', command: GenerateLocalLinuxWget(selectedIp, osType)}] } } diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js index d428c68ee..a699b7d7f 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js @@ -1,7 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import BackButton from './BackButton'; -import ManualRunOptions from '../../pages/RunMonkeyPage/RunOptions'; import {Col, Row, Container} from 'react-bootstrap'; @@ -21,22 +20,11 @@ export default function InlineSelection(WrappedComponent, props) { function renderBackButton(props){ if(props.onBackButtonClick !== undefined){ return (); - } else if(props.previousComponent === undefined){ - return ( {setPreviousComponent(props, props.previousComponent)}}/>); - } -} - -function setPreviousComponent(props) { - if (props.previousComponent === ManualRunOptions) { - return props.setComponent() - } else { - return props.setComponent(props.previousComponent, props) } } InlineSelection.propTypes = { setComponent: PropTypes.func, ips: PropTypes.arrayOf(PropTypes.string), - previousComponent: PropTypes.object, onBackButtonClick: PropTypes.func } From 5331095bf337d6dfca76ff11eaba2c68ebd4a7f7 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 18 Sep 2020 16:11:29 +0300 Subject: [PATCH 09/17] Removed unused function --- monkey/monkey_island/cc/ui/src/components/AuthComponent.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/AuthComponent.js b/monkey/monkey_island/cc/ui/src/components/AuthComponent.js index eb8427670..9eb02a397 100644 --- a/monkey/monkey_island/cc/ui/src/components/AuthComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/AuthComponent.js @@ -10,10 +10,4 @@ class AuthComponent extends React.Component { } } -export function authFetch(){ - const auth = new AuthService(); - const authFetch = auth.authFetch; - const jwtHeader = auth.jwtHeader(); -} - export default AuthComponent; From 3c410d61355406f2fc55d87c8e0ddf368be9663b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 18 Sep 2020 16:43:03 +0300 Subject: [PATCH 10/17] Fixed conflicting dependencies that require botocore >= 1.18.0 --- monkey/monkey_island/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt index 057ea6de2..2ab3b48a7 100644 --- a/monkey/monkey_island/requirements.txt +++ b/monkey/monkey_island/requirements.txt @@ -2,8 +2,8 @@ Flask-JWT-Extended==3.24.1 Flask-Pymongo>=2.3.0 Flask-Restful>=0.3.8 PyInstaller==3.6 -awscli>=1.18.131 -boto3>=1.14.54 +awscli==1.18.131 +boto3==1.14.54 botocore>=1.17.54,<1.18.0 cffi>=1.8,!=1.11.3 dpath>=2.0 @@ -26,4 +26,4 @@ virtualenv>=20.0.26 werkzeug>=1.0.1 wheel>=0.34.2 -pyjwt>=1.5.1 # not directly required, pinned by Snyk to avoid a vulnerability \ No newline at end of file +pyjwt>=1.5.1 # not directly required, pinned by Snyk to avoid a vulnerability From 4d8751432dd43abc29c2caea376c99bbb0a28063 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 18 Sep 2020 16:48:53 +0300 Subject: [PATCH 11/17] Revert "Fixed conflicting dependencies that require botocore >= 1.18.0" This reverts commit b8f70645 --- monkey/monkey_island/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt index 2ab3b48a7..057ea6de2 100644 --- a/monkey/monkey_island/requirements.txt +++ b/monkey/monkey_island/requirements.txt @@ -2,8 +2,8 @@ Flask-JWT-Extended==3.24.1 Flask-Pymongo>=2.3.0 Flask-Restful>=0.3.8 PyInstaller==3.6 -awscli==1.18.131 -boto3==1.14.54 +awscli>=1.18.131 +boto3>=1.14.54 botocore>=1.17.54,<1.18.0 cffi>=1.8,!=1.11.3 dpath>=2.0 @@ -26,4 +26,4 @@ virtualenv>=20.0.26 werkzeug>=1.0.1 wheel>=0.34.2 -pyjwt>=1.5.1 # not directly required, pinned by Snyk to avoid a vulnerability +pyjwt>=1.5.1 # not directly required, pinned by Snyk to avoid a vulnerability \ No newline at end of file From dcf1b4918683a7ee17fe23a17978d7de48e48e86 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 18 Sep 2020 16:58:08 +0300 Subject: [PATCH 12/17] Fixed conflicting dependencies that require botocore >= 1.18.0 --- monkey/monkey_island/requirements.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt index 057ea6de2..7e7272a87 100644 --- a/monkey/monkey_island/requirements.txt +++ b/monkey/monkey_island/requirements.txt @@ -2,9 +2,9 @@ Flask-JWT-Extended==3.24.1 Flask-Pymongo>=2.3.0 Flask-Restful>=0.3.8 PyInstaller==3.6 -awscli>=1.18.131 -boto3>=1.14.54 -botocore>=1.17.54,<1.18.0 +awscli==1.18.131 +boto3==1.14.54 +botocore==1.17.54 cffi>=1.8,!=1.11.3 dpath>=2.0 flask>=1.1 @@ -26,4 +26,4 @@ virtualenv>=20.0.26 werkzeug>=1.0.1 wheel>=0.34.2 -pyjwt>=1.5.1 # not directly required, pinned by Snyk to avoid a vulnerability \ No newline at end of file +pyjwt>=1.5.1 # not directly required, pinned by Snyk to avoid a vulnerability From 62708cf6b2f2579cd57c870a3905c2060ad3a620 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 18 Sep 2020 18:03:44 +0300 Subject: [PATCH 13/17] Fixed es-lint warnings and increased upper warning limit --- .travis.yml | 2 +- .../pages/RunMonkeyPage/InterfaceSelection.js | 15 +-------------- .../pages/RunMonkeyPage/LocalManualRunOptions.js | 1 - .../cc/ui/src/components/ui-components/Emoji.js | 8 ++++---- 4 files changed, 6 insertions(+), 20 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7715ff328..092090d23 100644 --- a/.travis.yml +++ b/.travis.yml @@ -89,7 +89,7 @@ script: - cd monkey_island/cc/ui - npm ci # See https://docs.npmjs.com/cli/ci.html - eslint ./src --quiet # Test for errors -- JS_WARNINGS_AMOUNT_UPPER_LIMIT=4 +- JS_WARNINGS_AMOUNT_UPPER_LIMIT=8 - eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT # Test for max warnings # Build documentation diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/InterfaceSelection.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/InterfaceSelection.js index 542edb568..6e1e9ca02 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/InterfaceSelection.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/InterfaceSelection.js @@ -1,7 +1,5 @@ -import React, {useEffect, useState} from 'react'; -import NextSelectionButton from '../../ui-components/inline-selection/NextSelectionButton'; +import React from 'react'; import InlineSelection from '../../ui-components/inline-selection/InlineSelection'; -import CommandSection from '../../ui-components/inline-selection/CommandSection'; import LocalManualRunOptions from './LocalManualRunOptions'; function InterfaceSelection(props) { @@ -15,15 +13,4 @@ const getContents = (props) => { return (
{ips}
); } -const setCommandAsContent = (props) => { - let commandComponent = () => InlineSelection(CommandSection, - { - commands: win64commands, - setComponent: props.setComponent - }, - LocalManualRunOptions - ); - props.setComponent(commandComponent, props); -} - export default InterfaceSelection; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/LocalManualRunOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/LocalManualRunOptions.js index 7c0838a80..3bd8accae 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/LocalManualRunOptions.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/LocalManualRunOptions.js @@ -7,7 +7,6 @@ import GenerateLocalWindowsPowershell from './commands/local_windows_powershell' import GenerateLocalLinuxWget from './commands/local_linux_wget'; import GenerateLocalLinuxCurl from './commands/local_linux_curl'; import CommandDisplay from './CommandDisplay'; -import RunOptions from './RunOptions'; const LocalManualRunOptions = (props) => { diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/Emoji.js b/monkey/monkey_island/cc/ui/src/components/ui-components/Emoji.js index 1773efbd2..580103500 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/Emoji.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/Emoji.js @@ -1,10 +1,10 @@ import React from 'react'; const Emoji = props => ( {props.symbol} From 6e10dd20d11ff640520b869f03703a3e0dc9f281 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 23 Sep 2020 14:47:05 +0300 Subject: [PATCH 14/17] Run monkey page: fixed a bunch of bugs, CR comments --- .travis.yml | 2 +- .../pages/RunMonkeyPage/CommandDisplay.js | 2 +- .../pages/RunMonkeyPage/InterfaceSelection.js | 2 +- .../RunMonkeyPage/LocalManualRunOptions.js | 2 +- .../pages/RunMonkeyPage/RunOnIslandButton.js | 42 ++++++++++++------- .../ui-components/DropdownSelect.js | 12 +----- 6 files changed, 32 insertions(+), 30 deletions(-) diff --git a/.travis.yml b/.travis.yml index 092090d23..cdf2049b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -89,7 +89,7 @@ script: - cd monkey_island/cc/ui - npm ci # See https://docs.npmjs.com/cli/ci.html - eslint ./src --quiet # Test for errors -- JS_WARNINGS_AMOUNT_UPPER_LIMIT=8 +- JS_WARNINGS_AMOUNT_UPPER_LIMIT=6 - eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT # Test for max warnings # Build documentation diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/CommandDisplay.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/CommandDisplay.js index ca312722a..ff2f877dd 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/CommandDisplay.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/CommandDisplay.js @@ -43,7 +43,7 @@ export default function commandDisplay(props) { {renderNav()}
- + diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/InterfaceSelection.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/InterfaceSelection.js index 6e1e9ca02..6e74fb4a0 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/InterfaceSelection.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/InterfaceSelection.js @@ -8,7 +8,7 @@ function InterfaceSelection(props) { const getContents = (props) => { const ips = props.ips.map((ip) => -
{ip}
+
{ip}
); return (
{ips}
); } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/LocalManualRunOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/LocalManualRunOptions.js index 3bd8accae..b28285a18 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/LocalManualRunOptions.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/LocalManualRunOptions.js @@ -49,7 +49,7 @@ const getContents = (props) => { return ( <> - + diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOnIslandButton.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOnIslandButton.js index 23de0cd21..b7bae48f1 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOnIslandButton.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOnIslandButton.js @@ -6,16 +6,24 @@ import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck'; import {faSync} from '@fortawesome/free-solid-svg-icons/faSync'; import AuthComponent from '../../AuthComponent'; -import MissingBinariesModal from '../../ui-components/MissingBinariesModal'; +import IslandMonkeyRunErrorModal from '../../ui-components/IslandMonkeyRunErrorModal'; import '../../../styles/components/RunOnIslandButton.scss'; +import {faTimes} from '@fortawesome/free-solid-svg-icons'; +const MONKEY_STATES = { + RUNNING: 'running', + NOT_RUNNING: 'not_running', + STARTING: 'starting', + FAILED: 'failed' +} + class RunOnIslandButton extends AuthComponent { constructor(props) { super(props); this.state = { - runningOnIslandState: 'not_running', + runningOnIslandState: MONKEY_STATES.NOT_RUNNING, showModal: false, errorDetails: '' }; @@ -28,19 +36,19 @@ class RunOnIslandButton extends AuthComponent { .then(res => res.json()) .then(res => { if (res['is_running']) { - this.setState({runningOnIslandState: 'running'}); + this.setState({runningOnIslandState: MONKEY_STATES.RUNNING}); } else { - this.setState({runningOnIslandState: 'not_running'}); + this.setState({runningOnIslandState: MONKEY_STATES.NOT_RUNNING}); } }); } runIslandMonkey = () => { - this.setState({runningOnIslandState: 'installing'}, this.sendRunMonkeyRequest) + this.setState({runningOnIslandState: MONKEY_STATES.STARTING}, this.sendRunMonkeyRequest) }; - sendRunMonkeyRequest = () => { + sendRunMonkeyRequest() { this.authFetch('/api/local-monkey', { method: 'POST', @@ -48,23 +56,22 @@ class RunOnIslandButton extends AuthComponent { body: JSON.stringify({action: 'run'}) }) .then(res => res.json()) - .then(res => { + .then(async res => { if (res['is_running']) { + await new Promise(r => setTimeout(r, 1000)); this.setState({ - runningOnIslandState: 'running' + runningOnIslandState: MONKEY_STATES.RUNNING }); } else { /* If Monkey binaries are missing, change the state accordingly */ - if (res['error_text'].startsWith('Copy file failed')) { + if (res['error_text'] !== '') { this.setState({ showModal: true, - errorDetails: res['error_text'] + errorDetails: res['error_text'], + runningOnIslandState: MONKEY_STATES.FAILED } ); } - this.setState({ - runningOnIslandState: 'not_running' - }); } }); } @@ -76,12 +83,15 @@ class RunOnIslandButton extends AuthComponent { }; getMonkeyRunStateIcon = () => { - if (this.state.runningOnIslandState === 'running') { + if (this.state.runningOnIslandState === MONKEY_STATES.RUNNING) { return () - } else if (this.state.runningOnIslandState === 'installing') { + } else if (this.state.runningOnIslandState === MONKEY_STATES.STARTING) { return () + } else if (this.state.runningOnIslandState === MONKEY_STATES.FAILED) { + return () } else { return ''; } @@ -93,7 +103,7 @@ class RunOnIslandButton extends AuthComponent { return ( - diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/DropdownSelect.js b/monkey/monkey_island/cc/ui/src/components/ui-components/DropdownSelect.js index 5585bbc1b..8628c0b60 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/DropdownSelect.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/DropdownSelect.js @@ -16,19 +16,11 @@ export default function DropdownSelect(props) { } function generateDropdownItemsFromArray(data) { - const dropdownItems = []; - for (let i = 0; i < data.length; i++) { - dropdownItems.push(generateDropdownItem(i, data[i])); - } - return dropdownItems; + return data.map((x, i) => generateDropdownItem(i, x)); } function generateDropdownItemsFromObject(data) { - const dropdownItems = []; - for (let [key, value] of Object.entries(data)) { - dropdownItems.push(generateDropdownItem(key, value)); - } - return dropdownItems; + return Object.entries(data).map(([key, value]) => generateDropdownItem(key, value)); } function generateDropdownItem(key, value) { From c5e4493db5092fa29080d73b5d3bc080efe2cc36 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 23 Sep 2020 14:47:34 +0300 Subject: [PATCH 15/17] Improved run monkey error modal to display any kind of error. --- .../monkey_island/cc/resources/local_run.py | 2 +- .../IslandMonkeyRunErrorModal.js | 100 ++++++++++++++++++ .../ui-components/MissingBinariesModal.js | 62 ----------- 3 files changed, 101 insertions(+), 63 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/ui-components/IslandMonkeyRunErrorModal.js delete mode 100644 monkey/monkey_island/cc/ui/src/components/ui-components/MissingBinariesModal.js diff --git a/monkey/monkey_island/cc/resources/local_run.py b/monkey/monkey_island/cc/resources/local_run.py index d743fc835..045729787 100644 --- a/monkey/monkey_island/cc/resources/local_run.py +++ b/monkey/monkey_island/cc/resources/local_run.py @@ -51,7 +51,7 @@ def run_local_monkey(): logger.error('popen failed', exc_info=True) return False, "popen failed: %s" % exc - return True, "pis: %s" % pid + return True, "" class LocalRun(flask_restful.Resource): diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/IslandMonkeyRunErrorModal.js b/monkey/monkey_island/cc/ui/src/components/ui-components/IslandMonkeyRunErrorModal.js new file mode 100644 index 000000000..8b2a8857d --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/IslandMonkeyRunErrorModal.js @@ -0,0 +1,100 @@ +import {Modal} from 'react-bootstrap'; +import React from 'react'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons'; + + +class IslandMonkeyRunErrorModal extends React.PureComponent { + + constructor(props) { + super(props); + + this.state = { + showModal: this.props.showModal, + errorDetails: this.props.errorDetails + }; + } + + componentDidUpdate(prevProps) { + if (this.props !== prevProps) { + this.setState({ + showModal: this.props.showModal, + errorDetails: this.props.errorDetails + }) + } + } + + getMissingBinariesContent() { + return ( +
+ Some Monkey binaries are not found where they should be...
+ You can download the files from here, + at the bottommost section titled "Assets", and place them under the + directory monkey/monkey_island/cc/binaries. +
+ ) + } + + getMonkeyAlreadyRunningContent() { + return ( +
+ Most likely, monkey is already running on the Island. Wait until it finishes or kill the process to run again. +
+ ) + } + + getUndefinedErrorContent() { + return ( +
+ You encountered an undefined error. Please report it to support@infectionmonkey.com or our slack channel. +
+ ) + } + + getDisplayContentByError(errorMsg) { + if (errorMsg.includes('Permission denied:')) { + return this.getMonkeyAlreadyRunningContent() + } else if (errorMsg.startsWith('Copy file failed')) { + return this.getMissingBinariesContent() + } else { + return this.getUndefinedErrorContent() + } + } + + render = () => { + return ( + this.props.onClose()}> + +

+
Uh oh...
+

+
+

+ + {this.getDisplayContentByError(this.state.errorDetails)} +

+
+
+

+ Error Details +

+
+
+              {this.state.errorDetails}
+            
+
+
+ +
+
+
+ ) + }; + +} + +export default IslandMonkeyRunErrorModal; diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/MissingBinariesModal.js b/monkey/monkey_island/cc/ui/src/components/ui-components/MissingBinariesModal.js deleted file mode 100644 index ae7f6ac4e..000000000 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/MissingBinariesModal.js +++ /dev/null @@ -1,62 +0,0 @@ -import {Modal} from 'react-bootstrap'; -import React from 'react'; - - -class MissingBinariesModal extends React.PureComponent { - - constructor(props) { - super(props); - - this.state = { - showModal: this.props.showModal, - errorDetails: this.props.errorDetails - }; - } - - componentDidUpdate(prevProps) { - if (this.props !== prevProps) { - this.setState({ - showModal: this.props.showModal, - errorDetails: this.props.errorDetails - }) - } - } - - render = () => { - return ( - this.props.onClose()}> - -

-
Uh oh...
-

-
-

- - Some Monkey binaries are not found where they should be...
- You can download the files from here, - at the bottommost section titled "Assets", and place them under the directory monkey/monkey_island/cc/binaries. -

-
-
-

- Error Details -

-
-
-              {this.state.errorDetails}
-            
-
-
- -
-
-
- ) - }; - -} - -export default MissingBinariesModal; From 12e7c40db919c8736ecf39d6df3b9d1ba57472ae Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 23 Sep 2020 14:54:30 +0300 Subject: [PATCH 16/17] Increased js warning limit to 7, because I used ` for classnames. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cdf2049b6..887b7cc67 100644 --- a/.travis.yml +++ b/.travis.yml @@ -89,7 +89,7 @@ script: - cd monkey_island/cc/ui - npm ci # See https://docs.npmjs.com/cli/ci.html - eslint ./src --quiet # Test for errors -- JS_WARNINGS_AMOUNT_UPPER_LIMIT=6 +- JS_WARNINGS_AMOUNT_UPPER_LIMIT=7 - eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT # Test for max warnings # Build documentation From 1559504691745d7326b8b5e47c656bda0eb95232 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 23 Sep 2020 15:06:27 +0300 Subject: [PATCH 17/17] Fixed js warning in error modal --- .../ui-components/IslandMonkeyRunErrorModal.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/IslandMonkeyRunErrorModal.js b/monkey/monkey_island/cc/ui/src/components/ui-components/IslandMonkeyRunErrorModal.js index 8b2a8857d..67e7ee77d 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/IslandMonkeyRunErrorModal.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/IslandMonkeyRunErrorModal.js @@ -26,29 +26,29 @@ class IslandMonkeyRunErrorModal extends React.PureComponent { getMissingBinariesContent() { return ( -
+ Some Monkey binaries are not found where they should be...
You can download the files from here, at the bottommost section titled "Assets", and place them under the directory monkey/monkey_island/cc/binaries. -
+ ) } getMonkeyAlreadyRunningContent() { return ( -
+ Most likely, monkey is already running on the Island. Wait until it finishes or kill the process to run again. -
+ ) } getUndefinedErrorContent() { return ( -
+ You encountered an undefined error. Please report it to support@infectionmonkey.com or our slack channel. -
+ ) }