forked from p15670423/monkey
Merge pull request #1326 from guardicore/ransomware_landing_page
Ransomware landing page
This commit is contained in:
commit
5a2bb51789
File diff suppressed because it is too large
Load Diff
|
@ -73,6 +73,7 @@
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.15.1",
|
"@fortawesome/free-solid-svg-icons": "^5.15.1",
|
||||||
"@fortawesome/react-fontawesome": "^0.1.12",
|
"@fortawesome/react-fontawesome": "^0.1.12",
|
||||||
"@kunukn/react-collapse": "^1.2.7",
|
"@kunukn/react-collapse": "^1.2.7",
|
||||||
|
"@types/react-router-dom": "^5.1.8",
|
||||||
"bootstrap": "^4.5.3",
|
"bootstrap": "^4.5.3",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"core-js": "^3.7.0",
|
"core-js": "^3.7.0",
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
import AuthComponent from "./AuthComponent";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export class Response{
|
||||||
|
body: any
|
||||||
|
status: number
|
||||||
|
|
||||||
|
constructor(body: any, status: number) {
|
||||||
|
this.body = body
|
||||||
|
this.status = status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IslandHttpClient extends AuthComponent {
|
||||||
|
post(endpoint: string, contents: any): Promise<Response>{
|
||||||
|
let status = null;
|
||||||
|
return this.authFetch(endpoint,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify(contents)
|
||||||
|
})
|
||||||
|
.then(res => {status = res.status; return res.json()})
|
||||||
|
.then(res => new Response(res, status));
|
||||||
|
}
|
||||||
|
|
||||||
|
get(endpoint: string): Promise<Response>{
|
||||||
|
let status = null;
|
||||||
|
return this.authFetch(endpoint)
|
||||||
|
.then(res => {status = res.status; return res.json()})
|
||||||
|
.then(res => new Response(res, status));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new IslandHttpClient();
|
|
@ -1,225 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import {BrowserRouter as Router, Redirect, Route, Switch} from 'react-router-dom';
|
|
||||||
import {Container} from 'react-bootstrap';
|
|
||||||
|
|
||||||
import GettingStartedPage from 'components/pages/GettingStartedPage';
|
|
||||||
import ConfigurePage from 'components/pages/ConfigurePage';
|
|
||||||
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';
|
|
||||||
import ReportPage from 'components/pages/ReportPage';
|
|
||||||
import LicensePage from 'components/pages/LicensePage';
|
|
||||||
import AuthComponent from 'components/AuthComponent';
|
|
||||||
import LoginPageComponent from 'components/pages/LoginPage';
|
|
||||||
import RegisterPageComponent from 'components/pages/RegisterPage';
|
|
||||||
import Notifier from 'react-desktop-notification';
|
|
||||||
import NotFoundPage from 'components/pages/NotFoundPage';
|
|
||||||
|
|
||||||
|
|
||||||
import 'normalize.css/normalize.css';
|
|
||||||
import 'react-data-components/css/table-twbs.css';
|
|
||||||
import 'styles/App.css';
|
|
||||||
import 'react-toggle/style.css';
|
|
||||||
import 'react-table/react-table.css';
|
|
||||||
import notificationIcon from '../images/notification-logo-512x512.png';
|
|
||||||
import {StandardLayoutComponent} from './layouts/StandardLayoutComponent';
|
|
||||||
import LoadingScreen from './ui-components/LoadingScreen';
|
|
||||||
|
|
||||||
const reportZeroTrustRoute = '/report/zeroTrust';
|
|
||||||
const islandModeRoute = '/api/island-mode'
|
|
||||||
|
|
||||||
class AppComponent extends AuthComponent {
|
|
||||||
updateStatus = () => {
|
|
||||||
if (this.state.isLoggedIn === false) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.auth.loggedIn()
|
|
||||||
.then(res => {
|
|
||||||
if (this.state.isLoggedIn !== res) {
|
|
||||||
this.setState({
|
|
||||||
isLoggedIn: res
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!res) {
|
|
||||||
this.auth.needsRegistration()
|
|
||||||
.then(result => {
|
|
||||||
this.setState({
|
|
||||||
needsRegistration: result
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res) {
|
|
||||||
this.authFetch('/api')
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(res => {
|
|
||||||
// This check is used to prevent unnecessary re-rendering
|
|
||||||
let isChanged = false;
|
|
||||||
for (let step in this.state.completedSteps) {
|
|
||||||
if (this.state.completedSteps[step] !== res['completed_steps'][step]) {
|
|
||||||
isChanged = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isChanged) {
|
|
||||||
this.setState({completedSteps: res['completed_steps']});
|
|
||||||
this.showInfectionDoneNotification();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
renderRoute = (route_path, page_component, is_exact_path = false) => {
|
|
||||||
let render_func = () => {
|
|
||||||
switch (this.state.isLoggedIn) {
|
|
||||||
case true:
|
|
||||||
return page_component;
|
|
||||||
case false:
|
|
||||||
switch (this.state.needsRegistration) {
|
|
||||||
case true:
|
|
||||||
return <Redirect to={{pathname: '/register'}}/>
|
|
||||||
case false:
|
|
||||||
return <Redirect to={{pathname: '/login'}}/>;
|
|
||||||
default:
|
|
||||||
return <LoadingScreen text={'Loading page...'}/>;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return <LoadingScreen text={'Loading page...'}/>;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (is_exact_path) {
|
|
||||||
return <Route exact path={route_path} render={render_func}/>;
|
|
||||||
} else {
|
|
||||||
return <Route path={route_path} render={render_func}/>;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
redirectTo = (userPath, targetPath) => {
|
|
||||||
let pathQuery = new RegExp(userPath + '[/]?$', 'g');
|
|
||||||
if (window.location.pathname.match(pathQuery)) {
|
|
||||||
return <Redirect to={{pathname: targetPath}}/>
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
completedSteps: {
|
|
||||||
run_server: true,
|
|
||||||
run_monkey: false,
|
|
||||||
infection_done: false,
|
|
||||||
report_done: false,
|
|
||||||
isLoggedIn: undefined,
|
|
||||||
needsRegistration: undefined,
|
|
||||||
islandMode: undefined
|
|
||||||
},
|
|
||||||
noAuthLoginAttempted: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
updateIslandMode() {
|
|
||||||
this.authFetch(islandModeRoute)
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(res => {
|
|
||||||
this.setState({islandMode: res.mode})
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.updateStatus();
|
|
||||||
this.interval = setInterval(this.updateStatus, 10000);
|
|
||||||
this.updateIslandMode()
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
clearInterval(this.interval);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Router>
|
|
||||||
<Container fluid>
|
|
||||||
<Switch>
|
|
||||||
<Route path='/login' render={() => (<LoginPageComponent onStatusChange={this.updateStatus}/>)}/>
|
|
||||||
<Route path='/register' render={() => (<RegisterPageComponent onStatusChange={this.updateStatus}/>)}/>
|
|
||||||
{this.renderRoute('/',
|
|
||||||
<StandardLayoutComponent component={GettingStartedPage}
|
|
||||||
completedSteps={this.state.completedSteps}
|
|
||||||
onStatusChange={this.updateStatus}
|
|
||||||
/>,
|
|
||||||
true)}
|
|
||||||
{this.renderRoute('/configure',
|
|
||||||
<StandardLayoutComponent component={ConfigurePage}
|
|
||||||
islandMode={this.state.islandMode}
|
|
||||||
onStatusChange={this.updateStatus}
|
|
||||||
completedSteps={this.state.completedSteps}/>)}
|
|
||||||
{this.renderRoute('/run-monkey',
|
|
||||||
<StandardLayoutComponent component={RunMonkeyPage}
|
|
||||||
islandMode={this.state.islandMode}
|
|
||||||
onStatusChange={this.updateStatus}
|
|
||||||
completedSteps={this.state.completedSteps}/>)}
|
|
||||||
{this.renderRoute('/infection/map',
|
|
||||||
<StandardLayoutComponent component={MapPage}
|
|
||||||
onStatusChange={this.updateStatus}
|
|
||||||
completedSteps={this.state.completedSteps}/>)}
|
|
||||||
{this.renderRoute('/infection/telemetry',
|
|
||||||
<StandardLayoutComponent component={TelemetryPage}
|
|
||||||
onStatusChange={this.updateStatus}
|
|
||||||
completedSteps={this.state.completedSteps}/>)}
|
|
||||||
{this.renderRoute('/start-over',
|
|
||||||
<StandardLayoutComponent component={StartOverPage}
|
|
||||||
onStatusChange={this.updateStatus}
|
|
||||||
completedSteps={this.state.completedSteps}/>)}
|
|
||||||
{this.redirectTo('/report', '/report/security')}
|
|
||||||
{this.renderRoute('/report/security',
|
|
||||||
<StandardLayoutComponent component={ReportPage}
|
|
||||||
completedSteps={this.state.completedSteps}/>)}
|
|
||||||
{this.renderRoute('/report/attack',
|
|
||||||
<StandardLayoutComponent component={ReportPage}
|
|
||||||
completedSteps={this.state.completedSteps}/>)}
|
|
||||||
{this.renderRoute('/report/zeroTrust',
|
|
||||||
<StandardLayoutComponent component={ReportPage}
|
|
||||||
completedSteps={this.state.completedSteps}/>)}
|
|
||||||
{this.renderRoute('/report/ransomware',
|
|
||||||
<StandardLayoutComponent component={ReportPage}
|
|
||||||
completedSteps={this.state.completedSteps}/>)}
|
|
||||||
{this.renderRoute('/license',
|
|
||||||
<StandardLayoutComponent component={LicensePage}
|
|
||||||
onStatusChange={this.updateStatus}
|
|
||||||
completedSteps={this.state.completedSteps}/>)}
|
|
||||||
<Route component={NotFoundPage}/>
|
|
||||||
</Switch>
|
|
||||||
</Container>
|
|
||||||
</Router>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
showInfectionDoneNotification() {
|
|
||||||
if (this.shouldShowNotification()) {
|
|
||||||
const hostname = window.location.hostname;
|
|
||||||
const port = window.location.port;
|
|
||||||
const protocol = window.location.protocol;
|
|
||||||
const url = `${protocol}//${hostname}:${port}${reportZeroTrustRoute}`;
|
|
||||||
|
|
||||||
Notifier.start(
|
|
||||||
'Monkey Island',
|
|
||||||
'Infection is done! Click here to go to the report page.',
|
|
||||||
url,
|
|
||||||
notificationIcon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldShowNotification() {
|
|
||||||
// No need to show the notification to redirect to the report if we're already in the report page
|
|
||||||
return (this.state.completedSteps.infection_done && !window.location.pathname.startsWith('/report'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AppComponent.defaultProps = {};
|
|
||||||
|
|
||||||
export default AppComponent;
|
|
|
@ -0,0 +1,266 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {BrowserRouter as Router, Redirect, Route, Switch} from 'react-router-dom';
|
||||||
|
import {Container} from 'react-bootstrap';
|
||||||
|
|
||||||
|
import ConfigurePage from './pages/ConfigurePage.js';
|
||||||
|
import RunMonkeyPage from './pages/RunMonkeyPage/RunMonkeyPage';
|
||||||
|
import MapPage from './pages/MapPage';
|
||||||
|
import TelemetryPage from './pages/TelemetryPage';
|
||||||
|
import StartOverPage from './pages/StartOverPage';
|
||||||
|
import ReportPage from './pages/ReportPage';
|
||||||
|
import LicensePage from './pages/LicensePage';
|
||||||
|
import AuthComponent from './AuthComponent';
|
||||||
|
import LoginPageComponent from './pages/LoginPage';
|
||||||
|
import RegisterPageComponent from './pages/RegisterPage';
|
||||||
|
import LandingPage from "./pages/LandingPage";
|
||||||
|
import Notifier from 'react-desktop-notification';
|
||||||
|
import NotFoundPage from './pages/NotFoundPage';
|
||||||
|
import GettingStartedPage from './pages/GettingStartedPage';
|
||||||
|
|
||||||
|
|
||||||
|
import 'normalize.css/normalize.css';
|
||||||
|
import 'react-data-components/css/table-twbs.css';
|
||||||
|
import 'styles/App.css';
|
||||||
|
import 'react-toggle/style.css';
|
||||||
|
import 'react-table/react-table.css';
|
||||||
|
import LoadingScreen from './ui-components/LoadingScreen';
|
||||||
|
import SidebarLayoutComponent from "./layouts/SidebarLayoutComponent";
|
||||||
|
import {CompletedSteps} from "./side-menu/CompletedSteps";
|
||||||
|
import Timeout = NodeJS.Timeout;
|
||||||
|
import IslandHttpClient from "./IslandHttpClient";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
|
|
||||||
|
let notificationIcon = require('../images/notification-logo-512x512.png');
|
||||||
|
|
||||||
|
const Routes = {
|
||||||
|
LandingPage: '/landing-page',
|
||||||
|
GettingStartedPage: '/',
|
||||||
|
Report: '/report',
|
||||||
|
AttackReport: '/report/attack',
|
||||||
|
ZeroTrustReport: '/report/zeroTrust',
|
||||||
|
SecurityReport: '/report/security',
|
||||||
|
RansomwareReport: '/report/ransomware',
|
||||||
|
LoginPage: '/login',
|
||||||
|
RegisterPage: '/register',
|
||||||
|
ConfigurePage: '/configure',
|
||||||
|
RunMonkeyPage: '/run-monkey',
|
||||||
|
MapPage: '/infection/map',
|
||||||
|
TelemetryPage: '/infection/telemetry',
|
||||||
|
StartOverPage: '/start-over',
|
||||||
|
LicensePage: '/license'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isReportRoute(route){
|
||||||
|
return route.startsWith(Routes.Report);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppComponent extends AuthComponent {
|
||||||
|
private interval: Timeout;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
let completedSteps = new CompletedSteps(false);
|
||||||
|
this.state = {
|
||||||
|
loading: true,
|
||||||
|
completedSteps: completedSteps,
|
||||||
|
islandMode: undefined,
|
||||||
|
noAuthLoginAttempted: undefined
|
||||||
|
};
|
||||||
|
this.interval = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStatus = () => {
|
||||||
|
if (this.state.isLoggedIn === false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.auth.loggedIn()
|
||||||
|
.then(res => {
|
||||||
|
if (this.state.isLoggedIn !== res) {
|
||||||
|
this.setState({
|
||||||
|
isLoggedIn: res
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res) {
|
||||||
|
this.auth.needsRegistration()
|
||||||
|
.then(result => {
|
||||||
|
this.setState({
|
||||||
|
needsRegistration: result
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
this.setMode()
|
||||||
|
.then(() => {
|
||||||
|
if (this.state.islandMode === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.authFetch('/api')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
let completedSteps = CompletedSteps.buildFromResponse(res.completed_steps);
|
||||||
|
// This check is used to prevent unnecessary re-rendering
|
||||||
|
if (_.isEqual(this.state.completedSteps, completedSteps)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({completedSteps: completedSteps});
|
||||||
|
this.showInfectionDoneNotification();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
setMode = () => {
|
||||||
|
return IslandHttpClient.get('/api/island-mode')
|
||||||
|
.then(res => {
|
||||||
|
this.setState({islandMode: res.body.mode});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderRoute = (route_path, page_component, is_exact_path = false) => {
|
||||||
|
let render_func = () => {
|
||||||
|
switch (this.state.isLoggedIn) {
|
||||||
|
case true:
|
||||||
|
if (this.needsRedirectionToLandingPage(route_path)) {
|
||||||
|
return <Redirect to={{pathname: Routes.LandingPage}}/>
|
||||||
|
} else if (this.needsRedirectionToGettingStarted(route_path)) {
|
||||||
|
return <Redirect to={{pathname: Routes.GettingStartedPage}}/>
|
||||||
|
}
|
||||||
|
return page_component;
|
||||||
|
case false:
|
||||||
|
switch (this.state.needsRegistration) {
|
||||||
|
case true:
|
||||||
|
return <Redirect to={{pathname: Routes.RegisterPage}}/>
|
||||||
|
case false:
|
||||||
|
return <Redirect to={{pathname: Routes.LoginPage}}/>;
|
||||||
|
default:
|
||||||
|
return <LoadingScreen text={'Loading page...'}/>;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return <LoadingScreen text={'Loading page...'}/>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (is_exact_path) {
|
||||||
|
return <Route exact path={route_path} render={render_func}/>;
|
||||||
|
} else {
|
||||||
|
return <Route path={route_path} render={render_func}/>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
needsRedirectionToLandingPage = (route_path) => {
|
||||||
|
return (this.state.islandMode === null && route_path !== Routes.LandingPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
needsRedirectionToGettingStarted = (route_path) => {
|
||||||
|
return route_path === Routes.LandingPage &&
|
||||||
|
this.state.islandMode !== null && this.state.islandMode !== undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
redirectTo = (userPath, targetPath) => {
|
||||||
|
let pathQuery = new RegExp(userPath + '[/]?$', 'g');
|
||||||
|
if (window.location.pathname.match(pathQuery)) {
|
||||||
|
return <Redirect to={{pathname: targetPath}}/>
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.updateStatus();
|
||||||
|
this.interval = setInterval(this.updateStatus, 10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
clearInterval(this.interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Router>
|
||||||
|
<Container fluid>
|
||||||
|
<Switch>
|
||||||
|
<Route path={Routes.LoginPage} render={() => (<LoginPageComponent onStatusChange={this.updateStatus}/>)}/>
|
||||||
|
<Route path={Routes.RegisterPage} render={() => (<RegisterPageComponent onStatusChange={this.updateStatus}/>)}/>
|
||||||
|
{this.renderRoute(Routes.LandingPage,
|
||||||
|
<SidebarLayoutComponent component={LandingPage}
|
||||||
|
sideNavDisabled={true}
|
||||||
|
completedSteps={new CompletedSteps()}
|
||||||
|
onStatusChange={this.updateStatus}/>)}
|
||||||
|
{this.renderRoute(Routes.GettingStartedPage,
|
||||||
|
<SidebarLayoutComponent component={GettingStartedPage}
|
||||||
|
completedSteps={this.state.completedSteps}
|
||||||
|
onStatusChange={this.updateStatus}
|
||||||
|
/>,
|
||||||
|
true)}
|
||||||
|
{this.renderRoute(Routes.ConfigurePage,
|
||||||
|
<SidebarLayoutComponent component={ConfigurePage}
|
||||||
|
islandMode={this.state.islandMode}
|
||||||
|
onStatusChange={this.updateStatus}
|
||||||
|
completedSteps={this.state.completedSteps}/>)}
|
||||||
|
{this.renderRoute(Routes.RunMonkeyPage,
|
||||||
|
<SidebarLayoutComponent component={RunMonkeyPage}
|
||||||
|
islandMode={this.state.islandMode}
|
||||||
|
onStatusChange={this.updateStatus}
|
||||||
|
completedSteps={this.state.completedSteps}/>)}
|
||||||
|
{this.renderRoute(Routes.MapPage,
|
||||||
|
<SidebarLayoutComponent component={MapPage}
|
||||||
|
onStatusChange={this.updateStatus}
|
||||||
|
completedSteps={this.state.completedSteps}/>)}
|
||||||
|
{this.renderRoute(Routes.TelemetryPage,
|
||||||
|
<SidebarLayoutComponent component={TelemetryPage}
|
||||||
|
onStatusChange={this.updateStatus}
|
||||||
|
completedSteps={this.state.completedSteps}/>)}
|
||||||
|
{this.renderRoute(Routes.StartOverPage,
|
||||||
|
<SidebarLayoutComponent component={StartOverPage}
|
||||||
|
onStatusChange={this.updateStatus}
|
||||||
|
completedSteps={this.state.completedSteps}/>)}
|
||||||
|
{this.redirectTo(Routes.Report, Routes.SecurityReport)}
|
||||||
|
{this.renderRoute(Routes.SecurityReport,
|
||||||
|
<SidebarLayoutComponent component={ReportPage}
|
||||||
|
completedSteps={this.state.completedSteps}/>)}
|
||||||
|
{this.renderRoute(Routes.AttackReport,
|
||||||
|
<SidebarLayoutComponent component={ReportPage}
|
||||||
|
completedSteps={this.state.completedSteps}/>)}
|
||||||
|
{this.renderRoute(Routes.ZeroTrustReport,
|
||||||
|
<SidebarLayoutComponent component={ReportPage}
|
||||||
|
completedSteps={this.state.completedSteps}/>)}
|
||||||
|
{this.renderRoute(Routes.RansomwareReport,
|
||||||
|
<SidebarLayoutComponent component={ReportPage}
|
||||||
|
completedSteps={this.state.completedSteps}/>)}
|
||||||
|
{this.renderRoute(Routes.LicensePage,
|
||||||
|
<SidebarLayoutComponent component={LicensePage}
|
||||||
|
onStatusChange={this.updateStatus}
|
||||||
|
completedSteps={this.state.completedSteps}/>)}
|
||||||
|
<Route component={NotFoundPage}/>
|
||||||
|
</Switch>
|
||||||
|
</Container>
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
showInfectionDoneNotification() {
|
||||||
|
if (this.shouldShowNotification()) {
|
||||||
|
const hostname = window.location.hostname;
|
||||||
|
const port = window.location.port;
|
||||||
|
const protocol = window.location.protocol;
|
||||||
|
const url = `${protocol}//${hostname}:${port}${Routes.ZeroTrustReport}`;
|
||||||
|
|
||||||
|
Notifier.start(
|
||||||
|
'Monkey Island',
|
||||||
|
'Infection is done! Click here to go to the report page.',
|
||||||
|
url,
|
||||||
|
notificationIcon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldShowNotification() {
|
||||||
|
// No need to show the notification to redirect to the report if we're already in the report page
|
||||||
|
return (this.state.completedSteps.infection_done && !window.location.pathname.startsWith(Routes.Report));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AppComponent;
|
|
@ -1,92 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import {NavLink} from 'react-router-dom';
|
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
|
||||||
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
|
|
||||||
import {faUndo} from '@fortawesome/free-solid-svg-icons/faUndo';
|
|
||||||
import {faExternalLinkAlt} from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import guardicoreLogoImage from '../images/guardicore-logo.png';
|
|
||||||
import logoImage from '../images/monkey-icon.svg';
|
|
||||||
import infectionMonkeyImage from '../images/infection-monkey.svg';
|
|
||||||
import VersionComponent from './side-menu/VersionComponent';
|
|
||||||
import '../styles/components/SideNav.scss';
|
|
||||||
|
|
||||||
|
|
||||||
class SideNavComponent extends React.Component {
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<NavLink to={'/'} exact={true}>
|
|
||||||
<div className='header'>
|
|
||||||
<img alt='logo' src={logoImage} style={{width: '5vw', margin: '15px'}}/>
|
|
||||||
<img src={infectionMonkeyImage} style={{width: '15vw'}} alt='Infection Monkey'/>
|
|
||||||
</div>
|
|
||||||
</NavLink>
|
|
||||||
|
|
||||||
<ul className='navigation'>
|
|
||||||
<li>
|
|
||||||
<NavLink to='/run-monkey'>
|
|
||||||
<span className='number'>1.</span>
|
|
||||||
Run Monkey
|
|
||||||
{this.props.completedSteps.run_monkey ?
|
|
||||||
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark'/>
|
|
||||||
: ''}
|
|
||||||
</NavLink>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<NavLink to='/infection/map'>
|
|
||||||
<span className='number'>2.</span>
|
|
||||||
Infection Map
|
|
||||||
{this.props.completedSteps.infection_done ?
|
|
||||||
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark'/>
|
|
||||||
: ''}
|
|
||||||
</NavLink>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<NavLink to='/report/security'
|
|
||||||
isActive={(_match, location) => {
|
|
||||||
return (location.pathname === '/report/attack'
|
|
||||||
|| location.pathname === '/report/zeroTrust'
|
|
||||||
|| location.pathname === '/report/security')
|
|
||||||
}}>
|
|
||||||
<span className='number'>3.</span>
|
|
||||||
Security Reports
|
|
||||||
{this.props.completedSteps.report_done ?
|
|
||||||
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark'/>
|
|
||||||
: ''}
|
|
||||||
</NavLink>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<NavLink to='/start-over'>
|
|
||||||
<span className='number'><FontAwesomeIcon icon={faUndo} style={{'marginLeft': '-1px'}}/></span>
|
|
||||||
Start Over
|
|
||||||
</NavLink>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
<ul>
|
|
||||||
<li><NavLink to='/configure'>Configuration</NavLink></li>
|
|
||||||
<li><NavLink to='/infection/telemetry'>Logs</NavLink></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
<div className='guardicore-link text-center' style={{'marginBottom': '0.5em'}}>
|
|
||||||
<span>Powered by</span>
|
|
||||||
<a href='http://www.guardicore.com' rel='noopener noreferrer' target='_blank'>
|
|
||||||
<img src={guardicoreLogoImage} alt='GuardiCore'/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className='license-link text-center'>
|
|
||||||
<a href='https://www.guardicore.com/infectionmonkey/docs' rel="noopener noreferrer" target="_blank">
|
|
||||||
<FontAwesomeIcon icon={faExternalLinkAlt} /> Documentation
|
|
||||||
</a>
|
|
||||||
<br/>
|
|
||||||
<NavLink to='/license'>License</NavLink>
|
|
||||||
</div>
|
|
||||||
<VersionComponent/>
|
|
||||||
</>)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SideNavComponent;
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {NavLink} from 'react-router-dom';
|
||||||
|
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||||
|
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
|
||||||
|
import {faUndo} from '@fortawesome/free-solid-svg-icons/faUndo';
|
||||||
|
import {faExternalLinkAlt} from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import VersionComponent from './side-menu/VersionComponent';
|
||||||
|
import '../styles/components/SideNav.scss';
|
||||||
|
import {CompletedSteps} from "./side-menu/CompletedSteps";
|
||||||
|
import {isReportRoute} from "./Main";
|
||||||
|
|
||||||
|
|
||||||
|
const guardicoreLogoImage = require('../images/guardicore-logo.png');
|
||||||
|
const logoImage = require('../images/monkey-icon.svg');
|
||||||
|
const infectionMonkeyImage = require('../images/infection-monkey.svg');
|
||||||
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
disabled?: boolean,
|
||||||
|
completedSteps: CompletedSteps
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const SideNavComponent = ({disabled=false, completedSteps}: Props) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NavLink to={'/'} exact={true}>
|
||||||
|
<div className='header'>
|
||||||
|
<img alt='logo' src={logoImage} style={{width: '5vw', margin: '15px'}}/>
|
||||||
|
<img src={infectionMonkeyImage} style={{width: '15vw'}} alt='Infection Monkey'/>
|
||||||
|
</div>
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
|
<ul className='navigation'>
|
||||||
|
<li>
|
||||||
|
<NavLink to='/run-monkey' className={getNavLinkClass()}>
|
||||||
|
<span className='number'>1.</span>
|
||||||
|
Run Monkey
|
||||||
|
{completedSteps.runMonkey ?
|
||||||
|
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark'/>
|
||||||
|
: ''}
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<NavLink to='/infection/map' className={getNavLinkClass()}>
|
||||||
|
<span className='number'>2.</span>
|
||||||
|
Infection Map
|
||||||
|
{completedSteps.infectionDone ?
|
||||||
|
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark'/>
|
||||||
|
: ''}
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<NavLink to='/report/security'
|
||||||
|
className={getNavLinkClass()}
|
||||||
|
isActive={(_match, location) => {
|
||||||
|
return (isReportRoute(location.pathname))
|
||||||
|
}}>
|
||||||
|
<span className='number'>3.</span>
|
||||||
|
Security Reports
|
||||||
|
{completedSteps.reportDone ?
|
||||||
|
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark'/>
|
||||||
|
: ''}
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<NavLink to='/start-over' className={getNavLinkClass()}>
|
||||||
|
<span className='number'><FontAwesomeIcon icon={faUndo} style={{'marginLeft': '-1px'}}/></span>
|
||||||
|
Start Over
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
<ul>
|
||||||
|
<li><NavLink to='/configure'
|
||||||
|
className={getNavLinkClass()}>
|
||||||
|
Configuration
|
||||||
|
</NavLink></li>
|
||||||
|
<li><NavLink to='/infection/telemetry'
|
||||||
|
className={getNavLinkClass()}>
|
||||||
|
Logs
|
||||||
|
</NavLink></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
<div className='guardicore-link text-center' style={{'marginBottom': '0.5em'}}>
|
||||||
|
<span>Powered by</span>
|
||||||
|
<a href='http://www.guardicore.com' rel='noopener noreferrer' target='_blank'>
|
||||||
|
<img src={guardicoreLogoImage} alt='GuardiCore'/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className='license-link text-center'>
|
||||||
|
<a href='https://www.guardicore.com/infectionmonkey/docs' rel="noopener noreferrer" target="_blank">
|
||||||
|
<FontAwesomeIcon icon={faExternalLinkAlt} /> Documentation
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
|
<NavLink to='/license'>License</NavLink>
|
||||||
|
</div>
|
||||||
|
<VersionComponent/>
|
||||||
|
</>);
|
||||||
|
|
||||||
|
function getNavLinkClass() {
|
||||||
|
if(disabled){
|
||||||
|
return `nav-link disabled`
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SideNavComponent;
|
|
@ -0,0 +1,22 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {Route} from 'react-router-dom';
|
||||||
|
import SideNavComponent from '../SideNavComponent.tsx';
|
||||||
|
import {Col, Row} from 'react-bootstrap';
|
||||||
|
|
||||||
|
const SidebarLayoutComponent = ({component: Component,
|
||||||
|
sideNavDisabled = false,
|
||||||
|
completedSteps = null,
|
||||||
|
...other
|
||||||
|
}) => (
|
||||||
|
<Route {...other} render={() => {
|
||||||
|
return (
|
||||||
|
<Row>
|
||||||
|
<Col sm={3} md={3} lg={3} xl={2} className='sidebar'>
|
||||||
|
<SideNavComponent disabled={sideNavDisabled} completedSteps={completedSteps}/>
|
||||||
|
</Col>
|
||||||
|
<Component {...other} />
|
||||||
|
</Row>)
|
||||||
|
}}/>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default SidebarLayoutComponent;
|
|
@ -1,15 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import {Route} from 'react-router-dom'
|
|
||||||
import SideNavComponent from '../SideNavComponent'
|
|
||||||
import {Col, Row} from 'react-bootstrap';
|
|
||||||
|
|
||||||
export const StandardLayoutComponent = ({component: Component, ...rest}) => (
|
|
||||||
<Route {...rest} render={() => (
|
|
||||||
<Row>
|
|
||||||
<Col sm={3} md={3} lg={3} xl={2} className='sidebar'>
|
|
||||||
<SideNavComponent completedSteps={rest['completedSteps']}/>
|
|
||||||
</Col>
|
|
||||||
<Component {...rest} />
|
|
||||||
</Row>
|
|
||||||
)}/>
|
|
||||||
)
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {Col, Row} from 'react-bootstrap';
|
||||||
|
import {Link} from 'react-router-dom';
|
||||||
|
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||||
|
import {faFileCode, faLightbulb} from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import '../../styles/pages/LandingPage.scss';
|
||||||
|
import IslandHttpClient from "../IslandHttpClient";
|
||||||
|
|
||||||
|
|
||||||
|
const LandingPageComponent = (props) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
|
||||||
|
lg={{offset: 3, span: 9}} xl={{offset: 2, span: 7}}
|
||||||
|
className={'landing-page'}>
|
||||||
|
<h1 className="page-title">Welcome to the Monkey Island Server</h1>
|
||||||
|
<div style={{'fontSize': '1.2em'}}>
|
||||||
|
<ScenarioButtons/>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
|
||||||
|
function ScenarioButtons() {
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<h2 className={'scenario-choice-title'}>Choose a scenario:</h2>
|
||||||
|
<div className="container">
|
||||||
|
<Row className="justify-content-center">
|
||||||
|
<div className="col-lg-6 col-sm-6">
|
||||||
|
<Link to="/run-monkey"
|
||||||
|
className="px-4 py-5 bg-white shadow text-center d-block"
|
||||||
|
onClick={() => {
|
||||||
|
setScenario('ransomware')
|
||||||
|
}}>
|
||||||
|
<h4><FontAwesomeIcon icon={faFileCode}/> Ransomware</h4>
|
||||||
|
<p>Simulate ransomware infection in the network.</p>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="col-lg-6 col-sm-6">
|
||||||
|
<Link to="/configure"
|
||||||
|
className="px-4 py-5 bg-white shadow text-center d-block"
|
||||||
|
onClick={() => {
|
||||||
|
setScenario('advanced')
|
||||||
|
}}>
|
||||||
|
<h4><FontAwesomeIcon icon={faLightbulb}/> Custom</h4>
|
||||||
|
<p>Fine tune the simulation to your needs.</p>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
<MonkeyInfo/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setScenario(scenario: string) {
|
||||||
|
IslandHttpClient.post('/api/island-mode', {'mode': scenario});
|
||||||
|
props.onStatusChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function MonkeyInfo() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h4 className={'monkey-description-title'}>What is Infection Monkey?</h4>
|
||||||
|
<strong>Infection Monkey</strong> is an open-source security tool for testing a data center's resiliency to
|
||||||
|
perimeter
|
||||||
|
breaches and internal server infections. The Monkey uses various methods to propagate across a data center
|
||||||
|
and reports to this Monkey Island Command and Control server.
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LandingPageComponent;
|
|
@ -38,11 +38,11 @@ class StartOverPageComponent extends AuthComponent {
|
||||||
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
|
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
|
||||||
lg={{offset: 3, span: 9}} xl={{offset: 2, span: 7}}
|
lg={{offset: 3, span: 9}} xl={{offset: 2, span: 7}}
|
||||||
className={'main'}>
|
className={'main'}>
|
||||||
<StartOverModal cleaned = {this.state.cleaned}
|
<StartOverModal cleaned={this.state.cleaned}
|
||||||
showCleanDialog = {this.state.showCleanDialog}
|
showCleanDialog={this.state.showCleanDialog}
|
||||||
allMonkeysAreDead = {this.state.allMonkeysAreDead}
|
allMonkeysAreDead={this.state.allMonkeysAreDead}
|
||||||
onVerify = {this.cleanup}
|
onVerify={this.cleanup}
|
||||||
onClose = {this.closeModal}/>
|
onClose={this.closeModal}/>
|
||||||
<h1 className="page-title">Start Over</h1>
|
<h1 className="page-title">Start Over</h1>
|
||||||
<div style={{'fontSize': '1.2em'}}>
|
<div style={{'fontSize': '1.2em'}}>
|
||||||
<p>
|
<p>
|
||||||
|
@ -88,7 +88,10 @@ class StartOverPageComponent extends AuthComponent {
|
||||||
cleaned: true
|
cleaned: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).then(this.updateMonkeysRunning());
|
}).then(() => {
|
||||||
|
this.updateMonkeysRunning();
|
||||||
|
this.props.onStatusChange();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
closeModal = () => {
|
closeModal = () => {
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
export class CompletedSteps {
|
||||||
|
runServer: boolean
|
||||||
|
runMonkey: boolean
|
||||||
|
infectionDone: boolean
|
||||||
|
reportDone: boolean
|
||||||
|
isLoggedIn: boolean
|
||||||
|
needsRegistration: boolean
|
||||||
|
|
||||||
|
public constructor(runServer?: boolean,
|
||||||
|
runMonkey?: boolean,
|
||||||
|
infectinDone?: boolean,
|
||||||
|
reportDone?: boolean) {
|
||||||
|
this.runServer = runServer || false;
|
||||||
|
this.runMonkey = runMonkey || false;
|
||||||
|
this.infectionDone = infectinDone || false;
|
||||||
|
this.reportDone = reportDone || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static buildFromResponse(response: CompletedStepsRequest) {
|
||||||
|
return new CompletedSteps(response.run_server,
|
||||||
|
response.run_monkey,
|
||||||
|
response.infection_done,
|
||||||
|
response.report_done);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompletedStepsRequest = {
|
||||||
|
run_server: boolean,
|
||||||
|
run_monkey: boolean,
|
||||||
|
infection_done: boolean,
|
||||||
|
report_done: boolean
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
.landing-page h1.page-title {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-page h2.scenario-choice-title {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-page .monkey-description-title {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
|
|
Loading…
Reference in New Issue