Merge pull request #867 from VakarisZ/aws_run_option_fix

Run on AWS instance option fix
This commit is contained in:
VakarisZ 2020-12-15 12:15:41 +02:00 committed by GitHub
commit d8440303ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 369 additions and 131 deletions

View File

@ -54,16 +54,15 @@ See [Amazon's documentation about working with SSM agents](https://docs.aws.amaz
### Running the monkey
When you run the monkey island on an AWS instance, the island detects it's running on AWS and present the following option in the _"Run Monkey"_ page, like so:
When you run the Monkey Island on an AWS instance, the island detects it's running on AWS and present the following option in the _"Run Monkey"_ page, like so:
![Running a Monkey on EC2 Instance](/images/usage/integrations/monkey-island-aws-screenshot-1.png "Running a Monkey on EC2 Instance")
And then you can choose one of the available instances as "patient zero" like so:
After you click on "AWS run" you can choose one of the available instances as "patient zero" like so:
1. Click on "Run on AWS"
2. Choose the relevant Network Interface
3. Select the machines you'd like to run the Monkey on
4. Click "Run on Selected Machines", and watch the monkey go! 🐒
1. Choose the relevant Network Interface
2. Select the machines you'd like to run the Monkey on
3. Click "Run on Selected Machines", and watch the monkey go! 🐒
![Running a Monkey on EC2 Instance](/images/usage/integrations/monkey-island-aws-screenshot-2.png "Running a Monkey on EC2 Instance")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 97 KiB

View File

@ -35,7 +35,8 @@
"comma-dangle": 1,
"quotes": [
1,
"single"
"single",
{"allowTemplateLiterals": true}
],
"no-undef": 1,
"global-strict": 0,

View File

@ -1,12 +1,12 @@
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';
import InlineSelection from '../../../ui-components/inline-selection/InlineSelection';
import DropdownSelect from '../../../ui-components/DropdownSelect';
import {OS_TYPES} from '../utils/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 '../utils/CommandDisplay';
const LocalManualRunOptions = (props) => {

View File

@ -0,0 +1,119 @@
import React, {useState} from 'react';
import ReactTable from 'react-table'
import checkboxHOC from 'react-table/lib/hoc/selectTable';
import PropTypes from 'prop-types';
const CheckboxTable = checkboxHOC(ReactTable);
const columns = [
{
Header: 'Machines',
columns: [
{Header: 'Machine', accessor: 'name'},
{Header: 'Instance ID', accessor: 'instance_id'},
{Header: 'IP Address', accessor: 'ip_address'},
{Header: 'OS', accessor: 'os'}
]
}
];
const pageSize = 10;
function AWSInstanceTable(props) {
const [allToggled, setAllToggled] = useState(false);
let checkboxTable = null;
function toggleSelection(key) {
key = key.replace('select-', '');
// start off with the existing state
let modifiedSelection = [...props.selection];
const keyIndex = modifiedSelection.indexOf(key);
// check to see if the key exists
if (keyIndex >= 0) {
// it does exist so we will remove it using destructing
modifiedSelection = [
...modifiedSelection.slice(0, keyIndex),
...modifiedSelection.slice(keyIndex + 1)
];
} else {
// it does not exist so add it
modifiedSelection.push(key);
}
// update the state
props.setSelection(modifiedSelection);
}
function isSelected(key) {
return props.selection.includes(key);
}
function toggleAll() {
const selectAll = !allToggled;
const selection = [];
if (selectAll) {
// we need to get at the internals of ReactTable
const wrappedInstance = checkboxTable.getWrappedInstance();
// the 'sortedData' property contains the currently accessible records based on the filter and sort
const currentRecords = wrappedInstance.getResolvedState().sortedData;
// we just push all the IDs onto the selection array
currentRecords.forEach(item => {
selection.push(item._original.instance_id);
});
}
setAllToggled(selectAll);
props.setSelection(selection);
}
function getTrProps(_, r) {
let color = 'inherit';
if (r) {
let instId = r.original.instance_id;
if (isSelected(instId)) {
color = '#ffed9f';
} else if (Object.prototype.hasOwnProperty.call(props.results, instId)) {
color = props.results[instId] ? '#00f01b' : '#f00000'
}
}
return {
style: {backgroundColor: color}
};
}
return (
<div className="data-table-container">
<CheckboxTable
ref={r => (checkboxTable = r)}
keyField="instance_id"
columns={columns}
data={props.data}
showPagination={true}
defaultPageSize={pageSize}
className="-highlight"
selectType="checkbox"
toggleSelection={toggleSelection}
isSelected={isSelected}
toggleAll={toggleAll}
selectAll={allToggled}
getTrProps={getTrProps}
/>
</div>
);
}
AWSInstanceTable.propTypes = {
data: PropTypes.arrayOf(PropTypes.exact({
instance_id: PropTypes.string,
name: PropTypes.string,
os: PropTypes.string,
ip_address: PropTypes.string
})),
results: PropTypes.arrayOf(PropTypes.string),
selection: PropTypes.arrayOf(PropTypes.string),
setSelection: PropTypes.func
}
export default AWSInstanceTable;

View File

@ -0,0 +1,80 @@
import React, {useEffect, useState} from 'react';
import AuthComponent from '../../../AuthComponent';
import '../../../../styles/components/RunOnIslandButton.scss';
import {faCloud} from '@fortawesome/free-solid-svg-icons';
import AWSRunOptions from './AWSRunOptions';
import NextSelectionButton from '../../../ui-components/inline-selection/NextSelectionButton';
import {Alert, Button} from 'react-bootstrap';
import LoadingIcon from '../../../ui-components/LoadingIcon';
function AWSRunButton(props) {
const authComponent = new AuthComponent({});
const [isOnAWS, setIsOnAWS] = useState(false);
const [AWSInstances, setAWSInstances] = useState([]);
const [awsMachineCollectionError, setAwsMachineCollectionError] = useState('');
const [componentLoading, setComponentLoading] = useState(true);
useEffect(() => {
checkIsOnAWS();
}, []);
function checkIsOnAWS() {
authComponent.authFetch('/api/remote-monkey?action=list_aws')
.then(res => res.json())
.then(res => {
let isAws = res['is_aws'];
setComponentLoading(false);
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(AWSRunOptions,
{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 &nbsp;
<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 (componentLoading) {
displayed = LoadingIcon();
}
if (awsMachineCollectionError !== '') {
displayed = getErrorDisplay();
} else if (isOnAWS) {
displayed = getAWSButton();
}
return displayed;
}
export default AWSRunButton;

View File

@ -0,0 +1,117 @@
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 {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle';
import AwsRunTable from './AWSInstanceTable';
import AuthComponent from '../../../AuthComponent';
import InlineSelection from '../../../ui-components/inline-selection/InlineSelection';
const AWSRunOptions = (props) => {
return InlineSelection(getContents, {
...props,
onBackButtonClick: () => {props.setComponent()}
})
}
const getContents = (props) => {
const authComponent = new AuthComponent({});
let [allIPs, setAllIPs] = useState([]);
let [selectedIp, setSelectedIp] = useState(null);
let [AWSClicked, setAWSClicked] = useState(false);
let [runResults, setRunResults] = useState([]);
let [selectedInstances, setSelectedInstances] = useState([]);
useEffect(() => {
getIps();
}, []);
function getIps() {
authComponent.authFetch('/api')
.then(res => res.json())
.then(res => {
setAllIPs(res['ip_addresses']);
setSelectedIp(res['ip_addresses'][0]);
});
}
function runOnAws() {
setAWSClicked(true);
let instances = selectedInstances.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 = result;
for (let key in result) {
if (result.hasOwnProperty(key)) {
prevRes[key] = result[key];
}
}
setRunResults(prevRes);
setSelectedInstances([]);
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}
results={runResults}
selection={selectedInstances}
setSelection={setSelectedInstances}
/>
<div className={'aws-run-button-container'}>
<Button
size={'lg'}
onClick={runOnAws}
className={'btn btn-default btn-md center-block'}
disabled={AWSClicked}>
Run on selected machines
{AWSClicked ?
<FontAwesomeIcon icon={faSync} className={`text-success spinning-icon`} style={{'marginLeft': '5px'}}/> : null}
</Button>
</div>
</div>
);
}
export default AWSRunOptions;

View File

@ -1,12 +1,13 @@
import React, {useEffect, useState} from 'react';
import NextSelectionButton from '../../ui-components/inline-selection/NextSelectionButton';
import LocalManualRunOptions from './LocalManualRunOptions';
import LocalManualRunOptions from './RunManually/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';
import AWSRunButton from './RunOnAWS/AWSRunButton';
function RunOptions(props) {
@ -61,6 +62,7 @@ function RunOptions(props) {
setComponent(LocalManualRunOptions,
{ips: ips, setComponent: setComponent})
}}/>
<AWSRunButton setComponent={setComponent}/>
</>
);
}

View File

@ -1,4 +1,4 @@
import {OS_TYPES} from '../OsTypes';
import {OS_TYPES} from '../utils/OsTypes';
export default function generateLocalLinuxCurl(ip, osType) {

View File

@ -1,4 +1,4 @@
import {OS_TYPES} from '../OsTypes';
import {OS_TYPES} from '../utils/OsTypes';
export default function generateLocalLinuxWget(ip, osType) {

View File

@ -1,4 +1,4 @@
import {OS_TYPES} from '../OsTypes';
import {OS_TYPES} from '../utils/OsTypes';
export default function generateLocalWindowsCmd(ip, osType) {

View File

@ -1,4 +1,4 @@
import {OS_TYPES} from '../OsTypes';
import {OS_TYPES} from '../utils/OsTypes';
export default function generateLocalWindowsPowershell(ip, osType) {

View File

@ -1,6 +1,6 @@
import React from 'react';
import InlineSelection from '../../ui-components/inline-selection/InlineSelection';
import LocalManualRunOptions from './LocalManualRunOptions';
import InlineSelection from '../../../ui-components/inline-selection/InlineSelection';
import LocalManualRunOptions from '../RunManually/LocalManualRunOptions';
function InterfaceSelection(props) {
return InlineSelection(getContents, props, LocalManualRunOptions)

View File

@ -1,109 +0,0 @@
import React from 'react';
import ReactTable from 'react-table'
import checkboxHOC from 'react-table/lib/hoc/selectTable';
const CheckboxTable = checkboxHOC(ReactTable);
const columns = [
{
Header: 'Machines',
columns: [
{Header: 'Machine', accessor: 'name'},
{Header: 'Instance ID', accessor: 'instance_id'},
{Header: 'IP Address', accessor: 'ip_address'},
{Header: 'OS', accessor: 'os'}
]
}
];
const pageSize = 10;
class AwsRunTableComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
selection: [],
selectAll: false,
result: {}
}
}
toggleSelection = (key) => {
// start off with the existing state
let selection = [...this.state.selection];
const keyIndex = selection.indexOf(key);
// check to see if the key exists
if (keyIndex >= 0) {
// it does exist so we will remove it using destructing
selection = [
...selection.slice(0, keyIndex),
...selection.slice(keyIndex + 1)
];
} else {
// it does not exist so add it
selection.push(key);
}
// update the state
this.setState({selection});
};
isSelected = key => {
return this.state.selection.includes(key);
};
toggleAll = () => {
const selectAll = !this.state.selectAll;
const selection = [];
if (selectAll) {
// we need to get at the internals of ReactTable
const wrappedInstance = this.checkboxTable.getWrappedInstance();
// the 'sortedData' property contains the currently accessible records based on the filter and sort
const currentRecords = wrappedInstance.getResolvedState().sortedData;
// we just push all the IDs onto the selection array
currentRecords.forEach(item => {
selection.push(item._original.instance_id);
});
}
this.setState({selectAll, selection});
};
getTrProps = (_, r) => {
let color = 'inherit';
if (r) {
let instId = r.original.instance_id;
if (this.isSelected(instId)) {
color = '#ffed9f';
} else if (Object.prototype.hasOwnProperty.call(this.state.result, instId)) {
color = this.state.result[instId] ? '#00f01b' : '#f00000'
}
}
return {
style: {backgroundColor: color}
};
};
render() {
return (
<div className="data-table-container">
<CheckboxTable
ref={r => (this.checkboxTable = r)}
keyField="instance_id"
columns={columns}
data={this.props.data}
showPagination={true}
defaultPageSize={pageSize}
className="-highlight"
selectType="checkbox"
toggleSelection={this.toggleSelection}
isSelected={this.isSelected}
toggleAll={this.toggleAll}
selectAll={this.state.selectAll}
getTrProps={this.getTrProps}
/>
</div>
);
}
}
export default AwsRunTableComponent;

View File

@ -0,0 +1,9 @@
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faSync} from '@fortawesome/free-solid-svg-icons';
import React from 'react';
function LoadingIcon() {
return <FontAwesomeIcon icon={faSync} className={`spinning-icon loading-icon`} />
}
export default LoadingIcon;

View File

@ -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

View File

@ -0,0 +1,6 @@
a.inline-link {
position: relative;
top: -1px;
margin: 0;
padding: 0;
}

View File

@ -1,3 +1,10 @@
.loading-icon {
color: $monkey-yellow;
font-size: 20px;
margin-left: 50%;
margin-right: 50%;
}
.spinning-icon {
animation: spin-animation 0.5s infinite;
display: inline-block;

View File

@ -14,3 +14,8 @@ div.run-on-os-buttons > .nav-item > .nav-link:hover{
color: $monkey-white;
background-color: $monkey-alt;
}
.aws-run-button-container {
margin-top: 2em;
text-align: center;
}

View File

@ -71,6 +71,7 @@ module.exports = {
publicPath: '/'
},
devServer: {
host: '0.0.0.0',
proxy: {
'/api': {
target: 'https://localhost:5000',