diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/AWSRunButton.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/AWSRunButton.js new file mode 100644 index 000000000..f176ba0b5 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/AWSRunButton.js @@ -0,0 +1,75 @@ +import React, {useEffect, useState} from 'react'; +import AuthComponent from '../../AuthComponent'; +import '../../../styles/components/RunOnIslandButton.scss'; +import {faCloud} from '@fortawesome/free-solid-svg-icons'; +import AWSRunInstances from './AWSRunInstances'; +import NextSelectionButton from '../../ui-components/inline-selection/NextSelectionButton'; +import {Alert, Button} from 'react-bootstrap'; + + +function AWSRunButton(props) { + + const authComponent = new AuthComponent({}); + + const [isOnAWS, setIsOnAWS] = useState(false); + const [AWSInstances, setAWSInstances] = useState([]); + const [awsMachineCollectionError, setAwsMachineCollectionError] = useState(''); + + useEffect(() => { + checkIsOnAWS(); + }, []); + + function checkIsOnAWS() { + authComponent.authFetch('/api/remote-monkey?action=list_aws') + .then(res => res.json()) + .then(res => { + let isAws = res['is_aws']; + + if (isAws) { + // On AWS! + // Checks if there was an error while collecting the aws machines. + let isErrorWhileCollectingAwsMachines = (res['error'] != null); + if (isErrorWhileCollectingAwsMachines) { + // There was an error. Finish loading, and display error message. + setIsOnAWS(true); + setAwsMachineCollectionError(res['error']); + } else { + // No error! Finish loading and display machines for user + setIsOnAWS(true); + setAWSInstances(res['instances']); + } + } + }); + } + + function getAWSButton() { + return <NextSelectionButton title={'AWS run'} + description={'Run on a chosen AWS instance in the cloud.'} + icon={faCloud} + onButtonClick={() => { + props.setComponent(AWSRunInstances, + {AWSInstances: AWSInstances, setComponent: props.setComponent}) + }}/> + } + + function getErrorDisplay() { + return ( + <Alert variant={'info'}>Detected ability to run on different AWS instances. + To enable this feature, follow the + <Button variant={'link'} className={'inline-link'} + href={'https://www.guardicore.com/infectionmonkey/docs/usage/integrations/aws-run-on-ec2-machine/'}> + Tutorial + </Button> and refresh the page. Error received while trying to list AWS instances: {awsMachineCollectionError} + </Alert> ); + } + + let displayed = ''; + if (awsMachineCollectionError !== '') { + displayed = getErrorDisplay(); + } else if (isOnAWS) { + displayed = getAWSButton(); + } + return displayed; +} + +export default AWSRunButton; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/AWSRunInstances.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/AWSRunInstances.js new file mode 100644 index 000000000..4cc4f4e88 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/AWSRunInstances.js @@ -0,0 +1,108 @@ +import React, {useEffect, useState} from 'react'; +import {Button, Nav} from 'react-bootstrap'; + +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {faSync} from '@fortawesome/free-solid-svg-icons/faSync'; +import '../../../styles/components/RunOnIslandButton.scss'; +import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle'; +import AwsRunTable from './AwsRunTable'; +import AuthComponent from '../../AuthComponent'; + + +function AWSRunInstances(props) { + + const authComponent = new AuthComponent({}); + + let awsTable = null + let [allIPs, setAllIPs] = useState([]); + let [selectedIp, setSelectedIp] = useState(null); + let [AWSClicked, setAWSClicked] = useState(false); + + useEffect(() => { + getIps(); + }, [allIPs]); + + function getIps() { + this.authFetch('/api') + .then(res => res.json()) + .then(res => { + setAllIPs(res['ip_addresses']); + setSelectedIp(res['ip_addresses'][0]); + }); + } + + function runOnAws() { + setAWSClicked(true); + let instances = awsTable.state.selection.map(x => instanceIdToInstance(x)); + + authComponent.authFetch('/api/remote-monkey', + { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({type: 'aws', instances: instances, island_ip: selectedIp}) + }).then(res => res.json()) + .then(res => { + let result = res['result']; + + // update existing state, not run-over + let prevRes = awsTable.state.result; + for (let key in result) { + if (result.hasOwnProperty(key)) { + prevRes[key] = result[key]; + } + } + awsTable.setState({ + result: prevRes, + selection: [], + selectAll: false + }); + setAWSClicked(false); + }); + } + + function instanceIdToInstance(instance_id) { + let instance = props.AWSInstances.find( + function (inst) { + return inst['instance_id'] === instance_id; + }); + + return {'instance_id': instance_id, 'os': instance['os']} + } + + return ( + <div style={{'marginBottom': '2em'}}> + <div style={{'marginTop': '1em', 'marginBottom': '1em'}}> + <p className="alert alert-info"> + <FontAwesomeIcon icon={faInfoCircle} style={{'marginRight': '5px'}}/> + Not sure what this is? Not seeing your AWS EC2 instances? <a + href="https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS-EC2-instances" + rel="noopener noreferrer" target="_blank">Read the documentation</a>! + </p> + </div> + { + allIPs.length > 1 ? + <Nav variant="pills" activeKey={selectedIp} onSelect={setSelectedIp} + style={{'marginBottom': '2em'}}> + {allIPs.map(ip => <Nav.Item key={ip}><Nav.Link eventKey={ip}>{ip}</Nav.Link></Nav.Item>)} + </Nav> + : <div style={{'marginBottom': '2em'}}/> + } + <AwsRunTable + data={props.AWSInstances} + ref={r => (awsTable = r)} + /> + <div style={{'marginTop': '1em'}}> + <Button + onClick={runOnAws} + className={'btn btn-default btn-md center-block'} + disabled={AWSClicked}> + Run on selected machines + {AWSClicked ? + <FontAwesomeIcon icon={faSync} className="text-success" style={{'marginLeft': '5px'}}/> : null} + </Button> + </div> + </div> + ); +} + +export default AWSRunInstances; diff --git a/monkey/monkey_island/cc/ui/src/components/run-monkey/AwsRunTable.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/AwsRunTable.js similarity index 100% rename from monkey/monkey_island/cc/ui/src/components/run-monkey/AwsRunTable.js rename to monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/AwsRunTable.js 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 index a9fe1f8cf..e7c14c217 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js @@ -7,6 +7,7 @@ import InlineSelection from '../../ui-components/inline-selection/InlineSelectio import {cloneDeep} from 'lodash'; import {faExpandArrowsAlt} from '@fortawesome/free-solid-svg-icons'; import RunOnIslandButton from './RunOnIslandButton'; +import AWSRunButton from './AWSRunButton'; function RunOptions(props) { @@ -61,6 +62,7 @@ function RunOptions(props) { setComponent(LocalManualRunOptions, {ips: ips, setComponent: setComponent}) }}/> + <AWSRunButton setComponent={setComponent}/> </> ); } diff --git a/monkey/monkey_island/cc/ui/src/styles/Main.scss b/monkey/monkey_island/cc/ui/src/styles/Main.scss index 616217e52..4e8419c82 100644 --- a/monkey/monkey_island/cc/ui/src/styles/Main.scss +++ b/monkey/monkey_island/cc/ui/src/styles/Main.scss @@ -17,6 +17,7 @@ @import 'components/inline-selection/BackButton'; @import 'components/inline-selection/CommandDisplay'; @import 'components/Icons'; +@import 'components/Buttons'; // Define custom elements after bootstrap import diff --git a/monkey/monkey_island/cc/ui/src/styles/components/Buttons.scss b/monkey/monkey_island/cc/ui/src/styles/components/Buttons.scss new file mode 100644 index 000000000..43bdc92fc --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/styles/components/Buttons.scss @@ -0,0 +1,6 @@ +a.inline-link { + position: relative; + top: -1px; + margin: 0; + padding: 0; +}