forked from p15670423/monkey
Merge pull request #840 from VakarisZ/run_page_ui_improvements
Run page ui improvements
This commit is contained in:
commit
fad19258d5
|
@ -89,7 +89,7 @@ script:
|
||||||
- cd monkey_island/cc/ui
|
- cd monkey_island/cc/ui
|
||||||
- npm ci # See https://docs.npmjs.com/cli/ci.html
|
- npm ci # See https://docs.npmjs.com/cli/ci.html
|
||||||
- eslint ./src --quiet # Test for errors
|
- 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
|
- eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT # Test for max warnings
|
||||||
|
|
||||||
# Build documentation
|
# Build documentation
|
||||||
|
|
|
@ -57,6 +57,7 @@ A quick reference for usernames on different machines (if in doubt check officia
|
||||||
- Ubuntu: ubuntu
|
- Ubuntu: ubuntu
|
||||||
- Oracle: clckwrk
|
- Oracle: clckwrk
|
||||||
- CentOS: centos
|
- CentOS: centos
|
||||||
|
- Debian: admin
|
||||||
- Everything else: ec2-user
|
- Everything else: ec2-user
|
||||||
|
|
||||||
To manually verify the machine is compatible use commands to download and execute the monkey.
|
To manually verify the machine is compatible use commands to download and execute the monkey.
|
||||||
|
|
|
@ -51,7 +51,7 @@ def run_local_monkey():
|
||||||
logger.error('popen failed', exc_info=True)
|
logger.error('popen failed', exc_info=True)
|
||||||
return False, "popen failed: %s" % exc
|
return False, "popen failed: %s" % exc
|
||||||
|
|
||||||
return True, "pis: %s" % pid
|
return True, ""
|
||||||
|
|
||||||
|
|
||||||
class LocalRun(flask_restful.Resource):
|
class LocalRun(flask_restful.Resource):
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {Container} from 'react-bootstrap';
|
||||||
|
|
||||||
import RunServerPage from 'components/pages/RunServerPage';
|
import RunServerPage from 'components/pages/RunServerPage';
|
||||||
import ConfigurePage from 'components/pages/ConfigurePage';
|
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 MapPage from 'components/pages/MapPage';
|
||||||
import TelemetryPage from 'components/pages/TelemetryPage';
|
import TelemetryPage from 'components/pages/TelemetryPage';
|
||||||
import StartOverPage from 'components/pages/StartOverPage';
|
import StartOverPage from 'components/pages/StartOverPage';
|
||||||
|
@ -30,7 +30,7 @@ const reportZeroTrustRoute = '/report/zeroTrust';
|
||||||
|
|
||||||
class AppComponent extends AuthComponent {
|
class AppComponent extends AuthComponent {
|
||||||
updateStatus = () => {
|
updateStatus = () => {
|
||||||
if (this.state.isLoggedIn === false){
|
if (this.state.isLoggedIn === false) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.auth.loggedIn()
|
this.auth.loggedIn()
|
||||||
|
|
|
@ -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 (
|
|
||||||
<Card key={'cmdDiv' + this.state.selectedIp} style={{'margin': '0.5em'}}>
|
|
||||||
<div style={{'overflow': 'auto', 'padding': '0.5em'}}>
|
|
||||||
<CopyToClipboard text={cmdText} className="pull-right btn-sm">
|
|
||||||
<Button style={{margin: '-0.5em'}} title="Copy to Clipboard">
|
|
||||||
<FontAwesomeIcon icon={faClipboard}/>
|
|
||||||
</Button>
|
|
||||||
</CopyToClipboard>
|
|
||||||
<code>{cmdText}</code>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
setSelectedOs = (key) => {
|
|
||||||
this.setState({
|
|
||||||
selectedOs: key
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
setSelectedIp = (key) => {
|
|
||||||
this.setState({
|
|
||||||
selectedIp: key
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
static renderIconByState(state) {
|
|
||||||
if (state === 'running') {
|
|
||||||
return (<FontAwesomeIcon icon={faCheck} className="text-success" style={{'marginLeft': '5px'}}/>)
|
|
||||||
} else if (state === 'installing') {
|
|
||||||
return (<FontAwesomeIcon icon={faSync} className="text-success" style={{'marginLeft': '5px'}}/>)
|
|
||||||
} 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 (
|
|
||||||
<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>
|
|
||||||
{
|
|
||||||
this.state.ips.length > 1 ?
|
|
||||||
<Nav variant="pills" activeKey={this.state.selectedIp} onSelect={this.setSelectedIp}
|
|
||||||
style={{'marginBottom': '2em'}}>
|
|
||||||
{this.state.ips.map(ip => <Nav.Item key={ip}><Nav.Link eventKey={ip}>{ip}</Nav.Link></Nav.Item>)}
|
|
||||||
</Nav>
|
|
||||||
: <div style={{'marginBottom': '2em'}}/>
|
|
||||||
}
|
|
||||||
|
|
||||||
<AwsRunTable
|
|
||||||
data={this.state.awsMachines}
|
|
||||||
ref={r => (this.awsTable = r)}
|
|
||||||
/>
|
|
||||||
<div style={{'marginTop': '1em'}}>
|
|
||||||
<Button
|
|
||||||
onClick={this.runOnAws}
|
|
||||||
className={'btn btn-default btn-md center-block'}
|
|
||||||
disabled={this.state.awsClicked}>
|
|
||||||
Run on selected machines
|
|
||||||
{this.state.awsClicked ?
|
|
||||||
<FontAwesomeIcon icon={faSync} className="text-success" style={{'marginLeft': '5px'}}/> : null}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
closeModal = () => {
|
|
||||||
this.setState({
|
|
||||||
showModal: false
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
|
|
||||||
lg={{offset: 3, span: 9}} xl={{offset: 2, span: 7}}
|
|
||||||
className={'main'}>
|
|
||||||
<h1 className="page-title">1. Run Monkey</h1>
|
|
||||||
<p style={{'marginBottom': '2em', 'fontSize': '1.2em'}}>
|
|
||||||
Go ahead and run the monkey!
|
|
||||||
<i> (Or <Link to="/configure">configure the monkey</Link> to fine tune its behavior)</i>
|
|
||||||
</p>
|
|
||||||
<p className={'text-center'}>
|
|
||||||
<Button onClick={this.runLocalMonkey}
|
|
||||||
variant={'outline-monkey'}
|
|
||||||
size='lg'
|
|
||||||
disabled={this.state.runningOnIslandState !== 'not_running'}
|
|
||||||
>
|
|
||||||
Run on Monkey Island Server
|
|
||||||
{RunMonkeyPageComponent.renderIconByState(this.state.runningOnIslandState)}
|
|
||||||
</Button>
|
|
||||||
<MissingBinariesModal
|
|
||||||
showModal={this.state.showModal}
|
|
||||||
onClose={this.closeModal}
|
|
||||||
errorDetails={this.state.errorDetails}/>
|
|
||||||
{
|
|
||||||
// TODO: implement button functionality
|
|
||||||
/*
|
|
||||||
<button
|
|
||||||
className="btn btn-default"
|
|
||||||
disabled={this.state.runningOnClientState !== 'not_running'}
|
|
||||||
style={{'marginLeft': '1em'}}>
|
|
||||||
Download and run locally
|
|
||||||
{ this.renderIconByState(this.state.runningOnClientState) }
|
|
||||||
</button>
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
</p>
|
|
||||||
<p className="text-center">
|
|
||||||
OR
|
|
||||||
</p>
|
|
||||||
<p className={'text-center'}
|
|
||||||
style={this.state.showManual || !this.state.isOnAws ? {'marginBottom': '2em'} : {}}>
|
|
||||||
<Button onClick={this.toggleManual}
|
|
||||||
variant={'outline-monkey'}
|
|
||||||
size='lg'
|
|
||||||
className={(this.state.showManual ? 'active' : '')}>
|
|
||||||
Run on a machine of your choice
|
|
||||||
</Button>
|
|
||||||
</p>
|
|
||||||
<Collapse in={this.state.showManual}>
|
|
||||||
<div style={{'marginBottom': '2em'}}>
|
|
||||||
<p style={{'fontSize': '1.2em'}}>
|
|
||||||
Choose the operating system where you want to run the monkey:
|
|
||||||
</p>
|
|
||||||
<Row>
|
|
||||||
<Col>
|
|
||||||
<Nav variant='pills' fill id={'bootstrap-override'} className={'run-on-os-buttons'}
|
|
||||||
activeKey={this.state.selectedOs} onSelect={this.setSelectedOs}>
|
|
||||||
<Nav.Item>
|
|
||||||
<Nav.Link eventKey={'windows-32'}>
|
|
||||||
Windows (32 bit)
|
|
||||||
</Nav.Link>
|
|
||||||
</Nav.Item>
|
|
||||||
<Nav.Item>
|
|
||||||
<Nav.Link eventKey='windows-64'>
|
|
||||||
Windows (64 bit)
|
|
||||||
</Nav.Link>
|
|
||||||
</Nav.Item>
|
|
||||||
<Nav.Item>
|
|
||||||
<Nav.Link eventKey='linux-32'>
|
|
||||||
Linux (32 bit)
|
|
||||||
</Nav.Link>
|
|
||||||
</Nav.Item>
|
|
||||||
<Nav.Item>
|
|
||||||
<Nav.Link eventKey='linux-64'>
|
|
||||||
Linux (64 bit)
|
|
||||||
</Nav.Link>
|
|
||||||
</Nav.Item>
|
|
||||||
</Nav>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
{this.state.ips.length > 1 ?
|
|
||||||
<div>
|
|
||||||
<Row>
|
|
||||||
<Col>
|
|
||||||
<p style={{'fontSize': '1.2em'}}>
|
|
||||||
Choose the interface to communicate with:
|
|
||||||
</p>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<Col>
|
|
||||||
<Nav variant="pills" fill activeKey={this.state.selectedIp} onSelect={this.setSelectedIp}
|
|
||||||
className={'run-on-os-buttons'}>
|
|
||||||
{this.state.ips.map(ip => <Nav.Item key={ip}>
|
|
||||||
<Nav.Link eventKey={ip}>{ip}</Nav.Link></Nav.Item>)}
|
|
||||||
</Nav>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
: <div style={{'marginBottom': '2em'}}/>
|
|
||||||
}
|
|
||||||
<p style={{'fontSize': '1.2em'}}>
|
|
||||||
Copy the following command to your machine and run it with Administrator or root privileges.
|
|
||||||
</p>
|
|
||||||
{this.generateCmdDiv()}
|
|
||||||
</div>
|
|
||||||
</Collapse>
|
|
||||||
{
|
|
||||||
this.state.isLoadingAws ?
|
|
||||||
<div style={{'marginBottom': '2em', 'align': 'center'}}>
|
|
||||||
<div className='sweet-loading'>
|
|
||||||
<GridLoader
|
|
||||||
css={loading_css_override}
|
|
||||||
sizeUnit={'px'}
|
|
||||||
size={30}
|
|
||||||
color={'#ffcc00'}
|
|
||||||
loading={this.state.loading}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
{
|
|
||||||
this.state.isOnAws ?
|
|
||||||
<p className="text-center">
|
|
||||||
OR
|
|
||||||
</p>
|
|
||||||
:
|
|
||||||
null
|
|
||||||
}
|
|
||||||
{
|
|
||||||
this.state.isOnAws ?
|
|
||||||
<p style={{'marginBottom': '2em'}} className={'text-center'}>
|
|
||||||
<Button onClick={this.toggleAws}
|
|
||||||
className={(this.state.showAws ? ' active' : '')}
|
|
||||||
size='lg'
|
|
||||||
variant={'outline-monkey'}>
|
|
||||||
Run on AWS machine of your choice
|
|
||||||
</Button>
|
|
||||||
</p>
|
|
||||||
:
|
|
||||||
null
|
|
||||||
}
|
|
||||||
<Collapse in={this.state.showAws}>
|
|
||||||
{
|
|
||||||
this.state.isErrorWhileCollectingAwsMachines ?
|
|
||||||
<div style={{'marginTop': '1em'}}>
|
|
||||||
<p className="alert alert-danger">
|
|
||||||
<FontAwesomeIcon icon={faExclamationTriangle} style={{'marginRight': '5px'}}/>
|
|
||||||
Error while collecting AWS machine data. Error
|
|
||||||
message: <code>{this.state.awsMachineCollectionErrorMsg}</code><br/>
|
|
||||||
Are you sure you've set the correct role on your Island AWS machine?<br/>
|
|
||||||
Not sure what this is? <a
|
|
||||||
href="https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS-EC2-instances">Read
|
|
||||||
the documentation</a>!
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
:
|
|
||||||
this.renderAwsMachinesDiv()
|
|
||||||
}
|
|
||||||
|
|
||||||
</Collapse>
|
|
||||||
|
|
||||||
<p style={{'fontSize': '1.2em'}}>
|
|
||||||
Go ahead and monitor the ongoing infection in the <Link to="/infection/map">Infection Map</Link> view.
|
|
||||||
</p>
|
|
||||||
</Col>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default RunMonkeyPageComponent;
|
|
|
@ -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 (
|
||||||
|
<Nav variant='tabs' activeKey={selectedCommand.type} onSelect={setSelectedCommandByName}>
|
||||||
|
{props.commands.map(command => {
|
||||||
|
return (
|
||||||
|
<Nav.Item key={command.type}>
|
||||||
|
<Nav.Link eventKey={command.type}>{command.type}</Nav.Link>
|
||||||
|
</Nav.Item>);
|
||||||
|
})}
|
||||||
|
</Nav>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={'command-display'}>
|
||||||
|
{renderNav()}
|
||||||
|
<Card>
|
||||||
|
<div style={{'overflow': 'auto', 'padding': '0.5em'}}>
|
||||||
|
<CopyToClipboard text={selectedCommand.command} className="pull-right btn-sm">
|
||||||
|
<Button style={{margin: '-0.5em'}} title="Copy to Clipboard">
|
||||||
|
<FontAwesomeIcon icon={faClipboard}/>
|
||||||
|
</Button>
|
||||||
|
</CopyToClipboard>
|
||||||
|
<code>{selectedCommand.command}</code>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
commandDisplay.propTypes = {
|
||||||
|
commands: PropTypes.arrayOf(PropTypes.exact({
|
||||||
|
type: PropTypes.string,
|
||||||
|
command: PropTypes.string
|
||||||
|
}))
|
||||||
|
}
|
|
@ -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) =>
|
||||||
|
<div key={ip}>{ip}</div>
|
||||||
|
);
|
||||||
|
return (<div>{ips}</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InterfaceSelection;
|
|
@ -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 (
|
||||||
|
<>
|
||||||
|
<DropdownSelect defaultKey={OS_TYPES.WINDOWS_64} options={osTypes} onClick={setOsType} variant={'outline-monkey'}/>
|
||||||
|
<DropdownSelect defaultKey={0} options={props.ips} onClick={setIp} variant={'outline-monkey'}/>
|
||||||
|
<CommandDisplay commands={commands}/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LocalManualRunOptions;
|
|
@ -0,0 +1,6 @@
|
||||||
|
export const OS_TYPES = {
|
||||||
|
WINDOWS_32: 'win32',
|
||||||
|
WINDOWS_64: 'win64',
|
||||||
|
LINUX_32: 'linux32',
|
||||||
|
LINUX_64: 'linux64'
|
||||||
|
}
|
|
@ -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 (
|
||||||
|
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
|
||||||
|
lg={{offset: 3, span: 9}} xl={{offset: 2, span: 7}}
|
||||||
|
className={'main'}>
|
||||||
|
<h1 className="page-title">1. Run Monkey</h1>
|
||||||
|
<p style={{'marginBottom': '2em', 'fontSize': '1.2em'}}>
|
||||||
|
Go ahead and run the monkey!
|
||||||
|
<i> (Or <Link to="/configure">configure the monkey</Link> to fine tune its behavior)</i>
|
||||||
|
</p>
|
||||||
|
<RunOptions />
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RunMonkeyPageComponent;
|
|
@ -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 (<FontAwesomeIcon icon={faCheck}
|
||||||
|
className={`monkey-on-island-run-state-icon text-success`}/>)
|
||||||
|
} else if (this.state.runningOnIslandState === MONKEY_STATES.STARTING) {
|
||||||
|
return (<FontAwesomeIcon icon={faSync}
|
||||||
|
className={`monkey-on-island-run-state-icon text-success spinning-icon`}/>)
|
||||||
|
} else if (this.state.runningOnIslandState === MONKEY_STATES.FAILED) {
|
||||||
|
return (<FontAwesomeIcon icon={faTimes}
|
||||||
|
className={`monkey-on-island-run-state-icon text-danger`}/>)
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let description = this.props.description !== undefined ? (<p>{this.props.description}</p>) : ''
|
||||||
|
let icon = this.props.icon !== undefined ? (<FontAwesomeIcon icon={this.props.icon}/>) : ''
|
||||||
|
return (
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<IslandMonkeyRunErrorModal
|
||||||
|
showModal={this.state.showModal}
|
||||||
|
onClose={this.closeModal}
|
||||||
|
errorDetails={this.state.errorDetails}/>
|
||||||
|
<Button variant={'outline-monkey'} size='lg' className={'selection-button'}
|
||||||
|
onClick={this.runIslandMonkey}>
|
||||||
|
{icon}
|
||||||
|
<h1>{this.props.title}</h1>
|
||||||
|
{description}
|
||||||
|
{this.getMonkeyRunStateIcon()}
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RunOnIslandButton;
|
|
@ -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 (<div>Loading</div>)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultContents() {
|
||||||
|
const newProps = cloneDeep({...props});
|
||||||
|
return InlineSelection(defaultContents, newProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultContents() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<RunOnIslandButton title={'From Island'}
|
||||||
|
description={'Start on Monkey Island server.'}
|
||||||
|
icon={faExpandArrowsAlt}/>
|
||||||
|
<NextSelectionButton title={'Manual'}
|
||||||
|
description={'Run on a machine via command.'}
|
||||||
|
icon={faLaptopCode}
|
||||||
|
onButtonClick={() => {
|
||||||
|
setComponent(LocalManualRunOptions,
|
||||||
|
{ips: ips, setComponent: setComponent})
|
||||||
|
}}/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RunOptions;
|
|
@ -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\`;`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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`;
|
||||||
|
}
|
|
@ -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';`;
|
||||||
|
}
|
|
@ -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';`;
|
||||||
|
}
|
|
@ -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 (
|
||||||
|
<Dropdown.Item onClick={() => { setSelectedOption(key);
|
||||||
|
props.onClick(key)}}
|
||||||
|
active={(key === selectedOption)}
|
||||||
|
key={value}>
|
||||||
|
{value}
|
||||||
|
</Dropdown.Item>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dropdown>
|
||||||
|
<Dropdown.Toggle variant={props.variant !== undefined ? props.variant : 'success'} id='dropdown-basic'>
|
||||||
|
{props.options[selectedOption]}
|
||||||
|
</Dropdown.Toggle>
|
||||||
|
|
||||||
|
<Dropdown.Menu>
|
||||||
|
{generateDropdownItems(props.options)}
|
||||||
|
</Dropdown.Menu>
|
||||||
|
</Dropdown>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
DropdownSelect.propTypes = {
|
||||||
|
options: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
|
||||||
|
defaultKey: PropTypes.oneOfType([PropTypes.string,PropTypes.number]),
|
||||||
|
onClick: PropTypes.func
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import React from 'react';
|
||||||
|
const Emoji = props => (
|
||||||
|
<span
|
||||||
|
className='emoji'
|
||||||
|
role='img'
|
||||||
|
aria-label={props.label ? props.label : ''}
|
||||||
|
aria-hidden={props.label ? 'false' : 'true'}
|
||||||
|
>
|
||||||
|
{props.symbol}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
export default Emoji;
|
|
@ -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 (
|
||||||
|
<span>
|
||||||
|
Some Monkey binaries are not found where they should be...<br/>
|
||||||
|
You can download the files from <a href="https://github.com/guardicore/monkey/releases/latest"
|
||||||
|
target="blank">here</a>,
|
||||||
|
at the bottommost section titled "Assets", and place them under the
|
||||||
|
directory <code>monkey/monkey_island/cc/binaries</code>.
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
getMonkeyAlreadyRunningContent() {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
Most likely, monkey is already running on the Island. Wait until it finishes or kill the process to run again.
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
getUndefinedErrorContent() {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
You encountered an undefined error. Please report it to support@infectionmonkey.com or our slack channel.
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Modal show={this.state.showModal} onHide={() => this.props.onClose()}>
|
||||||
|
<Modal.Body>
|
||||||
|
<h3>
|
||||||
|
<div className='text-center'>Uh oh...</div>
|
||||||
|
</h3>
|
||||||
|
<div style={{'marginTop': '1em', 'marginBottom': '1em'}}>
|
||||||
|
<p className="alert alert-warning">
|
||||||
|
<FontAwesomeIcon icon={faExclamationTriangle} style={{'marginRight': '5px'}}/>
|
||||||
|
{this.getDisplayContentByError(this.state.errorDetails)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<hr/>
|
||||||
|
<h4>
|
||||||
|
Error Details
|
||||||
|
</h4>
|
||||||
|
<div style={{'marginTop': '1em', 'marginBottom': '1em'}}>
|
||||||
|
<pre>
|
||||||
|
{this.state.errorDetails}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div className='text-center'>
|
||||||
|
<button type='button' className='btn btn-success btn-lg' style={{margin: '5px'}}
|
||||||
|
onClick={() => this.props.onClose()}>
|
||||||
|
Dismiss
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IslandMonkeyRunErrorModal;
|
|
@ -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 (
|
|
||||||
<Modal show={this.state.showModal} onHide={() => this.props.onClose()}>
|
|
||||||
<Modal.Body>
|
|
||||||
<h3>
|
|
||||||
<div className='text-center'>Uh oh...</div>
|
|
||||||
</h3>
|
|
||||||
<div style={{'marginTop': '1em', 'marginBottom': '1em'}}>
|
|
||||||
<p className="alert alert-warning">
|
|
||||||
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
|
|
||||||
Some Monkey binaries are not found where they should be...<br/>
|
|
||||||
You can download the files from <a href="https://github.com/guardicore/monkey/releases/latest" target="blank">here</a>,
|
|
||||||
at the bottommost section titled "Assets", and place them under the directory <code>monkey/monkey_island/cc/binaries</code>.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<hr/>
|
|
||||||
<h4>
|
|
||||||
Error Details
|
|
||||||
</h4>
|
|
||||||
<div style={{'marginTop': '1em', 'marginBottom': '1em'}}>
|
|
||||||
<pre>
|
|
||||||
{this.state.errorDetails}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
<div className='text-center'>
|
|
||||||
<button type='button' className='btn btn-success btn-lg' style={{margin: '5px'}}
|
|
||||||
onClick={() => this.props.onClose()}>
|
|
||||||
Dismiss
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</Modal.Body>
|
|
||||||
</Modal>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MissingBinariesModal;
|
|
|
@ -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 (
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<Button variant={'outline-dark'} onClick={props.onClick} className={'back-button'}>
|
||||||
|
<FontAwesomeIcon icon={faCaretLeft} />
|
||||||
|
<h1>Back</h1>
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
backButton.propTypes = {
|
||||||
|
onClick: PropTypes.func
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
|
||||||
|
export default function CommandSection(props){
|
||||||
|
return (
|
||||||
|
<div className={'command-section'}>
|
||||||
|
{props.commands[0].name}
|
||||||
|
{props.commands[0].command}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandSection.propTypes = {
|
||||||
|
commands: PropTypes.arrayOf(PropTypes.exact({
|
||||||
|
name: PropTypes.string,
|
||||||
|
command: PropTypes.string
|
||||||
|
}))
|
||||||
|
}
|
|
@ -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 (
|
||||||
|
<Container className={'inline-selection-component'}>
|
||||||
|
<Row>
|
||||||
|
<Col lg={8} md={10} sm={12}>
|
||||||
|
<WrappedComponent {...props}/>
|
||||||
|
{renderBackButton(props)}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBackButton(props){
|
||||||
|
if(props.onBackButtonClick !== undefined){
|
||||||
|
return (<BackButton onClick={props.onBackButtonClick}/>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InlineSelection.propTypes = {
|
||||||
|
setComponent: PropTypes.func,
|
||||||
|
ips: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
onBackButtonClick: PropTypes.func
|
||||||
|
}
|
|
@ -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 ? (<p>{props.description}</p>) : ''
|
||||||
|
let icon = props.icon !== undefined ? (<FontAwesomeIcon icon={props.icon}/>) : ''
|
||||||
|
return (
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<Button variant={'outline-monkey'} size='lg' className={'selection-button'}
|
||||||
|
onClick={props.onButtonClick}>
|
||||||
|
{icon}
|
||||||
|
<h1>{props.title}</h1>
|
||||||
|
{description}
|
||||||
|
<FontAwesomeIcon icon={faAngleRight} className={'angle-right'}/>
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
nextSelectionButton.propTypes = {
|
||||||
|
title: PropTypes.string,
|
||||||
|
icon: FontAwesomeIcon,
|
||||||
|
description: PropTypes.string,
|
||||||
|
onButtonClick: PropTypes.func
|
||||||
|
}
|
|
@ -12,6 +12,11 @@
|
||||||
@import 'components/PreviewPane';
|
@import 'components/PreviewPane';
|
||||||
@import 'components/AdvancedMultiSelect';
|
@import 'components/AdvancedMultiSelect';
|
||||||
@import 'components/particle-component/ParticleBackground';
|
@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
|
// Define custom elements after bootstrap import
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
.monkey-on-island-run-state-icon {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
right: 23px;
|
||||||
|
top: 28%;
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -2,9 +2,9 @@ Flask-JWT-Extended==3.24.1
|
||||||
Flask-Pymongo>=2.3.0
|
Flask-Pymongo>=2.3.0
|
||||||
Flask-Restful>=0.3.8
|
Flask-Restful>=0.3.8
|
||||||
PyInstaller==3.6
|
PyInstaller==3.6
|
||||||
awscli>=1.18.131
|
awscli==1.18.131
|
||||||
boto3>=1.14.54
|
boto3==1.14.54
|
||||||
botocore>=1.17.54,<1.18.0
|
botocore==1.17.54
|
||||||
cffi>=1.8,!=1.11.3
|
cffi>=1.8,!=1.11.3
|
||||||
dpath>=2.0
|
dpath>=2.0
|
||||||
flask>=1.1
|
flask>=1.1
|
||||||
|
@ -26,4 +26,4 @@ virtualenv>=20.0.26
|
||||||
werkzeug>=1.0.1
|
werkzeug>=1.0.1
|
||||||
wheel>=0.34.2
|
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
|
||||||
|
|
Loading…
Reference in New Issue