Merge pull request #309 from guardicore/feature/check-for-updates

Feature/check for updates
This commit is contained in:
itaymmguardicore 2019-05-12 11:23:23 +03:00 committed by GitHub
commit ca096d9bad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 183 additions and 11 deletions

View File

@ -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_feed import TelemetryFeed
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.consts import MONKEY_ISLAND_ABS_PATH
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
@ -83,18 +84,14 @@ def output_json(obj, code, headers=None):
return resp
def init_app(mongo_url):
app = Flask(__name__)
api = flask_restful.Api(app)
api.representations = {'application/json': output_json}
def init_app_config(app, mongo_url):
app.config['MONGO_URI'] = mongo_url
app.config['SECRET_KEY'] = str(uuid.getnode())
app.config['JWT_AUTH_URL_RULE'] = '/api/auth'
app.config['JWT_EXPIRATION_DELTA'] = env.get_auth_expiration_time()
def init_app_services(app):
init_jwt(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.
RemoteRunAwsService.init()
def init_app_url_rules(app):
app.add_url_rule('/', 'serve_home', serve_home)
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(Monkey, '/api/monkey', '/api/monkey/', '/api/monkey/<string:guid>')
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.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/')
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

View File

@ -13,6 +13,7 @@ class Environment(object):
_MONGO_URL = os.environ.get("MONKEY_MONGO_URL", "mongodb://localhost:27017/monkeyisland")
_DEBUG_SERVER = False
_AUTH_EXPIRATION_TIME = timedelta(hours=1)
_MONKEY_VERSION = "1.6.2"
def __init__(self):
self.config = None
@ -37,6 +38,21 @@ class Environment(object):
h.update(secret)
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
def get_auth_users(self):
return

View File

@ -32,6 +32,7 @@ def load_env_from_file():
config_json = load_server_configuration_from_file()
return config_json['server_config']
try:
config_json = load_server_configuration_from_file()
__env_type = config_json['server_config']

View File

@ -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()
}

View File

@ -1,3 +1,4 @@
{
"server_config": "standard"
"server_config": "standard",
"deployment": "develop"
}

View File

@ -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())

View File

@ -20,6 +20,7 @@ import 'react-data-components/css/table-twbs.css';
import 'styles/App.css';
import 'react-toggle/style.css';
import 'react-table/react-table.css';
import VersionComponent from "./side-menu/VersionComponent";
let logoImage = require('../images/monkey-icon.svg');
let infectionMonkeyImage = require('../images/infection-monkey.svg');
@ -85,7 +86,7 @@ class AppComponent extends AuthComponent {
infection_done: false,
report_done: false,
isLoggedIn: undefined
}
},
};
}
@ -175,6 +176,7 @@ class AppComponent extends AuthComponent {
<div className="license-link text-center">
<NavLink to="/license">License</NavLink>
</div>
<VersionComponent/>
</Col>
<Col sm={9} md={10} smOffset={3} mdOffset={2} className="main">
<Route path='/login' render={(props) => (<LoginPageComponent onStatusChange={this.updateStatus}/>)}/>

View File

@ -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;

View File

@ -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;
}

View File

@ -1,3 +1,4 @@
bson
python-dateutil
tornado==5.1.1
werkzeug
@ -18,7 +19,7 @@ boto3
botocore
PyInstaller
awscli
bson
cffi
virtualenv
wheel
requests