def default_sharing(self, id): """Change a user's default sharing. .. :quickref: User; Change default sharing When used on another user account, requires the `manage_users` permission. :param id: user id. :>json User user: modified user. """ self.ensure_permission(id) user = User(get_or_404(User.get_collection(), _id=id)) groups = request.form.get('groups', '').split(',') for group in groups: if group in user['groups']: break else: flash('You have to at least keep one of your groups.', 'danger') return redirect(request.referrer) user.update_value('default_sharing', groups) return redirect({'user': clean_users(user)}, request.referrer)
def configuration(self, id): """Configure a named configuration. .. :quickref: Module; Configure a named configuration Requires the `manage_modules` permission. For each configuration available, you should set the value in a form parameter named ``config_NAME``. For boolean values, any value not ``0`` or ``False`` is considered to be ``True``. If successful, will return the named configuration in ``config``. Otherwise, errors will be available in ``errors``. :param id: id of the named configuration. """ config = Config(get_or_404(Config.get_collection(), _id=id)) if request.method == "POST": errors = update_config(config['config']) if errors is not None: return errors config.save() dispatcher.reload() return redirect({'config': config}, url_for('ModulesView:index')) else: return render({'config': config}, 'modules/configuration.html')
def update(self, id): """Update a user. .. :quickref: User; Update existing user Requires the `manage_users` permission. When succesful, the new user will be returned in the ``user`` field. Otherwise, an ``errors`` field will list errors. :form name: full name :form email: email address :form groups: comma-delimited list of groups :form permission_VALUE: specify a value different than ``0`` or ``False`` for all permissions the user should have. """ name = request.form.get('name') email = request.form.get('email').lower() groups = [g for g in request.form.get('groups', '').split(',') if g] user = User(get_or_404(User.get_collection(), _id=id)) if not self._valid_form(name, email, groups, user['email']): return validation_error() user['name'] = name user['email'] = email user['groups'] = groups user['permissions'] = self.get_permissions(user['permissions']) user.save() return redirect({'user': clean_users(user)}, url_for('UsersView:get', id=user['_id']))
def repository_new(self): """Add a repository .. :quickref: Module; Add repository Requires the `manage_modules` permission. If successful, will return the repository in ``repository``. Otherwise, errors will be available in ``errors``. :form name: name of the repository (should be a valid package name). :form address: HTTPs or SSH address of the repository. :form private: boolean specifying if the repository is private. See Administration Guide for more details on private repositories. """ deploy_key = get_deploy_key() repository = Repository() if request.method == 'POST': for field in ['name', 'address']: repository[field] = request.form.get(field) if repository[field] is None or repository[field] == "": flash("{} is required.".format(field), 'danger') return validation_error() existing_repository = Repository.get( **{field: repository[field]}) if existing_repository: flash( "There is already a repository with this {}.".format( field), 'danger') return validation_error() value = request.form.get('private') repository['private'] = (value is not None) and (value not in [ '0', 'False' ]) if repository['private'] and deploy_key is None: flash( "Private repositories are disabled because of a problem with your installation (you do not have a deploy key in 'conf/id_rsa.pub')", 'danger') return validation_error() repository['status'] = 'cloning' repository.save() repository.clone() return redirect({'repository': clean_repositories(repository)}, url_for('ModulesView:index')) return render({ 'repository': repository, 'deploy_key': deploy_key }, 'modules/repository_new.html')
def refresh_iocs(self, id): """Refresh IOCs with Threat Intel modules .. :quickref: Analysis; Refresh IOCs with Threat Intel modules. :param id: id of the analysis. """ analysis = Analysis(get_or_404(current_user.analyses, _id=id)) analysis.refresh_iocs() return redirect(analysis, url_for('AnalysesView:get', id=analysis["_id"]))
def enable(self, id): """Enable a module .. :quickref: Module; Enable a module Requires the `manage_modules` permission. If successful, will return the module in ``module``. Otherwise, errors will be available in ``errors``. :param id: id of the module to enable. """ module = ModuleInfo(get_or_404(ModuleInfo.get_collection(), _id=id)) if 'error' in module: flash( "Cannot enable '{}' because of errors installing dependencies." .format(module['name']), 'danger') return validation_error(url_for('ModulesView:index')) # See if module is properly configured module_class = get_class(module['path'], module['class']) module_class.info = module try: module_class() except MissingConfiguration as e: if e.name: flash( "You must configure '{}' before trying to enable '{}'". format(e.name, module['name']), 'warning') return validation_error( url_for('ModulesView:configuration', id=e.id)) else: flash( "You must configure '{}' before trying to enable it.". format(module['name']), 'warning') return validation_error( url_for('ModulesView:configure', id=module['_id'])) module.update_value('enabled', True) dispatcher.reload() readme = module.get_readme() if readme: flash(readme, 'persistent') return redirect({'module': clean_modules(module)}, url_for('ModulesView:index'))
def repository_delete(self, id): """Delete a repository .. :quickref: Module; Delete repository Requires the `manage_modules` permission. Returns "ok". :param id: id of the repository. """ repository = Repository(get_or_404(Repository.get_collection(), _id=id)) repository.delete() dispatcher.reload() return redirect('ok', url_for('ModulesView:index'))
def disable(self, id): """Disable a user. .. :quickref: User; Disable a user Requires the `manage_users` permission. :param id: user id. :>json User user: modified user. """ user = User(get_or_404(User.get_collection(), _id=id)) user.update_value('enabled', False) return redirect({'user': clean_users(user)}, url_for('UsersView:index'))
def disable(self, id): """Disable a module .. :quickref: Module; Disable a module Requires the `manage_modules` permission. :param id: id of the module to disable. :>json Module module: resulting module. """ module = ModuleInfo(get_or_404(ModuleInfo.get_collection(), _id=id)) module.update_value('enabled', False) dispatcher.reload() return redirect({'module': clean_modules(module)}, url_for('ModulesView:index'))
def repository_update(self, id): """Update a repository .. :quickref: Module; Update repository Requires the `manage_modules` permission. :param id: id of the repository. :>json Repository repository: the repository. """ repository = Repository(get_or_404(Repository.get_collection(), _id=id)) repository.pull() return redirect({'repository': clean_repositories(repository)}, url_for('ModulesView:index'))
def reset_api(self, id): """Reset a user's API key. .. :quickref: User; Reset API key When used on another user account, requires the `manage_users` permission. :param id: user id. :>json User user: modified user. """ self.ensure_permission(id) user = User(get_or_404(User.get_collection(), _id=id)) user.update_value('api_key', User.generate_api_key()) return redirect({'user': clean_users(user)}, request.referrer)
def reload(self): """Reload the workers .. :quickref: Module; Reload workers Requires the `manage_modules` permission. Returns "ok". """ for repository in Repository.get_collection().find(): dispatcher.update_modules(Repository(repository)) updates = Internals( get_or_404(Internals.get_collection(), name="updates")) updates.update_value("last_update", time()) flash( 'Workers will reload once they are done with their current tasks', 'success') return redirect('ok', url_for('ModulesView:index'))
def create(self): """Create a user. .. :quickref: User; Create new user Requires the `manage_users` permission. When succesful, the new user will be returned in the ``user`` field. Otherwise, an ``errors`` field will list errors. :form name: full name :form email: email address :form groups: comma-delimited list of groups :form permission_VALUE: specify a value different than ``0`` or ``False`` for all permissions the user should have. """ name = request.form.get('name') email = request.form.get('email').lower() groups = [g for g in request.form.get('groups', '').split(',') if g] if not self._valid_form(name, email, groups): return validation_error() user = User({ 'name': name, 'email': email.lower(), 'groups': groups, 'default_sharing': groups, 'permissions': self.get_permissions(), 'enabled': True }) if not auth_module.create_user(user): return validation_error() user.save() return redirect({'user': clean_users(user)}, url_for('UsersView:index'))
def configure(self, id): """Configure a module. .. :quickref: Module; Configure a module Requires the `manage_modules` permission. For each configuration available, you should set the value in a form parameter named ``config_NAME``. For boolean values, any value not ``0`` or ``False`` is considered to be ``True``. If the setting should be an option (be available per analysis), you have to set ``config_NAME_option`` to any value but ``0`` or ``False``. If successful, will return the module in ``module``. Otherwise, errors will be available in ``errors``. :param id: id of the named configuration. :form acts_on: comma-delimited list of FAME types this module can act on (only for Processing modules). :form triggered_by: comma-delimited list of triggers (only for Processing modules). :form queue: name of the queue to use for this module (only for Processing modules). """ module = ModuleInfo(get_or_404(ModuleInfo.get_collection(), _id=id)) if request.method == "POST": if module['type'] == 'Processing': if 'acts_on' in request.form: module.update_setting_value( 'acts_on', request.form.get('acts_on', '')) if 'triggered_by' in request.form: module.update_setting_value( 'triggered_by', request.form.get('triggered_by', '')) if 'queue' in request.form: new_queue = request.form.get('queue') if module['queue'] == '': flash('queue cannot be empty', 'danger') return validation_error() else: if module['queue'] != new_queue: module.update_setting_value('queue', new_queue) updates = Internals( get_or_404(Internals.get_collection(), name="updates")) updates.update_value("last_update", time()) flash( 'Workers will reload once they are done with their current tasks', 'success') errors = update_config(module['config'], options=(module['type'] == 'Processing')) if errors is not None: return errors module.save() dispatcher.reload() return redirect({'module': clean_modules(module)}, url_for('ModulesView:index')) else: return render({'module': clean_modules(module)}, 'modules/module_configuration.html')
def configure(self, id): """Configure a module. .. :quickref: Module; Configure a module Requires the `manage_modules` permission. For each configuration available, you should set the value in a form parameter named ``config_NAME``. For boolean values, any value not ``0`` or ``False`` is considered to be ``True``. If the setting should be an option (be available per analysis), you have to set ``config_NAME_option`` to any value but ``0`` or ``False``. If successful, will return the module in ``module``. Otherwise, errors will be available in ``errors``. :param id: id of the named configuration. :form acts_on: comma-delimited list of FAME types this module can act on (only for Processing modules). :form triggered_by: comma-delimited list of triggers (only for Processing modules). :form queue: name of the queue to use for this module (for Processing and Preloading modules). """ module = ModuleInfo(get_or_404(ModuleInfo.get_collection(), _id=id)) module['readme'] = module.get_readme() if request.method == "POST": if module['type'] == 'Filetype': if 'acts_on' in request.form: module.update_setting_value( 'acts_on', request.form.get('acts_on', '')) elif module['type'] == 'Processing': if 'acts_on' in request.form: module.update_setting_value( 'acts_on', request.form.get('acts_on', '')) if 'triggered_by' in request.form: module.update_setting_value( 'triggered_by', request.form.get('triggered_by', '')) if 'queue' in request.form: update_queue(module, request.form.get('queue', '')) elif module['type'] == "Preloading": if 'queue' in request.form: update_queue(module, request.form.get('queue', '')) if 'priority' in request.form: update_priority(module, request.form.get('priority', '')) errors = update_config(module['config'], options=(module['type'] in ['Preloading', 'Processing'])) if errors is not None: return errors module.save() dispatcher.reload() return redirect({'module': clean_modules(module)}, url_for('ModulesView:index')) else: return render({'module': clean_modules(module)}, 'modules/module_configuration.html')
class ModulesView(FlaskView, UIView): @requires_permission('manage_modules') def index(self): """Get the list of modules. .. :quickref: Module; Get the list of modules Requires the `manage_modules` permission. The response is a dict with several elements: * ``modules``, which is a list of modules, sorted by type:: "modules": { "Antivirus": [ ... ], "Preloading": [ ... ], "Processing": [ { "_id": { "$oid": "MODULE_ID" }, "acts_on": [ ACTS_ON_FAME_TYPES ], "class": "CLASS_NAME", "config": [ CONFIG_OPTIONS ], "description": "DESCRIPTION", "enabled": false, "generates": [GENERATES], "name": "NAME", "path": "MODULE_PATH", "queue": "QUEUE", "triggered_by": [ TRIGGERS ], "type": "Processing" }, ... ], "Reporting": [ ... ], "Threat Intelligence": [ ... ], "Filetype": [ ... ] } * ``repositories``: list of configured repositories:: "repositories": [ { "_id": { "$oid": "ID" }, "address": "[email protected]:certsocietegenerale/fame_modules.git", "name": "community", "private": false, "status": "active" }, ... ] * ``configs``: list of named configurations:: "configs": [ { "_id": { "$oid": "ID" }, "config": [ { "description": "List of patterns (strings) to look for in malware configurations. There should be one pattern per line.", "name": "monitor", "type": "text", "value": null } ], "description": "Needed in order to be able to track malware targets", "name": "malware_config" }, ... ] """ types = { 'Preloading': [], 'Processing': [], 'Reporting': [], 'Threat Intelligence': [], 'Antivirus': [], 'Virtualization': [], 'Filetype': [] } for module in ModuleInfo.get_collection().find(): types[module['type']].append(clean_modules(module)) for type in types: types[type] = sorted(types[type], key=get_name) configs = Config.get_collection().find() repositories = clean_repositories( list(Repository.get_collection().find())) return render( { 'modules': types, 'configs': configs, 'repositories': repositories }, 'modules/index.html') @requires_permission('manage_modules') @route('/<id>/disable', methods=['POST']) def disable(self, id): """Disable a module .. :quickref: Module; Disable a module Requires the `manage_modules` permission. :param id: id of the module to disable. :>json Module module: resulting module. """ module = ModuleInfo(get_or_404(ModuleInfo.get_collection(), _id=id)) module.update_value('enabled', False) dispatcher.reload() return redirect({'module': clean_modules(module)}, url_for('ModulesView:index')) @requires_permission('manage_modules') @route('/<id>/enable', methods=['POST']) def enable(self, id): """Enable a module .. :quickref: Module; Enable a module Requires the `manage_modules` permission. If successful, will return the module in ``module``. Otherwise, errors will be available in ``errors``. :param id: id of the module to enable. """ module = ModuleInfo(get_or_404(ModuleInfo.get_collection(), _id=id)) if 'error' in module: flash( "Cannot enable '{}' because of errors installing dependencies." .format(module['name']), 'danger') return validation_error(url_for('ModulesView:index')) # See if module is properly configured module_class = get_class(module['path'], module['class']) module_class.info = module try: module_class() except MissingConfiguration, e: if e.name: flash( "You must configure '{}' before trying to enable '{}'". format(e.name, module['name']), 'warning') return validation_error( url_for('ModulesView:configuration', id=e.id)) else: flash( "You must configure '{}' before trying to enable it.". format(module['name']), 'warning') return validation_error( url_for('ModulesView:configure', id=module['_id'])) module.update_value('enabled', True) dispatcher.reload() readme = module.get_readme() if readme: flash(readme, 'persistent') return redirect({'module': clean_modules(module)}, url_for('ModulesView:index'))
def resume(self, id): analysis = Analysis(get_or_404(Analysis.get_collection(), _id=id)) analysis.resume() flash("Resumed analysis {}".format(analysis['_id'])) return redirect({}, url_for('SystemView:index'))
def post(self): """Create a new analysis. .. :quickref: Analysis; Create an analysis Launch a new analysis. You have to specify on which object this analysis will be made, by specifying one of: * ``file_id`` for an existing object * ``file`` for file uploads * ``url`` * ``hash`` if VirusTotal sample retrieval is enabled. You should also supply all enabled analysis options with the name ``options[OPTION_NAME]``. For boolean options, any value different than ``0`` or ``False`` means the option is enabled. If the submitted object already exists (and ``file_id`` was not specified), the response will be a file object. When a new analysis was successfuly created, the analysis object will be returned, in the ``analysis`` field. If there was error in your submission, they will be returned in the ``errors`` field. **Example request**:: headers = { 'Accept': "application/json", 'X-API-KEY': FAME_API_KEY } with open(filepath, 'rb') as f: params = { 'options[allow_internet_access]': "on", 'options[analysis_time]': "300", 'groups': "cert" } files = { 'file': f } r = requests.post(ENDPOINT, data=params, files=files, headers=headers) :form string file_id: (optional) the id of the object on which this analysis should run. :form file file: (optional) file to analyze. :form string url: (optional) url to analyze. :form string hash: (optional) hash to analyze. :form string module: (optional) the name of the target module. :form string groups: a comma-separated list of groups that will have access to this analysis. :form string comment: comment to add to this object. :form string option[*]: value of each enabled option. """ file_id = request.form.get('file_id') modules = filter(None, request.form.get('modules', '').split(',')) groups = request.form.get('groups', '').split(',') comment = request.form.get('comment', '') options = get_options() if options is None: return validation_error() valid_submission = self._validate_form(groups, modules, options) if not valid_submission: return validation_error() if file_id is not None: f = File(get_or_404(current_user.files, _id=file_id)) analysis = { 'analysis': f.analyze(groups, current_user['_id'], modules, options) } return redirect( analysis, url_for('AnalysesView:get', id=analysis['analysis']['_id'])) else: # When this is a new submission, validate the comment if not self._validate_comment(comment): return validation_error() f = self._get_object_to_analyze() if f is not None: f.add_owners(set(current_user['groups']) & set(groups)) if comment: f.add_comment(current_user['_id'], comment) if f.existing: f.add_groups(groups) flash( "File already exists, so the analysis was not launched." ) return redirect(clean_files(f), url_for('FilesView:get', id=f['_id'])) else: analysis = { 'analysis': clean_analyses( f.analyze(groups, current_user['_id'], modules, options)) } analysis['analysis']['file'] = clean_files(f) return redirect( analysis, url_for('AnalysesView:get', id=analysis['analysis']['_id'])) else: return render_template('analyses/new.html', options=dispatcher.options)