From 714c71b1a40e67d97559f5ddf083df3190be2155 Mon Sep 17 00:00:00 2001 From: Kamforka Date: Fri, 21 Feb 2025 15:13:32 +0100 Subject: [PATCH] #354 - Add case endpoint docs --- docs/examples/case.md | 253 +++++++++++++++++++++ docs/reference.md | 1 + examples/case/advanced.py | 15 ++ examples/case/delete.py | 13 ++ examples/case/fetch_with_find.py | 24 ++ examples/case/fetch_with_get.py | 13 ++ examples/case/merge.py | 16 ++ examples/case/minimalistic.py | 10 + examples/case/obervable_simple.py | 22 ++ examples/case/observable_file.py | 25 ++ examples/case/pages_after_creation.py | 29 +++ examples/case/pages_during_creation.py | 26 +++ examples/case/procedures_after_creation.py | 31 +++ examples/case/tasks_after_creation.py | 21 ++ examples/case/tasks_during_creation.py | 18 ++ examples/case/update_bulk.py | 23 ++ examples/case/update_single.py | 21 ++ mkdocs.yml | 1 + 18 files changed, 562 insertions(+) create mode 100644 docs/examples/case.md create mode 100644 examples/case/advanced.py create mode 100644 examples/case/delete.py create mode 100644 examples/case/fetch_with_find.py create mode 100644 examples/case/fetch_with_get.py create mode 100644 examples/case/merge.py create mode 100644 examples/case/minimalistic.py create mode 100644 examples/case/obervable_simple.py create mode 100644 examples/case/observable_file.py create mode 100644 examples/case/pages_after_creation.py create mode 100644 examples/case/pages_during_creation.py create mode 100644 examples/case/procedures_after_creation.py create mode 100644 examples/case/tasks_after_creation.py create mode 100644 examples/case/tasks_during_creation.py create mode 100644 examples/case/update_bulk.py create mode 100644 examples/case/update_single.py diff --git a/docs/examples/case.md b/docs/examples/case.md new file mode 100644 index 0000000..740b707 --- /dev/null +++ b/docs/examples/case.md @@ -0,0 +1,253 @@ +# Case + +## A minimalistic case + +The most minimalistic case requires at least the below fields to be defined: + +- `title`: The title of the case. +- `description`: The description of the case. + +Here's an example that demonstrates how to create the most minimalistic case possible using the [case.create][thehive4py.endpoints.case.CaseEndpoint.create] method: + +```python +--8<-- "examples/case/minimalistic.py" +``` +## An advanced case + +The previous example demonstrated how to create the simplest case ever. + +In the next example let's create a more advanced case by using the combination of the [InputCase][thehive4py.types.case.InputCase] type hint and the [case.create][thehive4py.endpoints.case.CaseEndpoint.create] method. + +```python +--8<-- "examples/case/advanced.py" +``` + +In the above snippet `input_case` is created before the create call and later passed to the `case` argument. + +Finally after the creation of the case we saved the response in the `output_case` to be able to use it later. + +!!! note + While the above case is a bit more advanced it's still far from the most complex example that is possible. + Should you be interested of what the Case API offers please check out the [official docs](https://docs.strangebee.com/thehive/api-docs/#tag/Case). + +## Get and find + +There are multiple ways to retrieve already existing cases: + +### Get a single case + +To get a single case one can use the [case.get][thehive4py.endpoints.case.CaseEndpoint.get] method with the case's id as follows: + +```python +--8<-- "examples/case/fetch_with_get.py" +``` + +### Find multiple cases + +To fetch multiple cases based on arbitrary conditions one can use the [case.find][thehive4py.endpoints.case.CaseEndpoint.find] method which is an abstraction on top of TheHive's Query API. + +In the next example we will create two cases with different tags. The first case will get the `antivirus` tag while the second one will get the `phishing` tag. + +Then we will construct a query filter that will look for cases with these tags on them: + +```python +--8<-- "examples/case/fetch_with_find.py" +``` + +The above example demonstrates how to construct query filters. + +These filter expressions can be chained together with different operators, just like we did with the `|` (`or`) operator in the example. + +Currently, the filter classes support the following operators: + +- `&`: Used for the Query API's `_and` construct. +- `|`: Used for the Query API's `_or` construct. +- `~`: Used for the Query API's `_not` construct. + +The full list of the filter builders can be found in the [query.filters][thehive4py.query.filters] module. + +## Update single and bulk + +Sometimes an existing case needs to be updated. TheHive offers multiple ways to accomplish this task either with a single case or multiple ones. + +### Update single + +A single case can be updated using [case.update][thehive4py.endpoints.case.CaseEndpoint.update] method. The method requires the `case_id` of the case to be updated and the `fields` to update. + +```python +--8<-- "examples/case/update_single.py" +``` + +In the above example we've updated the `title` and the `tags` fields. + +Be mindful though, `thehive4py` is a lightweight wrapper around TheHive API and offers no object relationship mapping functionalities, meaning that the original `original_case` won't reflect the changes of the update. + +To work with the updated case we fetched the latest version using the [case.get][thehive4py.endpoints.case.CaseEndpoint.get] method and stored it in the `updated_case` variable. + +Now the content of `updated_case` should reflect the changes we made with our update request. + +!!! tip + To see the full list of supported update fields please consult the [official docs](https://docs.strangebee.com/thehive/api-docs/#tag/Case/operation/Update%20case). + +### Update bulk + +To update the **same fields** with the **same values** on multiple cases at the same time, one can use [case.bulk_update][thehive4py.endpoints.case.CaseEndpoint.bulk_update] method. +The method accepts the same `fields` dictionary with an additional `ids` field on it, which should contain the list of ids of the cases to be bulk updated. + +```python +--8<-- "examples/case/update_bulk.py" +``` + +In the example we prepare two cases for the bulk update, and collect their ids in the `original_case_ids` list. +Then we update the fields `title` and `tags` on both cases using the bulk update method. + + +## Merge cases + +Many times during case triaging it occurs that individual cases turn out to be closely related. For this use case TheHive provides an option to merge individual cases using the [case.merge][thehive4py.endpoints.case.CaseEndpoint.merge] method. + +In the following example we will create two cases and will merge them into one new case: + +```python +--8<-- "examples/case/merge.py" +``` + +As you can see the merged cases will end up in one single case which is represented by the `merge_case` variable in our example. + +!!! important + In case you wonder what will happen to the original cases, during the merge they will be deleted and their legacy will live on in the final merge case. + +!!! note + In the example we merged two cases, but it's worth to mention that the merge endpoint lets us merge as many cases as we need into one final case. + +## Delete + +It's possible to delete a case using the [case.delete][thehive4py.endpoints.case.CaseEndpoint.delete] method. +Here's a simple example to demonstrate: + +```python +--8<-- "examples/case/delete.py" +``` + +!!! note + In contrast to the alert endpoint the case endpoint doesn't support bulk deletion of cases, so should you need to delete multiple cases the easiest options is to collect the case ids and iterate over them using the single delete endpoint. + +## Case observables + +TheHive API provides multiple ways to add observables to cases, let them be textual or file based observables. + +### Add observables to an existing case + +Unlike the Alert API, the Case API doesn't support adding observables to cases during their creation, this means that we can only add case observables retroactively. + +In the next example we're gonna create a case to add two observables to it using the [case.create_observable][thehive4py.endpoints.case.CaseEndpoint.create_observable] method: + +```python +--8<-- "examples/case/obervable_simple.py" +``` + +### Add file based observables + +In the previous example we've seen how to handle simple observables without attachments. Next we will create a temporary directory with a dummy file and some dummy content that will represent our file based observable and add it to a case: + + +```python +--8<-- "examples/case/observable_file.py" +``` + +As we can see from the above example a file based observable needs an actual file and its filepath, in our example these are represented by `observable_filepath` and `observable_file` + +Finally the `observable` metadata needs to be defined with its `dataType` as `file` and then the `data` field should be omitted. Finally the `observable_path` argument must be defined with the value of the actual `observable_filepath`. + +This way TheHive will pair the observable metadata with the file as its attachment behind the scenes. + +## Case tasks + +For more advanced case handling we can specify tasks which will serve as steps during the evaluation of the case. + +Fortunately TheHive API provides different options to add tasks to cases and we will check them out in the next sections. + +### Add tasks during case creation + +We can specify tasks already during case creation. This is a great way to combine case and task creation in an atomic way. + +Let's do an example to create a case with a `Triage` and `Respond` tasks: + +```python +--8<-- "examples/case/tasks_during_creation.py" +``` + +This snippet will create a case with the required tasks in one go. + +### Add tasks to an existing case + +In the previous section we could see that it's possible to specify tasks during case creation, however sometimes we want to add tasks after a case has been created. + +For this purpose we can use the [case.create_task][thehive4py.endpoints.case.CaseEndpoint.create_task] method. + +Now let's do an example by adding tasks retroactively to a case: + +```python +--8<-- "examples/case/tasks_after_creation.py" +``` + +In the above example we created an empty case as `case_to_enrich`, and then defined a list of two tasks in the `case_tasks` variable. + +Finally using a for loop and the `case.create_task` method we added them to our dummy case one by one. + +## Case pages + +In order to give more context to a case we can add pages to it, which could serve as additional notes or documentation during investigation. + +Like usual TheHive API provides different possibilities to add such pages to cases that we will see in the next sections. + +### Add pages during case creation + +We can add pages already during case creation. This is a great way to combine case and page creation in a single go. + +Let's create a case with two pages, one to take notes and another one to summarize the case: + +```python +--8<-- "examples/case/pages_during_creation.py" +``` + +As you can see we had to specify the `title`, `category` and `content` fields which are all mandatory for the page objects. + +On the other hand each of these fields are freetext fields so we have the freedom to specify any value for them. + + +### Add pages to an existing case + +In the previous section we could see that it's possible to add pages during case creation, however sometimes we want to add pages after a case has been created. + +For this purpose we can use the [case.create_page][thehive4py.endpoints.case.CaseEndpoint.create_page] method. + +Now let's do an example by adding pages retroactively to a case: + +```python +--8<-- "examples/case/pages_after_creation.py" +``` + +In the above example we created an empty case as `case_to_enrich`, and then defined a list of pages in the `page_tasks` variable. + +Finally using a for loop and the `case.create_page` method we added them to our dummy case one by one. + +## Case procedures + +TheHive considers the [MITRE ATT&CK](https://attack.mitre.org/) framework as a first class citizen and fully supports its tactics, techniques and procedures (TTPs for short), therefore it's possible to enrich cases with procedures from their catalog. + +TheHive simply refers to the MITRE ATT&CK TTPs as procedures, and next we will see how can these procedures be applied to cases. + +### Add procedures to an existing case + +In this example we will create an empty case and using the [case.create_task][thehive4py.endpoints.case.CaseEndpoint.create_task] method we will enrich it with the technique of [[T1566] Phishing](https://attack.mitre.org/techniques/T1566/) and the subtechnique of [[T1566.001] Phishing - Spearphishing Attachment](https://attack.mitre.org/techniques/T1566/001/): + + +```python +--8<-- "examples/case/procedures_after_creation.py" +``` + +The procedure entity requires `occurDate` and `patternId` as they are mandatory fields while `tactic` and `description` are optional. + +To simplify the example we also used the [helpers.now_to_ts][thehive4py.helpers.now_to_ts] function to generate a dummy timestamp for the procedures. + diff --git a/docs/reference.md b/docs/reference.md index 8e70d3e..f76e0c6 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -18,3 +18,4 @@ - types - query - errors + - helpers diff --git a/examples/case/advanced.py b/examples/case/advanced.py new file mode 100644 index 0000000..ec84c6c --- /dev/null +++ b/examples/case/advanced.py @@ -0,0 +1,15 @@ +from thehive4py import TheHiveApi +from thehive4py.types.case import InputCase + +hive = TheHiveApi(url="http://localhost:9000", apikey="h1v3b33") + +input_case: InputCase = { + "title": "an advanced case", + "description": "a bit more advanced case...", + "caseTemplate": "my-template", + "severity": 1, + "status": "New", + "tags": ["advanced", "example"], +} + +output_case = hive.case.create(case=input_case) diff --git a/examples/case/delete.py b/examples/case/delete.py new file mode 100644 index 0000000..4ab14f5 --- /dev/null +++ b/examples/case/delete.py @@ -0,0 +1,13 @@ +from thehive4py import TheHiveApi + +hive = TheHiveApi(url="http://localhost:9000", apikey="h1v3b33") + +case_to_delete = hive.case.create( + case={ + "title": "delete case", + "description": "an case to delete", + } +) + + +hive.case.delete(case_id=case_to_delete["_id"]) diff --git a/examples/case/fetch_with_find.py b/examples/case/fetch_with_find.py new file mode 100644 index 0000000..8862228 --- /dev/null +++ b/examples/case/fetch_with_find.py @@ -0,0 +1,24 @@ +from thehive4py import TheHiveApi +from thehive4py.query.filters import Eq + +hive = TheHiveApi(url="http://localhost:9000", apikey="h1v3b33") + +antivirus_case = hive.case.create( + case={ + "title": "case #1 to find", + "description": "a case to find with others", + "tags": ["antivirus"], + } +) + +phishing_case = hive.case.create( + case={ + "title": "case #2 to find", + "description": "a case to find with others", + "tags": ["phishing"], + } +) + + +filters = Eq(field="tags", value="antivirus") | Eq(field="tags", value="phishing") +fetched_cases = hive.case.find(filters=filters) diff --git a/examples/case/fetch_with_get.py b/examples/case/fetch_with_get.py new file mode 100644 index 0000000..74b40fc --- /dev/null +++ b/examples/case/fetch_with_get.py @@ -0,0 +1,13 @@ +from thehive4py import TheHiveApi + +hive = TheHiveApi(url="http://localhost:9000", apikey="h1v3b33") + +case_to_get = hive.case.create( + case={ + "title": "case to get", + "description": "a single case to fetch", + } +) + + +fetched_case = hive.case.get(case_id=case_to_get["_id"]) diff --git a/examples/case/merge.py b/examples/case/merge.py new file mode 100644 index 0000000..91fdbdf --- /dev/null +++ b/examples/case/merge.py @@ -0,0 +1,16 @@ +from thehive4py import TheHiveApi + +hive = TheHiveApi(url="http://localhost:9000", apikey="h1v3b33") + +case_ids_to_merge: list[str] = [] +for i in range(2): + case_to_merge = hive.case.create( + case={ + "title": f"original case #{i}", + "description": "a case to merge with another", + } + ) + case_ids_to_merge.append(case_to_merge["_id"]) + + +merge_case = hive.case.merge(case_ids=case_ids_to_merge) diff --git a/examples/case/minimalistic.py b/examples/case/minimalistic.py new file mode 100644 index 0000000..75c82ab --- /dev/null +++ b/examples/case/minimalistic.py @@ -0,0 +1,10 @@ +from thehive4py import TheHiveApi + +hive = TheHiveApi(url="http://localhost:9000", apikey="h1v3b33") + +minimalistic_case = hive.case.create( + case={ + "title": "a minimalistic case", + "description": "a bit too minimal", + } +) diff --git a/examples/case/obervable_simple.py b/examples/case/obervable_simple.py new file mode 100644 index 0000000..5c9df99 --- /dev/null +++ b/examples/case/obervable_simple.py @@ -0,0 +1,22 @@ +from thehive4py import TheHiveApi +from thehive4py.types.observable import InputObservable + +hive = TheHiveApi(url="thehive.example", apikey="h1v3b33") + + +case_to_enrich = hive.case.create( + case={ + "title": "case to enrich", + "description": "a case to enrich with simple observables", + } +) + + +observables: list[InputObservable] = [ + {"dataType": "ip", "data": "1.2.3.4"}, + {"dataType": "domain", "data": "example.com"}, +] + + +for observable in observables: + hive.case.create_observable(case_id=case_to_enrich["_id"], observable=observable) diff --git a/examples/case/observable_file.py b/examples/case/observable_file.py new file mode 100644 index 0000000..f45398f --- /dev/null +++ b/examples/case/observable_file.py @@ -0,0 +1,25 @@ +import os.path +import tempfile + +from thehive4py import TheHiveApi + +hive = TheHiveApi(url="http://localhost:9000", apikey="h1v3b33") + +case_to_enrich = hive.case.create( + case={ + "title": "case to enrich", + "description": "a case to enrich with a file observable", + } +) + +with tempfile.TemporaryDirectory() as tmpdir: + + observable_filepath = os.path.join(tmpdir, "my-observable.txt") + with open(observable_filepath) as observable_file: + observable_file.write("some observable content") + + hive.case.create_observable( + case_id=case_to_enrich["_id"], + observable={"dataType": "file", "message": "a file based observable"}, + observable_path=observable_filepath, + ) diff --git a/examples/case/pages_after_creation.py b/examples/case/pages_after_creation.py new file mode 100644 index 0000000..069fa76 --- /dev/null +++ b/examples/case/pages_after_creation.py @@ -0,0 +1,29 @@ +from thehive4py import TheHiveApi +from thehive4py.types.page import InputCasePage + +hive = TheHiveApi(url="thehive.example", apikey="h1v3b33") + + +case_to_enrich = hive.case.create( + case={ + "title": "case to enrich", + "description": "a case to enrich with pages", + } +) + +case_pages: list[InputCasePage] = [ + { + "title": "Playbooks", + "category": "general", + "content": "Some playbook content", + }, + { + "title": "Resources", + "category": "general", + "content": "Some useful resources", + }, +] + + +for case_page in case_pages: + hive.case.create_page(case_id=case_to_enrich["_id"], page=case_page) diff --git a/examples/case/pages_during_creation.py b/examples/case/pages_during_creation.py new file mode 100644 index 0000000..b6e9766 --- /dev/null +++ b/examples/case/pages_during_creation.py @@ -0,0 +1,26 @@ +from thehive4py import TheHiveApi +from thehive4py.types.page import InputCasePage + +hive = TheHiveApi(url="thehive.example", apikey="h1v3b33") + + +case_pages: list[InputCasePage] = [ + { + "title": "Notes", + "category": "default", + "content": "Some notes to take during case triage.", + }, + { + "title": "Summary", + "category": "default", + "content": "Some summary to wrap up the case triage.", + }, +] + +case_with_tasks = hive.case.create( + case={ + "title": "case with tasks", + "description": "a case enriched with tasks", + "pages": case_pages, + } +) diff --git a/examples/case/procedures_after_creation.py b/examples/case/procedures_after_creation.py new file mode 100644 index 0000000..1116503 --- /dev/null +++ b/examples/case/procedures_after_creation.py @@ -0,0 +1,31 @@ +from thehive4py import TheHiveApi +from thehive4py.helpers import now_to_ts +from thehive4py.types.procedure import InputProcedure + +hive = TheHiveApi(url="thehive.example", apikey="h1v3b33") + +case_to_enrich = hive.case.create( + case={ + "title": "case to enrich", + "description": "a case to enrich with procedures", + } +) + +case_procedures: list[InputProcedure] = [ + { + "occurDate": now_to_ts(), + "patternId": "T1566", + "tactic": "initial-access", + "description": "Phishing", + }, + { + "occurDate": now_to_ts(), + "patternId": "T1566.001", + "tactic": "initial-access", + "description": "Spearphishing Attachment", + }, +] + + +for procedure in case_procedures: + hive.case.create_procedure(case_id=case_to_enrich["_id"], procedure=procedure) diff --git a/examples/case/tasks_after_creation.py b/examples/case/tasks_after_creation.py new file mode 100644 index 0000000..e9af62a --- /dev/null +++ b/examples/case/tasks_after_creation.py @@ -0,0 +1,21 @@ +from thehive4py import TheHiveApi +from thehive4py.types.task import InputTask + +hive = TheHiveApi(url="thehive.example", apikey="h1v3b33") + + +case_to_enrich = hive.case.create( + case={ + "title": "case to enrich", + "description": "a case to enrich with tasks", + } +) + +case_tasks: list[InputTask] = [ + {"title": "Summarize", "description": "Summarize the investigation"}, + {"title": "Report", "description": "Create a report for the CISO"}, +] + + +for case_task in case_tasks: + hive.case.create_task(case_id=case_to_enrich["_id"], task=case_task) diff --git a/examples/case/tasks_during_creation.py b/examples/case/tasks_during_creation.py new file mode 100644 index 0000000..101e1a5 --- /dev/null +++ b/examples/case/tasks_during_creation.py @@ -0,0 +1,18 @@ +from thehive4py import TheHiveApi +from thehive4py.types.task import InputTask + +hive = TheHiveApi(url="thehive.example", apikey="h1v3b33") + + +case_tasks: list[InputTask] = [ + {"title": "Triage", "description": "Conduct the initial investigation"}, + {"title": "Respond", "description": "Execute the required actions"}, +] + +case_with_tasks = hive.case.create( + case={ + "title": "case with tasks", + "description": "a case enriched with tasks", + "tasks": case_tasks, + } +) diff --git a/examples/case/update_bulk.py b/examples/case/update_bulk.py new file mode 100644 index 0000000..16b1301 --- /dev/null +++ b/examples/case/update_bulk.py @@ -0,0 +1,23 @@ +from thehive4py import TheHiveApi + +hive = TheHiveApi(url="http://localhost:9000", apikey="h1v3b33") + +original_case_ids = [] +for i in range(2): + original_case = hive.case.create( + case={ + "title": f"original case #{i}", + "description": "a case to update in bulk", + } + ) + + original_case_ids.append(original_case["_id"]) + + +hive.case.bulk_update( + fields={ + "ids": original_case_ids, + "title": "bulk updated case", + "tags": ["update-bulk"], + }, +) diff --git a/examples/case/update_single.py b/examples/case/update_single.py new file mode 100644 index 0000000..9e991b4 --- /dev/null +++ b/examples/case/update_single.py @@ -0,0 +1,21 @@ +from thehive4py import TheHiveApi + +hive = TheHiveApi(url="http://localhost:9000", apikey="h1v3b33") + +original_case = hive.case.create( + case={ + "title": "original case", + "description": "a single case to update", + } +) + + +hive.case.update( + case_id=original_case["_id"], + fields={ + "title": "updated case", + "tags": ["update-single"], + }, +) + +updated_case = hive.case.get(case_id=original_case["_id"]) diff --git a/mkdocs.yml b/mkdocs.yml index f914c95..96a8eca 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -6,6 +6,7 @@ nav: - Examples: - Client: examples/client.md - Alert: examples/alert.md + - Case: examples/case.md - Reference: reference.md theme: name: material