-
Congrats! You have successfully set up the Monkey Island server. 👏 👏
+
Congrats! You have successfully set up the Monkey Island
+ server. 👏 👏
The Infection Monkey is an open source security tool for testing a data center's resiliency to perimeter
breaches and internal server infections.
@@ -20,7 +21,8 @@ class RunServerPageComponent extends React.Component {
center and reports to this Monkey Island Command and Control server.
- To read more about the Monkey, visit infectionmonkey.com
+ To read more about the Monkey, visit infectionmonkey.com
Go ahead and run the monkey.
diff --git a/monkey_island/cc/ui/src/components/pages/StartOverPage.js b/monkey_island/cc/ui/src/components/pages/StartOverPage.js
index 2889a7067..87716659f 100644
--- a/monkey_island/cc/ui/src/components/pages/StartOverPage.js
+++ b/monkey_island/cc/ui/src/components/pages/StartOverPage.js
@@ -2,8 +2,9 @@ import React from 'react';
import {Col} from 'react-bootstrap';
import {Link} from 'react-router-dom';
import {ModalContainer, ModalDialog} from 'react-modal-dialog';
+import AuthComponent from '../AuthComponent';
-class StartOverPageComponent extends React.Component {
+class StartOverPageComponent extends AuthComponent {
constructor(props) {
super(props);
@@ -15,7 +16,7 @@ class StartOverPageComponent extends React.Component {
}
updateMonkeysRunning = () => {
- fetch('/api')
+ this.authFetch('/api')
.then(res => res.json())
.then(res => {
// This check is used to prevent unnecessary re-rendering
@@ -104,7 +105,7 @@ class StartOverPageComponent extends React.Component {
this.setState({
cleaned: false
});
- fetch('/api?action=reset')
+ this.authFetch('/api?action=reset')
.then(res => res.json())
.then(res => {
if (res['status'] === 'OK') {
diff --git a/monkey_island/cc/ui/src/components/pages/TelemetryPage.js b/monkey_island/cc/ui/src/components/pages/TelemetryPage.js
index 03c57807e..099c20a43 100644
--- a/monkey_island/cc/ui/src/components/pages/TelemetryPage.js
+++ b/monkey_island/cc/ui/src/components/pages/TelemetryPage.js
@@ -2,6 +2,7 @@ import React from 'react';
import {Col} from 'react-bootstrap';
import JSONTree from 'react-json-tree'
import {DataTable} from 'react-data-components';
+import AuthComponent from '../AuthComponent';
const renderJson = (val) => ;
const renderTime = (val) => val.split('.')[0];
@@ -13,7 +14,7 @@ const columns = [
{ title: 'Details', prop: 'data', render: renderJson, width: '40%' }
];
-class TelemetryPageComponent extends React.Component {
+class TelemetryPageComponent extends AuthComponent {
constructor(props) {
super(props);
this.state = {
@@ -22,7 +23,7 @@ class TelemetryPageComponent extends React.Component {
}
componentDidMount = () => {
- fetch('/api/telemetry')
+ this.authFetch('/api/telemetry')
.then(res => res.json())
.then(res => this.setState({data: res.objects}));
};
diff --git a/monkey_island/cc/ui/src/favicon.ico b/monkey_island/cc/ui/src/favicon.ico
index 322b37e1f..0a9976256 100644
Binary files a/monkey_island/cc/ui/src/favicon.ico and b/monkey_island/cc/ui/src/favicon.ico differ
diff --git a/monkey_island/cc/ui/src/server_config/AwsConfig.js b/monkey_island/cc/ui/src/server_config/AwsConfig.js
new file mode 100644
index 000000000..1c5814b5a
--- /dev/null
+++ b/monkey_island/cc/ui/src/server_config/AwsConfig.js
@@ -0,0 +1,9 @@
+import BaseConfig from './BaseConfig';
+
+class AwsConfig extends BaseConfig{
+ isAuthEnabled() {
+ return true;
+ }
+}
+
+export default AwsConfig;
diff --git a/monkey_island/cc/ui/src/server_config/BaseConfig.js b/monkey_island/cc/ui/src/server_config/BaseConfig.js
new file mode 100644
index 000000000..1ca82506d
--- /dev/null
+++ b/monkey_island/cc/ui/src/server_config/BaseConfig.js
@@ -0,0 +1,8 @@
+class BaseConfig {
+
+ isAuthEnabled() {
+ throw new Error('Abstract function');
+ }
+}
+
+export default BaseConfig;
diff --git a/monkey_island/cc/ui/src/server_config/ServerConfig.js b/monkey_island/cc/ui/src/server_config/ServerConfig.js
new file mode 100644
index 000000000..faff47abc
--- /dev/null
+++ b/monkey_island/cc/ui/src/server_config/ServerConfig.js
@@ -0,0 +1,12 @@
+import StandardConfig from './StandardConfig';
+import AwsConfig from './AwsConfig';
+
+const SERVER_CONFIG_JSON = require('json-loader!../../../server_config.json');
+
+const CONFIG_DICT =
+ {
+ 'standard': StandardConfig,
+ 'aws': AwsConfig
+ };
+
+export const SERVER_CONFIG = new CONFIG_DICT[SERVER_CONFIG_JSON['server_config']]();
diff --git a/monkey_island/cc/ui/src/server_config/StandardConfig.js b/monkey_island/cc/ui/src/server_config/StandardConfig.js
new file mode 100644
index 000000000..f549f7112
--- /dev/null
+++ b/monkey_island/cc/ui/src/server_config/StandardConfig.js
@@ -0,0 +1,10 @@
+import BaseConfig from './BaseConfig';
+
+class StandardConfig extends BaseConfig {
+
+ isAuthEnabled () {
+ return false;
+ }
+}
+
+export default StandardConfig;
diff --git a/monkey_island/cc/ui/src/services/AuthService.js b/monkey_island/cc/ui/src/services/AuthService.js
new file mode 100644
index 000000000..c5a474ebf
--- /dev/null
+++ b/monkey_island/cc/ui/src/services/AuthService.js
@@ -0,0 +1,106 @@
+import decode from 'jwt-decode';
+import {SERVER_CONFIG} from '../server_config/ServerConfig';
+
+export default class AuthService {
+ AUTH_ENABLED = SERVER_CONFIG.isAuthEnabled();
+
+ login = (username, password) => {
+ if (this.AUTH_ENABLED) {
+ return this._login(username, password);
+ } else {
+ return {result: true};
+ }
+ };
+
+ authFetch = (url, options) => {
+ if (this.AUTH_ENABLED) {
+ return this._authFetch(url, options);
+ } else {
+ return fetch(url, options);
+ }
+ };
+
+ _login = (username, password) => {
+ return this._authFetch('/api/auth', {
+ method: 'POST',
+ body: JSON.stringify({
+ username,
+ password
+ })
+ }).then(response => response.json())
+ .then(res => {
+ if (res.hasOwnProperty('access_token')) {
+ this._setToken(res['access_token']);
+ return {result: true};
+ } else {
+ this._removeToken();
+ return {result: false};
+ }
+
+ })
+ };
+
+ _authFetch = (url, options = {}) => {
+ const headers = {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ };
+
+ if (this.loggedIn()) {
+ headers['Authorization'] = 'JWT ' + this._getToken();
+ }
+
+ if (options.hasOwnProperty('headers')) {
+ for (let header in headers) {
+ options['headers'][header] = headers[header];
+ }
+ } else {
+ options['headers'] = headers;
+ }
+
+ return fetch(url, options)
+ .then(res => {
+ if (res.status === 401) {
+ this._removeToken();
+ }
+ return res;
+ });
+ };
+
+ loggedIn() {
+ if (!this.AUTH_ENABLED) {
+ return true;
+ }
+
+ const token = this._getToken();
+ return ((token !== null) && !this._isTokenExpired(token));
+ }
+
+ logout() {
+ if (this.AUTH_ENABLED) {
+ this._removeToken();
+ }
+ }
+
+ _isTokenExpired(token) {
+ try {
+ return decode(token)['exp'] < Date.now() / 1000;
+ }
+ catch (err) {
+ return false;
+ }
+ }
+
+ _setToken(idToken) {
+ localStorage.setItem('jwt', idToken);
+ }
+
+ _removeToken() {
+ localStorage.removeItem('jwt');
+ }
+
+ _getToken() {
+ return localStorage.getItem('jwt')
+ }
+
+}
diff --git a/monkey_island/deb-package/monkey_island_pip_requirements.txt b/monkey_island/deb-package/monkey_island_pip_requirements.txt
index 404aad8b0..4b4e9d523 100644
--- a/monkey_island/deb-package/monkey_island_pip_requirements.txt
+++ b/monkey_island/deb-package/monkey_island_pip_requirements.txt
@@ -8,6 +8,7 @@ click
flask
Flask-Pymongo
Flask-Restful
+Flask-JWT
jsonschema
netifaces
ipaddress
diff --git a/monkey_island/requirements.txt b/monkey_island/requirements.txt
index 9d8bfbfb8..6aea32b84 100644
--- a/monkey_island/requirements.txt
+++ b/monkey_island/requirements.txt
@@ -8,6 +8,7 @@ click
flask
Flask-Pymongo
Flask-Restful
+Flask-JWT
jsonschema
netifaces
ipaddress