Merge pull request #309 from guardicore/feature/check-for-updates
Feature/check for updates
This commit is contained in:
commit
ca096d9bad
|
@ -28,6 +28,7 @@ from monkey_island.cc.resources.root import Root
|
||||||
from monkey_island.cc.resources.telemetry import Telemetry
|
from monkey_island.cc.resources.telemetry import Telemetry
|
||||||
from monkey_island.cc.resources.telemetry_feed import TelemetryFeed
|
from monkey_island.cc.resources.telemetry_feed import TelemetryFeed
|
||||||
from monkey_island.cc.resources.pba_file_download import PBAFileDownload
|
from monkey_island.cc.resources.pba_file_download import PBAFileDownload
|
||||||
|
from monkey_island.cc.resources.version_update import VersionUpdate
|
||||||
from monkey_island.cc.services.config import ConfigService
|
from monkey_island.cc.services.config import ConfigService
|
||||||
from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH
|
from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH
|
||||||
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
|
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
|
||||||
|
@ -83,18 +84,14 @@ def output_json(obj, code, headers=None):
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
||||||
def init_app(mongo_url):
|
def init_app_config(app, mongo_url):
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
api = flask_restful.Api(app)
|
|
||||||
api.representations = {'application/json': output_json}
|
|
||||||
|
|
||||||
app.config['MONGO_URI'] = mongo_url
|
app.config['MONGO_URI'] = mongo_url
|
||||||
|
|
||||||
app.config['SECRET_KEY'] = str(uuid.getnode())
|
app.config['SECRET_KEY'] = str(uuid.getnode())
|
||||||
app.config['JWT_AUTH_URL_RULE'] = '/api/auth'
|
app.config['JWT_AUTH_URL_RULE'] = '/api/auth'
|
||||||
app.config['JWT_EXPIRATION_DELTA'] = env.get_auth_expiration_time()
|
app.config['JWT_EXPIRATION_DELTA'] = env.get_auth_expiration_time()
|
||||||
|
|
||||||
|
|
||||||
|
def init_app_services(app):
|
||||||
init_jwt(app)
|
init_jwt(app)
|
||||||
mongo.init_app(app)
|
mongo.init_app(app)
|
||||||
|
|
||||||
|
@ -105,9 +102,13 @@ def init_app(mongo_url):
|
||||||
# If running on AWS, this will initialize the instance data, which is used "later" in the execution of the island.
|
# If running on AWS, this will initialize the instance data, which is used "later" in the execution of the island.
|
||||||
RemoteRunAwsService.init()
|
RemoteRunAwsService.init()
|
||||||
|
|
||||||
|
|
||||||
|
def init_app_url_rules(app):
|
||||||
app.add_url_rule('/', 'serve_home', serve_home)
|
app.add_url_rule('/', 'serve_home', serve_home)
|
||||||
app.add_url_rule('/<path:static_path>', 'serve_static_file', serve_static_file)
|
app.add_url_rule('/<path:static_path>', 'serve_static_file', serve_static_file)
|
||||||
|
|
||||||
|
|
||||||
|
def init_api_resources(api):
|
||||||
api.add_resource(Root, '/api')
|
api.add_resource(Root, '/api')
|
||||||
api.add_resource(Monkey, '/api/monkey', '/api/monkey/', '/api/monkey/<string:guid>')
|
api.add_resource(Monkey, '/api/monkey', '/api/monkey/', '/api/monkey/<string:guid>')
|
||||||
api.add_resource(LocalRun, '/api/local-monkey', '/api/local-monkey/')
|
api.add_resource(LocalRun, '/api/local-monkey', '/api/local-monkey/')
|
||||||
|
@ -130,5 +131,18 @@ def init_app(mongo_url):
|
||||||
'/api/fileUpload/<string:file_type>?restore=<string:filename>')
|
'/api/fileUpload/<string:file_type>?restore=<string:filename>')
|
||||||
api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/')
|
api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/')
|
||||||
api.add_resource(AttackTelem, '/api/attack/<string:technique>')
|
api.add_resource(AttackTelem, '/api/attack/<string:technique>')
|
||||||
|
api.add_resource(VersionUpdate, '/api/version-update', '/api/version-update/')
|
||||||
|
|
||||||
|
|
||||||
|
def init_app(mongo_url):
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
api = flask_restful.Api(app)
|
||||||
|
api.representations = {'application/json': output_json}
|
||||||
|
|
||||||
|
init_app_config(app, mongo_url)
|
||||||
|
init_app_services(app)
|
||||||
|
init_app_url_rules(app)
|
||||||
|
init_api_resources(api)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -13,6 +13,7 @@ class Environment(object):
|
||||||
_MONGO_URL = os.environ.get("MONKEY_MONGO_URL", "mongodb://localhost:27017/monkeyisland")
|
_MONGO_URL = os.environ.get("MONKEY_MONGO_URL", "mongodb://localhost:27017/monkeyisland")
|
||||||
_DEBUG_SERVER = False
|
_DEBUG_SERVER = False
|
||||||
_AUTH_EXPIRATION_TIME = timedelta(hours=1)
|
_AUTH_EXPIRATION_TIME = timedelta(hours=1)
|
||||||
|
_MONKEY_VERSION = "1.6.2"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.config = None
|
self.config = None
|
||||||
|
@ -37,6 +38,21 @@ class Environment(object):
|
||||||
h.update(secret)
|
h.update(secret)
|
||||||
return h.hexdigest()
|
return h.hexdigest()
|
||||||
|
|
||||||
|
def get_deployment(self):
|
||||||
|
return self._get_from_config('deployment', 'unknown')
|
||||||
|
|
||||||
|
def is_develop(self):
|
||||||
|
return self.get_deployment() == 'develop'
|
||||||
|
|
||||||
|
def get_version(self):
|
||||||
|
return self._MONKEY_VERSION + ('-dev' if self.is_develop() else '')
|
||||||
|
|
||||||
|
def _get_from_config(self, key, default_value=None):
|
||||||
|
val = default_value
|
||||||
|
if self.config is not None:
|
||||||
|
val = self.config.get(key, val)
|
||||||
|
return val
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_auth_users(self):
|
def get_auth_users(self):
|
||||||
return
|
return
|
||||||
|
|
|
@ -32,6 +32,7 @@ def load_env_from_file():
|
||||||
config_json = load_server_configuration_from_file()
|
config_json = load_server_configuration_from_file()
|
||||||
return config_json['server_config']
|
return config_json['server_config']
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
config_json = load_server_configuration_from_file()
|
config_json = load_server_configuration_from_file()
|
||||||
__env_type = config_json['server_config']
|
__env_type = config_json['server_config']
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import flask_restful
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from monkey_island.cc.environment.environment import env
|
||||||
|
from monkey_island.cc.auth import jwt_required
|
||||||
|
from monkey_island.cc.services.version_update import VersionUpdateService
|
||||||
|
|
||||||
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class VersionUpdate(flask_restful.Resource):
|
||||||
|
def __init__(self):
|
||||||
|
super(VersionUpdate, self).__init__()
|
||||||
|
|
||||||
|
# We don't secure this since it doesn't give out any private info and we want UI to know version
|
||||||
|
# even when not authenticated
|
||||||
|
def get(self):
|
||||||
|
return {
|
||||||
|
'current_version': env.get_version(),
|
||||||
|
'newer_version': VersionUpdateService.get_newer_version(),
|
||||||
|
'download_link': VersionUpdateService.get_download_link()
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"server_config": "standard"
|
"server_config": "standard",
|
||||||
|
"deployment": "develop"
|
||||||
}
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from monkey_island.cc.environment.environment import env
|
||||||
|
|
||||||
|
__author__ = "itay.mizeretz"
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class VersionUpdateService:
|
||||||
|
VERSION_SERVER_URL_PREF = 'https://monkey.guardicore.com'
|
||||||
|
VERSION_SERVER_CHECK_NEW_URL = VERSION_SERVER_URL_PREF + '?deployment=%s&monkey_version=%s'
|
||||||
|
VERSION_SERVER_DOWNLOAD_URL = VERSION_SERVER_CHECK_NEW_URL + '&is_download=true'
|
||||||
|
|
||||||
|
newer_version = None
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_newer_version():
|
||||||
|
"""
|
||||||
|
Checks for newer version if never checked before.
|
||||||
|
:return: None if failed checking for newer version, result of '_check_new_version' otherwise
|
||||||
|
"""
|
||||||
|
if VersionUpdateService.newer_version is None:
|
||||||
|
try:
|
||||||
|
VersionUpdateService.newer_version = VersionUpdateService._check_new_version()
|
||||||
|
except Exception:
|
||||||
|
logger.exception('Failed updating version number')
|
||||||
|
|
||||||
|
return VersionUpdateService.newer_version
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _check_new_version():
|
||||||
|
"""
|
||||||
|
Checks if newer monkey version is available
|
||||||
|
:return: False if not, version in string format ('1.6.2') otherwise
|
||||||
|
"""
|
||||||
|
url = VersionUpdateService.VERSION_SERVER_CHECK_NEW_URL % (env.get_deployment(), env.get_version())
|
||||||
|
|
||||||
|
reply = requests.get(url, timeout=15)
|
||||||
|
|
||||||
|
res = reply.json().get('newer_version', None)
|
||||||
|
|
||||||
|
if res is False:
|
||||||
|
return res
|
||||||
|
|
||||||
|
[int(x) for x in res.split('.')] # raises value error if version is invalid format
|
||||||
|
return res
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_download_link():
|
||||||
|
return VersionUpdateService.VERSION_SERVER_DOWNLOAD_URL % (env.get_deployment(), env.get_version())
|
||||||
|
|
|
@ -20,6 +20,7 @@ import 'react-data-components/css/table-twbs.css';
|
||||||
import 'styles/App.css';
|
import 'styles/App.css';
|
||||||
import 'react-toggle/style.css';
|
import 'react-toggle/style.css';
|
||||||
import 'react-table/react-table.css';
|
import 'react-table/react-table.css';
|
||||||
|
import VersionComponent from "./side-menu/VersionComponent";
|
||||||
|
|
||||||
let logoImage = require('../images/monkey-icon.svg');
|
let logoImage = require('../images/monkey-icon.svg');
|
||||||
let infectionMonkeyImage = require('../images/infection-monkey.svg');
|
let infectionMonkeyImage = require('../images/infection-monkey.svg');
|
||||||
|
@ -85,7 +86,7 @@ class AppComponent extends AuthComponent {
|
||||||
infection_done: false,
|
infection_done: false,
|
||||||
report_done: false,
|
report_done: false,
|
||||||
isLoggedIn: undefined
|
isLoggedIn: undefined
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,6 +176,7 @@ class AppComponent extends AuthComponent {
|
||||||
<div className="license-link text-center">
|
<div className="license-link text-center">
|
||||||
<NavLink to="/license">License</NavLink>
|
<NavLink to="/license">License</NavLink>
|
||||||
</div>
|
</div>
|
||||||
|
<VersionComponent/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col sm={9} md={10} smOffset={3} mdOffset={2} className="main">
|
<Col sm={9} md={10} smOffset={3} mdOffset={2} className="main">
|
||||||
<Route path='/login' render={(props) => (<LoginPageComponent onStatusChange={this.updateStatus}/>)}/>
|
<Route path='/login' render={(props) => (<LoginPageComponent onStatusChange={this.updateStatus}/>)}/>
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {Icon} from 'react-fa';
|
||||||
|
|
||||||
|
class VersionComponent extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
currentVersion: undefined,
|
||||||
|
newerVersion: undefined,
|
||||||
|
downloadLink: undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
fetch('/api/version-update') // This is not authenticated on purpose
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
this.setState({
|
||||||
|
currentVersion: res['current_version'],
|
||||||
|
newerVersion: res['newer_version'],
|
||||||
|
downloadLink: res['download_link'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="version-text text-center">
|
||||||
|
Infection Monkey Version: {this.state.currentVersion}
|
||||||
|
{
|
||||||
|
this.state.newerVersion ?
|
||||||
|
<div>
|
||||||
|
<b>Newer version available!</b>
|
||||||
|
<br/>
|
||||||
|
<b><a target="_blank" href={this.state.downloadLink}>Download here <Icon name="download"/></a></b>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
undefined
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default VersionComponent;
|
|
@ -515,3 +515,13 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.version-text {
|
||||||
|
font-size: 0.9em;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 5px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
bson
|
||||||
python-dateutil
|
python-dateutil
|
||||||
tornado==5.1.1
|
tornado==5.1.1
|
||||||
werkzeug
|
werkzeug
|
||||||
|
@ -18,7 +19,7 @@ boto3
|
||||||
botocore
|
botocore
|
||||||
PyInstaller
|
PyInstaller
|
||||||
awscli
|
awscli
|
||||||
bson
|
|
||||||
cffi
|
cffi
|
||||||
virtualenv
|
virtualenv
|
||||||
wheel
|
wheel
|
||||||
|
requests
|
||||||
|
|
Loading…
Reference in New Issue