diff --git a/.travis.yml b/.travis.yml
index 7715ff328..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=4
+- JS_WARNINGS_AMOUNT_UPPER_LIMIT=7
- eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT # Test for max warnings
# Build documentation
diff --git a/envs/os_compatibility/README.md b/envs/os_compatibility/README.md
index 6b97b6612..e0ad30dec 100644
--- a/envs/os_compatibility/README.md
+++ b/envs/os_compatibility/README.md
@@ -57,6 +57,7 @@ A quick reference for usernames on different machines (if in doubt check officia
- Ubuntu: ubuntu
- Oracle: clckwrk
- CentOS: centos
+- Debian: admin
- Everything else: ec2-user
To manually verify the machine is compatible use commands to download and execute the monkey.
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/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js
index 46f5e20bc..32480db8e 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';
@@ -30,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()
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js
deleted file mode 100644
index 467812373..000000000
--- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js
+++ /dev/null
@@ -1,481 +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';
-
-const loading_css_override = css`
- display: block;
- margin-right: auto;
- margin-left: auto;
-`;
-
-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,
- 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 = 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 ( )
- } 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 (Object.prototype.hasOwnProperty.call(result, 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 (
-
-
- {
- this.state.ips.length > 1 ?
-
- {this.state.ips.map(ip => {ip} )}
-
- :
- }
-
-
(this.awsTable = r)}
- />
-
-
- Run on selected machines
- {this.state.awsClicked ?
- : null}
-
-
-
- )
- }
-
- 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)
-
-
-
- Run on Monkey Island Server
- {RunMonkeyPageComponent.renderIconByState(this.state.runningOnIslandState)}
-
-
- {
- // TODO: implement button functionality
- /*
-
- Download and run locally
- { this.renderIconByState(this.state.runningOnClientState) }
-
- */
- }
-
-
- OR
-
-
-
- Run on a machine of your choice
-
-
-
-
-
- Choose the operating system where you want to run the monkey:
-
-
-
-
-
-
- Windows (32 bit)
-
-
-
-
- Windows (64 bit)
-
-
-
-
- Linux (32 bit)
-
-
-
-
- Linux (64 bit)
-
-
-
-
-
-
- {this.state.ips.length > 1 ?
-
-
-
-
- Choose the interface to communicate with:
-
-
-
-
-
-
- {this.state.ips.map(ip =>
- {ip} )}
-
-
-
-
- :
- }
-
- 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 ?
-
-
- Run on AWS machine of your choice
-
-
- :
- 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 RunMonkeyPageComponent;
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
new file mode 100644
index 000000000..ff2f877dd
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/CommandDisplay.js
@@ -0,0 +1,63 @@
+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, {useEffect, useState} from 'react';
+import PropTypes from 'prop-types';
+
+export default function commandDisplay(props) {
+
+ 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 (
+
+ {props.commands.map(command => {
+ return (
+
+ {command.type}
+ );
+ })}
+ );
+ }
+
+ return (
+
+ {renderNav()}
+
+
+
+
+
+
+
+ {selectedCommand.command}
+
+
+
+ )
+}
+
+commandDisplay.propTypes = {
+ commands: PropTypes.arrayOf(PropTypes.exact({
+ type: PropTypes.string,
+ command: PropTypes.string
+ }))
+}
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..6e74fb4a0
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/InterfaceSelection.js
@@ -0,0 +1,16 @@
+import React from 'react';
+import InlineSelection from '../../ui-components/inline-selection/InlineSelection';
+import LocalManualRunOptions from './LocalManualRunOptions';
+
+function InterfaceSelection(props) {
+ return InlineSelection(getContents, props, LocalManualRunOptions)
+}
+
+const getContents = (props) => {
+ const ips = props.ips.map((ip) =>
+ {ip}
+ );
+ return ({ips}
);
+}
+
+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..b28285a18
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/LocalManualRunOptions.js
@@ -0,0 +1,59 @@
+import React, {useEffect, useState} from 'react';
+import InlineSelection from '../../ui-components/inline-selection/InlineSelection';
+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,
+ onBackButtonClick: () => {props.setComponent()}
+ })
+}
+
+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 [{type: 'CMD', command: GenerateLocalWindowsCmd(selectedIp, osType)},
+ {type: 'Powershell', command: GenerateLocalWindowsPowershell(selectedIp, osType)}]
+ } else {
+ return [{type: 'CURL', command: GenerateLocalLinuxCurl(selectedIp, osType)},
+ {type: 'WGET', command: GenerateLocalLinuxWget(selectedIp, osType)}]
+ }
+ }
+
+ return (
+ <>
+
+
+
+ >
+ )
+}
+
+export default LocalManualRunOptions;
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
new file mode 100644
index 000000000..2a27c5be3
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import {Col} from 'react-bootstrap';
+import {Link} from 'react-router-dom';
+import AuthComponent from '../../AuthComponent';
+import RunOptions from './RunOptions';
+
+
+class RunMonkeyPageComponent extends AuthComponent {
+
+ render() {
+ return (
+
+ 1. Run Monkey
+
+ Go ahead and run the monkey!
+ (Or configure the monkey to fine tune its behavior)
+
+
+
+ );
+ }
+}
+
+export default RunMonkeyPageComponent;
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..b7bae48f1
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOnIslandButton.js
@@ -0,0 +1,123 @@
+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 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: MONKEY_STATES.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: MONKEY_STATES.RUNNING});
+ } else {
+ this.setState({runningOnIslandState: MONKEY_STATES.NOT_RUNNING});
+ }
+ });
+ }
+
+ runIslandMonkey = () => {
+ this.setState({runningOnIslandState: MONKEY_STATES.STARTING}, 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(async res => {
+ if (res['is_running']) {
+ await new Promise(r => setTimeout(r, 1000));
+ this.setState({
+ runningOnIslandState: MONKEY_STATES.RUNNING
+ });
+ } else {
+ /* If Monkey binaries are missing, change the state accordingly */
+ if (res['error_text'] !== '') {
+ this.setState({
+ showModal: true,
+ errorDetails: res['error_text'],
+ runningOnIslandState: MONKEY_STATES.FAILED
+ }
+ );
+ }
+ }
+ });
+ }
+
+ closeModal = () => {
+ this.setState({
+ showModal: false
+ })
+ };
+
+ getMonkeyRunStateIcon = () => {
+ if (this.state.runningOnIslandState === MONKEY_STATES.RUNNING) {
+ return ( )
+ } else if (this.state.runningOnIslandState === MONKEY_STATES.STARTING) {
+ return ( )
+ } else if (this.state.runningOnIslandState === MONKEY_STATES.FAILED) {
+ return ( )
+ } else {
+ return '';
+ }
+ }
+
+ render() {
+ let description = this.props.description !== undefined ? ({this.props.description}
) : ''
+ let icon = this.props.icon !== undefined ? () : ''
+ return (
+
+
+
+
+ {icon}
+ {this.props.title}
+ {description}
+ {this.getMonkeyRunStateIcon()}
+
+
+
+ );
+ }
+}
+
+export default RunOnIslandButton;
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js
new file mode 100644
index 000000000..a9fe1f8cf
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js
@@ -0,0 +1,71 @@
+import React, {useEffect, useState} from 'react';
+import NextSelectionButton from '../../ui-components/inline-selection/NextSelectionButton';
+import LocalManualRunOptions from './LocalManualRunOptions';
+import AuthComponent from '../../AuthComponent';
+import {faLaptopCode} from '@fortawesome/free-solid-svg-icons/faLaptopCode';
+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 RunOptions(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']][0]);
+ 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() {
+ const newProps = cloneDeep({...props});
+ return InlineSelection(defaultContents, newProps);
+ }
+
+ function defaultContents() {
+ return (
+ <>
+
+ {
+ setComponent(LocalManualRunOptions,
+ {ips: ips, setComponent: setComponent})
+ }}/>
+ >
+ );
+ }
+
+ return currentContent;
+}
+
+export default RunOptions;
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..8628c0b60
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/ui-components/DropdownSelect.js
@@ -0,0 +1,55 @@
+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 items from arrays and objects.'
+ }
+ }
+
+ function generateDropdownItemsFromArray(data) {
+ return data.map((x, i) => generateDropdownItem(i, x));
+ }
+
+ function generateDropdownItemsFromObject(data) {
+ return Object.entries(data).map(([key, value]) => generateDropdownItem(key, value));
+ }
+
+ function generateDropdownItem(key, value) {
+ return (
+ { setSelectedOption(key);
+ props.onClick(key)}}
+ active={(key === selectedOption)}
+ key={value}>
+ {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..580103500
--- /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/IslandMonkeyRunErrorModal.js b/monkey/monkey_island/cc/ui/src/components/ui-components/IslandMonkeyRunErrorModal.js
new file mode 100644
index 000000000..67e7ee77d
--- /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}
+
+
+
+ this.props.onClose()}>
+ Dismiss
+
+
+
+
+ )
+ };
+
+}
+
+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}
-
-
-
- this.props.onClose()}>
- Dismiss
-
-
-
-
- )
- };
-
-}
-
-export default MissingBinariesModal;
diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/BackButton.js b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/BackButton.js
new file mode 100644
index 000000000..5799a39ea
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/BackButton.js
@@ -0,0 +1,22 @@
+import {Button, Col, Row} from 'react-bootstrap';
+import React from 'react';
+import PropTypes from 'prop-types';
+import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
+import {faCaretLeft} from '@fortawesome/free-solid-svg-icons/faCaretLeft';
+
+export default function backButton(props) {
+ return (
+
+
+
+
+ Back
+
+
+
+ )
+}
+
+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..a699b7d7f
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js
@@ -0,0 +1,30 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import BackButton from './BackButton';
+import {Col, Row, Container} from 'react-bootstrap';
+
+
+export default function InlineSelection(WrappedComponent, props) {
+ return (
+
+
+
+
+ {renderBackButton(props)}
+
+
+
+ )
+}
+
+function renderBackButton(props){
+ if(props.onBackButtonClick !== undefined){
+ return ();
+ }
+}
+
+InlineSelection.propTypes = {
+ setComponent: PropTypes.func,
+ ips: PropTypes.arrayOf(PropTypes.string),
+ onBackButtonClick: PropTypes.func
+}
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..7a7c47087
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/NextSelectionButton.js
@@ -0,0 +1,30 @@
+import {Button, Row, Col} from 'react-bootstrap';
+import React from 'react';
+import PropTypes from 'prop-types';
+import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
+import {faAngleRight} from '@fortawesome/free-solid-svg-icons';
+
+export default function nextSelectionButton(props) {
+ let description = props.description !== undefined ? ({props.description}
) : ''
+ let icon = props.icon !== undefined ? () : ''
+ return (
+
+
+
+ {icon}
+ {props.title}
+ {description}
+
+
+
+
+ )
+}
+
+nextSelectionButton.propTypes = {
+ 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 e26220d0d..616217e52 100644
--- a/monkey/monkey_island/cc/ui/src/styles/Main.scss
+++ b/monkey/monkey_island/cc/ui/src/styles/Main.scss
@@ -12,6 +12,11 @@
@import 'components/PreviewPane';
@import 'components/AdvancedMultiSelect';
@import 'components/particle-component/ParticleBackground';
+@import 'components/inline-selection/InlineSelection';
+@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;
+}
diff --git a/monkey/monkey_island/cc/ui/src/styles/components/inline-selection/BackButton.scss b/monkey/monkey_island/cc/ui/src/styles/components/inline-selection/BackButton.scss
new file mode 100644
index 000000000..02c5c409e
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/styles/components/inline-selection/BackButton.scss
@@ -0,0 +1,20 @@
+.inline-selection-component .back-button {
+ width: 100%;
+ text-align: left;
+}
+
+.inline-selection-component .back-button h1{
+ font-size: 1.3em;
+ margin-top: 5px;
+ margin-bottom: 5px;
+ text-align: left;
+ display: inline-block;
+}
+
+.inline-selection-component .back-button svg{
+ font-size: 1.5em;
+ display: inline-block;
+ margin-right: 10px;
+ position: relative;
+ top: 1px;
+}
diff --git a/monkey/monkey_island/cc/ui/src/styles/components/inline-selection/CommandDisplay.scss b/monkey/monkey_island/cc/ui/src/styles/components/inline-selection/CommandDisplay.scss
new file mode 100644
index 000000000..ebec04e20
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/styles/components/inline-selection/CommandDisplay.scss
@@ -0,0 +1,21 @@
+.command-display {
+ margin-top: 30px;
+}
+
+.command-display .nav-tabs .nav-item a{
+ font-size: 0.8em;
+ color: $monkey-black;
+}
+
+.command-display .nav-tabs .nav-item a.active{
+ color: $monkey-alt;
+}
+
+.command-display .nav-tabs{
+ border-bottom: none;
+}
+
+.command-display div.card{
+ margin: 0;
+ border-top-left-radius: 0;
+}
diff --git a/monkey/monkey_island/cc/ui/src/styles/components/inline-selection/InlineSelection.scss b/monkey/monkey_island/cc/ui/src/styles/components/inline-selection/InlineSelection.scss
new file mode 100644
index 000000000..c66ccbd6d
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/styles/components/inline-selection/InlineSelection.scss
@@ -0,0 +1,17 @@
+.inline-selection-component.container{
+ padding: 0;
+ margin-left: 15px;
+}
+
+.inline-selection-component .selection-button {
+ width: 100%;
+}
+
+.inline-selection-component .dropdown {
+ display: inline-block;
+ margin-right: 10px;
+}
+
+.inline-selection-component .command-display {
+ margin-bottom: 20px;
+}
diff --git a/monkey/monkey_island/cc/ui/src/styles/components/inline-selection/NextSelectionButton.scss b/monkey/monkey_island/cc/ui/src/styles/components/inline-selection/NextSelectionButton.scss
new file mode 100644
index 000000000..71bac3053
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/styles/components/inline-selection/NextSelectionButton.scss
@@ -0,0 +1,34 @@
+.inline-selection-component .selection-button {
+ width: 100%;
+ text-align: left;
+ margin-bottom: 20px;
+ padding-right: 40px;
+}
+
+.inline-selection-component .selection-button svg,
+.inline-selection-component .selection-button h1 {
+ display: inline-block;
+}
+
+.inline-selection-component .selection-button h1 {
+ margin: 0;
+ font-size: 1.3em;
+}
+
+.inline-selection-component .selection-button p {
+ margin: 0;
+ font-size: 0.8em;
+}
+
+.inline-selection-component .selection-button svg {
+ margin-bottom: 1px;
+ margin-right: 7px;
+}
+
+.inline-selection-component .selection-button .angle-right {
+ display: inline-block;
+ position: absolute;
+ right: 23px;
+ top: 22%;
+ font-size: 1.7em;
+}
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