forked from p15670423/monkey
Merge remote-tracking branch 'upstream/develop' into feature/configuration_improvements
This commit is contained in:
commit
eba779cd2d
|
@ -10,7 +10,8 @@ from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH
|
|||
from monkey_island.cc.database import database, mongo
|
||||
from monkey_island.cc.resources.attack.attack_config import AttackConfiguration
|
||||
from monkey_island.cc.resources.attack.attack_report import AttackReport
|
||||
from monkey_island.cc.resources.auth.auth import init_jwt
|
||||
from monkey_island.cc.resources.auth.auth import Authenticate, init_jwt
|
||||
from monkey_island.cc.resources.auth.registration import Registration
|
||||
from monkey_island.cc.resources.bootloader import Bootloader
|
||||
from monkey_island.cc.resources.client_run import ClientRun
|
||||
from monkey_island.cc.resources.edge import Edge
|
||||
|
@ -31,7 +32,6 @@ from monkey_island.cc.resources.node import Node
|
|||
from monkey_island.cc.resources.node_states import NodeStates
|
||||
from monkey_island.cc.resources.pba_file_download import PBAFileDownload
|
||||
from monkey_island.cc.resources.pba_file_upload import FileUpload
|
||||
from monkey_island.cc.resources.registration import Registration
|
||||
from monkey_island.cc.resources.remote_run import RemoteRun
|
||||
from monkey_island.cc.resources.reporting.report import Report
|
||||
from monkey_island.cc.resources.root import Root
|
||||
|
@ -71,9 +71,12 @@ def serve_home():
|
|||
|
||||
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_singleton.env.get_auth_expiration_time()
|
||||
|
||||
# See https://flask-jwt-extended.readthedocs.io/en/stable/options
|
||||
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = env_singleton.env.get_auth_expiration_time()
|
||||
# Invalidate the signature of JWTs if the server process restarts. This avoids the edge case of getting a JWT,
|
||||
# deciding to reset credentials and then still logging in with the old JWT.
|
||||
app.config['JWT_SECRET_KEY'] = str(uuid.uuid4())
|
||||
|
||||
|
||||
def init_app_services(app):
|
||||
|
@ -96,6 +99,7 @@ def init_app_url_rules(app):
|
|||
def init_api_resources(api):
|
||||
api.add_resource(Root, '/api')
|
||||
api.add_resource(Registration, '/api/registration')
|
||||
api.add_resource(Authenticate, '/api/auth')
|
||||
api.add_resource(Environment, '/api/environment')
|
||||
api.add_resource(Monkey, '/api/monkey', '/api/monkey/', '/api/monkey/<string:guid>')
|
||||
api.add_resource(Bootloader, '/api/bootloader/<string:os>')
|
||||
|
|
|
@ -23,7 +23,7 @@ class Environment(object, metaclass=ABCMeta):
|
|||
_MONGO_URL = os.environ.get("MONKEY_MONGO_URL",
|
||||
"mongodb://{0}:{1}/{2}".format(_MONGO_DB_HOST, _MONGO_DB_PORT, str(_MONGO_DB_NAME)))
|
||||
_DEBUG_SERVER = False
|
||||
_AUTH_EXPIRATION_TIME = timedelta(hours=1)
|
||||
_AUTH_EXPIRATION_TIME = timedelta(minutes=30)
|
||||
|
||||
_testing = False
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ __author__ = "VakarisZ"
|
|||
|
||||
|
||||
class AttackConfiguration(flask_restful.Resource):
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def get(self):
|
||||
return current_app.response_class(json.dumps({"configuration": AttackConfig.get_config()},
|
||||
indent=None,
|
||||
|
@ -16,7 +16,7 @@ class AttackConfiguration(flask_restful.Resource):
|
|||
sort_keys=False) + "\n",
|
||||
mimetype=current_app.config['JSONIFY_MIMETYPE'])
|
||||
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def post(self):
|
||||
"""
|
||||
Based on request content this endpoint either resets ATT&CK configuration or updates it.
|
||||
|
|
|
@ -10,7 +10,7 @@ __author__ = "VakarisZ"
|
|||
|
||||
class AttackReport(flask_restful.Resource):
|
||||
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def get(self):
|
||||
response_content = {'techniques': AttackReportService.get_latest_report()['techniques'], 'schema': SCHEMA}
|
||||
return current_app.response_class(json.dumps(response_content,
|
||||
|
|
|
@ -1,40 +1,67 @@
|
|||
import json
|
||||
import logging
|
||||
from functools import wraps
|
||||
|
||||
from flask import abort, current_app
|
||||
from flask_jwt import JWT, JWTError, _jwt_required
|
||||
import flask_jwt_extended
|
||||
import flask_restful
|
||||
from flask import make_response, request
|
||||
from flask_jwt_extended.exceptions import JWTExtendedException
|
||||
from jwt import PyJWTError
|
||||
from werkzeug.security import safe_str_cmp
|
||||
|
||||
import monkey_island.cc.environment.environment_singleton as env_singleton
|
||||
import monkey_island.cc.resources.auth.user_store as user_store
|
||||
|
||||
__author__ = 'itay.mizeretz'
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def init_jwt(app):
|
||||
user_store.UserStore.set_users(env_singleton.env.get_auth_users())
|
||||
_ = flask_jwt_extended.JWTManager(app)
|
||||
logger.debug("Initialized JWT with secret key that started with " + app.config["JWT_SECRET_KEY"][:4])
|
||||
|
||||
def authenticate(username, secret):
|
||||
|
||||
class Authenticate(flask_restful.Resource):
|
||||
"""
|
||||
Resource for user authentication. The user provides the username and hashed password and we give them a JWT.
|
||||
See `AuthService.js` file for the frontend counterpart for this code.
|
||||
"""
|
||||
@staticmethod
|
||||
def _authenticate(username, secret):
|
||||
user = user_store.UserStore.username_table.get(username, None)
|
||||
if user and safe_str_cmp(user.secret.encode('utf-8'), secret.encode('utf-8')):
|
||||
return user
|
||||
|
||||
def identity(payload):
|
||||
user_id = payload['identity']
|
||||
return user_store.UserStore.user_id_table.get(user_id, None)
|
||||
|
||||
JWT(app, authenticate, identity)
|
||||
def post(self):
|
||||
"""
|
||||
Example request:
|
||||
{
|
||||
"username": "my_user",
|
||||
"password": "343bb87e553b05430e5c44baf99569d4b66..."
|
||||
}
|
||||
"""
|
||||
credentials = json.loads(request.data)
|
||||
# Unpack auth info from request
|
||||
username = credentials["username"]
|
||||
secret = credentials["password"]
|
||||
# If the user and password have been previously registered
|
||||
if self._authenticate(username, secret):
|
||||
access_token = flask_jwt_extended.create_access_token(identity=user_store.UserStore.username_table[username].id)
|
||||
logger.debug(f"Created access token for user {username} that begins with {access_token[:4]}")
|
||||
return make_response({"access_token": access_token, "error": ""}, 200)
|
||||
else:
|
||||
return make_response({"error": "Invalid credentials"}, 401)
|
||||
|
||||
|
||||
def jwt_required(realm=None):
|
||||
def wrapper(fn):
|
||||
# See https://flask-jwt-extended.readthedocs.io/en/stable/custom_decorators/
|
||||
def jwt_required(fn):
|
||||
@wraps(fn)
|
||||
def decorator(*args, **kwargs):
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
_jwt_required(realm or current_app.config['JWT_DEFAULT_REALM'])
|
||||
flask_jwt_extended.verify_jwt_in_request()
|
||||
return fn(*args, **kwargs)
|
||||
except JWTError:
|
||||
abort(401)
|
||||
|
||||
return decorator
|
||||
# Catch authentication related errors in the verification or inside the called function. All other exceptions propagate
|
||||
except (JWTExtendedException, PyJWTError) as e:
|
||||
return make_response({"error": f"Authentication error: {str(e)}"}, 401)
|
||||
|
||||
return wrapper
|
||||
|
|
|
@ -8,12 +8,12 @@ from monkey_island.cc.services.config import ConfigService
|
|||
|
||||
|
||||
class IslandConfiguration(flask_restful.Resource):
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def get(self):
|
||||
return jsonify(schema=ConfigService.get_config_schema(),
|
||||
configuration=ConfigService.get_config(False, True, True))
|
||||
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def post(self):
|
||||
config_json = json.loads(request.data)
|
||||
if 'reset' in config_json:
|
||||
|
|
|
@ -11,7 +11,7 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
class IslandLog(flask_restful.Resource):
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def get(self):
|
||||
try:
|
||||
return IslandLogService.get_log_file()
|
||||
|
|
|
@ -14,7 +14,7 @@ __author__ = "itay.mizeretz"
|
|||
|
||||
|
||||
class Log(flask_restful.Resource):
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def get(self):
|
||||
monkey_id = request.args.get('id')
|
||||
exists_monkey_id = request.args.get('exists')
|
||||
|
|
|
@ -10,11 +10,11 @@ __author__ = 'Barak'
|
|||
|
||||
|
||||
class MonkeyConfiguration(flask_restful.Resource):
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def get(self):
|
||||
return jsonify(schema=ConfigService.get_config_schema(), configuration=ConfigService.get_config(False, True))
|
||||
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def post(self):
|
||||
config_json = json.loads(request.data)
|
||||
if 'reset' in config_json:
|
||||
|
|
|
@ -8,7 +8,7 @@ __author__ = 'Barak'
|
|||
|
||||
|
||||
class NetMap(flask_restful.Resource):
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def get(self, **kw):
|
||||
net_nodes = NetNodeService.get_all_net_nodes()
|
||||
net_edges = NetEdgeService.get_all_net_edges()
|
||||
|
|
|
@ -8,7 +8,7 @@ __author__ = 'Barak'
|
|||
|
||||
|
||||
class Node(flask_restful.Resource):
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def get(self):
|
||||
node_id = request.args.get('id')
|
||||
if node_id:
|
||||
|
|
|
@ -6,6 +6,6 @@ from monkey_island.cc.services.utils.node_states import \
|
|||
|
||||
|
||||
class NodeStates(flask_restful.Resource):
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def get(self):
|
||||
return {'node_states': [state.value for state in NodeStateList]}
|
||||
|
|
|
@ -27,7 +27,7 @@ class FileUpload(flask_restful.Resource):
|
|||
# Create all directories on the way if they don't exist
|
||||
UPLOADS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def get(self, file_type):
|
||||
"""
|
||||
Sends file to filepond
|
||||
|
@ -41,7 +41,7 @@ class FileUpload(flask_restful.Resource):
|
|||
filename = ConfigService.get_config_value(copy.deepcopy(PBA_WINDOWS_FILENAME_PATH))
|
||||
return send_from_directory(UPLOADS_DIR, filename)
|
||||
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def post(self, file_type):
|
||||
"""
|
||||
Receives user's uploaded file from filepond
|
||||
|
@ -55,7 +55,7 @@ class FileUpload(flask_restful.Resource):
|
|||
status=200, mimetype='text/plain')
|
||||
return response
|
||||
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def delete(self, file_type):
|
||||
"""
|
||||
Deletes file that has been deleted on the front end
|
||||
|
|
|
@ -24,7 +24,7 @@ class RemoteRun(flask_restful.Resource):
|
|||
island_ip = request_body.get('island_ip')
|
||||
return RemoteRunAwsService.run_aws_monkeys(instances, island_ip)
|
||||
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def get(self):
|
||||
action = request.args.get('action')
|
||||
if action == 'list_aws':
|
||||
|
@ -43,7 +43,7 @@ class RemoteRun(flask_restful.Resource):
|
|||
|
||||
return {}
|
||||
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def post(self):
|
||||
body = json.loads(request.data)
|
||||
resp = {}
|
||||
|
|
|
@ -21,7 +21,7 @@ __author__ = ["itay.mizeretz", "shay.nehmad"]
|
|||
|
||||
class Report(flask_restful.Resource):
|
||||
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def get(self, report_type=SECURITY_REPORT_TYPE, report_data=None):
|
||||
if report_type == SECURITY_REPORT_TYPE:
|
||||
return ReportService.get_report()
|
||||
|
|
|
@ -26,15 +26,15 @@ class Root(flask_restful.Resource):
|
|||
if not action:
|
||||
return self.get_server_info()
|
||||
elif action == "reset":
|
||||
return jwt_required()(Database.reset_db)()
|
||||
return jwt_required(Database.reset_db)()
|
||||
elif action == "killall":
|
||||
return jwt_required()(InfectionLifecycle.kill_all)()
|
||||
return jwt_required(InfectionLifecycle.kill_all)()
|
||||
elif action == "is-up":
|
||||
return {'is-up': True}
|
||||
else:
|
||||
return make_response(400, {'error': 'unknown action'})
|
||||
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def get_server_info(self):
|
||||
return jsonify(
|
||||
ip_addresses=local_ip_addresses(),
|
||||
|
|
|
@ -20,7 +20,7 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
class Telemetry(flask_restful.Resource):
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def get(self, **kw):
|
||||
monkey_guid = request.args.get('monkey_guid')
|
||||
telem_category = request.args.get('telem_category')
|
||||
|
|
|
@ -16,7 +16,7 @@ __author__ = 'itay.mizeretz'
|
|||
|
||||
|
||||
class TelemetryFeed(flask_restful.Resource):
|
||||
@jwt_required()
|
||||
@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...
|
||||
|
|
|
@ -17,7 +17,7 @@ class ClearCaches(flask_restful.Resource):
|
|||
so we use this to clear the caches.
|
||||
:note: DO NOT CALL THIS IN PRODUCTION CODE as this will slow down the user experience.
|
||||
"""
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def get(self, **kw):
|
||||
try:
|
||||
logger.warning("Trying to clear caches! Make sure this is not production")
|
||||
|
|
|
@ -7,7 +7,7 @@ from monkey_island.cc.resources.auth.auth import jwt_required
|
|||
|
||||
|
||||
class LogTest(flask_restful.Resource):
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def get(self):
|
||||
find_query = json_util.loads(request.args.get('find_query'))
|
||||
log = mongo.db.log.find_one(find_query)
|
||||
|
|
|
@ -7,7 +7,7 @@ from monkey_island.cc.resources.auth.auth import jwt_required
|
|||
|
||||
|
||||
class MonkeyTest(flask_restful.Resource):
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def get(self, **kw):
|
||||
find_query = json_util.loads(request.args.get('find_query'))
|
||||
return {'results': list(mongo.db.monkey.find(find_query))}
|
||||
|
|
|
@ -9,6 +9,6 @@ from monkey_island.cc.services.reporting.zero_trust_service import \
|
|||
|
||||
class ZeroTrustFindingEvent(flask_restful.Resource):
|
||||
|
||||
@jwt_required()
|
||||
@jwt_required
|
||||
def get(self, finding_id: str):
|
||||
return {'events_json': json.dumps(ZeroTrustService.get_events_by_finding(finding_id), default=str)}
|
||||
|
|
|
@ -13797,9 +13797,9 @@
|
|||
}
|
||||
},
|
||||
"react-tooltip-lite": {
|
||||
"version": "1.11.2",
|
||||
"resolved": "https://registry.npmjs.org/react-tooltip-lite/-/react-tooltip-lite-1.11.2.tgz",
|
||||
"integrity": "sha512-zFVKAFta5nC1763B1XCeFhSBRYblVgLxRCnXu4ucMqaJQ7UxpZV4gFgou9gesfLWKr4Cgvx1bpy6L9msKXQqCw==",
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/react-tooltip-lite/-/react-tooltip-lite-1.12.0.tgz",
|
||||
"integrity": "sha512-QjDnmDmjtLNKvLY6bzUOG8W6ZDBTiE4UXugGzClOQEGvMvbkJn2GvZvLwRaxsN/GCx7589RgbGaESMiJAm+zWg==",
|
||||
"requires": {
|
||||
"prop-types": "^15.5.8"
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@
|
|||
"react-spinners": "^0.9.0",
|
||||
"react-table": "^6.10.3",
|
||||
"react-toggle": "^4.1.1",
|
||||
"react-tooltip-lite": "^1.10.0",
|
||||
"react-tooltip-lite": "^1.12.0",
|
||||
"redux": "^4.0.4",
|
||||
"sha3": "^2.0.7",
|
||||
"snyk": "^1.361.3"
|
||||
|
|
|
@ -26,13 +26,13 @@ class RegisterPageComponent extends React.Component {
|
|||
};
|
||||
|
||||
setNoAuth = () => {
|
||||
let options = {}
|
||||
let options = {};
|
||||
options['headers'] = {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
options['method'] = 'PATCH'
|
||||
options['body'] = JSON.stringify({'server_config': 'standard'})
|
||||
options['method'] = 'PATCH';
|
||||
options['body'] = JSON.stringify({'server_config': 'standard'});
|
||||
|
||||
return fetch(this.NO_AUTH_API_ENDPOINT, options)
|
||||
.then(res => {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import 'core-js/stable';
|
||||
import 'regenerator-runtime/runtime';
|
||||
import 'core-js/fn/object/assign';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './components/Main';
|
||||
|
|
|
@ -83,7 +83,7 @@ export default class AuthService {
|
|||
};
|
||||
|
||||
if (this._loggedIn()) {
|
||||
headers['Authorization'] = 'JWT ' + this._getToken();
|
||||
headers['Authorization'] = 'Bearer ' + this._getToken();
|
||||
}
|
||||
|
||||
if (options.hasOwnProperty('headers')) {
|
||||
|
@ -97,6 +97,9 @@ export default class AuthService {
|
|||
return fetch(url, options)
|
||||
.then(res => {
|
||||
if (res.status === 401) {
|
||||
res.clone().json().then(res_json => {
|
||||
console.log('Got 401 from server while trying to authFetch: ' + JSON.stringify(res_json));
|
||||
});
|
||||
this._removeToken();
|
||||
}
|
||||
return res;
|
||||
|
@ -156,6 +159,4 @@ export default class AuthService {
|
|||
_toHexStr(byteArr) {
|
||||
return byteArr.reduce((acc, x) => (acc + ('0' + x.toString(0x10)).slice(-2)), '');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Flask-JWT>=0.3.2
|
||||
Flask-JWT-Extended==3.24.1
|
||||
Flask-Pymongo>=2.3.0
|
||||
Flask-Restful>=0.3.8
|
||||
PyInstaller==3.6
|
||||
|
@ -25,3 +25,5 @@ tqdm>=4.47
|
|||
virtualenv>=20.0.26
|
||||
werkzeug>=1.0.1
|
||||
wheel>=0.34.2
|
||||
|
||||
pyjwt>=1.5.1 # not directly required, pinned by Snyk to avoid a vulnerability
|
Loading…
Reference in New Issue