From 4bb569dd897cb54e4d23390d307b0d863827296b Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Thu, 22 Feb 2018 10:33:37 +0200 Subject: [PATCH 01/12] Add JWT authentication to backend --- monkey_island/cc/app.py | 20 +++++-- monkey_island/cc/auth.py | 54 +++++++++++++++++++ monkey_island/cc/island_config.py | 4 ++ .../monkey_island_pip_requirements.txt | 1 + monkey_island/requirements.txt | 1 + 5 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 monkey_island/cc/auth.py diff --git a/monkey_island/cc/app.py b/monkey_island/cc/app.py index 9c85f6230..a5601ba74 100644 --- a/monkey_island/cc/app.py +++ b/monkey_island/cc/app.py @@ -1,22 +1,26 @@ +import os from datetime import datetime + import bson -from bson.json_util import dumps -from flask import Flask, send_from_directory, redirect, make_response import flask_restful +from bson.json_util import dumps +from flask import Flask, send_from_directory, make_response from werkzeug.exceptions import NotFound +from cc.auth import init_jwt from cc.database import mongo +from cc.island_config import AUTH_EXPIRATION_TIME from cc.resources.client_run import ClientRun -from cc.resources.monkey import Monkey +from cc.resources.edge import Edge from cc.resources.local_run import LocalRun -from cc.resources.telemetry import Telemetry +from cc.resources.monkey import Monkey from cc.resources.monkey_configuration import MonkeyConfiguration from cc.resources.monkey_download import MonkeyDownload from cc.resources.netmap import NetMap -from cc.resources.edge import Edge from cc.resources.node import Node from cc.resources.report import Report from cc.resources.root import Root +from cc.resources.telemetry import Telemetry from cc.resources.telemetry_feed import TelemetryFeed from cc.services.config import ConfigService @@ -70,6 +74,12 @@ def init_app(mongo_url): api.representations = {'application/json': output_json} app.config['MONGO_URI'] = mongo_url + + app.config['SECRET_KEY'] = os.urandom(32) + app.config['JWT_AUTH_URL_RULE'] = '/api/auth' + app.config['JWT_EXPIRATION_DELTA'] = AUTH_EXPIRATION_TIME + + init_jwt(app) mongo.init_app(app) with app.app_context(): diff --git a/monkey_island/cc/auth.py b/monkey_island/cc/auth.py new file mode 100644 index 000000000..510a741ad --- /dev/null +++ b/monkey_island/cc/auth.py @@ -0,0 +1,54 @@ +from functools import wraps + +import flask_jwt +from flask_jwt import JWT +from werkzeug.security import safe_str_cmp + +from cc.island_config import AUTH_ENABLED + +__author__ = 'itay.mizeretz' + + +class User(object): + def __init__(self, id, username, password): + self.id = id + self.username = username + self.password = password + + def __str__(self): + return "User(id='%s')" % self.id + + +users = [ + User(1, 'monkey', 'infection') +] +username_table = {u.username: u for u in users} +userid_table = {u.id: u for u in users} + + +def authenticate(username, password): + user = username_table.get(username, None) + if user and safe_str_cmp(user.password.encode('utf-8'), password.encode('utf-8')): + return user + + +def identity(payload): + user_id = payload['identity'] + return userid_table.get(user_id, None) + + +def init_jwt(app): + if AUTH_ENABLED: + JWT(app, authenticate, identity) + + +def jwt_required(realm=None): + if AUTH_ENABLED: + return flask_jwt.jwt_required(realm) + else: + def wrapper(fn): + @wraps(fn) + def decorator(*args, **kwargs): + return fn(*args, **kwargs) + return decorator + return wrapper diff --git a/monkey_island/cc/island_config.py b/monkey_island/cc/island_config.py index 0a8f33bac..87fe97263 100644 --- a/monkey_island/cc/island_config.py +++ b/monkey_island/cc/island_config.py @@ -1,5 +1,9 @@ +from datetime import timedelta + __author__ = 'itay.mizeretz' ISLAND_PORT = 5000 DEFAULT_MONGO_URL = "mongodb://localhost:27017/monkeyisland" DEBUG_SERVER = False +AUTH_ENABLED = True +AUTH_EXPIRATION_TIME = timedelta(hours=1) 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 From e02d282c03bf8944c60788522093f0d5921e76a6 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Thu, 22 Feb 2018 10:35:31 +0200 Subject: [PATCH 02/12] Add AuthService --- monkey_island/cc/ui/package.json | 1 + .../cc/ui/src/components/AuthComponent.js | 12 +++ .../cc/ui/src/services/AuthService.js | 84 +++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 monkey_island/cc/ui/src/components/AuthComponent.js create mode 100644 monkey_island/cc/ui/src/services/AuthService.js diff --git a/monkey_island/cc/ui/package.json b/monkey_island/cc/ui/package.json index 5ee2e5389..e8fb04eaf 100644 --- a/monkey_island/cc/ui/package.json +++ b/monkey_island/cc/ui/package.json @@ -65,6 +65,7 @@ "core-js": "^2.5.1", "fetch": "^1.1.0", "js-file-download": "^0.4.1", + "jwt-decode": "^2.2.0", "normalize.css": "^4.0.0", "prop-types": "^15.5.10", "rc-progress": "^2.2.5", diff --git a/monkey_island/cc/ui/src/components/AuthComponent.js b/monkey_island/cc/ui/src/components/AuthComponent.js new file mode 100644 index 000000000..428c3272a --- /dev/null +++ b/monkey_island/cc/ui/src/components/AuthComponent.js @@ -0,0 +1,12 @@ +import React from 'react'; +import AuthService from '../services/AuthService'; + +class AuthComponent extends React.Component { + constructor(props) { + super(props); + this.auth = new AuthService(); + this.authFetch = this.auth.authFetch; + } +} + +export default AuthComponent; 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..36e352ed4 --- /dev/null +++ b/monkey_island/cc/ui/src/services/AuthService.js @@ -0,0 +1,84 @@ +import decode from 'jwt-decode'; + +export default class AuthService { + AUTH_ENABLED = true; + + login = (username, password) => { + if (this.AUTH_ENABLED) { + return this._login(username, password); + } else { + return {}; + } + }; + + 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 => { + this._setToken(res['access_token']); + }) + }; + + _authFetch = (url, options) => { + const headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }; + + if (this.loggedIn()) { + headers['Authorization'] = 'JWT ' + this._getToken(); + } + + return fetch(url, { + headers, + ...options + }); + + }; + + loggedIn() { + if (!this.AUTH_ENABLED) { + return true; + } + const token = this._getToken(); + return (token && !this._isTokenExpired(token)); + } + + logout() { + if (this.AUTH_ENABLED) { + localStorage.removeItem('jwt'); + } + } + + _isTokenExpired(token) { + try { + return decode(token)['exp'] < Date.now() / 1000; + } + catch (err) { + return false; + } + } + + _setToken(idToken) { + localStorage.setItem('jwt', idToken); + } + + _getToken() { + return localStorage.getItem('jwt') + } + + +} From df95cc73c575da7b31464a06e26abb11198118db Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Thu, 22 Feb 2018 10:36:10 +0200 Subject: [PATCH 03/12] Make all components with server interaction AuthComponents --- .../src/components/map/preview-pane/PreviewPane.js | 5 +++-- .../cc/ui/src/components/pages/ConfigurePage.js | 11 ++++++----- monkey_island/cc/ui/src/components/pages/MapPage.js | 13 +++++++------ .../cc/ui/src/components/pages/ReportPage.js | 9 +++++---- .../cc/ui/src/components/pages/RunMonkeyPage.js | 11 ++++++----- .../cc/ui/src/components/pages/RunServerPage.js | 5 +++++ .../cc/ui/src/components/pages/StartOverPage.js | 7 ++++--- .../cc/ui/src/components/pages/TelemetryPage.js | 5 +++-- 8 files changed, 39 insertions(+), 27 deletions(-) diff --git a/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js b/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js index 842440149..ca3aed268 100644 --- a/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js +++ b/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js @@ -2,8 +2,9 @@ import React from 'react'; import {Icon} from 'react-fa'; import Toggle from 'react-toggle'; import {OverlayTrigger, Tooltip} from 'react-bootstrap'; +import AuthComponent from '../../AuthComponent'; -class PreviewPaneComponent extends React.Component { +class PreviewPaneComponent extends AuthComponent { generateToolTip(text) { return ( @@ -64,7 +65,7 @@ class PreviewPaneComponent extends React.Component { forceKill(event, asset) { let newConfig = asset.config; newConfig['alive'] = !event.target.checked; - fetch('/api/monkey/' + asset.guid, + this.authFetch('/api/monkey/' + asset.guid, { method: 'PATCH', headers: {'Content-Type': 'application/json'}, diff --git a/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 3f60ab026..afa42d6e7 100644 --- a/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -2,8 +2,9 @@ import React from 'react'; import Form from 'react-jsonschema-form'; import {Col, Nav, NavItem} from 'react-bootstrap'; import fileDownload from 'js-file-download'; +import AuthComponent from '../AuthComponent'; -class ConfigurePageComponent extends React.Component { +class ConfigurePageComponent extends AuthComponent { constructor(props) { super(props); @@ -23,7 +24,7 @@ class ConfigurePageComponent extends React.Component { } componentDidMount() { - fetch('/api/configuration') + this.authFetch('/api/configuration') .then(res => res.json()) .then(res => { let sections = []; @@ -43,7 +44,7 @@ class ConfigurePageComponent extends React.Component { onSubmit = ({formData}) => { this.currentFormData = formData; this.updateConfigSection(); - fetch('/api/configuration', + this.authFetch('/api/configuration', { method: 'POST', headers: {'Content-Type': 'application/json'}, @@ -82,7 +83,7 @@ class ConfigurePageComponent extends React.Component { }; resetConfig = () => { - fetch('/api/configuration', + this.authFetch('/api/configuration', { method: 'POST', headers: {'Content-Type': 'application/json'}, @@ -126,7 +127,7 @@ class ConfigurePageComponent extends React.Component { }; updateMonkeysRunning = () => { - fetch('/api') + this.authFetch('/api') .then(res => res.json()) .then(res => { // This check is used to prevent unnecessary re-rendering diff --git a/monkey_island/cc/ui/src/components/pages/MapPage.js b/monkey_island/cc/ui/src/components/pages/MapPage.js index ba5a655b1..4a54aeb8c 100644 --- a/monkey_island/cc/ui/src/components/pages/MapPage.js +++ b/monkey_island/cc/ui/src/components/pages/MapPage.js @@ -6,8 +6,9 @@ import PreviewPane from 'components/map/preview-pane/PreviewPane'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; import {ModalContainer, ModalDialog} from 'react-modal-dialog'; import {options, edgeGroupToColor} from 'components/map/MapOptions'; +import AuthComponent from '../AuthComponent'; -class MapPageComponent extends React.Component { +class MapPageComponent extends AuthComponent { constructor(props) { super(props); this.state = { @@ -40,7 +41,7 @@ class MapPageComponent extends React.Component { }; updateMapFromServer = () => { - fetch('/api/netmap') + this.authFetch('/api/netmap') .then(res => res.json()) .then(res => { res.edges.forEach(edge => { @@ -52,7 +53,7 @@ class MapPageComponent extends React.Component { }; updateTelemetryFromServer = () => { - fetch('/api/telemetry-feed?timestamp='+this.state.telemetryLastTimestamp) + this.authFetch('/api/telemetry-feed?timestamp='+this.state.telemetryLastTimestamp) .then(res => res.json()) .then(res => { let newTelem = this.state.telemetry.concat(res['telemetries']); @@ -68,7 +69,7 @@ class MapPageComponent extends React.Component { selectionChanged(event) { if (event.nodes.length === 1) { - fetch('/api/netmap/node?id=' + event.nodes[0]) + this.authFetch('/api/netmap/node?id=' + event.nodes[0]) .then(res => res.json()) .then(res => this.setState({selected: res, selectedType: 'node'})); } @@ -80,7 +81,7 @@ class MapPageComponent extends React.Component { if (displayedEdge['group'] === 'island') { this.setState({selected: displayedEdge, selectedType: 'island_edge'}); } else { - fetch('/api/netmap/edge?id=' + event.edges[0]) + this.authFetch('/api/netmap/edge?id=' + event.edges[0]) .then(res => res.json()) .then(res => this.setState({selected: res.edge, selectedType: 'edge'})); } @@ -91,7 +92,7 @@ class MapPageComponent extends React.Component { } killAllMonkeys = () => { - fetch('/api?action=killall') + this.authFetch('/api?action=killall') .then(res => res.json()) .then(res => this.setState({killPressed: (res.status === 'OK')})); }; diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index 440000596..145048db9 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -7,11 +7,12 @@ import {edgeGroupToColor, options} from 'components/map/MapOptions'; import StolenPasswords from 'components/report-components/StolenPasswords'; import CollapsibleWellComponent from 'components/report-components/CollapsibleWell'; import {Line} from 'rc-progress'; +import AuthComponent from '../AuthComponent'; let guardicoreLogoImage = require('../../images/guardicore-logo.png'); let monkeyLogoImage = require('../../images/monkey-icon.svg'); -class ReportPageComponent extends React.Component { +class ReportPageComponent extends AuthComponent { Issue = { @@ -76,7 +77,7 @@ class ReportPageComponent extends React.Component { } updateMonkeysRunning = () => { - return fetch('/api') + return this.authFetch('/api') .then(res => res.json()) .then(res => { // This check is used to prevent unnecessary re-rendering @@ -89,7 +90,7 @@ class ReportPageComponent extends React.Component { }; updateMapFromServer = () => { - fetch('/api/netmap') + this.authFetch('/api/netmap') .then(res => res.json()) .then(res => { res.edges.forEach(edge => { @@ -102,7 +103,7 @@ class ReportPageComponent extends React.Component { getReportFromServer(res) { if (res['completed_steps']['run_monkey']) { - fetch('/api/report') + this.authFetch('/api/report') .then(res => res.json()) .then(res => { this.setState({ diff --git a/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js b/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js index 5574a73ba..591db5e67 100644 --- a/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js +++ b/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js @@ -3,8 +3,9 @@ import {Button, Col, Well, Nav, NavItem, Collapse} from 'react-bootstrap'; import CopyToClipboard from 'react-copy-to-clipboard'; import {Icon} from 'react-fa'; import {Link} from 'react-router-dom'; +import AuthComponent from '../AuthComponent'; -class RunMonkeyPageComponent extends React.Component { +class RunMonkeyPageComponent extends AuthComponent { constructor(props) { super(props); @@ -19,14 +20,14 @@ class RunMonkeyPageComponent extends React.Component { } componentDidMount() { - fetch('/api') + this.authFetch('/api') .then(res => res.json()) .then(res => this.setState({ ips: res['ip_addresses'], selectedIp: res['ip_addresses'][0] })); - fetch('/api/local-monkey') + this.authFetch('/api/local-monkey') .then(res => res.json()) .then(res =>{ if (res['is_running']) { @@ -36,7 +37,7 @@ class RunMonkeyPageComponent extends React.Component { } }); - fetch('/api/client-monkey') + this.authFetch('/api/client-monkey') .then(res => res.json()) .then(res => { if (res['is_running']) { @@ -60,7 +61,7 @@ class RunMonkeyPageComponent extends React.Component { } runLocalMonkey = () => { - fetch('/api/local-monkey', + this.authFetch('/api/local-monkey', { method: 'POST', headers: {'Content-Type': 'application/json'}, diff --git a/monkey_island/cc/ui/src/components/pages/RunServerPage.js b/monkey_island/cc/ui/src/components/pages/RunServerPage.js index 5143f24fc..c6d72a0cb 100644 --- a/monkey_island/cc/ui/src/components/pages/RunServerPage.js +++ b/monkey_island/cc/ui/src/components/pages/RunServerPage.js @@ -2,12 +2,17 @@ import React from 'react'; import {Col} from 'react-bootstrap'; import {Link} from 'react-router-dom'; +import AuthService from '../../services/AuthService' + class RunServerPageComponent extends React.Component { constructor(props) { super(props); } render() { + // TODO: something real + let auth = new AuthService(); + auth.login('monkey', 'infectio1n'); return (

1. Monkey Island C&C Server

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})); }; From 52d75de86459bb61a3233aa271232debfaf94b83 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Thu, 22 Feb 2018 15:22:35 +0200 Subject: [PATCH 04/12] Fix AuthService --- .../cc/ui/src/services/AuthService.js | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/monkey_island/cc/ui/src/services/AuthService.js b/monkey_island/cc/ui/src/services/AuthService.js index 36e352ed4..25e214088 100644 --- a/monkey_island/cc/ui/src/services/AuthService.js +++ b/monkey_island/cc/ui/src/services/AuthService.js @@ -7,7 +7,7 @@ export default class AuthService { if (this.AUTH_ENABLED) { return this._login(username, password); } else { - return {}; + return {result: true}; } }; @@ -28,7 +28,14 @@ export default class AuthService { }) }).then(response => response.json()) .then(res => { - this._setToken(res['access_token']); + if (res.hasOwnProperty('access_token')) { + this._setToken(res['access_token']); + return {result: true}; + } else { + this._removeToken(); + return {result: false}; + } + }) }; @@ -45,21 +52,26 @@ export default class AuthService { return fetch(url, { headers, ...options + }).then(res => { + if (res.status === 401) { + this._removeToken(); + } + return res; }); - }; loggedIn() { if (!this.AUTH_ENABLED) { return true; } + const token = this._getToken(); - return (token && !this._isTokenExpired(token)); + return ((token !== null) && !this._isTokenExpired(token)); } logout() { if (this.AUTH_ENABLED) { - localStorage.removeItem('jwt'); + this._removeToken(); } } @@ -76,6 +88,10 @@ export default class AuthService { localStorage.setItem('jwt', idToken); } + _removeToken() { + localStorage.removeItem('jwt'); + } + _getToken() { return localStorage.getItem('jwt') } From c9d644f88ac5ed7b737e9960ad87b6b3afbc4802 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Thu, 22 Feb 2018 15:23:47 +0200 Subject: [PATCH 05/12] Add login page, and auto redirection to/from login page --- monkey_island/cc/ui/src/components/Main.js | 73 ++++++++++------- .../cc/ui/src/components/pages/LoginPage.js | 78 +++++++++++++++++++ .../ui/src/components/pages/RunServerPage.js | 11 +-- 3 files changed, 128 insertions(+), 34 deletions(-) create mode 100644 monkey_island/cc/ui/src/components/pages/LoginPage.js diff --git a/monkey_island/cc/ui/src/components/Main.js b/monkey_island/cc/ui/src/components/Main.js index 881c3a2ec..d80d70c24 100644 --- a/monkey_island/cc/ui/src/components/Main.js +++ b/monkey_island/cc/ui/src/components/Main.js @@ -1,5 +1,5 @@ import React from 'react'; -import {NavLink, Route, BrowserRouter as Router} from 'react-router-dom'; +import {BrowserRouter as Router, NavLink, Redirect, Route} from 'react-router-dom'; import {Col, Grid, Row} from 'react-bootstrap'; import {Icon} from 'react-fa'; @@ -11,6 +11,8 @@ 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'; require('normalize.css/normalize.css'); require('react-data-components/css/table-twbs.css'); @@ -22,21 +24,9 @@ let logoImage = require('../images/monkey-icon.svg'); let infectionMonkeyImage = require('../images/infection-monkey.svg'); let guardicoreLogoImage = require('../images/guardicore-logo.png'); -class AppComponent extends React.Component { - constructor(props) { - super(props); - this.state = { - completedSteps: { - run_server: true, - run_monkey: false, - infection_done: false, - report_done: false - } - }; - } - +class AppComponent extends AuthComponent { updateStatus = () => { - fetch('/api') + this.authFetch('/api') .then(res => res.json()) .then(res => { // This check is used to prevent unnecessary re-rendering @@ -53,6 +43,34 @@ class AppComponent extends React.Component { }); }; + renderRoute = (route_path, page_component, is_exact_path = false) => { + let render_func = (props) => { + if (this.auth.loggedIn()) { + return page_component; + } else { + return ; + } + }; + + if (is_exact_path) { + return ; + } else { + return ; + } + }; + + constructor(props) { + super(props); + this.state = { + completedSteps: { + run_server: true, + run_monkey: false, + infection_done: false, + report_done: false + } + }; + } + componentDidMount() { this.updateStatus(); this.interval = setInterval(this.updateStatus, 2000); @@ -78,7 +96,7 @@ class AppComponent extends React.Component { 1. Run C&C Server - { this.state.completedSteps.run_server ? + {this.state.completedSteps.run_server ? : ''} @@ -87,7 +105,7 @@ class AppComponent extends React.Component { 2. Run Monkey - { this.state.completedSteps.run_monkey ? + {this.state.completedSteps.run_monkey ? : ''} @@ -96,7 +114,7 @@ class AppComponent extends React.Component { 3. Infection Map - { this.state.completedSteps.infection_done ? + {this.state.completedSteps.infection_done ? : ''} @@ -105,7 +123,7 @@ class AppComponent extends React.Component { 4. Security Report - { this.state.completedSteps.report_done ? + {this.state.completedSteps.report_done ? : ''} @@ -136,14 +154,15 @@ class AppComponent extends React.Component { - ( )} /> - ( )} /> - ( )} /> - ( )} /> - ( )} /> - ( )} /> - ( )} /> - ( )} /> + ()}/> + {this.renderRoute('/', , true)} + {this.renderRoute('/configure', )} + {this.renderRoute('/run-monkey', )} + {this.renderRoute('/infection/map', )} + {this.renderRoute('/infection/telemetry', )} + {this.renderRoute('/start-over', )} + {this.renderRoute('/report', )} + {this.renderRoute('/license', )} diff --git a/monkey_island/cc/ui/src/components/pages/LoginPage.js b/monkey_island/cc/ui/src/components/pages/LoginPage.js new file mode 100644 index 000000000..cc1eefecd --- /dev/null +++ b/monkey_island/cc/ui/src/components/pages/LoginPage.js @@ -0,0 +1,78 @@ +import React from 'react'; +import {Col} from 'react-bootstrap'; + +import AuthService from '../../services/AuthService' + +class LoginPageComponent extends React.Component { + login = () => { + this.auth.login(this.username, this.password).then(res => { + if (res['result']) { + this.redirectToHome(); + } else { + this.setState({failed: true}); + } + }); + }; + + updateUsername = (evt) => { + this.username = evt.target.value; + }; + + updatePassword = (evt) => { + this.password = evt.target.value; + }; + + redirectToHome = () => { + window.location.href = '/'; + }; + + constructor(props) { + super(props); + this.username = ''; + this.password = ''; + this.auth = new AuthService(); + this.state = { + failed: false + }; + if (this.auth.loggedIn()) { + this.redirectToHome(); + } + } + + render() { + return ( + +

Login

+
+
+
+ Login +
+
+
+ this.updateUsername(evt)}/> + this.updatePassword(evt)}/> + + { + this.state.failed ? +
Login failed. Bad credentials.
+ : + '' + } +
+
+
+
+ + ); + } +} + +export default LoginPageComponent; diff --git a/monkey_island/cc/ui/src/components/pages/RunServerPage.js b/monkey_island/cc/ui/src/components/pages/RunServerPage.js index c6d72a0cb..65949532f 100644 --- a/monkey_island/cc/ui/src/components/pages/RunServerPage.js +++ b/monkey_island/cc/ui/src/components/pages/RunServerPage.js @@ -2,22 +2,18 @@ import React from 'react'; import {Col} from 'react-bootstrap'; import {Link} from 'react-router-dom'; -import AuthService from '../../services/AuthService' - class RunServerPageComponent extends React.Component { constructor(props) { super(props); } render() { - // TODO: something real - let auth = new AuthService(); - auth.login('monkey', 'infectio1n'); return (

1. Monkey Island C&C Server

-

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. @@ -25,7 +21,8 @@ class RunServerPageComponent extends React.Component { center and reports to this Command and Control (C&C) 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. From 28ef2d870824425f5bfb1efb2947e886029f38c6 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Thu, 22 Feb 2018 15:43:51 +0200 Subject: [PATCH 06/12] return 401 on invalid token --- monkey_island/cc/auth.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/monkey_island/cc/auth.py b/monkey_island/cc/auth.py index 510a741ad..7ee787a75 100644 --- a/monkey_island/cc/auth.py +++ b/monkey_island/cc/auth.py @@ -1,7 +1,7 @@ from functools import wraps -import flask_jwt -from flask_jwt import JWT +from flask import current_app, abort +from flask_jwt import JWT, _jwt_required, JWTError from werkzeug.security import safe_str_cmp from cc.island_config import AUTH_ENABLED @@ -43,12 +43,16 @@ def init_jwt(app): def jwt_required(realm=None): - if AUTH_ENABLED: - return flask_jwt.jwt_required(realm) - else: - def wrapper(fn): - @wraps(fn) - def decorator(*args, **kwargs): - return fn(*args, **kwargs) - return decorator - return wrapper + def wrapper(fn): + @wraps(fn) + def decorator(*args, **kwargs): + if AUTH_ENABLED: + try: + _jwt_required(realm or current_app.config['JWT_DEFAULT_REALM']) + except JWTError: + abort(401) + return fn(*args, **kwargs) + + return decorator + + return wrapper From 9e169980e3710eaba6dfdfed54d0649d90d81408 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Thu, 22 Feb 2018 16:18:14 +0200 Subject: [PATCH 07/12] Don't query completed steps if not logged in --- monkey_island/cc/ui/src/components/Main.js | 30 ++++++++++++---------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/monkey_island/cc/ui/src/components/Main.js b/monkey_island/cc/ui/src/components/Main.js index d80d70c24..e518c8416 100644 --- a/monkey_island/cc/ui/src/components/Main.js +++ b/monkey_island/cc/ui/src/components/Main.js @@ -26,21 +26,23 @@ let guardicoreLogoImage = require('../images/guardicore-logo.png'); class AppComponent extends AuthComponent { updateStatus = () => { - 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 (this.auth.loggedIn()){ + 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']}); - } - }); + if (isChanged) { + this.setState({completedSteps: res['completed_steps']}); + } + }); + } }; renderRoute = (route_path, page_component, is_exact_path = false) => { From 9bb7148f50af24ad42d303e6dec408d0ea57b6a2 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Thu, 22 Feb 2018 16:21:03 +0200 Subject: [PATCH 08/12] Secure all endpoints --- monkey_island/cc/resources/monkey.py | 14 ++++++-------- monkey_island/cc/resources/monkey_configuration.py | 7 ++++--- monkey_island/cc/resources/monkey_download.py | 3 +++ monkey_island/cc/resources/netmap.py | 2 ++ monkey_island/cc/resources/node.py | 2 ++ monkey_island/cc/resources/report.py | 3 +++ monkey_island/cc/resources/root.py | 3 +++ monkey_island/cc/resources/telemetry.py | 3 +++ monkey_island/cc/resources/telemetry_feed.py | 2 ++ 9 files changed, 28 insertions(+), 11 deletions(-) diff --git a/monkey_island/cc/resources/monkey.py b/monkey_island/cc/resources/monkey.py index 37722262c..d344949bc 100644 --- a/monkey_island/cc/resources/monkey.py +++ b/monkey_island/cc/resources/monkey.py @@ -15,23 +15,20 @@ __author__ = 'Barak' class Monkey(flask_restful.Resource): + + # Used by monkey. can't secure. def get(self, guid=None, **kw): NodeService.update_dead_monkeys() # refresh monkeys status if not guid: guid = request.args.get('guid') - timestamp = request.args.get('timestamp') if guid: monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid}) return monkey_json - else: - result = {'timestamp': datetime.now().isoformat()} - find_filter = {} - if timestamp is not None: - find_filter['modifytime'] = {'$gt': dateutil.parser.parse(timestamp)} - result['objects'] = [x for x in mongo.db.monkey.find(find_filter)] - return result + return {} + + # Used by monkey. can't secure. def patch(self, guid): monkey_json = json.loads(request.data) update = {"$set": {'modifytime': datetime.now()}} @@ -51,6 +48,7 @@ class Monkey(flask_restful.Resource): return mongo.db.monkey.update({"_id": monkey["_id"]}, update, upsert=False) + # Used by monkey. can't secure. def post(self, **kw): monkey_json = json.loads(request.data) monkey_json['creds'] = [] diff --git a/monkey_island/cc/resources/monkey_configuration.py b/monkey_island/cc/resources/monkey_configuration.py index 6d622b1cd..0bd30db3f 100644 --- a/monkey_island/cc/resources/monkey_configuration.py +++ b/monkey_island/cc/resources/monkey_configuration.py @@ -1,18 +1,20 @@ import json -from flask import request, jsonify import flask_restful +from flask import request, jsonify -from cc.database import mongo +from cc.auth import jwt_required from cc.services.config import ConfigService __author__ = 'Barak' class MonkeyConfiguration(flask_restful.Resource): + @jwt_required() def get(self): return jsonify(schema=ConfigService.get_config_schema(), configuration=ConfigService.get_config()) + @jwt_required() def post(self): config_json = json.loads(request.data) if config_json.has_key('reset'): @@ -20,4 +22,3 @@ class MonkeyConfiguration(flask_restful.Resource): else: ConfigService.update_config(config_json) return self.get() - diff --git a/monkey_island/cc/resources/monkey_download.py b/monkey_island/cc/resources/monkey_download.py index b311c4472..ac1f9de2d 100644 --- a/monkey_island/cc/resources/monkey_download.py +++ b/monkey_island/cc/resources/monkey_download.py @@ -47,9 +47,12 @@ def get_monkey_executable(host_os, machine): class MonkeyDownload(flask_restful.Resource): + + # Used by monkey. can't secure. def get(self, path): return send_from_directory('binaries', path) + # Used by monkey. can't secure. def post(self): host_json = json.loads(request.data) host_os = host_json.get('os') diff --git a/monkey_island/cc/resources/netmap.py b/monkey_island/cc/resources/netmap.py index 12418ef6b..3ba7fafa8 100644 --- a/monkey_island/cc/resources/netmap.py +++ b/monkey_island/cc/resources/netmap.py @@ -1,5 +1,6 @@ import flask_restful +from cc.auth import jwt_required from cc.services.edge import EdgeService from cc.services.node import NodeService from cc.database import mongo @@ -8,6 +9,7 @@ __author__ = 'Barak' class NetMap(flask_restful.Resource): + @jwt_required() def get(self, **kw): monkeys = [NodeService.monkey_to_net_node(x) for x in mongo.db.monkey.find({})] nodes = [NodeService.node_to_net_node(x) for x in mongo.db.node.find({})] diff --git a/monkey_island/cc/resources/node.py b/monkey_island/cc/resources/node.py index 5a6c52e1b..bc00c40cf 100644 --- a/monkey_island/cc/resources/node.py +++ b/monkey_island/cc/resources/node.py @@ -1,12 +1,14 @@ from flask import request import flask_restful +from cc.auth import jwt_required from cc.services.node import NodeService __author__ = 'Barak' class Node(flask_restful.Resource): + @jwt_required() def get(self): node_id = request.args.get('id') if node_id: diff --git a/monkey_island/cc/resources/report.py b/monkey_island/cc/resources/report.py index e967b207f..1a00fa609 100644 --- a/monkey_island/cc/resources/report.py +++ b/monkey_island/cc/resources/report.py @@ -1,10 +1,13 @@ import flask_restful +from cc.auth import jwt_required from cc.services.report import ReportService __author__ = "itay.mizeretz" class Report(flask_restful.Resource): + + @jwt_required() def get(self): return ReportService.get_report() diff --git a/monkey_island/cc/resources/root.py b/monkey_island/cc/resources/root.py index 25d7dfed7..04129f257 100644 --- a/monkey_island/cc/resources/root.py +++ b/monkey_island/cc/resources/root.py @@ -3,6 +3,7 @@ from datetime import datetime import flask_restful from flask import request, make_response, jsonify +from cc.auth import jwt_required from cc.database import mongo from cc.services.config import ConfigService from cc.services.node import NodeService @@ -13,6 +14,8 @@ __author__ = 'Barak' class Root(flask_restful.Resource): + + @jwt_required() def get(self, action=None): if not action: action = request.args.get('action') diff --git a/monkey_island/cc/resources/telemetry.py b/monkey_island/cc/resources/telemetry.py index 94c4046b5..e1b17ac9a 100644 --- a/monkey_island/cc/resources/telemetry.py +++ b/monkey_island/cc/resources/telemetry.py @@ -7,6 +7,7 @@ import dateutil import flask_restful from flask import request +from cc.auth import jwt_required from cc.database import mongo from cc.services.config import ConfigService from cc.services.edge import EdgeService @@ -16,6 +17,7 @@ __author__ = 'Barak' class Telemetry(flask_restful.Resource): + @jwt_required() def get(self, **kw): monkey_guid = request.args.get('monkey_guid') telem_type = request.args.get('telem_type') @@ -36,6 +38,7 @@ class Telemetry(flask_restful.Resource): result['objects'] = self.telemetry_to_displayed_telemetry(mongo.db.telemetry.find(find_filter)) return result + # Used by monkey. can't secure. def post(self): telemetry_json = json.loads(request.data) telemetry_json['timestamp'] = datetime.now() diff --git a/monkey_island/cc/resources/telemetry_feed.py b/monkey_island/cc/resources/telemetry_feed.py index 9a7e507ef..f14c5d29f 100644 --- a/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey_island/cc/resources/telemetry_feed.py @@ -5,6 +5,7 @@ import flask_restful from flask import request import flask_pymongo +from cc.auth import jwt_required from cc.database import mongo from cc.services.node import NodeService @@ -12,6 +13,7 @@ __author__ = 'itay.mizeretz' class TelemetryFeed(flask_restful.Resource): + @jwt_required() def get(self, **kw): timestamp = request.args.get('timestamp') if "null" == timestamp or timestamp is None: # special case to avoid ugly JS code... From 4364156416844c088cdd383a8f52259eead6293e Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Thu, 22 Feb 2018 20:33:40 +0200 Subject: [PATCH 09/12] Add server config file and use in frontend+backend --- monkey_island/cc/app.py | 4 +- monkey_island/cc/auth.py | 37 ++++++++----------- monkey_island/cc/environment/__init__.py | 33 +++++++++++++++++ monkey_island/cc/environment/aws.py | 24 ++++++++++++ monkey_island/cc/environment/environment.py | 18 +++++++++ monkey_island/cc/environment/standard.py | 12 ++++++ monkey_island/cc/island_config.py | 9 ----- monkey_island/cc/main.py | 10 ++--- monkey_island/cc/resources/local_run.py | 4 +- monkey_island/cc/server_config.json | 3 ++ monkey_island/cc/services/config.py | 6 +-- .../cc/ui/src/server_config/AwsConfig.js | 9 +++++ .../cc/ui/src/server_config/BaseConfig.js | 8 ++++ .../cc/ui/src/server_config/ServerConfig.js | 12 ++++++ .../cc/ui/src/server_config/StandardConfig.js | 10 +++++ .../cc/ui/src/services/AuthService.js | 4 +- 16 files changed, 159 insertions(+), 44 deletions(-) create mode 100644 monkey_island/cc/environment/__init__.py create mode 100644 monkey_island/cc/environment/aws.py create mode 100644 monkey_island/cc/environment/environment.py create mode 100644 monkey_island/cc/environment/standard.py delete mode 100644 monkey_island/cc/island_config.py create mode 100644 monkey_island/cc/server_config.json create mode 100644 monkey_island/cc/ui/src/server_config/AwsConfig.js create mode 100644 monkey_island/cc/ui/src/server_config/BaseConfig.js create mode 100644 monkey_island/cc/ui/src/server_config/ServerConfig.js create mode 100644 monkey_island/cc/ui/src/server_config/StandardConfig.js diff --git a/monkey_island/cc/app.py b/monkey_island/cc/app.py index a5601ba74..4733d5089 100644 --- a/monkey_island/cc/app.py +++ b/monkey_island/cc/app.py @@ -9,7 +9,7 @@ from werkzeug.exceptions import NotFound from cc.auth import init_jwt from cc.database import mongo -from cc.island_config import AUTH_EXPIRATION_TIME +from cc.environment.environment import env from cc.resources.client_run import ClientRun from cc.resources.edge import Edge from cc.resources.local_run import LocalRun @@ -77,7 +77,7 @@ def init_app(mongo_url): app.config['SECRET_KEY'] = os.urandom(32) app.config['JWT_AUTH_URL_RULE'] = '/api/auth' - app.config['JWT_EXPIRATION_DELTA'] = AUTH_EXPIRATION_TIME + app.config['JWT_EXPIRATION_DELTA'] = env.get_auth_expiration_time() init_jwt(app) mongo.init_app(app) diff --git a/monkey_island/cc/auth.py b/monkey_island/cc/auth.py index 7ee787a75..99667d837 100644 --- a/monkey_island/cc/auth.py +++ b/monkey_island/cc/auth.py @@ -4,7 +4,7 @@ from flask import current_app, abort from flask_jwt import JWT, _jwt_required, JWTError from werkzeug.security import safe_str_cmp -from cc.island_config import AUTH_ENABLED +from cc.environment.environment import env __author__ = 'itay.mizeretz' @@ -19,26 +19,21 @@ class User(object): return "User(id='%s')" % self.id -users = [ - User(1, 'monkey', 'infection') -] -username_table = {u.username: u for u in users} -userid_table = {u.id: u for u in users} - - -def authenticate(username, password): - user = username_table.get(username, None) - if user and safe_str_cmp(user.password.encode('utf-8'), password.encode('utf-8')): - return user - - -def identity(payload): - user_id = payload['identity'] - return userid_table.get(user_id, None) - - def init_jwt(app): - if AUTH_ENABLED: + users = env.get_auth_users() + username_table = {u.username: u for u in users} + userid_table = {u.id: u for u in users} + + def authenticate(username, password): + user = username_table.get(username, None) + if user and safe_str_cmp(user.password.encode('utf-8'), password.encode('utf-8')): + return user + + def identity(payload): + user_id = payload['identity'] + return userid_table.get(user_id, None) + + if env.is_auth_enabled(): JWT(app, authenticate, identity) @@ -46,7 +41,7 @@ def jwt_required(realm=None): def wrapper(fn): @wraps(fn) def decorator(*args, **kwargs): - if AUTH_ENABLED: + if env.is_auth_enabled(): try: _jwt_required(realm or current_app.config['JWT_DEFAULT_REALM']) except JWTError: diff --git a/monkey_island/cc/environment/__init__.py b/monkey_island/cc/environment/__init__.py new file mode 100644 index 000000000..712ba232a --- /dev/null +++ b/monkey_island/cc/environment/__init__.py @@ -0,0 +1,33 @@ +import abc +from datetime import timedelta + +__author__ = 'itay.mizeretz' + + +class Environment(object): + __metaclass__ = abc.ABCMeta + + _ISLAND_PORT = 5000 + _MONGO_URL = "mongodb://localhost:27017/monkeyisland" + _DEBUG_SERVER = False + _AUTH_EXPIRATION_TIME = timedelta(hours=1) + + def get_island_port(self): + return self._ISLAND_PORT + + def get_mongo_url(self): + return self._MONGO_URL + + def is_debug(self): + return self._DEBUG_SERVER + + def get_auth_expiration_time(self): + return self._AUTH_EXPIRATION_TIME + + @abc.abstractmethod + def is_auth_enabled(self): + return + + @abc.abstractmethod + def get_auth_users(self): + return diff --git a/monkey_island/cc/environment/aws.py b/monkey_island/cc/environment/aws.py new file mode 100644 index 000000000..b85a7d2e4 --- /dev/null +++ b/monkey_island/cc/environment/aws.py @@ -0,0 +1,24 @@ +import urllib2 + +import cc.auth +from cc.environment import Environment + +__author__ = 'itay.mizeretz' + + +class AwsEnvironment(Environment): + def __init__(self): + super(AwsEnvironment, self).__init__() + self._instance_id = AwsEnvironment._get_instance_id() + + @staticmethod + def _get_instance_id(): + return urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read() + + def is_auth_enabled(self): + return True + + def get_auth_users(self): + return [ + cc.auth.User(1, 'monkey', self._instance_id) + ] diff --git a/monkey_island/cc/environment/environment.py b/monkey_island/cc/environment/environment.py new file mode 100644 index 000000000..5cb992c7c --- /dev/null +++ b/monkey_island/cc/environment/environment.py @@ -0,0 +1,18 @@ +import json +import standard +import aws + +ENV_DICT = { + 'standard': standard.StandardEnvironment, + 'aws': aws.AwsEnvironment +} + + +def load_env_from_file(): + with open('server_config.json', 'r') as f: + config_content = f.read() + config_json = json.loads(config_content) + return config_json['server_config'] + + +env = ENV_DICT[load_env_from_file()]() diff --git a/monkey_island/cc/environment/standard.py b/monkey_island/cc/environment/standard.py new file mode 100644 index 000000000..8df00a2c3 --- /dev/null +++ b/monkey_island/cc/environment/standard.py @@ -0,0 +1,12 @@ +from cc.environment import Environment + +__author__ = 'itay.mizeretz' + + +class StandardEnvironment(Environment): + + def is_auth_enabled(self): + return False + + def get_auth_users(self): + return [] diff --git a/monkey_island/cc/island_config.py b/monkey_island/cc/island_config.py deleted file mode 100644 index 87fe97263..000000000 --- a/monkey_island/cc/island_config.py +++ /dev/null @@ -1,9 +0,0 @@ -from datetime import timedelta - -__author__ = 'itay.mizeretz' - -ISLAND_PORT = 5000 -DEFAULT_MONGO_URL = "mongodb://localhost:27017/monkeyisland" -DEBUG_SERVER = False -AUTH_ENABLED = True -AUTH_EXPIRATION_TIME = timedelta(hours=1) diff --git a/monkey_island/cc/main.py b/monkey_island/cc/main.py index bb1ed9eaf..c36b564e7 100644 --- a/monkey_island/cc/main.py +++ b/monkey_island/cc/main.py @@ -11,7 +11,7 @@ if BASE_PATH not in sys.path: from cc.app import init_app from cc.utils import local_ip_addresses -from cc.island_config import DEFAULT_MONGO_URL, ISLAND_PORT, DEBUG_SERVER +from cc.environment.environment import env from cc.database import is_db_server_up if __name__ == '__main__': @@ -19,20 +19,20 @@ if __name__ == '__main__': from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoop - mongo_url = os.environ.get('MONGO_URL', DEFAULT_MONGO_URL) + mongo_url = os.environ.get('MONGO_URL', env.get_mongo_url()) while not is_db_server_up(mongo_url): print('Waiting for MongoDB server') time.sleep(1) app = init_app(mongo_url) - if DEBUG_SERVER: + if env.is_debug(): app.run(host='0.0.0.0', debug=True, ssl_context=('server.crt', 'server.key')) else: http_server = HTTPServer(WSGIContainer(app), ssl_options={'certfile': os.environ.get('SERVER_CRT', 'server.crt'), 'keyfile': os.environ.get('SERVER_KEY', 'server.key')}) - http_server.listen(ISLAND_PORT) - print('Monkey Island C&C Server is running on https://{}:{}'.format(local_ip_addresses()[0], ISLAND_PORT)) + http_server.listen(env.get_island_port()) + print('Monkey Island C&C Server is running on https://{}:{}'.format(local_ip_addresses()[0], env.get_island_port())) IOLoop.instance().start() diff --git a/monkey_island/cc/resources/local_run.py b/monkey_island/cc/resources/local_run.py index 3d18b49e6..c588eaf80 100644 --- a/monkey_island/cc/resources/local_run.py +++ b/monkey_island/cc/resources/local_run.py @@ -6,8 +6,8 @@ import sys from flask import request, jsonify, make_response import flask_restful +from cc.environment.environment import env from cc.resources.monkey_download import get_monkey_executable -from cc.island_config import ISLAND_PORT from cc.services.node import NodeService from cc.utils import local_ip_addresses @@ -36,7 +36,7 @@ def run_local_monkey(): # run the monkey try: - args = ['"%s" m0nk3y -s %s:%s' % (target_path, local_ip_addresses()[0], ISLAND_PORT)] + args = ['"%s" m0nk3y -s %s:%s' % (target_path, local_ip_addresses()[0], env.get_island_port())] if sys.platform == "win32": args = "".join(args) pid = subprocess.Popen(args, shell=True).pid diff --git a/monkey_island/cc/server_config.json b/monkey_island/cc/server_config.json new file mode 100644 index 000000000..2d1a5995b --- /dev/null +++ b/monkey_island/cc/server_config.json @@ -0,0 +1,3 @@ +{ + "server_config": "standard" +} \ No newline at end of file diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index ea755312f..cd2999a49 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -1,7 +1,7 @@ from cc.database import mongo from jsonschema import Draft4Validator, validators -from cc.island_config import ISLAND_PORT +from cc.environment.environment import env from cc.utils import local_ip_addresses __author__ = "itay.mizeretz" @@ -885,8 +885,8 @@ class ConfigService: @staticmethod def set_server_ips_in_config(config): ips = local_ip_addresses() - config["cnc"]["servers"]["command_servers"] = ["%s:%d" % (ip, ISLAND_PORT) for ip in ips] - config["cnc"]["servers"]["current_server"] = "%s:%d" % (ips[0], ISLAND_PORT) + config["cnc"]["servers"]["command_servers"] = ["%s:%d" % (ip, env.get_island_port()) for ip in ips] + config["cnc"]["servers"]["current_server"] = "%s:%d" % (ips[0], env.get_island_port()) @staticmethod def save_initial_config_if_needed(): 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 index 25e214088..ea5db3b8b 100644 --- a/monkey_island/cc/ui/src/services/AuthService.js +++ b/monkey_island/cc/ui/src/services/AuthService.js @@ -1,7 +1,8 @@ import decode from 'jwt-decode'; +import {SERVER_CONFIG} from '../server_config/ServerConfig'; export default class AuthService { - AUTH_ENABLED = true; + AUTH_ENABLED = SERVER_CONFIG.isAuthEnabled(); login = (username, password) => { if (this.AUTH_ENABLED) { @@ -96,5 +97,4 @@ export default class AuthService { return localStorage.getItem('jwt') } - } From 6f827e5aa9931bd108f1d1b3b5eb736e51033a1e Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 25 Feb 2018 14:16:57 +0200 Subject: [PATCH 10/12] Add json-loader to package.json --- monkey_island/cc/ui/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey_island/cc/ui/package.json b/monkey_island/cc/ui/package.json index e8fb04eaf..6759c4530 100644 --- a/monkey_island/cc/ui/package.json +++ b/monkey_island/cc/ui/package.json @@ -65,6 +65,7 @@ "core-js": "^2.5.1", "fetch": "^1.1.0", "js-file-download": "^0.4.1", + "json-loader": "^0.5.7", "jwt-decode": "^2.2.0", "normalize.css": "^4.0.0", "prop-types": "^15.5.10", From 38cf36e165e3fcd240ac2e1938461ca0544c4b8e Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 25 Feb 2018 17:06:40 +0200 Subject: [PATCH 11/12] append options to existsing ones --- .../cc/ui/src/services/AuthService.js | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/monkey_island/cc/ui/src/services/AuthService.js b/monkey_island/cc/ui/src/services/AuthService.js index ea5db3b8b..c5a474ebf 100644 --- a/monkey_island/cc/ui/src/services/AuthService.js +++ b/monkey_island/cc/ui/src/services/AuthService.js @@ -40,7 +40,7 @@ export default class AuthService { }) }; - _authFetch = (url, options) => { + _authFetch = (url, options = {}) => { const headers = { 'Accept': 'application/json', 'Content-Type': 'application/json' @@ -50,15 +50,21 @@ export default class AuthService { headers['Authorization'] = 'JWT ' + this._getToken(); } - return fetch(url, { - headers, - ...options - }).then(res => { - if (res.status === 401) { - this._removeToken(); + if (options.hasOwnProperty('headers')) { + for (let header in headers) { + options['headers'][header] = headers[header]; } - return res; - }); + } else { + options['headers'] = headers; + } + + return fetch(url, options) + .then(res => { + if (res.status === 401) { + this._removeToken(); + } + return res; + }); }; loggedIn() { From ddc93a67fa08258632d8376fb396288f70a96408 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 25 Feb 2018 18:23:52 +0200 Subject: [PATCH 12/12] Fix CR --- monkey_island/cc/auth.py | 8 ++++---- monkey_island/cc/environment/environment.py | 7 ++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/monkey_island/cc/auth.py b/monkey_island/cc/auth.py index 99667d837..a32d6ec9d 100644 --- a/monkey_island/cc/auth.py +++ b/monkey_island/cc/auth.py @@ -10,10 +10,10 @@ __author__ = 'itay.mizeretz' class User(object): - def __init__(self, id, username, password): + def __init__(self, id, username, secret): self.id = id self.username = username - self.password = password + self.secret = secret def __str__(self): return "User(id='%s')" % self.id @@ -24,9 +24,9 @@ def init_jwt(app): username_table = {u.username: u for u in users} userid_table = {u.id: u for u in users} - def authenticate(username, password): + def authenticate(username, secret): user = username_table.get(username, None) - if user and safe_str_cmp(user.password.encode('utf-8'), password.encode('utf-8')): + if user and safe_str_cmp(user.secret.encode('utf-8'), secret.encode('utf-8')): return user def identity(payload): diff --git a/monkey_island/cc/environment/environment.py b/monkey_island/cc/environment/environment.py index 5cb992c7c..8eb97a999 100644 --- a/monkey_island/cc/environment/environment.py +++ b/monkey_island/cc/environment/environment.py @@ -15,4 +15,9 @@ def load_env_from_file(): return config_json['server_config'] -env = ENV_DICT[load_env_from_file()]() +try: + __env_type = load_env_from_file() + env = ENV_DICT[__env_type]() +except Exception: + print('Failed initializing environment: %s' % __env_type) + raise