This commit is contained in:
319
iris-web/source/app/blueprints/dim_tasks/dim_tasks.py
Normal file
319
iris-web/source/app/blueprints/dim_tasks/dim_tasks.py
Normal file
@@ -0,0 +1,319 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
#
|
||||
# IRIS Source Code
|
||||
# Copyright (C) 2021 - Airbus CyberSecurity (SAS)
|
||||
# ir@cyberactionlab.net
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 3 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
# IMPORTS ------------------------------------------------
|
||||
import json
|
||||
import os
|
||||
import pickle
|
||||
from flask import Blueprint
|
||||
from flask import redirect
|
||||
from flask import render_template
|
||||
from flask import request
|
||||
from flask import url_for
|
||||
from flask_wtf import FlaskForm
|
||||
from sqlalchemy import desc
|
||||
|
||||
import app
|
||||
from app.iris_engine.module_handler.module_handler import call_modules_hook
|
||||
from app.models import CaseAssets
|
||||
from app.models import CaseReceivedFile
|
||||
from app.models import CaseTasks
|
||||
from app.models import Cases
|
||||
from app.models import CasesEvent
|
||||
from app.models import CeleryTaskMeta
|
||||
from app.models import GlobalTasks
|
||||
from app.models import Ioc
|
||||
from app.models import IrisHook
|
||||
from app.models import IrisModule
|
||||
from app.models import IrisModuleHook
|
||||
from app.models import Notes
|
||||
from app.models.alerts import Alert
|
||||
from app.models.authorization import CaseAccessLevel
|
||||
from app.models.authorization import Permissions
|
||||
from app.util import ac_api_case_requires
|
||||
from app.util import ac_api_requires
|
||||
from app.util import ac_case_requires
|
||||
from app.util import ac_requires
|
||||
from app.util import response_error
|
||||
from app.util import response_success
|
||||
from iris_interface.IrisInterfaceStatus import IIStatus
|
||||
|
||||
dim_tasks_blueprint = Blueprint(
|
||||
'dim_tasks',
|
||||
__name__,
|
||||
template_folder='templates'
|
||||
)
|
||||
|
||||
basedir = os.path.abspath(os.path.dirname(app.__file__))
|
||||
|
||||
|
||||
# CONTENT ------------------------------------------------
|
||||
@dim_tasks_blueprint.route('/dim/tasks', methods=['GET'])
|
||||
@ac_requires(Permissions.standard_user)
|
||||
def dim_index(caseid: int, url_redir):
|
||||
if url_redir:
|
||||
return redirect(url_for('dim.dim_index', cid=caseid))
|
||||
|
||||
form = FlaskForm()
|
||||
|
||||
return render_template('dim_tasks.html', form=form)
|
||||
|
||||
|
||||
@dim_tasks_blueprint.route('/dim/hooks/options/<hook_type>/list', methods=['GET'])
|
||||
@ac_api_requires()
|
||||
def list_dim_hook_options_ioc(hook_type, caseid):
|
||||
mods_options = IrisModuleHook.query.with_entities(
|
||||
IrisModuleHook.manual_hook_ui_name,
|
||||
IrisHook.hook_name,
|
||||
IrisModule.module_name
|
||||
).filter(
|
||||
IrisHook.hook_name == f"on_manual_trigger_{hook_type}",
|
||||
IrisModule.is_active == True
|
||||
).join(
|
||||
IrisModuleHook.module,
|
||||
IrisModuleHook.hook
|
||||
).all()
|
||||
|
||||
data = [options._asdict() for options in mods_options]
|
||||
|
||||
return response_success("", data=data)
|
||||
|
||||
|
||||
@dim_tasks_blueprint.route('/dim/hooks/call', methods=['POST'])
|
||||
@ac_api_case_requires(CaseAccessLevel.full_access)
|
||||
def dim_hooks_call(caseid):
|
||||
logs = []
|
||||
js_data = request.json
|
||||
|
||||
if not js_data:
|
||||
return response_error('Invalid data')
|
||||
|
||||
hook_name = js_data.get('hook_name')
|
||||
if not hook_name:
|
||||
return response_error('Missing hook_name')
|
||||
|
||||
hook_ui_name = js_data.get('hook_ui_name')
|
||||
|
||||
targets = js_data.get('targets')
|
||||
if not targets:
|
||||
return response_error('Missing targets')
|
||||
|
||||
data_type = js_data.get('type')
|
||||
if not data_type:
|
||||
return response_error('Missing data type')
|
||||
|
||||
module_name = js_data.get('module_name')
|
||||
|
||||
index = 0
|
||||
obj_targets = []
|
||||
for target in js_data.get('targets'):
|
||||
if type(target) == str:
|
||||
try:
|
||||
target = int(target)
|
||||
except ValueError:
|
||||
return response_error('Invalid target')
|
||||
|
||||
elif type(target) != int:
|
||||
return response_error('Invalid target')
|
||||
|
||||
if data_type == 'ioc':
|
||||
obj = Ioc.query.filter(Ioc.ioc_id == target).first()
|
||||
|
||||
elif data_type == "case":
|
||||
obj = Cases.query.filter(Cases.case_id == caseid).first()
|
||||
|
||||
elif data_type == "asset":
|
||||
obj = CaseAssets.query.filter(
|
||||
CaseAssets.asset_id == target,
|
||||
CaseAssets.case_id == caseid
|
||||
).first()
|
||||
|
||||
elif data_type == "note":
|
||||
obj = Notes.query.filter(
|
||||
Notes.note_id == target,
|
||||
Notes.note_case_id == caseid
|
||||
).first()
|
||||
|
||||
elif data_type == "event":
|
||||
obj = CasesEvent.query.filter(
|
||||
CasesEvent.event_id == target,
|
||||
CasesEvent.case_id == caseid
|
||||
).first()
|
||||
|
||||
elif data_type == "task":
|
||||
obj = CaseTasks.query.filter(
|
||||
CaseTasks.id == target,
|
||||
CaseTasks.task_case_id == caseid
|
||||
).first()
|
||||
|
||||
elif data_type == "evidence":
|
||||
obj = CaseReceivedFile.query.filter(
|
||||
CaseReceivedFile.id == target,
|
||||
CaseReceivedFile.case_id == caseid
|
||||
).first()
|
||||
|
||||
elif data_type == "global_task":
|
||||
obj = GlobalTasks.query.filter(
|
||||
GlobalTasks.id == target
|
||||
).first()
|
||||
|
||||
elif data_type == 'alert':
|
||||
obj = Alert.query.filter(
|
||||
Alert.alert_id == target
|
||||
).first()
|
||||
|
||||
else:
|
||||
logs.append(f'Data type {data_type} not supported')
|
||||
continue
|
||||
|
||||
if not obj:
|
||||
logs.append(f'Object ID {target} not found')
|
||||
continue
|
||||
obj_targets.append(obj)
|
||||
|
||||
# Call to queue task
|
||||
index += 1
|
||||
|
||||
if len(obj_targets) > 0:
|
||||
call_modules_hook(hook_name=hook_name, hook_ui_name=hook_ui_name, data=obj_targets,
|
||||
caseid=caseid, module_name=module_name)
|
||||
|
||||
if len(logs) > 0:
|
||||
return response_error(f"Errors encountered during processing of data. Queued task with {index} objects",
|
||||
data=logs)
|
||||
|
||||
return response_success(f'Queued task with {index} objects')
|
||||
|
||||
|
||||
@dim_tasks_blueprint.route('/dim/tasks/list/<int:count>', methods=['GET'])
|
||||
@ac_api_requires()
|
||||
def list_dim_tasks(count, caseid):
|
||||
tasks = CeleryTaskMeta.query.filter(
|
||||
~ CeleryTaskMeta.name.like('app.iris_engine.updater.updater.%')
|
||||
).order_by(desc(CeleryTaskMeta.date_done)).limit(count).all()
|
||||
|
||||
data = []
|
||||
|
||||
for row in tasks:
|
||||
|
||||
tkp = {}
|
||||
tkp['state'] = row.status
|
||||
tkp['case'] = "Unknown"
|
||||
tkp['module'] = row.name
|
||||
tkp['task_id'] = row.task_id
|
||||
tkp['date_done'] = row.date_done
|
||||
tkp['user'] = "Unknown"
|
||||
|
||||
try:
|
||||
tinfo = row.result
|
||||
except AttributeError:
|
||||
# Legacy task
|
||||
data.append(tkp)
|
||||
continue
|
||||
|
||||
if row.name is not None and 'task_hook_wrapper' in row.name:
|
||||
task_name = f"{row.kwargs}::{row.kwargs}"
|
||||
else:
|
||||
task_name = row.name
|
||||
|
||||
user = None
|
||||
case_name = None
|
||||
if row.kwargs and row.kwargs != b'{}':
|
||||
kwargs = json.loads(row.kwargs.decode('utf-8'))
|
||||
if kwargs:
|
||||
user = kwargs.get('init_user')
|
||||
case_name = f"Case #{kwargs.get('caseid')}"
|
||||
task_name = f"{kwargs.get('module_name')}::{kwargs.get('hook_name')}"
|
||||
|
||||
try:
|
||||
result = pickle.loads(row.result)
|
||||
except:
|
||||
result = None
|
||||
|
||||
if isinstance(result, IIStatus):
|
||||
try:
|
||||
success = result.is_success()
|
||||
except:
|
||||
success = None
|
||||
else:
|
||||
success = None
|
||||
|
||||
tkp['state'] = "success" if success else str(row.result)
|
||||
tkp['user'] = user if user else "Shadow Iris"
|
||||
tkp['module'] = task_name
|
||||
tkp['case'] = case_name if case_name else ""
|
||||
|
||||
data.append(tkp)
|
||||
|
||||
return response_success("", data=data)
|
||||
|
||||
|
||||
@dim_tasks_blueprint.route('/dim/tasks/status/<task_id>', methods=['GET'])
|
||||
@ac_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
|
||||
def task_status(task_id, caseid, url_redir):
|
||||
if url_redir:
|
||||
return response_error("Invalid request")
|
||||
|
||||
task = app.celery.AsyncResult(task_id)
|
||||
|
||||
try:
|
||||
tinfo = task.info
|
||||
except AttributeError:
|
||||
# Legacy task
|
||||
task_info = {}
|
||||
task_info['Danger'] = 'This task was executed in a previous version of IRIS and the status cannot be read ' \
|
||||
'anymore.'
|
||||
task_info['Note'] = 'All the data readable by the current IRIS version is displayed in ' \
|
||||
'the table.'
|
||||
task_info['Additional information'] = 'The results of this tasks were stored in a pickled Class which does' \
|
||||
' not exists anymore in current IRIS version.'
|
||||
return render_template("modal_task_info.html", data=task_info, task_id=task.id)
|
||||
|
||||
task_info = {}
|
||||
task_info['Task ID'] = task_id
|
||||
task_info['Task finished on'] = task.date_done
|
||||
task_info['Task state']: task.state.lower()
|
||||
task_info['Engine']: task.name if task.name else "No engine. Unrecoverable shadow failure"
|
||||
|
||||
task_meta = task._get_task_meta()
|
||||
|
||||
if task_meta.get('name') \
|
||||
and ('task_hook_wrapper' in task_meta.get('name') or 'pipeline_dispatcher' in task_meta.get('name')):
|
||||
task_info['Module name'] = task_meta.get('kwargs').get('module_name')
|
||||
task_info['Hook name'] = task_meta.get('kwargs').get('hook_name')
|
||||
task_info['User'] = task_meta.get('kwargs').get('init_user')
|
||||
task_info['Case ID'] = task_meta.get('kwargs').get('caseid')
|
||||
|
||||
if isinstance(task.info, IIStatus):
|
||||
success = task.info.is_success()
|
||||
task_info['Logs'] = task.info.get_logs()
|
||||
|
||||
else:
|
||||
success = None
|
||||
task_info['User'] = "Shadow Iris"
|
||||
task_info['Logs'] = ['Task did not returned a valid IIStatus object']
|
||||
|
||||
if task_meta.get('traceback'):
|
||||
task_info['Traceback'] = task.traceback
|
||||
|
||||
task_info['Success'] = "Success" if success else "Failure"
|
||||
|
||||
return render_template("modal_task_info.html", data=task_info, task_id=task.id)
|
@@ -0,0 +1,69 @@
|
||||
{% extends "layouts/default.html" %}
|
||||
|
||||
{% block title %} DIM Tasks {% endblock title %}
|
||||
|
||||
{% block stylesheets %}
|
||||
<link href="/static/assets/css/dataTables.buttons.min.css" rel="stylesheet">
|
||||
{% endblock stylesheets %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
<div class="page-inner">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="loader1 text-center ml-mr-auto" id="loading_msg">Loading...</div>
|
||||
<div class="card" id="card_main_load" style="display:none;">
|
||||
<div class="card-header">
|
||||
<div class="card-title">DFIR-IRIS Modules Tasks
|
||||
<button type="button" class="btn btn-icon btn-round btn-outline-dark float-right ml-2" onclick="get_activities();">
|
||||
<i class="fas fa-sync rotate"></i></button></div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive" id="activities_table_wrapper">
|
||||
<div class="selectgroup">
|
||||
<span id="table_buttons"></span>
|
||||
</div>
|
||||
<table class="table display table-striped table-hover" width="100%" cellspacing="0" id="activities_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Task ID</th>
|
||||
<th>State</th>
|
||||
<th>Date ended</th>
|
||||
<th>Case</th>
|
||||
<th>Processing module</th>
|
||||
<th>Initiating user</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th>Task ID</th>
|
||||
<th>State</th>
|
||||
<th>Date ended</th>
|
||||
<th>Case</th>
|
||||
<th>Processing module</th>
|
||||
<th>Initiating user</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ form.hidden_tag() }}
|
||||
{% endif %}
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% block javascripts %}
|
||||
<script src="/static/assets/js/plugin/datatables/dataTables.cellEdit.js"></script>
|
||||
<script src="/static/assets/js/plugin/datatables/dataTables.buttons.min.js"></script>
|
||||
<script src="/static/assets/js/plugin/datatables/buttons.html5.min.js"></script>
|
||||
<script src="/static/assets/js/plugin/datatables/buttons.print.min.js"></script>
|
||||
|
||||
<script src="/static/assets/js/iris/datatablesUtils.js"></script>
|
||||
<script src="/static/assets/js/iris/dim_tasks.js"></script>
|
||||
{% endblock javascripts %}
|
@@ -0,0 +1,27 @@
|
||||
|
||||
<div class="container col-md-12">
|
||||
<div class="form-group">
|
||||
{% for element in data %}
|
||||
{% if element != "Logs" %}
|
||||
{% if data[element] != None %}
|
||||
<div class="input-group">
|
||||
<label for="en">{{ element }}: </label>
|
||||
<span type="text" class="text-faded ml-1" id="en"> {{ data[element] }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if data['Logs'] %}
|
||||
<div class="form-group mt--3">
|
||||
<label>Logs</label>
|
||||
<ul>
|
||||
{% for log in data['Logs'] %}
|
||||
<li>{{ log }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user