Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DFIQ Analyzer Implementation #3178

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
30 changes: 23 additions & 7 deletions timesketch/api/v1/resources/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
logger = logging.getLogger("timesketch.analysis_api")


# TODO: Filter DFIQ analyzer results from this!
class AnalysisResource(resources.ResourceMixin, Resource):
"""Resource to get analyzer session."""

Expand Down Expand Up @@ -180,6 +181,7 @@ def get(self, sketch_id):
* display_name: Display name of the analyzer for the UI
* description: Description of the analyzer provided in the class
* is_multi: Boolean indicating if the analyzer is a multi analyzer
* is_dfiq: Boolean indicating if the analyzer is a dfiq analyzer
jkppr marked this conversation as resolved.
Show resolved Hide resolved
"""
sketch = Sketch.get_with_acl(sketch_id)
if not sketch:
Expand All @@ -188,22 +190,26 @@ def get(self, sketch_id):
abort(
HTTP_STATUS_CODE_FORBIDDEN, "User does not have read access to sketch"
)
analyzers = [x for x, y in analyzer_manager.AnalysisManager.get_analyzers()]

analyzers = analyzer_manager.AnalysisManager.get_analyzers()
include_dfiq = (
request.args.get("include_dfiq", default="false").lower() == "true"
)

analyzers = analyzer_manager.AnalysisManager.get_analyzers(
include_dfiq=include_dfiq
)
analyzers_detail = []
for analyzer_name, analyzer_class in analyzers:
# TODO: update the multi_analyzer detection logic for edgecases
# where analyzers are using custom parameters (e.g. misp)
multi = False
if len(analyzer_class.get_kwargs()) > 0:
multi = True
analyzers_detail.append(
{
"name": analyzer_name,
"display_name": analyzer_class.DISPLAY_NAME,
"description": analyzer_class.DESCRIPTION,
"is_multi": multi,
"is_multi": len(analyzer_class.get_kwargs()) > 0,
"is_dfiq": hasattr(analyzer_class, "IS_DFIQ_ANALYZER")
berggren marked this conversation as resolved.
Show resolved Hide resolved
and analyzer_class.IS_DFIQ_ANALYZER,
}
)

Expand Down Expand Up @@ -266,8 +272,17 @@ def post(self, sketch_id):
if form.get("analyzer_force_run"):
analyzer_force_run = True

include_dfiq = False
if form.get("include_dfiq"):
include_dfiq = True

analyzers = []
all_analyzers = [x for x, _ in analyzer_manager.AnalysisManager.get_analyzers()]
all_analyzers = [
x
for x, _ in analyzer_manager.AnalysisManager.get_analyzers(
include_dfiq=include_dfiq
)
]
for analyzer in analyzer_names:
for correct_name in all_analyzers:
if fnmatch.fnmatch(correct_name, analyzer):
Expand Down Expand Up @@ -301,6 +316,7 @@ def post(self, sketch_id):
analyzer_kwargs=analyzer_kwargs,
timeline_id=timeline_id,
analyzer_force_run=analyzer_force_run,
include_dfiq=include_dfiq,
)
except KeyError as e:
logger.warning(
Expand Down
70 changes: 70 additions & 0 deletions timesketch/api/v1/resources/scenarios.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from timesketch.models.sketch import InvestigativeQuestion
from timesketch.models.sketch import InvestigativeQuestionApproach
from timesketch.models.sketch import InvestigativeQuestionConclusion
from timesketch.lib.analyzers.dfiq_plugins.manager import DFIQAnalyzerManager


logger = logging.getLogger("timesketch.scenario_api")
Expand All @@ -58,6 +59,59 @@ def load_dfiq_from_config():
return DFIQ(dfiq_path)


def check_and_run_dfiq_analysis_steps(dfiq_obj, sketch, analyzer_manager=None):
"""Checks if any DFIQ analyzers need to be executed for the given DFIQ object.

Args:
dfiq_obj: The DFIQ object (Scenario, Question, or Approach).
sketch: The sketch object associated with the DFIQ object.
analyzer_manager: Optional. An existing instance of DFIQAnalyzerManager.

Returns:
List of analyzer_session objects (can be empty) or False.
"""
# Initialize the analyzer manager only once.
if not analyzer_manager:
analyzer_manager = DFIQAnalyzerManager(sketch=sketch)

analyzer_sessions = []
if isinstance(dfiq_obj, InvestigativeQuestionApproach):
session = analyzer_manager.trigger_analyzers_for_approach(approach=dfiq_obj)
if session:
analyzer_sessions.extend(session)
elif isinstance(dfiq_obj, InvestigativeQuestion):
for approach in dfiq_obj.approaches:
session = analyzer_manager.trigger_analyzers_for_approach(approach=approach)
if session:
analyzer_sessions.extend(session)
elif isinstance(dfiq_obj, Facet):
for question in dfiq_obj.questions:
result = check_and_run_dfiq_analysis_steps(
question, sketch, analyzer_manager
)
if result:
analyzer_sessions.extend(result)
elif isinstance(dfiq_obj, Scenario):
if dfiq_obj.facets:
for facet in dfiq_obj.facets:
result = check_and_run_dfiq_analysis_steps(
facet, sketch, analyzer_manager
)
if result:
analyzer_sessions.extend(result)
if dfiq_obj.questions:
for question in dfiq_obj.questions:
result = check_and_run_dfiq_analysis_steps(
question, sketch, analyzer_manager
)
if result:
analyzer_sessions.extend(result)
else:
return False # Invalid DFIQ object type

return analyzer_sessions if analyzer_sessions else False


class ScenarioTemplateListResource(resources.ResourceMixin, Resource):
"""List all scenarios available."""

Expand Down Expand Up @@ -241,9 +295,23 @@ def post(self, sketch_id):

question_sql.approaches.append(approach_sql)

db_session.add(question_sql)

# TODO: Remove commit and check function here when questions are
# linked to Scenarios again!
# Needs a tmp commit here so we can run the analyzer on the question.
db_session.commit()
# Check if any of the questions contains analyzer approaches
check_and_run_dfiq_analysis_steps(question_sql, sketch)

db_session.add(scenario_sql)
db_session.commit()

# This does not work, since we don't have Scnearios linked down to
# Approaches anymore! We intentionally broke the link to facets to show
# Questions in the frontend.
# check_and_run_dfiq_analysis_steps(scenario_sql, sketch)

return self.to_json(scenario_sql)


Expand Down Expand Up @@ -594,6 +662,8 @@ def post(self, sketch_id):
db_session.add(new_question)
db_session.commit()

check_and_run_dfiq_analysis_steps(new_question, sketch)

return self.to_json(new_question)


Expand Down
1 change: 1 addition & 0 deletions timesketch/lib/analyzers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@

import timesketch.lib.analyzers.authentication
import timesketch.lib.analyzers.contrib
import timesketch.lib.analyzers.dfiq_plugins
5 changes: 5 additions & 0 deletions timesketch/lib/analyzers/dfiq_plugins/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""DFIQ Analyzer module."""

from timesketch.lib.analyzers.dfiq_plugins import manager as dfiq_analyzer_manager

dfiq_analyzer_manager.load_dfiq_analyzers()
Loading
Loading