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

Enable passing on auto-run analyzers parameter when using importer library #3143

Merged
merged 29 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a1f0659
add to pass all the analyzers
YiChiCanCode Aug 16, 2024
2d76d93
add the flag and automatically run
YiChiCanCode Aug 16, 2024
6470b68
passes test
YiChiCanCode Aug 19, 2024
e2cde0f
check flag
YiChiCanCode Aug 19, 2024
a8500f0
use black formatter to format all files
YiChiCanCode Aug 21, 2024
8529e04
fix for pylint warning: Dangerous default value [] as argument (dange…
YiChiCanCode Aug 21, 2024
ba4c2e9
Fix linters & formatting
jkppr Aug 22, 2024
1db6794
refactor so we don't even pass in analyzer_names if users don't specifiy
YiChiCanCode Aug 23, 2024
124e01a
correct dictionary get key -> shall be analyzer_names instead of anal…
YiChiCanCode Aug 23, 2024
335036f
fix typo
YiChiCanCode Aug 23, 2024
da8be4b
specifying passing in comma separted list
YiChiCanCode Aug 23, 2024
0bc4696
black format
YiChiCanCode Aug 23, 2024
70470ed
pylint fix
YiChiCanCode Aug 23, 2024
4db3112
add timeline_ids for specifying timelines for analyzers to run on
YiChiCanCode Aug 27, 2024
98ff90f
add flag timeline_id
YiChiCanCode Aug 27, 2024
52cd887
change command line timeline id input as comma_separated_int_list
YiChiCanCode Aug 27, 2024
bc4cf6a
format
YiChiCanCode Aug 27, 2024
09df164
fix line too long
YiChiCanCode Aug 27, 2024
2cd6763
Merge branch 'master' into chaplin-auto-run
jkppr Aug 28, 2024
b3fa3cb
refactor importer library function to separate streamer.run_anlyzers(…
YiChiCanCode Sep 4, 2024
a5baa5a
delete parts for timeline ids, pass only analyzer names
YiChiCanCode Sep 12, 2024
543f050
Merge branch 'master' into chaplin-auto-run
jkppr Sep 13, 2024
e71b444
Adjusted logic to work with the timesketch_importer script.
jkppr Sep 13, 2024
a3fffda
Merge branch 'master' into chaplin-auto-run
jkppr Sep 24, 2024
413c3bd
Revert changes to analysis API endpoint.
jkppr Sep 24, 2024
b833ae4
fix function call
jkppr Sep 24, 2024
32fa0e8
Merge branch 'master' into chaplin-auto-run
jkppr Oct 2, 2024
6af4416
Adding unit tests
jkppr Oct 2, 2024
939302b
Merge branch 'chaplin-auto-run' of github.com:YiChiCanCode/timesketch…
jkppr Oct 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 42 additions & 8 deletions importer_client/python/timesketch_import_client/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,41 @@

from timesketch_api_client import timeline
from timesketch_api_client import definitions
from timesketch_api_client.error import UnableToRunAnalyzer
from timesketch_import_client import utils

logger = logging.getLogger("timesketch_importer.importer")


def run_analyzers(analyzer_names=None, timeline_obj=None):
"""Run the analyzers on the uploaded timeline."""

if not timeline_obj:
logger.error("Unable to run analyzers: Timeline object not found.")
raise ValueError("Timeline object not found.")

if timeline_obj.status not in ("ready", "success"):
logger.error("The provided timeline '%s' is not ready yet!", timeline_obj.name)
return None

if not analyzer_names:
logger.info("No analyzer names provided, skipping analysis.")
return None

try:
analyzer_results = timeline_obj.run_analyzers(analyzer_names)
except UnableToRunAnalyzer as e:
logger.error(
"Failed to run requested analyzers '%s'! Error: %s",
str(analyzer_names),
str(e),
)
return None

logger.debug("Analyzer results: %s", analyzer_results)
return analyzer_results


class ImportStreamer(object):
"""Upload object used to stream results to Timesketch."""

Expand Down Expand Up @@ -708,8 +738,18 @@ def celery_task_id(self):
"""Return the celery task identification for the upload."""
return self._celery_task_id

def _trigger_analyzers(self, analyzer_names=None):
"""Run the analyzers on the uploaded timeline."""

self._ready()

if self._data_lines:
self.flush(end_stream=True)

return run_analyzers(analyzer_names=analyzer_names, timeline_obj=self.timeline)

def close(self):
"""Close the streamer."""
"""Close the streamer"""
try:
self._ready()
except ValueError:
Expand All @@ -718,13 +758,6 @@ def close(self):
if self._data_lines:
self.flush(end_stream=True)

# Trigger auto analyzer pipeline to kick in.
pipe_resource = "{0:s}/sketches/{1:d}/analyzer/".format(
self._sketch.api.api_root, self._sketch.id
)
data = {"index_name": self._index}
_ = self._sketch.api.session.post(pipe_resource, json=data)

def flush(self, end_stream=True):
"""Flushes the buffer and uploads to timesketch.

Expand All @@ -736,6 +769,7 @@ def flush(self, end_stream=True):
ValueError: if the stream object is not fully configured.
RuntimeError: if the stream was not uploaded.
"""

if not self._data_lines:
return

Expand Down
67 changes: 66 additions & 1 deletion importer_client/python/timesketch_import_client/importer_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
import json
import unittest
import mock

import pandas

from timesketch_api_client.error import UnableToRunAnalyzer

from . import importer


Expand Down Expand Up @@ -270,3 +271,67 @@ def _run_all_tests(self, columns, lines):
]
)
self.assertSetEqual(set(messages), message_correct)


class RunAnalyzersTest(unittest.TestCase):
"""Tests for the run_analyzers function."""

@mock.patch("timesketch_import_client.importer.logger")
def test_run_analyzers_without_timeline(self, mock_logger):
"""Test calling run_analyzers without a timeline object."""
with self.assertRaises(ValueError):
importer.run_analyzers(analyzer_names=["test_analyzer"])
mock_logger.error.assert_called_with(
"Unable to run analyzers: Timeline object not found."
)

@mock.patch("timesketch_import_client.importer.logger")
def test_run_analyzers_timeline_not_ready(self, mock_logger):
"""Test calling run_analyzers with a timeline that is not ready."""
timeline_obj = mock.Mock(status="pending", name="test")
importer.run_analyzers(
analyzer_names=["test_analyzer"], timeline_obj=timeline_obj
)
mock_logger.error.assert_called_with(
"The provided timeline '%s' is not ready yet!", timeline_obj.name
)

@mock.patch("timesketch_import_client.importer.logger")
def test_run_analyzers_without_analyzers(self, mock_logger):
"""Test calling run_analyzers without analyzers."""
timeline_obj = mock.Mock(status="ready")
importer.run_analyzers(timeline_obj=timeline_obj)
mock_logger.info.assert_called_with(
"No analyzer names provided, skipping analysis."
)

@mock.patch("timesketch_import_client.importer.logger")
def test_run_analyzers_success(self, mock_logger):
"""Test calling run_analyzers successfully."""
timeline_obj = mock.Mock(
status="ready", run_analyzers=mock.Mock(return_value="Success")
)
return_value = importer.run_analyzers(
analyzer_names=["test_analyzer"], timeline_obj=timeline_obj
)
self.assertEqual(return_value, "Success")
mock_logger.debug.assert_called_with("Analyzer results: %s", "Success")

@mock.patch("timesketch_import_client.importer.logger")
def test_run_analyzers_failed(self, mock_logger):
"""Test calling run_analyzers with an exception."""
timeline_obj = mock.Mock(
status="ready",
run_analyzers=mock.Mock(
side_effect=UnableToRunAnalyzer("Analyzer failed.")
),
)
return_value = importer.run_analyzers(
analyzer_names=["test_analyzer"], timeline_obj=timeline_obj
)
self.assertIsNone(return_value)
mock_logger.error.assert_called_with(
"Failed to run requested analyzers '%s'! Error: %s",
"['test_analyzer']",
"Analyzer failed.",
)
32 changes: 32 additions & 0 deletions importer_client/python/tools/timesketch_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ def upload_file(
timeline = streamer.timeline
jkppr marked this conversation as resolved.
Show resolved Hide resolved
task_id = streamer.celery_task_id

streamer.close()

logger.info("File upload completed.")
return timeline, task_id

Expand Down Expand Up @@ -462,6 +464,20 @@ def main(args=None):
help=("Path to the file that is to be imported."),
)

config_group.add_argument(
"--analyzer_names",
"--analyzer-names",
nargs="*",
action="store",
dest="analyzer_names",
default=[],
help=(
"Set of analyzers that we will automatically run right after the "
"timelines are uploaded. The input needs to be the analyzers names."
"Provided as strings separated by space"
),
)

options = argument_parser.parse_args(args)

if options.show_version:
Expand Down Expand Up @@ -616,6 +632,7 @@ def main(args=None):
"log_config_file": options.log_config_file,
"data_label": options.data_label,
"context": options.context,
"analyzer_names": options.analyzer_names,
}

logger.info("Uploading file.")
Expand All @@ -627,6 +644,11 @@ def main(args=None):
logger.info(
"File got successfully uploaded to sketch: {0:d}".format(my_sketch.id)
)
if options.analyzer_names:
logger.warning(
"Argument 'analyzer_names' only works with 'wait_timeline = "
"True'! Skipping execution of analyzers: {analyzer_names}"
)
return

if not timeline:
Expand Down Expand Up @@ -664,6 +686,16 @@ def main(args=None):
print(f"Status of the index is: {task_state}")
break

if options.analyzer_names:
logger.info(
"Trigger analyzers: %s on Timeline '%s'",
str(options.analyzer_names),
str(timeline.name),
)
_ = importer.run_analyzers(
analyzer_names=options.analyzer_names, timeline_obj=timeline
)


if __name__ == "__main__":
main()
Loading