From 1a456b874c98a58b2553290a8c0b8879ea44c1ae Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Thu, 16 May 2024 10:33:46 -0400 Subject: [PATCH 01/30] Bump version -> 1.4.4 --- src/falconpy/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/falconpy/_version.py b/src/falconpy/_version.py index addc5581e..76be60c27 100644 --- a/src/falconpy/_version.py +++ b/src/falconpy/_version.py @@ -35,7 +35,7 @@ For more information, please refer to """ -_VERSION = '1.4.3' +_VERSION = '1.4.4' _MAINTAINER = 'Joshua Hiller' _AUTHOR = 'CrowdStrike' _AUTHOR_EMAIL = 'falconpy@crowdstrike.com' From 2967b33fe138e7e62d2d8bd03cdc181cf741a82a Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Thu, 16 May 2024 10:55:00 -0400 Subject: [PATCH 02/30] Add params_to_keywords handler. Related to #1160. --- src/falconpy/_util/__init__.py | 4 +++- src/falconpy/_util/_functions.py | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/falconpy/_util/__init__.py b/src/falconpy/_util/__init__.py index e653011be..59cea51ce 100644 --- a/src/falconpy/_util/__init__.py +++ b/src/falconpy/_util/__init__.py @@ -58,6 +58,7 @@ log_class_startup, deprecated_operation, deprecated_class, + params_to_keywords, _ALLOWED_METHODS ) from ._service import service_override_payload @@ -77,5 +78,6 @@ "confirm_base_region", "return_preferred_default", "base_url_regions", "_ALLOWED_METHODS", "login_payloads", "logout_payloads", "sanitize_dictionary", "calc_content_return", "log_class_startup", "service_override_payload", - "deprecated_operation", "deprecated_class", "review_provided_credentials" + "deprecated_operation", "deprecated_class", "review_provided_credentials", + "params_to_keywords" ] diff --git a/src/falconpy/_util/_functions.py b/src/falconpy/_util/_functions.py index 0e85f7bdc..2289f9ed8 100644 --- a/src/falconpy/_util/_functions.py +++ b/src/falconpy/_util/_functions.py @@ -863,3 +863,14 @@ def deprecated_class(pythonic: bool, log: Logger, old: str, new: str): else: if log: log.warning(DeprecatedClass(class_name=old, new_class_name=new)) + + +def params_to_keywords(arg_list: list, param_dict: dict, keyword_dict: dict): + """Craft a properly formatted kwargs dictionary from the parameters dictionary contents.""" + for kwa in arg_list: + if param_dict.get(kwa, None): + if not keyword_dict.get(kwa, None): + keyword_dict[kwa] = param_dict.get(kwa) + param_dict.pop(kwa) + + return keyword_dict From e482480d9383254f881c4b71e292297d0b309fee Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Thu, 16 May 2024 11:30:49 -0400 Subject: [PATCH 03/30] Refactor formData payload generation. Closes #1160. --- src/falconpy/sample_uploads.py | 49 ++++++++++++++++++++++++---------- tests/test_sample_uploads.py | 8 +++--- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/falconpy/sample_uploads.py b/src/falconpy/sample_uploads.py index a8428eea2..43e738917 100644 --- a/src/falconpy/sample_uploads.py +++ b/src/falconpy/sample_uploads.py @@ -35,13 +35,13 @@ For more information, please refer to """ -import json from typing import Dict, Union from ._util import ( force_default, process_service_request, handle_single_argument, - generate_error_result + generate_error_result, + params_to_keywords ) from ._payload import extraction_payload from ._service_class import ServiceClass @@ -232,6 +232,11 @@ def upload_archive(self: object, Swagger URL https://assets.falcon.crowdstrike.com/support/api/swagger.html#/sample-uploads/ArchiveUploadV2 """ + method_args = ["name", "archive", "file", "file_data", "is_confidential", "comment", "password"] + kwargs = params_to_keywords(method_args, + parameters, + kwargs + ) # Check for file name if not name: return generate_error_result("You must provide an archive filename.", code=400) @@ -254,6 +259,8 @@ def upload_archive(self: object, file_extended = {"name": name} if kwargs.get("password", None): file_extended["password"] = kwargs.get("password") + if kwargs.get("comment", None): + file_extended["comment"] = kwargs.get("comment") if kwargs.get("is_confidential", None): file_extended["is_confidential"] = kwargs.get("is_confidential") @@ -263,9 +270,7 @@ def upload_archive(self: object, operation_id="ArchiveUploadV2", body=body, files=file_tuple, - data=file_extended, - keywords=kwargs, - params=parameters + data=file_extended ) @force_default(defaults=["parameters"], default_types=["dict"]) @@ -458,24 +463,40 @@ def upload_sample(self: object, Swagger URL https://assets.falcon.crowdstrike.com/support/api/swagger.html#/sample-uploads/UploadSampleV3 """ + # Check for raw parameters dictionary and convert it's contents to keywords + method_args = ["file_name", "sample", "upfile", "file_data", "is_confidential", "comment"] + kwargs = params_to_keywords(method_args, + parameters, + kwargs + ) + + # Check for file name + file_name = kwargs.get("file_name", None) + if not file_name: + return generate_error_result("'file_name' must be specified", code=400) + # Try to find the binary object they provided us if not file_data: file_data = kwargs.get("sample", None) if not file_data: file_data = kwargs.get("upfile", None) - # Create a copy of our default header dictionary - header_payload = json.loads(json.dumps(self.headers)) - # Set our content-type header - header_payload["Content-Type"] = "application/octet-stream" + if not file_data: + return generate_error_result("You must provide a file to upload.", code=400) + + # Create the form data dictionary + file_extended = {"file_name": file_name} + if kwargs.get("comment", None): + file_extended["comment"] = kwargs.get("comment") + if kwargs.get("is_confidential", None): + file_extended["is_confidential"] = kwargs.get("is_confidential") + return process_service_request( calling_object=self, endpoints=Endpoints, operation_id="UploadSampleV3", - headers=header_payload, # Pass our custom headers - body=body, - data=file_data, - keywords=kwargs, - params=parameters + files=[("sample", (file_name, file_data))], # Passed as a list of tuples + data=file_extended, + body=body # Not used but maintained for backwards compatibility with method signature ) @force_default(defaults=["parameters"], default_types=["dict"]) diff --git a/tests/test_sample_uploads.py b/tests/test_sample_uploads.py index 2889f26df..2a6b4fbec 100644 --- a/tests/test_sample_uploads.py +++ b/tests/test_sample_uploads.py @@ -34,12 +34,13 @@ def sample_upload_download_delete(self, style: str = "file_data", expanded_resul SOURCE = "%s_source.png" % jdate TARGET = "tests/%s_target.png" % jdate PAYLOAD = open(FILENAME, 'rb').read() + params_payload = {"file_name": SOURCE} if style.lower() == "sample": - response = falcon.UploadSampleV3(file_name=SOURCE, sample=PAYLOAD) + response = falcon.upload_sample(file_name=SOURCE, sample=PAYLOAD) elif style.lower() == "upfile": - response = falcon.UploadSampleV3(file_name=SOURCE, upfile=PAYLOAD) + response = falcon.upload_sample(file_name=SOURCE, upfile=PAYLOAD, comment="Whatever") else: - response = falcon.UploadSampleV3(file_name=SOURCE, file_data=PAYLOAD) + response = falcon.upload_sample(parameters=params_payload, file_data=PAYLOAD, is_confidential=True) try: sha = response["body"]["resources"][0]["sha256"] except (KeyError, IndexError): @@ -101,6 +102,7 @@ def sample_errors(self): error_checks = True tests = { "upload_sample": falcon.UploadSampleV3(body={}), + "upload_sample_as_well": falcon.upload_sample(file_name="NotHere.jpg"), "get_sample": falcon.GetSampleV3(ids='DoesNotExist'), "delete_sample": falcon.DeleteSampleV3(ids='12345678'), "ArchiveListV1": falcon.ArchiveListV1(id="12345678"), From 37f7c2a7967a7128685eff32bc18cbe2536e19af Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Thu, 16 May 2024 11:51:29 -0400 Subject: [PATCH 04/30] Allow 204 from DeletePolicy unit testing. --- tests/test_image_assessment_policies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_image_assessment_policies.py b/tests/test_image_assessment_policies.py index 3ebbde7a4..b9c612293 100644 --- a/tests/test_image_assessment_policies.py +++ b/tests/test_image_assessment_policies.py @@ -16,7 +16,7 @@ auth = Authorization.TestAuthorization() config = auth.getConfigObject() falcon = ImageAssessmentPolicies(auth_object=config) -AllowedResponses = [200, 201, 207, 400, 502] # Allowing 502 from CreatePolicyGroups for now +AllowedResponses = [200, 201, 204, 207, 400, 502] # Allowing 502 from CreatePolicyGroups for now class TestImageAssessmentPolicies: From ea463933d7d1f821014dc269b94903cd73bda236 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Thu, 16 May 2024 11:52:04 -0400 Subject: [PATCH 05/30] Add uber class context authentication testing. --- tests/test_zero_trust_assessment.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/test_zero_trust_assessment.py b/tests/test_zero_trust_assessment.py index a1c59f675..8454c6e1d 100644 --- a/tests/test_zero_trust_assessment.py +++ b/tests/test_zero_trust_assessment.py @@ -15,7 +15,7 @@ # Import our sibling src folder into the path sys.path.append(os.path.abspath('src')) # Classes to test - manually imported from sibling folder -from falconpy import ZeroTrustAssessment +from falconpy import ZeroTrustAssessment, APIHarnessV2 auth = Authorization.TestAuthorization() config = auth.getConfigObject() @@ -63,6 +63,20 @@ def test_context_authentication_no_base(self): request_context.reset(tok) assert bool(zta.get_audit().status_code == 200) + @pytest.mark.skipif(config.base_url != "https://api.crowdstrike.com", + reason="Unit testing unavailable in this region" + ) + def test_uber_context_authentication_no_base(self): + request_context = ContextVar("request", default=BaselessContextRequest()) + req: BaselessContextRequest = request_context.get() + req.access_token = auth.authorization.token()["body"]["access_token"] + tok = request_context.set(req) + uber = APIHarnessV2(pythonic=True, debug=config.debug) + request_context.reset(tok) + assert min(bool(uber.command("QueryDevicesByFilterScroll").status_code == 200), + bool(uber.auth_style == "CONTEXT") + ) + @pytest.mark.skipif(config.base_url != "https://api.crowdstrike.com", reason="Unit testing unavailable in this region" ) From 232c332a5a4d1aa77393667c7c4ae999ef1ee895 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Fri, 17 May 2024 02:05:03 -0400 Subject: [PATCH 06/30] Refactor formData payload generation (UploadSampleV2). Relates to #1160. --- src/falconpy/falconx_sandbox.py | 40 ++++++++++++++++++++++++--------- tests/test_falconx_sandbox.py | 14 +++++++----- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/falconpy/falconx_sandbox.py b/src/falconpy/falconx_sandbox.py index a578cd03e..34f0855bb 100644 --- a/src/falconpy/falconx_sandbox.py +++ b/src/falconpy/falconx_sandbox.py @@ -37,7 +37,13 @@ """ import json from typing import Dict, Union -from ._util import force_default, process_service_request, handle_single_argument +from ._util import ( + force_default, + process_service_request, + handle_single_argument, + params_to_keywords, + generate_error_result + ) from ._payload import generic_payload_list, falconx_payload from ._service_class import ServiceClass from ._endpoint._falconx_sandbox import _falconx_sandbox_endpoints as Endpoints @@ -450,25 +456,39 @@ def upload_sample(self: object, Swagger URL https://assets.falcon.crowdstrike.com/support/api/swagger.html#/falconx-sandbox/UploadSampleV2 """ + method_args = ["file_name", "sample", "upfile", "file_data", "is_confidential", "comment"] + kwargs = params_to_keywords(method_args, + parameters, + kwargs + ) + + # Check for file name + file_name = kwargs.get("file_name", None) + if not file_name: + return generate_error_result("'file_name' must be specified", code=400) + # Try to find the binary object they provided us if not file_data: file_data = kwargs.get("sample", None) if not file_data: file_data = kwargs.get("upfile", None) + if not file_data: + return generate_error_result("You must provide a file to upload.", code=400) + + # Create the form data dictionary + file_extended = {"file_name": file_name} + if kwargs.get("comment", None): + file_extended["comment"] = kwargs.get("comment") + if kwargs.get("is_confidential", None): + file_extended["is_confidential"] = kwargs.get("is_confidential") - # Create a copy of our default header dictionary - header_payload = json.loads(json.dumps(self.headers)) - # Set our content-type header - header_payload["Content-Type"] = "application/octet-stream" return process_service_request( calling_object=self, endpoints=Endpoints, operation_id="UploadSampleV2", - body=body, - data=file_data, - params=parameters, - keywords=kwargs, - headers=header_payload + files=[("sample", (file_name, file_data))], # Passed as a list of tuples + data=file_extended, + body=body # Not used but maintained for backwards compatibility with method signature ) @force_default(defaults=["parameters"], default_types=["dict"]) diff --git a/tests/test_falconx_sandbox.py b/tests/test_falconx_sandbox.py index 91da59f6f..970fd50c2 100644 --- a/tests/test_falconx_sandbox.py +++ b/tests/test_falconx_sandbox.py @@ -27,13 +27,13 @@ def falconx_generate_errors(self): Executes every statement in every method of the class, accepts all errors except 500 """ error_checks = True - # filename = "testfile.png" + filename = "tests/testfile.png" # FILENAME = f"tests/{filename}" # fmt = '%Y-%m-%d %H:%M:%S' - # with open(FILENAME, 'rb') as testfile: - # PAYLOAD = testfile.read() - filename = None - PAYLOAD = None + with open(filename, 'rb') as testfile: + PAYLOAD = testfile.read() + #filename = None + #PAYLOAD = None tests = { "get_artifacts": falcon.GetArtifacts(parameters={}), "get_summary_reports": falcon.GetSummaryReports(ids='12345678'), @@ -49,7 +49,9 @@ def falconx_generate_errors(self): "query_reports": falcon.QueryReports(), "query_submissions": falcon.QuerySubmissions(), "get_sample": falcon.GetSampleV2(ids='12345678'), - "upload_sample": falcon.UploadSampleV2(file_name=filename, file_data=PAYLOAD), + "upload_sample": falcon.UploadSampleV2(file_name=filename, upfile=PAYLOAD, comment="testing", is_confidential=True), + "upload_sample": falcon.UploadSampleV2(file_data=PAYLOAD), + "upload_sample": falcon.UploadSampleV2(file_data=None, file_name="whatever.png"), "delete_sample": falcon.DeleteSampleV2(ids='12345678'), "query_sample": falcon.QuerySampleV1(sha256s='12345678'), "get_memory_dump": falcon.get_memory_dump("12345678"), From c8454efe5e0df29787df6ef0807b7db5703c6611 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Fri, 17 May 2024 02:05:36 -0400 Subject: [PATCH 07/30] Refactor formData payload generation (CaseAddAttachment). Relates to #1160. --- src/falconpy/message_center.py | 43 +++++++++++++++++++++++++--------- tests/test_message_center.py | 11 +++++---- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/falconpy/message_center.py b/src/falconpy/message_center.py index fc7cea629..62ba9a712 100644 --- a/src/falconpy/message_center.py +++ b/src/falconpy/message_center.py @@ -35,9 +35,14 @@ For more information, please refer to """ -import json from typing import Dict, Union -from ._util import force_default, process_service_request, handle_single_argument +from ._util import ( + force_default, + process_service_request, + handle_single_argument, + params_to_keywords, + generate_error_result + ) from ._payload import generic_payload_list, aggregate_payload, activity_payload, case_payload from ._service_class import ServiceClass from ._endpoint._message_center import _message_center_endpoints as Endpoints @@ -252,6 +257,7 @@ def add_case_attachment(self: object, Adobe PDF: .pdf Office documents: .doc, .docx, .xls, .xlsx, .pptx Text: .txt, .csv + file_name -- File name for the attached file. String. parameters -- full parameters payload, not required if using other keywords. user_uuid -- User UUID performing the attachment. String. @@ -264,24 +270,39 @@ def add_case_attachment(self: object, Swagger URL https://assets.falcon.crowdstrike.com/support/api/swagger.html#/message-center/CaseAddAttachment """ + method_args = ["case_id", "file_data", "user_uuid"] + kwargs = params_to_keywords(method_args, + parameters, + kwargs + ) + + # Check for file name + file_name = kwargs.get("file_name", None) + if not file_name: + return generate_error_result("'file_name' must be specified", code=400) + # Try to find the binary object they provided us if not file_data: file_data = kwargs.get("sample", None) if not file_data: file_data = kwargs.get("upfile", None) - # Create a copy of our default header dictionary - header_payload = json.loads(json.dumps(self.headers)) - # Set our content-type header - header_payload["Content-Type"] = "multipart/form-data" + if not file_data: + return generate_error_result("You must provide a file to upload.", code=400) + + # Create the form data dictionary + file_extended = {} + if kwargs.get("case_id", None): + file_extended["case_id"] = kwargs.get("case_id") + if kwargs.get("user_uuid", None): + file_extended["user_uuid"] = kwargs.get("user_uuid") + return process_service_request( calling_object=self, endpoints=Endpoints, operation_id="CaseAddAttachment", - headers=header_payload, # Pass our custom headers - body=body, - data=file_data, - keywords=kwargs, - params=parameters + files=[("file", (file_name, file_data))], # Passed as a list of tuples + data=file_extended, + body=body # Not used but maintained for backwards compatibility with method signature ) @force_default(defaults=["body"], default_types=["dict"]) diff --git a/tests/test_message_center.py b/tests/test_message_center.py index 91639f365..20fc11f65 100644 --- a/tests/test_message_center.py +++ b/tests/test_message_center.py @@ -12,7 +12,7 @@ auth = Authorization.TestAuthorization() config = auth.getConfigObject() falcon = MessageCenter(auth_object=config) -AllowedResponses = [200, 403, 429, 405] # pre-1.2.16 - UpdateCase appears to be decomm'd +AllowedResponses = [200, 400, 403, 429, 405] # pre-1.2.16 - UpdateCase appears to be decomm'd class TestMessageCenter: @@ -33,15 +33,16 @@ def message_center_full_series(self): "download_case_attachment": falcon.download_case_attachment(ids="12345678"), "add_case_attachment": falcon.add_case_attachment(case_id="12345678", file_data=PAYLOAD, - user_uuid="bob@nowhere.com" + user_uuid="bob@nowhere.com", ), "add_case_attachment2": falcon.add_case_attachment(case_id="12345678", upfile=PAYLOAD, - user_uuid="bob@nowhere.com" + user_uuid="bob@nowhere.com", + file_name="testfile.png" ), "add_case_attachment3": falcon.add_case_attachment(case_id="12345678", - sample=PAYLOAD, - user_uuid="bob@nowhere.com" + user_uuid="bob@nowhere.com", + file_name="testfile.png" ), "create_case": falcon.create_case(content="Case content goes here", detections={ From 4c5cbd6ee5f0e93c967cd3e0349978e820cfcb54 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Sat, 18 May 2024 01:27:59 -0400 Subject: [PATCH 08/30] Refactor formData payload generation (WorkflowDefinitionsImport). Relates to #1160. --- src/falconpy/workflows.py | 5 +++-- tests/test_workflows.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/falconpy/workflows.py b/src/falconpy/workflows.py index 450286b14..e44b4184a 100644 --- a/src/falconpy/workflows.py +++ b/src/falconpy/workflows.py @@ -178,10 +178,11 @@ def import_definition(self: object, parameters: dict = None, **kwargs) -> Dict[s try: with open(data_file, "r", encoding="utf-8") as yaml_file: file_data = yaml_file.read() - file_extended = {"name": "yaml_upload", "data_file": file_data, "type": content_type} + file_extended = [("data_file", ("yaml_upload", file_data, content_type))] except FileNotFoundError: data_file = None - + # Remove the data file from the keywords dictionary before args_to_params + kwargs.pop("data_file") if data_file and file_data: returned = process_service_request( calling_object=self, diff --git a/tests/test_workflows.py b/tests/test_workflows.py index d5a08c1bf..70ca5a702 100644 --- a/tests/test_workflows.py +++ b/tests/test_workflows.py @@ -36,7 +36,7 @@ def run_all_tests(self): "WorkflowDefinitionsImport": falcon.import_definition(validate_only=True, data_file="this_will_415"), "WorkflowDefinitionsImport2": falcon.import_definition(validate_only=True, file_data="this_will_500"), "WorkflowDefinitionsImport3": falcon.import_definition(validate_only=True, data_file="not_here.yml"), - "WorkflowDefinitionsImport4": falcon.import_definition(validate_only=True, data_file="tests/test.yml"), + "WorkflowDefinitionsImport4": falcon.import_definition(validate_only=True, data_file="tests/test.yml", name="workflow_name"), "WorkflowDefinitionsUpdate": falcon.update_definition(change_log="testing"), "WorkflowGetHumanInputV1": falcon.get_human_input(ids="1234567"), "WorkflowUpdateHumanInputV1": falcon.update_human_input(input="whatever", note="whatever"), From 8a79554ee66a9461a23dfec4397f9d4381701211 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Sat, 18 May 2024 02:07:06 -0400 Subject: [PATCH 09/30] Prevent data keys from converting to query string parameters. Related to #1160. --- src/falconpy/foundry_logscale.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/falconpy/foundry_logscale.py b/src/falconpy/foundry_logscale.py index 5869cd8cc..eb2660ce2 100644 --- a/src/falconpy/foundry_logscale.py +++ b/src/falconpy/foundry_logscale.py @@ -115,6 +115,7 @@ def ingest_data(self: object, for key in data_keys: if kwargs.get(key, None): form_data[key] = kwargs.get(key) + kwargs.pop(key) # Prevent it from converting to a query string param # Create a multipart form payload for our upload file file_tuple = [("file", ("data-upload", data_file, "application/json"))] @@ -163,6 +164,7 @@ def ingest_data_async(self: object, for key in data_keys: if kwargs.get(key, None): form_data[key] = kwargs.get(key) + kwargs.pop(key) # Prevent it from converting to a query string param # Create a multipart form payload for our upload file file_tuple = [("file", ("data-upload", data_file, "application/json"))] From 0f3bc6ebfa6c9fc9c8616e2f0001b5dac3f170bf Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Tue, 4 Jun 2024 03:10:41 -0400 Subject: [PATCH 10/30] Resolve action keyword collision. Closes #1161. --- src/falconpy/_util/_uber.py | 6 +++--- src/falconpy/api_complete/_advanced.py | 16 ++++++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/falconpy/_util/_uber.py b/src/falconpy/_util/_uber.py index b7e77e0e4..0444657e2 100644 --- a/src/falconpy/_util/_uber.py +++ b/src/falconpy/_util/_uber.py @@ -65,7 +65,7 @@ def handle_field(tgt: str, kwa: dict, fld: str) -> str: def handle_body_payload_ids(kwa: dict) -> dict: """Migrates the IDs parameter list over to the body payload.""" - if kwa.get("action", None) in PREFER_IDS_IN_BODY: + if kwa.get("api_operation", None) in PREFER_IDS_IN_BODY: if kwa.get("ids", None): # Handle the GET to POST method redirection for passed IDs if not kwa.get("body", {}).get("ids", None): @@ -115,12 +115,12 @@ def handle_container_operations(kwa: dict, base_string: str) -> Tuple[dict, str, """Handle Base URLs and keyword arguments for container registry operations.""" # Default to non-container registry operations do_container = False - if kwa.get("action", None) in MOCK_OPERATIONS: + if kwa.get("api_operation", None) in MOCK_OPERATIONS: for base in [burl for burl in dir(BaseURL) if "__" not in burl]: if BaseURL[base].value == base_string.replace("https://", ""): base_string = f"https://{ContainerBaseURL[base].value}" do_container = True - if kwa.get("action", None) == "ImageMatchesPolicy": + if kwa.get("api_operation", None) == "ImageMatchesPolicy": if "parameters" not in kwa: kwa["parameters"] = {} kwa["parameters"]["policy_type"] = "image-prevention-policy" diff --git a/src/falconpy/api_complete/_advanced.py b/src/falconpy/api_complete/_advanced.py index 9bf3b7bba..d4419707c 100644 --- a/src/falconpy/api_complete/_advanced.py +++ b/src/falconpy/api_complete/_advanced.py @@ -137,8 +137,10 @@ def command(self, *args, **kwargs) -> Union[Dict[str, Union[int, dict]], bytes]: Keyword arguments ---- - action : str (Default: None) + api_operation : str (Default: None) API Operation ID to perform + Please note: The keyword "action" will also be accepted but + may collide with operation parameters and is not recommended. parameters : dict (Default: {}) Parameter payload (Query string) body : dict (Default: {}) @@ -170,21 +172,23 @@ def command(self, *args, **kwargs) -> Union[Dict[str, Union[int, dict]], bytes]: Arguments ---- - The first argument passed to this method is assumed to be 'action'. All others are ignored. + The first argument passed to this method is assumed to be 'api_operation'. All others are ignored. Returns ---- dict or bytes Dictionary or binary object containing API response depending on requested operation. """ + # Issue #1161 - operation is specified using the action keyword + if kwargs.get("action", None) and not kwargs.get("api_operation", None): + kwargs["api_operation"] = kwargs.get("action") try: - if not kwargs.get("action", None): + if not kwargs.get("api_operation", None): # Assume they're passing it in as the first argument. - kwargs["action"] = args[0] + kwargs["api_operation"] = args[0] except IndexError: pass # They didn't specify an action, try for an override instead. - - uber_command = [a for a in self.commands if a[0] == kwargs.get("action", None)] + uber_command = [a for a in self.commands if a[0] == kwargs.get("api_operation", None)] if kwargs.get("override", None): uber_command = [["Manual"] + kwargs["override"].split(",")] if uber_command: From 7fc4e639d150e4dd43e8264e625880685b0f5e11 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Tue, 4 Jun 2024 03:45:16 -0400 Subject: [PATCH 11/30] Add cql_master, cql_update and cql_changelog to GetLatestIntelRuleFile --- src/falconpy/_endpoint/_intel.py | 4 ++-- src/falconpy/intel.py | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/falconpy/_endpoint/_intel.py b/src/falconpy/_endpoint/_intel.py index 89f7a17e6..28a89201b 100644 --- a/src/falconpy/_endpoint/_intel.py +++ b/src/falconpy/_endpoint/_intel.py @@ -427,7 +427,7 @@ "type": "string", "description": "The rule news report type. Accepted values:\n\nsnort-suricata-master\n\nsnort-" "suricata-update\n\nsnort-suricata-changelog\n\nyara-master\n\nyara-update\n\nyara-changelog\n\ncommon-event-" - "format\n\nnetwitness", + "format\n\nnetwitness\n\ncql-master\n\ncql-update\n\ncql-changelog", "name": "type", "in": "query", "required": True @@ -751,7 +751,7 @@ "type": "string", "description": "The rule news report type. Accepted values:\n\nsnort-suricata-master\n\nsnort-" "suricata-update\n\nsnort-suricata-changelog\n\nyara-master\n\nyara-update\n\nyara-changelog\n\ncommon-event-" - "format\n\nnetwitness", + "format\n\nnetwitness\n\ncql-master\n\ncql-update\n\ncql-changelog", "name": "type", "in": "query", "required": True diff --git a/src/falconpy/intel.py b/src/falconpy/intel.py index e29021e11..a6bf4c1a1 100644 --- a/src/falconpy/intel.py +++ b/src/falconpy/intel.py @@ -468,6 +468,8 @@ def get_latest_rule_file(self: object, *args, parameters: dict = None, **kwargs) netwitness yara-changelog snort-suricata-changelog yara-master snort-suricata-master yara-update + cql-master cql-changelog + cql-update Arguments: When not specified, the first argument to this method is assumed to be 'type'. All others are ignored. @@ -742,6 +744,8 @@ def query_rule_ids(self: object, parameters: dict = None, **kwargs) -> Dict[str, netwitness yara-changelog snort-suricata-changelog yara-master snort-suricata-master yara-update + cql-master cql-changelog + cql-update This method only supports keywords for providing arguments. From e4ffec9655f54e7b6396c588d607cbcadbd4df74 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Tue, 4 Jun 2024 06:11:47 -0400 Subject: [PATCH 12/30] Add API Integrations service collection --- src/falconpy/__init__.py | 3 +- src/falconpy/_endpoint/__init__.py | 2 + src/falconpy/_endpoint/_api_integrations.py | 90 ++++++++++++ src/falconpy/_payload/__init__.py | 3 +- src/falconpy/_payload/_api_integrations.py | 73 ++++++++++ src/falconpy/api_integrations.py | 145 ++++++++++++++++++++ tests/test_api_integrations.py | 41 ++++++ 7 files changed, 355 insertions(+), 2 deletions(-) create mode 100644 src/falconpy/_endpoint/_api_integrations.py create mode 100644 src/falconpy/_payload/_api_integrations.py create mode 100644 src/falconpy/api_integrations.py create mode 100644 tests/test_api_integrations.py diff --git a/src/falconpy/__init__.py b/src/falconpy/__init__.py index 8c286b775..e517701a5 100644 --- a/src/falconpy/__init__.py +++ b/src/falconpy/__init__.py @@ -88,6 +88,7 @@ RequestValidator ) from .alerts import Alerts +from .api_integrations import APIIntegrations from .api_complete import APIHarness, APIHarnessV2 from .cloud_snapshots import CloudSnapshots from .configuration_assessment_evaluation_logic import ConfigurationAssessmentEvaluationLogic @@ -195,7 +196,7 @@ "SDKDeprecationWarning", "ConfigurationAssessmentEvaluationLogic", "ConfigurationAssessment", "ContainerAlerts", "ContainerDetections", "ContainerImages", "ContainerPackages", "ContainerVulnerabilities", "DriftIndicators", "UnidentifiedContainers", - "ImageAssessmentPolicies" + "ImageAssessmentPolicies", "APIIntegrations" ] """ This is free and unencumbered software released into the public domain. diff --git a/src/falconpy/_endpoint/__init__.py b/src/falconpy/_endpoint/__init__.py index b7c338f93..bfca9e56c 100644 --- a/src/falconpy/_endpoint/__init__.py +++ b/src/falconpy/_endpoint/__init__.py @@ -40,6 +40,7 @@ from .deprecated import _deprecated_class_mapping from ._alerts import _alerts_endpoints +from ._api_integrations import _api_integrations_endpoints from ._cloud_connect_aws import _cloud_connect_aws_endpoints from ._cloud_snapshots import _cloud_snapshots_endpoints from ._configuration_assessment_evaluation_logic import _configuration_assessment_evaluation_logic_endpoints @@ -108,6 +109,7 @@ api_endpoints: List[Any] = [] api_endpoints.extend(_alerts_endpoints) +api_endpoints.extend(_api_integrations_endpoints) api_endpoints.extend(_cloud_connect_aws_endpoints) api_endpoints.extend(_cloud_snapshots_endpoints) api_endpoints.extend(_configuration_assessment_evaluation_logic_endpoints) diff --git a/src/falconpy/_endpoint/_api_integrations.py b/src/falconpy/_endpoint/_api_integrations.py new file mode 100644 index 000000000..05a9d4cdd --- /dev/null +++ b/src/falconpy/_endpoint/_api_integrations.py @@ -0,0 +1,90 @@ +"""Internal API endpoint constant library. + + _______ __ _______ __ __ __ +| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----. +|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__| +|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____| +|: 1 | |: 1 | +|::.. . | CROWDSTRIKE FALCON |::.. . | FalconPy +`-------' `-------' + +OAuth2 API - Customer SDK + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +""" + +_api_integrations_endpoints = [ + [ + "GetCombinedPluginConfigs", + "GET", + "/plugins/combined/configs/v1", + "Queries for config resources and returns details", + "api_integrations", + [ + { + "type": "string", + "description": "Filter items using a query in Falcon Query Language (FQL).", + "name": "filter", + "in": "query" + }, + { + "type": "integer", + "default": 100, + "description": "The number of items to return in this response (default: 100, max: 500). Use with the " + "offset parameter to manage pagination of results.", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "The first item to return, where 0 is the latest item. Use with the limit parameter to " + "manage pagination of results.", + "name": "offset", + "in": "query" + }, + { + "type": "string", + "description": "Sort items using their properties.", + "name": "sort", + "in": "query" + } + ] + ], + [ + "ExecuteCommand", + "POST", + "/plugins/entities/execute/v1", + "Execute a command.", + "api_integrations", + [ + { + "name": "body", + "in": "body", + "required": True + } + ] + ] +] diff --git a/src/falconpy/_payload/__init__.py b/src/falconpy/_payload/__init__.py index f30d7e906..eb2f6e63b 100644 --- a/src/falconpy/_payload/__init__.py +++ b/src/falconpy/_payload/__init__.py @@ -27,6 +27,7 @@ simple_action_parameter, token_settings_payload ) +from ._api_integrations import api_plugin_command_payload from ._host_group import host_group_create_payload, host_group_update_payload from ._recon import ( recon_action_payload, @@ -125,5 +126,5 @@ "workflow_template_payload", "foundry_execute_search_payload", "foundry_dynamic_search_payload", "image_policy_payload", "image_exclusions_payload", "image_group_payload", "workflow_definition_payload", "workflow_human_input", "workflow_mock_payload", - "cspm_service_account_validate_payload" + "cspm_service_account_validate_payload", "api_plugin_command_payload" ] diff --git a/src/falconpy/_payload/_api_integrations.py b/src/falconpy/_payload/_api_integrations.py new file mode 100644 index 000000000..4d35253cc --- /dev/null +++ b/src/falconpy/_payload/_api_integrations.py @@ -0,0 +1,73 @@ +"""Internal payload handling library - API Integrations. + + _______ __ _______ __ __ __ +| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----. +|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__| +|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____| +|: 1 | |: 1 | +|::.. . | CROWDSTRIKE FALCON |::.. . | FalconPy +`-------' `-------' + +OAuth2 API - Customer SDK + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +""" + + +def api_plugin_command_payload(passed_keywords: dict) -> dict: + """Craft a properly formatted plugin execution command payload. + + { + "resources": [ + { + "config_auth_type": "string", + "config_id": "string", + "definition_id": "string", + "id": "string", + "operation_id": "string", + "request": { + "description": "string" + }, + "version": integer + } + ] + } + """ + returned = {} + returned["resources"] = [] + item = {} + keys = ["config_auth_type", "config_id", "definition_id", "id", "operation_id", + "description", "version" + ] + for key in keys: + if passed_keywords.get(key, None): + if key == "description": + item["request"] = {"description": passed_keywords.get(key)} + else: + item[key] = passed_keywords.get(key) + returned["resources"].append(item) + + return returned diff --git a/src/falconpy/api_integrations.py b/src/falconpy/api_integrations.py new file mode 100644 index 000000000..fe7afecad --- /dev/null +++ b/src/falconpy/api_integrations.py @@ -0,0 +1,145 @@ +"""CrowdStrike Falcon ApiIntegrations API interface class. + + _______ __ _______ __ __ __ +| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----. +|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__| +|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____| +|: 1 | |: 1 | +|::.. . | CROWDSTRIKE FALCON |::.. . | FalconPy +`-------' `-------' + +OAuth2 API - Customer SDK + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +""" +from typing import Dict, Union +from ._util import force_default, process_service_request +from ._payload import api_plugin_command_payload +from ._service_class import ServiceClass +from ._endpoint._api_integrations import _api_integrations_endpoints as Endpoints + + +class APIIntegrations(ServiceClass): + """The only requirement to instantiate an instance of this class is one of the following. + + - a valid client_id and client_secret provided as keywords. + - a credential dictionary with client_id and client_secret containing valid API credentials + { + "client_id": "CLIENT_ID_HERE", + "client_secret": "CLIENT_SECRET_HERE" + } + - a previously-authenticated instance of the authentication service class (oauth2.py) + - a valid token provided by the authentication service class (oauth2.py) + """ + + @force_default(defaults=["parameters"], default_types=["dict"]) + def get_plugin_configs(self: object, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Query for config resources and returns details. + + Keyword arguments: + filter -- Filter items using a query in Falcon Query Language (FQL). String. + limit -- The number of items to return in this response (default: 100, max: 500). + Use with the offset parameter to manage pagination of results. Integer. + offset -- The first item to return, where 0 is the latest item. + Use with the limit parameter to manage pagination of results. Integer. + sort -- Sort items using their properties. String. + parameters -- Full parameters payload dictionary. Not required if using other keywords. + + This method only supports keywords for providing arguments. + + Returns: dict object containing API response. + + HTTP Method: GET + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/api-integrations/GetCombinedPluginConfigs + """ + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="GetCombinedPluginConfigs", + keywords=kwargs, + params=parameters + ) + + @force_default(defaults=["body"], default_types=["dict"]) + def execute_command(self: object, body: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Execute a command. + + Keyword arguments: + body -- Full body payload as a dictionary. Not required if other keywords are provided. + { + "resources": [ + { + "config_auth_type": "string", + "config_id": "string", + "definition_id": "string", + "id": "string", + "operation_id": "string", + "request": { + "description": "string" + }, + "version": integer + } + ] + } + config_auth_type -- Configuration authorization type for plugin to execute. + Only application for security scheme plugins. If not + provided, execution will use the default authorization type. String. + config_id -- Configuration ID. If omitted, the oldest configuration ID will be used. String. + definition_id -- ID of the definition containing the operation to execute. String. + id -- ID of the specific plugin to execute provided in "definition_name.operation_name" + format. String. + operation_id -- The specific operation to execute. String. + description -- Command description. String. + version -- The version of the definition to execute. Integer. + parameters -- Full parameters payload dictionary. Not required if using other keywords. + + This method only supports keywords for providing arguments. + + Returns: dict object containing API response. + + HTTP Method: GET + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/api-integrations/ExecuteCommand + """ + if not body: + body = api_plugin_command_payload(kwargs) + + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="ExecuteCommand", + keywords=kwargs, + body=body + ) + + # These method names align to the operation IDs in the API but + # do not conform to snake_case / PEP8 and are defined here for + # backwards compatibility / ease of use purposes. + GetCombinedPluginConfigs = get_plugin_configs + ExecuteCommand = execute_command diff --git a/tests/test_api_integrations.py b/tests/test_api_integrations.py new file mode 100644 index 000000000..21600b711 --- /dev/null +++ b/tests/test_api_integrations.py @@ -0,0 +1,41 @@ +# test_api_integraitons.py +# This class tests the API integrations service collection + +# import json +import os +import sys + +# Authentication via the test_authorization.py +from tests import test_authorization as Authorization + +# Import our sibling src folder into the path +sys.path.append(os.path.abspath('src')) +# Classes to test - manually imported from sibling folder +from falconpy import APIIntegrations + +auth = Authorization.TestAuthorization() +config = auth.getConfigObject() +falcon = APIIntegrations(auth_object=config) +AllowedResponses = [200, 201, 207, 400, 403, 404, 429] + + +class TestAPIIntegrations: + def test_all_code_paths(self): + error_checks = True + tests = { + "GetCombinedPluginConfigs": falcon.get_plugin_configs(), + "ExecuteCommand": falcon.execute_command(config_auth_type="string", + config_id="string", + definition_id="string", + id="string", + operation_id="string", + description="string", + version=123 + ) + } + for key in tests: + if tests[key]["status_code"] not in AllowedResponses: + error_checks = False + # print(key) + # print(tests[key]) + assert error_checks From bca47f1f7453170fd18c77ab1be146e9d82a71a6 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Tue, 4 Jun 2024 07:00:19 -0400 Subject: [PATCH 13/30] Add ThreatGraph service collection --- src/falconpy/__init__.py | 3 +- src/falconpy/_endpoint/__init__.py | 2 + src/falconpy/_endpoint/_threatgraph.py | 897 +++++++++++++++++++++++++ src/falconpy/_util/_functions.py | 5 +- src/falconpy/_util/_uber.py | 5 +- src/falconpy/threatgraph.py | 545 +++++++++++++++ tests/test_threatgraph.py | 38 ++ tests/test_uber.py | 4 + 8 files changed, 1496 insertions(+), 3 deletions(-) create mode 100644 src/falconpy/_endpoint/_threatgraph.py create mode 100644 src/falconpy/threatgraph.py create mode 100644 tests/test_threatgraph.py diff --git a/src/falconpy/__init__.py b/src/falconpy/__init__.py index e517701a5..1105000af 100644 --- a/src/falconpy/__init__.py +++ b/src/falconpy/__init__.py @@ -152,6 +152,7 @@ from .spotlight_vulnerabilities import SpotlightVulnerabilities from .spotlight_evaluation_logic import SpotlightEvaluationLogic from .tailored_intelligence import TailoredIntelligence +from .threatgraph import ThreatGraph from .unidentified_containers import UnidentifiedContainers from .user_management import UserManagement from .workflows import Workflows @@ -196,7 +197,7 @@ "SDKDeprecationWarning", "ConfigurationAssessmentEvaluationLogic", "ConfigurationAssessment", "ContainerAlerts", "ContainerDetections", "ContainerImages", "ContainerPackages", "ContainerVulnerabilities", "DriftIndicators", "UnidentifiedContainers", - "ImageAssessmentPolicies", "APIIntegrations" + "ImageAssessmentPolicies", "APIIntegrations", "ThreatGraph" ] """ This is free and unencumbered software released into the public domain. diff --git a/src/falconpy/_endpoint/__init__.py b/src/falconpy/_endpoint/__init__.py index bfca9e56c..7481db82f 100644 --- a/src/falconpy/_endpoint/__init__.py +++ b/src/falconpy/_endpoint/__init__.py @@ -102,6 +102,7 @@ from ._spotlight_evaluation_logic import _spotlight_evaluation_logic_endpoints from ._spotlight_vulnerabilities import _spotlight_vulnerabilities_endpoints from ._tailored_intelligence import _tailored_intelligence_endpoints +from ._threatgraph import _threatgraph_endpoints from ._unidentified_containers import _unidentified_containers_endpoints from ._user_management import _user_management_endpoints from ._workflows import _workflows_endpoints @@ -171,6 +172,7 @@ api_endpoints.extend(_spotlight_evaluation_logic_endpoints) api_endpoints.extend(_spotlight_vulnerabilities_endpoints) api_endpoints.extend(_tailored_intelligence_endpoints) +api_endpoints.extend(_threatgraph_endpoints) api_endpoints.extend(_unidentified_containers_endpoints) api_endpoints.extend(_user_management_endpoints) api_endpoints.extend(_workflows_endpoints) diff --git a/src/falconpy/_endpoint/_threatgraph.py b/src/falconpy/_endpoint/_threatgraph.py new file mode 100644 index 000000000..7d9a119fb --- /dev/null +++ b/src/falconpy/_endpoint/_threatgraph.py @@ -0,0 +1,897 @@ +"""Internal API endpoint constant library. + + _______ __ _______ __ __ __ +| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----. +|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__| +|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____| +|: 1 | |: 1 | +|::.. . | CROWDSTRIKE FALCON |::.. . | FalconPy +`-------' `-------' + +OAuth2 API - Customer SDK + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +""" + +_threatgraph_endpoints = [ + [ + "combined_edges_get", + "GET", + "/threatgraph/combined/edges/v1", + "Retrieve edges for a given vertex id. One edge type must be specified", + "threatgraph", + [ + { + "type": "string", + "description": "Vertex ID to get details for. Only one value is supported", + "name": "ids", + "in": "query", + "required": True + }, + { + "maximum": 100, + "type": "integer", + "default": 100, + "description": "How many edges to return in a single request [1-100]", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "description": "The offset to use to retrieve the next page of results", + "name": "offset", + "in": "query" + }, + { + "enum": [ + "accessed_ad_computer", + "accessed_adfs_application", + "accessed_azure_application", + "accessed_by_kerberos_ticket", + "accessed_by_session", + "accessed_okta_application", + "accessed_ping_fed_application", + "accessed_service_account", + "agent_to_self_diagnostic", + "allowed_by_process", + "allowed_firewall_rule", + "app_uninstalled_from_host", + "asep_file_change", + "asep_key_update", + "asep_value_update", + "assigned_ipv4_address", + "assigned_ipv6_address", + "assigned_to_sensor", + "associated_by_ad_computer", + "associated_by_ad_group", + "associated_by_ad_user", + "associated_by_aggregate_indicator", + "associated_by_app", + "associated_by_azure_ad_user", + "associated_by_azure_app", + "associated_by_certificate", + "associated_by_control_graph", + "associated_by_domain", + "associated_by_host", + "associated_by_host_name", + "associated_by_idp_session", + "associated_by_incident", + "associated_by_indicator", + "associated_by_ip", + "associated_by_ip4", + "associated_by_ip6", + "associated_by_okta_user", + "associated_by_service_ticket", + "associated_control_graph", + "associated_firewall_rule", + "associated_idp_indicator", + "associated_incident", + "associated_indicator", + "associated_k8s_cluster", + "associated_k8s_sensor", + "associated_mobile_forensics_report", + "associated_mobile_indicator", + "associated_module", + "associated_primary_module", + "associated_quarantined_file", + "associated_quarantined_module", + "associated_root_process", + "associated_to_ad_computer", + "associated_to_sensor", + "associated_user_session", + "associated_with_process", + "associated_with_sensor", + "attributed_by_process", + "attributed_from_domain", + "attributed_from_module", + "attributed_on", + "attributed_on_domain", + "attributed_on_module", + "attributed_to", + "attributed_to_actor", + "authenticated_from_incident", + "authenticated_host", + "blocked_by_app", + "blocked_by_process", + "blocked_by_sensor", + "blocked_dns", + "blocked_ip4", + "blocked_ip6", + "blocked_module", + "bundled_in_app", + "bundles_module", + "cert_is_presented_by", + "cert_presented", + "child_process", + "closed_ip4_socket", + "closed_ip6_socket", + "command_history", + "command_line_parent_process", + "connected_from_app", + "connected_from_host", + "connected_from_process", + "connected_ip4", + "connected_ip6", + "connected_on_customer", + "connected_on_sensor", + "connected_to_accessory", + "connected_to_wifi_ap", + "connection_killed_by_app", + "connection_killed_by_process", + "containerized_app", + "containerized_by_sensor", + "control_graph", + "created_by_incident", + "created_by_process", + "created_by_user", + "created_quarantined_file", + "created_service", + "critical_file_accessed", + "critical_file_modified", + "customer_agent_has_user", + "customer_has_sensor", + "customer_ioc", + "customer_sensor_to_sensor", + "customer_user_to_sensor_user", + "deleted_by_process", + "deleted_rule", + "denied_by_firewall_rule", + "denied_by_process", + "denied_firewall_rule", + "detected_module", + "detection", + "device", + "disconnect_from_wifi_ap", + "disconnected_from_accessory", + "disconnected_from_host", + "dns", + "dns_request", + "duplicated_by_app", + "duplicates_app", + "elf_file_written", + "established_on_ad_computer", + "established_on_host_name", + "established_on_ip4", + "established_on_ip6", + "established_on_sensor", + "established_session", + "established_user_session", + "executed_app", + "executed_by_process", + "executed_macro_script", + "executed_script", + "extracted_file", + "failed_to_authenticate_ad_user", + "failed_to_authenticate_to_ad_computer", + "failed_to_authenticate_to_adfs_app", + "failed_to_authenticate_to_azure_app", + "failed_to_authenticate_to_okta_app", + "failed_to_authenticate_to_ping_app", + "failed_to_authenticate_to_service_account", + "file_create_info", + "file_open_info", + "fs_post_create", + "fs_post_open", + "generated_by_renewing", + "generated_by_session", + "generated_dce_rpc_epm_request_against_dc", + "generated_dce_rpc_request_against_dc", + "generated_failed_authentication_to_ad_computer", + "generated_failed_authentication_to_adfs_app", + "generated_failed_authentication_to_azure_app", + "generated_failed_authentication_to_okta_app", + "generated_failed_authentication_to_ping_app", + "generated_failed_authentication_to_service_account", + "generated_ldap_search_against_dc", + "generated_service_ticket", + "had_code_injected_by_process", + "has_app_installed", + "has_attributed_process", + "has_attribution", + "has_firmware", + "hunting_lead", + "implicated_by_incident", + "implicated_sensor", + "included_by_hunting_lead", + "includes_process", + "indexed", + "initiated_by_ad_computer", + "initiated_by_azure_ad_user", + "initiated_by_okta_user", + "initiated_by_user", + "initiated_session", + "injected_code_into_process", + "injected_thread", + "injected_thread_from_process", + "installed_app", + "installed_by_app", + "installed_on_host", + "invalid_firewall_rule", + "invalid_from_process", + "invalidated_by_process", + "invalidated_firewall_rule", + "involved_ad_computer", + "involved_service_account", + "ip4_socket_closed_by_app", + "ip4_socket_closed_by_process", + "ip4_socket_opened_by_process", + "ip6_socket_closed_by_app", + "ip6_socket_closed_by_process", + "ip6_socket_opened_by_process", + "ipv4", + "ipv4_close", + "ipv4_listen", + "ipv6", + "ipv6_close", + "ipv6_listen", + "jar_file_written", + "killed_ip4_connection", + "killed_ip6_connection", + "known_by_md5", + "known_by_sha256", + "linking_event", + "loaded_by_process", + "loaded_module", + "macho_file_written", + "macro_executed_by_process", + "member_of_full_command_line", + "module", + "module_written", + "mounted_on_host", + "mounted_to_host", + "network_close_ip4", + "network_close_ip6", + "network_connect_ip4", + "network_connect_ip6", + "network_listen_ip4", + "network_listen_ip6", + "new_executable_written", + "new_script_written", + "opened_ip4_socket", + "opened_ip6_socket", + "parent_of_command_line", + "parent_process", + "parented_by_process", + "participating_process", + "pe_file_written", + "performed_psexec_against_dc", + "presented_by_cloud", + "primary_module", + "primary_module_of_process", + "quarantined_file", + "queried_by_process", + "queried_by_sensor", + "queried_dns", + "queried_on_customer", + "queried_on_sensor", + "received_from_cloud", + "registered_by_incident", + "registered_scheduledtask", + "renewed_to_generate", + "reports_aggregate_indicator", + "resolved_from_domain", + "resolved_to_ip4", + "resolved_to_ip6", + "rooted_control_graph", + "rule_set_by_process", + "script", + "self_diagnostic_to_agent", + "set_by_process", + "set_firewall_rule", + "set_rule", + "shell_io_redirect", + "suspicious_dns_request", + "trigger_process", + "triggered_by_control_graph", + "triggered_by_process", + "triggered_control_graph", + "triggered_detection", + "triggered_indicator", + "triggered_mobile_indicator", + "triggered_xdr", + "triggering_domain", + "triggering_network", + "uncontainerized_app", + "uncontainerized_by_sensor", + "uninstalled_app", + "unmounted_from_host", + "unmounted_on_host", + "user", + "user_session", + "witnessed_by_sensor", + "witnessed_process", + "wmicreated_by_incident", + "wmicreated_process", + "written_by_process", + "wrote_module" + ], + "type": "string", + "description": "The type of edges that you would like to retrieve", + "name": "edge_type", + "in": "query", + "required": True + }, + { + "type": "string", + "description": "The direction of edges that you would like to retrieve.", + "name": "direction", + "in": "query" + }, + { + "enum": [ + "cspm", + "customer", + "cwpp", + "device", + "global" + ], + "type": "string", + "default": "device", + "description": "Scope of the request", + "name": "scope", + "in": "query" + }, + { + "type": "boolean", + "default": False, + "description": "Return nano-precision entity timestamps", + "name": "nano", + "in": "query" + } + ] + ], + [ + "combined_ran_on_get", + "GET", + "/threatgraph/combined/ran-on/v1", + "Look up instances of indicators such as hashes, domain names, and ip addresses that have been seen on " + "devices in your environment.", + "threatgraph", + [ + { + "type": "string", + "description": "The value of the indicator to search by.", + "name": "value", + "in": "query", + "required": True + }, + { + "enum": [ + "domain", + "ipv4", + "ipv6", + "md5", + "sha1", + "sha256" + ], + "type": "string", + "description": "The type of indicator that you would like to retrieve", + "name": "type", + "in": "query", + "required": True + }, + { + "maximum": 100, + "type": "integer", + "default": 100, + "description": "How many edges to return in a single request [1-100]", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "description": "The offset to use to retrieve the next page of results", + "name": "offset", + "in": "query" + }, + { + "type": "boolean", + "default": False, + "description": "Return nano-precision entity timestamps", + "name": "nano", + "in": "query" + } + ] + ], + [ + "combined_summary_get", + "GET", + "/threatgraph/combined/{}/summary/v1", + "Retrieve summary for a given vertex ID", + "threatgraph", + [ + { + "enum": [ + "accessories", + "accessory", + "actor", + "ad-computers", + "ad-groups", + "ad_computer", + "ad_group", + "adfs-applications", + "adfs_application", + "aggregate-indicators", + "aggregate_indicator", + "any-vertex", + "azure-ad-users", + "azure-applications", + "azure_ad_user", + "azure_application", + "certificate", + "certificates", + "command-lines", + "command_line", + "containerized-apps", + "containerized_app", + "control-graphs", + "control_graph", + "customer", + "customers", + "detection", + "detection-indices", + "detection_index", + "detections", + "devices", + "direct", + "directs", + "domain", + "domains", + "extracted-files", + "extracted_file", + "firewall", + "firewall_rule_match", + "firewall_rule_matches", + "firewalls", + "firmware", + "firmwares", + "host-names", + "host_name", + "hunting-leads", + "hunting_lead", + "idp-indicators", + "idp-sessions", + "idp_indicator", + "idp_session", + "incident", + "incidents", + "indicator", + "indicators", + "ipv4", + "ipv6", + "k8s_cluster", + "k8s_clusters", + "kerberos-tickets", + "kerberos_ticket", + "legacy-detections", + "legacy_detection", + "macro_script", + "macro_scripts", + "mobile-apps", + "mobile-fs-volumes", + "mobile-indicators", + "mobile_app", + "mobile_fs_volume", + "mobile_indicator", + "mobile_os_forensics_report", + "mobile_os_forensics_reports", + "module", + "modules", + "okta-applications", + "okta-users", + "okta_application", + "okta_user", + "ping-fed-applications", + "ping_fed_application", + "process", + "processes", + "quarantined-files", + "quarantined_file", + "script", + "scripts", + "sensor", + "sensor-self-diagnostics", + "sensor_self_diagnostic", + "tag", + "tags", + "user-sessions", + "user_id", + "user_session", + "users", + "wifi-access-points", + "wifi_access_point", + "xdr" + ], + "type": "string", + "description": "Type of vertex to get properties for", + "name": "vertex-type", + "in": "path", + "required": True + }, + { + "maxItems": 100, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "description": "Vertex ID to get details for", + "name": "ids", + "in": "query", + "required": True + }, + { + "enum": [ + "cspm", + "customer", + "cwpp", + "device", + "global" + ], + "type": "string", + "default": "device", + "description": "Scope of the request", + "name": "scope", + "in": "query" + }, + { + "type": "boolean", + "default": False, + "description": "Return nano-precision entity timestamps", + "name": "nano", + "in": "query" + } + ] + ], + [ + "entities_vertices_get", + "GET", + "/threatgraph/entities/{}/v1", + "Retrieve metadata for a given vertex ID", + "threatgraph", + [ + { + "enum": [ + "accessories", + "accessory", + "actor", + "ad-computers", + "ad-groups", + "ad_computer", + "ad_group", + "adfs-applications", + "adfs_application", + "aggregate-indicators", + "aggregate_indicator", + "any-vertex", + "azure-ad-users", + "azure-applications", + "azure_ad_user", + "azure_application", + "certificate", + "certificates", + "command-lines", + "command_line", + "containerized-apps", + "containerized_app", + "control-graphs", + "control_graph", + "customer", + "customers", + "detection", + "detection-indices", + "detection_index", + "detections", + "devices", + "direct", + "directs", + "domain", + "domains", + "extracted-files", + "extracted_file", + "firewall", + "firewall_rule_match", + "firewall_rule_matches", + "firewalls", + "firmware", + "firmwares", + "host-names", + "host_name", + "hunting-leads", + "hunting_lead", + "idp-indicators", + "idp-sessions", + "idp_indicator", + "idp_session", + "incident", + "incidents", + "indicator", + "indicators", + "ipv4", + "ipv6", + "k8s_cluster", + "k8s_clusters", + "kerberos-tickets", + "kerberos_ticket", + "legacy-detections", + "legacy_detection", + "macro_script", + "macro_scripts", + "mobile-apps", + "mobile-fs-volumes", + "mobile-indicators", + "mobile_app", + "mobile_fs_volume", + "mobile_indicator", + "mobile_os_forensics_report", + "mobile_os_forensics_reports", + "module", + "modules", + "okta-applications", + "okta-users", + "okta_application", + "okta_user", + "ping-fed-applications", + "ping_fed_application", + "process", + "processes", + "quarantined-files", + "quarantined_file", + "script", + "scripts", + "sensor", + "sensor-self-diagnostics", + "sensor_self_diagnostic", + "tag", + "tags", + "user-sessions", + "user_id", + "user_session", + "users", + "wifi-access-points", + "wifi_access_point", + "xdr" + ], + "type": "string", + "description": "Type of vertex to get properties for", + "name": "vertex-type", + "in": "path", + "required": True + }, + { + "maxItems": 100, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "description": "Vertex ID to get details for", + "name": "ids", + "in": "query", + "required": True + }, + { + "enum": [ + "cspm", + "customer", + "cwpp", + "device", + "global" + ], + "type": "string", + "default": "device", + "description": "Scope of the request", + "name": "scope", + "in": "query" + }, + { + "type": "boolean", + "default": False, + "description": "Return nano-precision entity timestamps", + "name": "nano", + "in": "query" + } + ] + ], + [ + "entities_vertices_getv2", + "GET", + "/threatgraph/entities/{}/v2", + "Retrieve metadata for a given vertex ID", + "threatgraph", + [ + { + "enum": [ + "accessories", + "accessory", + "actor", + "ad-computers", + "ad-groups", + "ad_computer", + "ad_group", + "adfs-applications", + "adfs_application", + "aggregate-indicators", + "aggregate_indicator", + "any-vertex", + "azure-ad-users", + "azure-applications", + "azure_ad_user", + "azure_application", + "certificate", + "certificates", + "command-lines", + "command_line", + "containerized-apps", + "containerized_app", + "control-graphs", + "control_graph", + "customer", + "customers", + "detection", + "detection-indices", + "detection_index", + "detections", + "devices", + "direct", + "directs", + "domain", + "domains", + "extracted-files", + "extracted_file", + "firewall", + "firewall_rule_match", + "firewall_rule_matches", + "firewalls", + "firmware", + "firmwares", + "host-names", + "host_name", + "hunting-leads", + "hunting_lead", + "idp-indicators", + "idp-sessions", + "idp_indicator", + "idp_session", + "incident", + "incidents", + "indicator", + "indicators", + "ipv4", + "ipv6", + "k8s_cluster", + "k8s_clusters", + "kerberos-tickets", + "kerberos_ticket", + "legacy-detections", + "legacy_detection", + "macro_script", + "macro_scripts", + "mobile-apps", + "mobile-fs-volumes", + "mobile-indicators", + "mobile_app", + "mobile_fs_volume", + "mobile_indicator", + "mobile_os_forensics_report", + "mobile_os_forensics_reports", + "module", + "modules", + "okta-applications", + "okta-users", + "okta_application", + "okta_user", + "ping-fed-applications", + "ping_fed_application", + "process", + "processes", + "quarantined-files", + "quarantined_file", + "script", + "scripts", + "sensor", + "sensor-self-diagnostics", + "sensor_self_diagnostic", + "tag", + "tags", + "user-sessions", + "user_id", + "user_session", + "users", + "wifi-access-points", + "wifi_access_point", + "xdr" + ], + "type": "string", + "description": "Type of vertex to get properties for", + "name": "vertex-type", + "in": "path", + "required": True + }, + { + "maxItems": 100, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "description": "Vertex ID to get details for", + "name": "ids", + "in": "query", + "required": True + }, + { + "enum": [ + "cspm", + "customer", + "cwpp", + "device", + "global" + ], + "type": "string", + "default": "device", + "description": "Scope of the request", + "name": "scope", + "in": "query" + }, + { + "type": "boolean", + "default": False, + "description": "Return nano-precision entity timestamps", + "name": "nano", + "in": "query" + } + ] + ], + [ + "queries_edgetypes_get", + "GET", + "/threatgraph/queries/edge-types/v1", + "Show all available edge types", + "threatgraph", + [] + ] +] diff --git a/src/falconpy/_util/_functions.py b/src/falconpy/_util/_functions.py index 2289f9ed8..783b3df85 100644 --- a/src/falconpy/_util/_functions.py +++ b/src/falconpy/_util/_functions.py @@ -246,7 +246,7 @@ def service_request(caller: ServiceClass = None, **kwargs) -> Union[Dict[str, Un # do_pythonic: Optional[bool] = caller.pythonic except AttributeError: kwargs["pythonic"] = None - + # pylint: disable=E0606 return perform_request(proxy=proxy, timeout=timeout, user_agent=user_agent, @@ -719,6 +719,9 @@ def handle_path_variables(passed: dict, route_url: str): if passed_object_key: collect_args["object_key"] = str(passed_object_key) route_url = route_url.format(**collect_args) + passed_vertex_type = passed.get("vertex_type", None) + if passed_vertex_type: + route_url = route_url.format(str(passed_vertex_type)) return route_url diff --git a/src/falconpy/_util/_uber.py b/src/falconpy/_util/_uber.py index 0444657e2..bb8aa85e9 100644 --- a/src/falconpy/_util/_uber.py +++ b/src/falconpy/_util/_uber.py @@ -89,7 +89,10 @@ def scrub_target(oper: str, scrubbed: str, kwas: dict) -> str: "GetObject": ["collection_name", "object_key"], "PutObject": ["collection_name", "object_key"], "DeleteObject": ["collection_name", "object_key"], - "GetObjectMetadata": ["collection_name", "object_key"] + "GetObjectMetadata": ["collection_name", "object_key"], + "combined_summary_get": ["vertex_type"], + "entities_vertices_get": ["vertex_type"], + "entities_vertices_getv2": ["vertex_type"] } for field_value, field_names in field_mapping.items(): if oper == field_value: # Only perform replacements on mapped operation IDs. diff --git a/src/falconpy/threatgraph.py b/src/falconpy/threatgraph.py new file mode 100644 index 000000000..5f60f27ed --- /dev/null +++ b/src/falconpy/threatgraph.py @@ -0,0 +1,545 @@ +"""CrowdStrike Falcon Threatgraph API interface class. + + _______ __ _______ __ __ __ +| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----. +|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__| +|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____| +|: 1 | |: 1 | +|::.. . | CROWDSTRIKE FALCON |::.. . | FalconPy +`-------' `-------' + +OAuth2 API - Customer SDK + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +""" +from typing import Dict, Union +from ._util import force_default, process_service_request +from ._service_class import ServiceClass +from ._endpoint._threatgraph import _threatgraph_endpoints as Endpoints + + +class ThreatGraph(ServiceClass): + """The only requirement to instantiate an instance of this class is one of the following. + + - a valid client_id and client_secret provided as keywords. + - a credential dictionary with client_id and client_secret containing valid API credentials + { + "client_id": "CLIENT_ID_HERE", + "client_secret": "CLIENT_SECRET_HERE" + } + - a previously-authenticated instance of the authentication service class (oauth2.py) + - a valid token provided by the authentication service class (oauth2.py) + """ + + @force_default(defaults=["parameters"], default_types=["dict"]) + def get_edges(self: object, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Retrieve edges for a given vertex id. One edge type must be specified. + + Keyword arguments: + direction -- The direction of edges that you would like to retrieve. + edge_type -- The type of edges that you would like to retrieve. String. + Available values: + accessed_ad_computer failed_to_authenticate_to_azure_app + accessed_adfs_application failed_to_authenticate_to_okta_app + accessed_azure_application failed_to_authenticate_to_ping_app + accessed_by_kerberos_ticket failed_to_authenticate_to_service_account + accessed_by_session file_create_info + accessed_okta_application file_open_info + accessed_ping_fed_application fs_post_create + accessed_service_account fs_post_open + agent_to_self_diagnostic generated_by_renewing + allowed_by_process generated_by_session + allowed_firewall_rule generated_dce_rpc_epm_request_against_dc + app_uninstalled_from_host generated_dce_rpc_request_against_dc + asep_file_change generated_failed_authentication_to_ad_computer + asep_key_update generated_failed_authentication_to_adfs_app + asep_value_update generated_failed_authentication_to_azure_app + assigned_ipv4_address generated_failed_authentication_to_okta_app + assigned_ipv6_address generated_failed_authentication_to_ping_app + assigned_to_sensor generated_failed_authentication_to_service_account + associated_by_ad_computer generated_ldap_search_against_dc + associated_by_ad_group generated_service_ticket + associated_by_ad_user had_code_injected_by_process + associated_by_aggregate_indicator has_app_installed + associated_by_app has_attributed_process + associated_by_azure_ad_user has_attribution + associated_by_azure_app has_firmware + associated_by_certificate hunting_lead + associated_by_control_graph implicated_by_incident + associated_by_domain implicated_sensor + associated_by_host included_by_hunting_lead + associated_by_host_name includes_process + associated_by_idp_session indexed + associated_by_incident initiated_by_ad_computer + associated_by_indicator initiated_by_azure_ad_user + associated_by_ip initiated_by_okta_user + associated_by_ip4 initiated_by_user + associated_by_ip6 initiated_session + associated_by_okta_user injected_code_into_process + associated_by_service_ticket injected_thread + associated_control_graph injected_thread_from_process + associated_firewall_rule installed_app + associated_idp_indicator installed_by_app + associated_incident installed_on_host + associated_indicator invalid_firewall_rule + associated_k8s_cluster invalid_from_process + associated_k8s_sensor invalidated_by_process + associated_mobile_forensics_report invalidated_firewall_rule + associated_mobile_indicator involved_ad_computer + associated_module involved_service_account + associated_primary_module ip4_socket_closed_by_app + associated_quarantined_file ip4_socket_closed_by_process + associated_quarantined_module ip4_socket_opened_by_process + associated_root_process ip6_socket_closed_by_app + associated_to_ad_computer ip6_socket_closed_by_process + associated_to_sensor ip6_socket_opened_by_process + associated_user_session ipv4 + associated_with_process ipv4_close + associated_with_sensor ipv4_listen + attributed_by_process ipv6 + attributed_from_domain ipv6_close + attributed_from_module ipv6_listen + attributed_on jar_file_written + attributed_on_domain killed_ip4_connection + attributed_on_module killed_ip6_connection + attributed_to known_by_md5 + attributed_to_actor known_by_sha256 + authenticated_from_incident linking_event + authenticated_host loaded_by_process + blocked_by_app loaded_module + blocked_by_process macho_file_written + blocked_by_sensor macro_executed_by_process + blocked_dns member_of_full_command_line + blocked_ip4 module + blocked_ip6 module_written + blocked_module mounted_on_host + bundled_in_app mounted_to_host + bundles_module network_close_ip4 + cert_is_presented_by network_close_ip6 + cert_presented network_connect_ip4 + child_process network_connect_ip6 + closed_ip4_socket network_listen_ip4 + closed_ip6_socket network_listen_ip6 + command_history new_executable_written + command_line_parent_process new_script_written + connected_from_app opened_ip4_socket + connected_from_host opened_ip6_socket + connected_from_process parent_of_command_line + connected_ip4 parent_process + connected_ip6 parented_by_process + connected_on_customer participating_process + connected_on_sensor pe_file_written + connected_to_accessory performed_psexec_against_dc + connected_to_wifi_ap presented_by_cloud + connection_killed_by_app primary_module + connection_killed_by_process primary_module_of_process + containerized_app quarantined_file + containerized_by_sensor queried_by_process + control_graph queried_by_sensor + created_by_incident queried_dns + created_by_process queried_on_customer + created_by_user queried_on_sensor + created_quarantined_file received_from_cloud + created_service registered_by_incident + critical_file_accessed registered_scheduledtask + critical_file_modified renewed_to_generate + customer_agent_has_user reports_aggregate_indicator + customer_has_sensor resolved_from_domain + customer_ioc resolved_to_ip4 + customer_sensor_to_sensor resolved_to_ip6 + customer_user_to_sensor_user rooted_control_graph + deleted_by_process rule_set_by_process + deleted_rule script + denied_by_firewall_rule self_diagnostic_to_agent + denied_by_process set_by_process + denied_firewall_rule set_firewall_rule + detected_module set_rule + detection shell_io_redirect + device suspicious_dns_request + disconnect_from_wifi_ap trigger_process + disconnected_from_accessory triggered_by_control_graph + disconnected_from_host triggered_by_process + dns triggered_control_graph + dns_request triggered_detection + duplicated_by_app triggered_indicator + duplicates_app triggered_mobile_indicator + elf_file_written triggered_xdr + established_on_ad_computer triggering_domain + established_on_host_name triggering_network + established_on_ip4 uncontainerized_app + established_on_ip6 uncontainerized_by_sensor + established_on_sensor uninstalled_app + established_session unmounted_from_host + established_user_session unmounted_on_host + executed_app user + executed_by_process user_session + executed_macro_script witnessed_by_sensor + executed_script witnessed_process + extracted_file wmicreated_by_incident + failed_to_authenticate_ad_user wmicreated_process + failed_to_authenticate_to_ad_computer written_by_process + failed_to_authenticate_to_adfs_app wrote_module + ids -- Vertex ID to get details for. Only one value is supported. String. + limit -- How many edges to return in a single request [1-100]. Integer. + nano -- Return nano-precision entity timestamps. Boolean. + offset -- The offset to use to retrieve the next page of results. Integer. + parameters -- Full parameters payload dictionary. Not required if using other keywords. + scope -- Scope of the request. String. + Available values: cspm, customer, cwpp, device, global + + This method only supports keywords for providing arguments. + + Returns: dict object containing API response. + + HTTP Method: GET + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/threatgraph/combined_edges_get + """ + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="combined_edges_get", + keywords=kwargs, + params=parameters + ) + + @force_default(defaults=["parameters"], default_types=["dict"]) + def get_ran_on(self: object, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Look up instances of indicators. + + (Such as hashes, domain names, and ip addresses that have been seen on devices in your environment.) + + Keyword arguments: + limit -- How many edges to return in a single request [1-100]. Integer. + nano -- Return nano-precision entity timestamps. Boolean. + offset -- The offset to use to retrieve the next page of results. Integer. + parameters -- Full parameters payload dictionary. Not required if using other keywords. + type -- The type of indicator that you would like to retrieve. String. + Available values: domain, ipv4, ipv6, md5, sha1, sha256 + value -- The value of the indicator to search by. String. + + This method only supports keywords for providing arguments. + + Returns: dict object containing API response. + + HTTP Method: GET + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/threatgraph/combined_ran_on_get + """ + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="combined_ran_on_get", + keywords=kwargs, + params=parameters + ) + + @force_default(defaults=["parameters"], default_types=["dict"]) + def get_summary(self: object, + parameters: dict = None, + vertex_type: str = "any-vertext", + **kwargs + ) -> Dict[str, Union[int, dict]]: + """Retrieve summary for a given vertex ID. + + Keyword arguments: + ids -- Vertex ID to get details for. String or list of strings. + scope -- Scope of the request. String. + Available values: cspm, customer, cwpp, device, global + nano -- Return nano-precision entity timestamps. Boolean. + parameters -- Full parameters payload dictionary. Not required if using other keywords. + vertex_type -- Type of vertex to get properties for. String. + Allowed values: + accessories idp_session + accessory incident + actor incidents + ad-computers indicator + ad-groups indicators + ad_computer ipv4 + ad_group ipv6 + adfs-applications k8s_cluster + adfs_application k8s_clusters + aggregate-indicators kerberos-tickets + aggregate_indicator kerberos_ticket + any-vertex legacy-detections + azure-ad-users legacy_detection + azure-applications macro_script + azure_ad_user macro_scripts + azure_application mobile-apps + certificate mobile-fs-volumes + certificates mobile-indicators + command-lines mobile_app + command_line mobile_fs_volume + containerized-apps mobile_indicator + containerized_app mobile_os_forensics_report + control-graphs mobile_os_forensics_reports + control_graph module + customer modules + customers okta-applications + detection okta-users + detection-indices okta_application + detection_index okta_user + detections ping-fed-applications + devices ping_fed_application + direct process + directs processes + domain quarantined-files + domains quarantined_file + extracted-files script + extracted_file scripts + firewall sensor + firewall_rule_match sensor-self-diagnostics + firewall_rule_matches sensor_self_diagnostic + firewalls tag + firmware tags + firmwares user-sessions + host-names user_id + host_name user_session + hunting-leads users + hunting_lead wifi-access-points + idp-indicators wifi_access_point + idp-sessions xdr + idp_indicator + + This method only supports keywords for providing arguments. + + Returns: dict object containing API response. + + HTTP Method: GET + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/threatgraph/combined_summary_get + """ + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="combined_summary_get", + keywords=kwargs, + params=parameters, + vertex_type=vertex_type + ) + + @force_default(defaults=["parameters"], default_types=["dict"]) + def get_vertices_v1(self: object, + parameters: dict = None, + vertex_type: str = "any-vertex", + **kwargs + ) -> Dict[str, Union[int, dict]]: + """Retrieve metadata for a given vertex ID. + + Keyword arguments: + ids -- Vertex ID to get details for. String or list of strings. + scope -- Scope of the request. String. + Available values: cspm, customer, cwpp, device, global + nano -- Return nano-precision entity timestamps. Boolean. + parameters -- Full parameters payload dictionary. Not required if using other keywords. + vertex_type -- Type of vertex to get properties for. String. + Allowed values: + accessories idp_session + accessory incident + actor incidents + ad-computers indicator + ad-groups indicators + ad_computer ipv4 + ad_group ipv6 + adfs-applications k8s_cluster + adfs_application k8s_clusters + aggregate-indicators kerberos-tickets + aggregate_indicator kerberos_ticket + any-vertex legacy-detections + azure-ad-users legacy_detection + azure-applications macro_script + azure_ad_user macro_scripts + azure_application mobile-apps + certificate mobile-fs-volumes + certificates mobile-indicators + command-lines mobile_app + command_line mobile_fs_volume + containerized-apps mobile_indicator + containerized_app mobile_os_forensics_report + control-graphs mobile_os_forensics_reports + control_graph module + customer modules + customers okta-applications + detection okta-users + detection-indices okta_application + detection_index okta_user + detections ping-fed-applications + devices ping_fed_application + direct process + directs processes + domain quarantined-files + domains quarantined_file + extracted-files script + extracted_file scripts + firewall sensor + firewall_rule_match sensor-self-diagnostics + firewall_rule_matches sensor_self_diagnostic + firewalls tag + firmware tags + firmwares user-sessions + host-names user_id + host_name user_session + hunting-leads users + hunting_lead wifi-access-points + idp-indicators wifi_access_point + idp-sessions xdr + idp_indicator + + + This method only supports keywords for providing arguments. + + Returns: dict object containing API response. + + HTTP Method: GET + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/threatgraph/entities_vertices_get + """ + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="entities_vertices_get", + keywords=kwargs, + params=parameters, + vertex_type=vertex_type + ) + + @force_default(defaults=["parameters"], default_types=["dict"]) + def get_vertices(self: object, + parameters: dict = None, + vertex_type: str = "any-vertex", + **kwargs + ) -> Dict[str, Union[int, dict]]: + """Retrieve metadata for a given vertex ID. + + Keyword arguments: + ids -- Vertex ID to get details for. String or list of strings. + scope -- Scope of the request. String. + Available values: cspm, customer, cwpp, device, global + nano -- Return nano-precision entity timestamps. Boolean. + parameters -- Full parameters payload dictionary. Not required if using other keywords. + vertex_type -- Type of vertex to get properties for. String. + Allowed values: + accessories idp_session + accessory incident + actor incidents + ad-computers indicator + ad-groups indicators + ad_computer ipv4 + ad_group ipv6 + adfs-applications k8s_cluster + adfs_application k8s_clusters + aggregate-indicators kerberos-tickets + aggregate_indicator kerberos_ticket + any-vertex legacy-detections + azure-ad-users legacy_detection + azure-applications macro_script + azure_ad_user macro_scripts + azure_application mobile-apps + certificate mobile-fs-volumes + certificates mobile-indicators + command-lines mobile_app + command_line mobile_fs_volume + containerized-apps mobile_indicator + containerized_app mobile_os_forensics_report + control-graphs mobile_os_forensics_reports + control_graph module + customer modules + customers okta-applications + detection okta-users + detection-indices okta_application + detection_index okta_user + detections ping-fed-applications + devices ping_fed_application + direct process + directs processes + domain quarantined-files + domains quarantined_file + extracted-files script + extracted_file scripts + firewall sensor + firewall_rule_match sensor-self-diagnostics + firewall_rule_matches sensor_self_diagnostic + firewalls tag + firmware tags + firmwares user-sessions + host-names user_id + host_name user_session + hunting-leads users + hunting_lead wifi-access-points + idp-indicators wifi_access_point + idp-sessions xdr + idp_indicator + + This method only supports keywords for providing arguments. + + Returns: dict object containing API response. + + HTTP Method: GET + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/threatgraph/entities_vertices_getv2 + """ + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="entities_vertices_getv2", + keywords=kwargs, + params=parameters, + vertex_type=vertex_type + ) + + def get_edge_types(self: object) -> Dict[str, Union[int, dict]]: + """Show all available edge types. + + This method does not accept arguments. + + Returns: dict object containing API response. + + HTTP Method: GET + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/threatgraph/queries_edgetypes_get + """ + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="queries_edgetypes_get" + ) + + # These method names align to the operation IDs in the API but + # do not conform to snake_case / PEP8 and are defined here for + # backwards compatibility / ease of use purposes. + combined_edges_get = get_edges + combined_ran_on_get = get_ran_on + combined_summary_get = get_summary + entities_vertices_get = get_vertices_v1 + entities_vertices_getv2 = get_vertices + get_vertices_v2 = get_vertices + queries_edgetypes_get = get_edge_types diff --git a/tests/test_threatgraph.py b/tests/test_threatgraph.py new file mode 100644 index 000000000..254f76df9 --- /dev/null +++ b/tests/test_threatgraph.py @@ -0,0 +1,38 @@ +# test_threatgraph.py +# This class tests the ThreatGraph service collection + +# import json +import os +import sys + +# Authentication via the test_authorization.py +from tests import test_authorization as Authorization + +# Import our sibling src folder into the path +sys.path.append(os.path.abspath('src')) +# Classes to test - manually imported from sibling folder +from falconpy import ThreatGraph + +auth = Authorization.TestAuthorization() +config = auth.getConfigObject() +falcon = ThreatGraph(auth_object=config) +AllowedResponses = [200, 201, 207, 400, 401, 403, 404, 429] + + +class TestThreatGraph: + def test_all_code_paths(self): + error_checks = True + tests = { + "combined_edges_get": falcon.get_edges(), + "combined_ran_on_get": falcon.get_ran_on(), + "combined_summary_get": falcon.get_summary(ids="1234567"), + "entities_vertices_get": falcon.get_vertices_v1(ids="whatever123"), + "entities_vertices_getv2": falcon.get_vertices(ids="someotherID", vertex_type="incident"), + "queries_edgetypes_get": falcon.get_edge_types() + } + for key in tests: + if tests[key]["status_code"] not in AllowedResponses: + error_checks = False + # print(key) + # print(tests[key]) + assert error_checks diff --git a/tests/test_uber.py b/tests/test_uber.py index fc2d656fe..4487cd673 100644 --- a/tests/test_uber.py +++ b/tests/test_uber.py @@ -356,6 +356,10 @@ def test_uber_deprecated_methods(self): and falcon.token ) + @pytest.mark.skipif("laggar" in falcon.base_url, reason="US-GOV-1 testing disabled") + def test_vertex_type_path_variable(self): + assert(falcon.command("entities_vertices_getv2", vertex_type="incident", ids="whatever")["status_code"]==400) + def test_uber_deprecated_attributes(self): _success = False falcon.token_renew_window = 180 From 106b713d04c4ab843ea1e09052bce8f399aabb00 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Tue, 4 Jun 2024 07:26:29 -0400 Subject: [PATCH 14/30] Resolve ValueError on invalid input --- src/falconpy/_version.py | 42 ++++++++++++++++++++++++---------------- tests/test_timeout.py | 13 ++++++++++++- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/falconpy/_version.py b/src/falconpy/_version.py index 76be60c27..802144761 100644 --- a/src/falconpy/_version.py +++ b/src/falconpy/_version.py @@ -69,24 +69,32 @@ def version(compare: str = None, agent_string: bool = None): returned = _VERSION if agent_string: returned = f"{_TITLE}/{str(_VERSION)}" - - if compare: - returned = False + else: ver = _VERSION.split(".") - chk = compare.split(".") - chk_minor = 0 - chk_patch = 0 - if chk: - chk_major = chk[0] - if len(chk) > 1: - chk_minor = chk[1] - if len(chk) > 2: - chk_patch = int(chk[2]) major_minor = float(f"{ver[0]}.{ver[1]}") - chk_major_minor = float(f"{chk_major}.{chk_minor}") - if major_minor > chk_major_minor: - returned = True - elif major_minor == chk_major_minor and int(ver[2]) >= chk_patch: - returned = True + if isinstance(compare, str): + compare = compare.strip() + elif isinstance(compare, (int, float)): + compare = str(compare) + if compare: + returned = False + chk = compare.split(".") + chk_major = 0 + chk_minor = 0 + chk_patch = 0 + if chk: + chk_major = chk[0] + if len(chk) > 1: + chk_minor = chk[1] + if len(chk) > 2: + chk_patch = int(chk[2]) + try: + chk_major_minor = float(f"{chk_major}.{chk_minor}") + except ValueError as bad_value: + raise ValueError("Invalid version comparison value specified") from bad_value + if major_minor > chk_major_minor: + returned = True + elif major_minor == chk_major_minor and int(ver[2]) >= chk_patch: + returned = True return returned diff --git a/tests/test_timeout.py b/tests/test_timeout.py index 6ca395bbc..d1fa11a4f 100644 --- a/tests/test_timeout.py +++ b/tests/test_timeout.py @@ -9,7 +9,7 @@ sys.path.append(os.path.abspath('src')) # Classes to test - manually imported from sibling folder from falconpy import CloudConnectAWS -from falconpy import OAuth2 +from falconpy import OAuth2, version auth = Authorization.TestAuthorization() config = auth.getConfigObject() @@ -122,3 +122,14 @@ def test_ReadTimeout(self): def test_LegacyTimeout(self): assert self.timeout_legacy_auth() is True + + def test_version_compare(self): + assert version(compare=1.4) + + def test_bad_version_compare(self): + _success = False + try: + version(compare="$") + except ValueError: + _success = True + assert _success From 741d73f58cabc1c6643a02568d99d9e641c33d92 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Tue, 4 Jun 2024 07:27:13 -0400 Subject: [PATCH 15/30] Update .pylintrc --- .pylintrc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pylintrc b/.pylintrc index a8bf583e7..b7adee2c6 100644 --- a/.pylintrc +++ b/.pylintrc @@ -387,8 +387,8 @@ preferred-modules= [EXCEPTIONS] # Exceptions that will emit a warning when caught. -overgeneral-exceptions=BaseException, - Exception +overgeneral-exceptions=builtins.BaseException, + builtins.Exception [REFACTORING] From 4ee584eaf666183ca341a304422c5f91b5e4c252 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Tue, 4 Jun 2024 08:27:13 -0400 Subject: [PATCH 16/30] Add WorkflowActivitiesCombined and WorkflowTriggersCombined operations --- src/falconpy/_endpoint/_workflows.py | 53 ++++++++++++++++++++++++++ src/falconpy/workflows.py | 56 ++++++++++++++++++++++++++++ tests/test_workflows.py | 2 + 3 files changed, 111 insertions(+) diff --git a/src/falconpy/_endpoint/_workflows.py b/src/falconpy/_endpoint/_workflows.py index f164184e7..a6ff003c4 100644 --- a/src/falconpy/_endpoint/_workflows.py +++ b/src/falconpy/_endpoint/_workflows.py @@ -37,6 +37,42 @@ """ _workflows_endpoints = [ + [ + "WorkflowActivitiesCombined", + "GET", + "/workflows/combined/activities/v1", + "Search for activities by name. Returns all supported activities if no filter specified", + "workflows", + [ + { + "type": "string", + "description": "FQL query specifying filter parameters.", + "name": "filter", + "in": "query", + "allowEmptyValue": True + }, + { + "type": "string", + "description": "Starting pagination offset of records to return.", + "name": "offset", + "in": "query" + }, + { + "type": "integer", + "description": "Maximum number of records to return.", + "name": "limit", + "in": "query" + }, + { + "pattern": "^\\w+(\\.asc|\\.desc)?(,\\w+(\\.asc|\\.desc)?)*$", + "type": "string", + "description": "Sort items by providing a comma separated list of property and direction (eg " + "name.desc,time.asc). If direction is omitted, defaults to descending.", + "name": "sort", + "in": "query" + } + ] + ], [ "WorkflowDefinitionsCombined", "GET", @@ -109,6 +145,23 @@ } ] ], + [ + "WorkflowTriggersCombined", + "GET", + "/workflows/combined/triggers/v1", + "Search for triggers by namespaced identifier, i.e. FalconAudit, Detection, or " + "FalconAudit/Detection/Status. Returns all triggers if no filter specified", + "workflows", + [ + { + "type": "string", + "description": "FQL query specifying filter parameters.", + "name": "filter", + "in": "query", + "allowEmptyValue": True + } + ] + ], [ "WorkflowDefinitionsExport", "GET", diff --git a/src/falconpy/workflows.py b/src/falconpy/workflows.py index e44b4184a..1b8db69a8 100644 --- a/src/falconpy/workflows.py +++ b/src/falconpy/workflows.py @@ -68,6 +68,35 @@ class Workflows(ServiceClass): - a valid token provided by the authentication service class (oauth2.py) """ + @force_default(defaults=["parameters"], default_types=["dict"]) + def search_activities(self: object, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Search workflow activities based on the provided filter. + + Keyword arguments: + filter -- FQL query specifying filter parameters. String. + offset -- Starting pagination offset of records to return. String. + limit -- Maximum number of records to return. Integer. + sort -- FQL formatted sort (ex: name.desc,time.asc). String. + If direction is omitted, defaults to descending. + parameters -- Full parameters payload dictionary. Not required if using other keywords. + + This method only supports keywords for providing arguments. + + Returns: dict object containing API response. + + HTTP Method: GET + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/workflows/WorkflowActivitiesCombined + """ + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="WorkflowActivitiesCombined", + keywords=kwargs, + params=parameters + ) + @force_default(defaults=["parameters"], default_types=["dict"]) def search_definitions(self: object, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: """Search workflow definitions based on the provided filter. @@ -126,6 +155,31 @@ def search_executions(self: object, parameters: dict = None, **kwargs) -> Dict[s params=parameters ) + @force_default(defaults=["parameters"], default_types=["dict"]) + def search_triggers(self: object, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Search workflow triggers based on the provided filter. + + Keyword arguments: + filter -- FQL query specifying filter parameters. String. + parameters -- Full parameters payload dictionary. Not required if using other keywords. + + This method only supports keywords for providing arguments. + + Returns: dict object containing API response. + + HTTP Method: GET + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/workflows/WorkflowTriggersCombined + """ + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="WorkflowTriggersCombined", + keywords=kwargs, + params=parameters + ) + @force_default(defaults=["parameters"], default_types=["dict"]) def export_definition(self: object, *args, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: """Export a workflow definition for the given definition ID. @@ -709,8 +763,10 @@ def provision(self: object, body: dict = None, **kwargs) -> Dict[str, Union[int, # These method names align to the operation IDs in the API but # do not conform to snake_case / PEP8 and are defined here for # backwards compatibility / ease of use purposes + WorkflowActivitiesCombined = search_activities WorkflowDefinitionsCombined = search_definitions WorkflowExecutionsCombined = search_executions + WorkflowTriggersCombined = search_triggers WorkflowDefinitionsExport = export_definition WorkflowDefinitionsImport = import_definition WorkflowDefinitionsUpdate = update_definition diff --git a/tests/test_workflows.py b/tests/test_workflows.py index 70ca5a702..158b65fc1 100644 --- a/tests/test_workflows.py +++ b/tests/test_workflows.py @@ -31,6 +31,8 @@ def run_all_tests(self): "WorkflowSystemDefinitionsPromote" : falcon.promote(customer_definition_id="12345", activities={}), "WorkflowSystemDefinitionsProvision" : falcon.provision(name="FalconPyTesting", configuration=[{}]), "WorkflowDefinitionsCombined": falcon.search_definitions(), + "WorkflowActivitesCombined": falcon.search_activities(), + "WorkflowTriggersCombined": falcon.search_triggers(), "WorkflowExecutionsCombined": falcon.search_executions(), "WorkflowDefinitionsExport": falcon.export_definition(), "WorkflowDefinitionsImport": falcon.import_definition(validate_only=True, data_file="this_will_415"), From 069e06d84c98b5c6c978193f47a1a740605a9472 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Tue, 4 Jun 2024 08:46:05 -0400 Subject: [PATCH 17/30] Add RequestDeviceEnrollmentV4 operation and generic payload handler --- src/falconpy/_endpoint/_mobile_enrollment.py | 31 +++++++++ src/falconpy/_payload/__init__.py | 3 +- src/falconpy/_payload/_mobile_enrollment.py | 63 +++++++++++++++++ src/falconpy/mobile_enrollment.py | 72 +++++++++++++++++--- tests/test_mobile_enrollment.py | 11 ++- 5 files changed, 167 insertions(+), 13 deletions(-) create mode 100644 src/falconpy/_payload/_mobile_enrollment.py diff --git a/src/falconpy/_endpoint/_mobile_enrollment.py b/src/falconpy/_endpoint/_mobile_enrollment.py index 52a617a08..573cafc9b 100644 --- a/src/falconpy/_endpoint/_mobile_enrollment.py +++ b/src/falconpy/_endpoint/_mobile_enrollment.py @@ -67,5 +67,36 @@ "required": True } ] + ], + [ + "RequestDeviceEnrollmentV4", + "POST", + "/enrollments/entities/details/v4", + "Trigger on-boarding process for a mobile device", + "mobile_enrollment", + [ + { + "enum": [ + "enroll", + "re-enroll" + ], + "type": "string", + "description": "Action to perform", + "name": "action_name", + "in": "query", + "allowEmptyValue": True + }, + { + "type": "string", + "description": "FQL filter", + "name": "filter", + "in": "query" + }, + { + "name": "body", + "in": "body", + "required": True + } + ] ] ] diff --git a/src/falconpy/_payload/__init__.py b/src/falconpy/_payload/__init__.py index eb2f6e63b..a4659cfbd 100644 --- a/src/falconpy/_payload/__init__.py +++ b/src/falconpy/_payload/__init__.py @@ -48,6 +48,7 @@ from ._detects import update_detects_payload from ._incidents import incident_action_parameters from ._ioa import ioa_exclusion_payload, ioa_custom_payload +from ._mobile_enrollment import mobile_enrollment_payload from ._prevention_policy import prevention_policy_payload from ._sensor_update_policy import sensor_policy_payload from ._response_policy import response_policy_payload @@ -126,5 +127,5 @@ "workflow_template_payload", "foundry_execute_search_payload", "foundry_dynamic_search_payload", "image_policy_payload", "image_exclusions_payload", "image_group_payload", "workflow_definition_payload", "workflow_human_input", "workflow_mock_payload", - "cspm_service_account_validate_payload", "api_plugin_command_payload" + "cspm_service_account_validate_payload", "api_plugin_command_payload", "mobile_enrollment_payload" ] diff --git a/src/falconpy/_payload/_mobile_enrollment.py b/src/falconpy/_payload/_mobile_enrollment.py new file mode 100644 index 000000000..867d7fbca --- /dev/null +++ b/src/falconpy/_payload/_mobile_enrollment.py @@ -0,0 +1,63 @@ +"""Internal payload handling library - Mobile Enrollment Payloads. + + _______ __ _______ __ __ __ +| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----. +|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__| +|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____| +|: 1 | |: 1 | +|::.. . | CROWDSTRIKE FALCON |::.. . | FalconPy +`-------' `-------' + +OAuth2 API - Customer SDK + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +""" + + +def mobile_enrollment_payload(passed_keywords: dict): + """Craft a properly formatted mobile enrollment payload. + + { + "email_addresses": [ + "string" + ], + "enrollment_type": "string", + "expires_at": "UTC date string" + } + """ + returned = {} + keys = ["email_addresses", "enrollment_type", "expires_at"] + for key in keys: + if passed_keywords.get(key, None): + if key == "email_addresses": + provided = passed_keywords.get(key, None) + if isinstance(provided, str): + provided = provided.split(",") + returned["email_addresses"] = provided + else: + returned[key] = passed_keywords.get(key, None) + + return returned diff --git a/src/falconpy/mobile_enrollment.py b/src/falconpy/mobile_enrollment.py index 80b5be743..cfa836423 100644 --- a/src/falconpy/mobile_enrollment.py +++ b/src/falconpy/mobile_enrollment.py @@ -37,6 +37,7 @@ """ from typing import Dict, Union from ._util import process_service_request, force_default +from ._payload import mobile_enrollment_payload from ._service_class import ServiceClass from ._endpoint._mobile_enrollment import _mobile_enrollment_endpoints as Endpoints @@ -68,8 +69,8 @@ def device_enroll(self: object, body: dict = None, parameters: dict = None, **kw Keyword arguments ---- action_name : str - Action to perform. String. Allowed values: enroll, re-enroll. - body : str + Action to perform. Allowed values: enroll, re-enroll. + body : dict Full body payload, not required if using `email_addresses` and `expires_at` keywords. { "email_addresses": [ @@ -78,12 +79,12 @@ def device_enroll(self: object, body: dict = None, parameters: dict = None, **kw "expires_at": "2022-08-07T02:37:16.797Z" } email_addresses : str or list[str] (required) - Email addresses to use for enrollment. String or list of strings. + Email addresses to use for enrollment. expires_at : str (required) Date enrollment expires. UTC date format. filter : str FQL filter. - parameters : str + parameters : dict Full parameters payload, not required if using `action_name` keyword. Arguments @@ -96,13 +97,7 @@ def device_enroll(self: object, body: dict = None, parameters: dict = None, **kw Dictionary containing API response. """ if not body: - provided = kwargs.get("email_addresses", None) - if provided: - if isinstance(provided, str): - provided = provided.split(",") - body["email_addresses"] = provided - if kwargs.get("expires_at", None): - body["expires_at"] = kwargs.get("expires_at", None) + body = mobile_enrollment_payload(kwargs) return process_service_request( calling_object=self, @@ -113,10 +108,65 @@ def device_enroll(self: object, body: dict = None, parameters: dict = None, **kw params=parameters ) + @force_default(defaults=["body", "parameters"], default_types=["dict", "dict"]) + def device_enroll_v4(self: object, body: dict = None, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Trigger on-boarding process for a mobile device. + + HTTP Method: POST + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/mobile-enrollment/RequestDeviceEnrollmentV4 + + Keyword arguments + ---- + action_name : str + Action to perform. Allowed values: enroll, re-enroll. + body : dict + Full body payload, not required if using `email_addresses` and `expires_at` keywords. + { + "email_addresses": [ + "string" + ], + "enrollment_type": "string", + "expires_at": "2022-08-07T02:37:16.797Z" + } + email_addresses : str or list[str] (required) + Email addresses to use for enrollment. + enrollment_type : str + Mobile enrollment type. + expires_at : str (required) + Date enrollment expires. UTC date format. + filter : str + FQL filter. + parameters : dict + Full parameters payload, not required if using `action_name` keyword. + + Arguments + ---- + This method only supports keywords for providing arguments. + + Returns + ---- + dict + Dictionary containing API response. + """ + if not body: + body = mobile_enrollment_payload(kwargs) + + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="RequestDeviceEnrollmentV4", + body=body, + keywords=kwargs, + params=parameters + ) + # This method name aligns to the operation ID in the API but # does not conform to snake_case / PEP8 and is defined here for # backwards compatibility / ease of use purposes RequestDeviceEnrollmentV3 = device_enroll + RequestDeviceEnrollmentV4 = device_enroll_v4 # The legacy name for this class does not conform to PascalCase / PEP8 diff --git a/tests/test_mobile_enrollment.py b/tests/test_mobile_enrollment.py index b6b292fe6..5e7b35404 100644 --- a/tests/test_mobile_enrollment.py +++ b/tests/test_mobile_enrollment.py @@ -20,7 +20,7 @@ class TestMobileEnrollment: """Class to test the Mobile Enrollment Service Class.""" - def test_get_credentials(self): + def test_device_enroll(self): """Pytest harness hook""" result = falcon.device_enroll( action_name="re-enroll", @@ -28,3 +28,12 @@ def test_get_credentials(self): expires_at=(datetime.utcnow() + timedelta(minutes=5)).strftime("%Y-%m-%dT%H:%M:%SZ") ) assert bool(result["status_code"] in AllowedResponses) is True + + def test_device_enroll_v4(self): + """Pytest harness hook""" + result = falcon.device_enroll_v4( + action_name="re-enroll", + email_addresses="no_reply@crowdstrike.com", + expires_at=(datetime.utcnow() + timedelta(minutes=5)).strftime("%Y-%m-%dT%H:%M:%SZ") + ) + assert bool(result["status_code"] in AllowedResponses) is True From 0434428e5c251b3abfd2856a27be980f526a29de Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Tue, 4 Jun 2024 08:47:10 -0400 Subject: [PATCH 18/30] Update enumerator for sort parameter definition (QueryCasesIdsByFilter) --- src/falconpy/_endpoint/_message_center.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/falconpy/_endpoint/_message_center.py b/src/falconpy/_endpoint/_message_center.py index 8c23a62b0..d7da51015 100644 --- a/src/falconpy/_endpoint/_message_center.py +++ b/src/falconpy/_endpoint/_message_center.py @@ -242,8 +242,8 @@ }, { "enum": [ - "case.id.asc", - "case.id.desc" + "case.type.asc", + "case.type.desc" ], "type": "string", "description": "The property to sort on, followed by a dot (.), followed by the sort direction, either " From 95ba939c8790ccfd402dc6629e8a99fb55da888c Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Tue, 4 Jun 2024 08:51:21 -0400 Subject: [PATCH 19/30] Update filter parameter description (query_iot_hosts) --- src/falconpy/_endpoint/_discover.py | 11 ++++++----- src/falconpy/_endpoint/deprecated/_discover.py | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/falconpy/_endpoint/_discover.py b/src/falconpy/_endpoint/_discover.py index 6149743a1..fccbfc01b 100644 --- a/src/falconpy/_endpoint/_discover.py +++ b/src/falconpy/_endpoint/_discover.py @@ -374,11 +374,12 @@ "anaged'
  • product_type_desc:'Workstation'
  • platform_name:'Windows'
  • last_seen_timestamp:>' " "now-7d'
  • \n\t\t\tAvailable filter fields that support exact match: device_family, device_class, " "device_type, device_mode, business_criticality, line_of_business, virtual_zone, subnet, purdue_level, vlan, " - "local_ip_addresses, mac_addresses, physical_connections_count, data_providers\n\t\t\tAvailable filter fields " - "that supports wildcard (*): device_family, device_class, device_type, device_mode, business_criticality, " - "line_of_business, virtual_zone, subnet, purdue_level, vlan, local_ip_addresses, mac_addresses, " - "data_providers\n\t\t\tAvailable filter fields that supports range comparisons (>, <, >=, <=): " - "physical_connections_count\n\t\t\tAll filter fields and operations supports negation (!).", + "local_ip_addresses, mac_addresses, physical_connections_count, data_providers, local_ips_count, " + "network_interfaces.local_ip, classification\n\t\t\tAvailable filter fields that supports wildcard (*): " + "device_family, device_class, device_type, device_mode, business_criticality, line_of_business, virtual_zone, " + "subnet, purdue_level, vlan, local_ip_addresses, mac_addresses, data_providers\n\t\t\tAvailable filter fields " + "that supports range comparisons (>, <, >=, <=): physical_connections_count, local_ips_count\n\t\t\tAll filter " + "fields and operations supports negation (!).", "name": "filter", "in": "query" } diff --git a/src/falconpy/_endpoint/deprecated/_discover.py b/src/falconpy/_endpoint/deprecated/_discover.py index 0a9be09eb..30a76dc40 100644 --- a/src/falconpy/_endpoint/deprecated/_discover.py +++ b/src/falconpy/_endpoint/deprecated/_discover.py @@ -374,11 +374,12 @@ "anaged'
  • product_type_desc:'Workstation'
  • platform_name:'Windows'
  • last_seen_timestamp:>' " "now-7d'
  • \n\t\t\tAvailable filter fields that support exact match: device_family, device_class, " "device_type, device_mode, business_criticality, line_of_business, virtual_zone, subnet, purdue_level, vlan, " - "local_ip_addresses, mac_addresses, physical_connections_count, data_providers\n\t\t\tAvailable filter fields " - "that supports wildcard (*): device_family, device_class, device_type, device_mode, business_criticality, " - "line_of_business, virtual_zone, subnet, purdue_level, vlan, local_ip_addresses, mac_addresses, " - "data_providers\n\t\t\tAvailable filter fields that supports range comparisons (>, <, >=, <=): " - "physical_connections_count\n\t\t\tAll filter fields and operations supports negation (!).", + "local_ip_addresses, mac_addresses, physical_connections_count, data_providers, local_ips_count, " + "network_interfaces.local_ip, classification\n\t\t\tAvailable filter fields that supports wildcard (*): " + "device_family, device_class, device_type, device_mode, business_criticality, line_of_business, virtual_zone, " + "subnet, purdue_level, vlan, local_ip_addresses, mac_addresses, data_providers\n\t\t\tAvailable filter fields " + "that supports range comparisons (>, <, >=, <=): physical_connections_count, local_ips_count\n\t\t\tAll filter " + "fields and operations supports negation (!).", "name": "filter", "in": "query" } From a1fb4609a1fcc21280547c55d3c3caf1766c5bdd Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Tue, 4 Jun 2024 08:57:40 -0400 Subject: [PATCH 20/30] Add new parameters for the GetD4CAWSAccountScriptsAttachment operation --- src/falconpy/_endpoint/_d4c_registration.py | 68 +++++++++++++++++++++ src/falconpy/d4c_registration.py | 8 +++ 2 files changed, 76 insertions(+) diff --git a/src/falconpy/_endpoint/_d4c_registration.py b/src/falconpy/_endpoint/_d4c_registration.py index cad65892f..c5be6787e 100644 --- a/src/falconpy/_endpoint/_d4c_registration.py +++ b/src/falconpy/_endpoint/_d4c_registration.py @@ -184,6 +184,74 @@ "description": "AWS account IDs", "name": "ids", "in": "query" + }, + { + "enum": [ + "aws-bash", + "aws-terraform" + ], + "type": "string", + "description": "Template to be rendered", + "name": "template", + "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "description": "The list of accounts to register", + "name": "accounts", + "in": "query" + }, + { + "enum": [ + "true", + "false" + ], + "type": "string", + "name": "behavior_assessment_enabled", + "in": "query" + }, + { + "enum": [ + "true", + "false" + ], + "type": "string", + "name": "sensor_management_enabled", + "in": "query" + }, + { + "enum": [ + "true", + "false" + ], + "type": "string", + "name": "use_existing_cloudtrail", + "in": "query" + }, + { + "pattern": ".*", + "type": "string", + "description": "The AWS organization ID to be registered", + "name": "organization_id", + "in": "query" + }, + { + "pattern": ".*", + "type": "string", + "description": "The AWS profile to be used during registration", + "name": "aws_profile", + "in": "query" + }, + { + "pattern": ".*", + "type": "string", + "description": "The custom IAM role to be used during registration", + "name": "custom_role_name", + "in": "query" } ] ], diff --git a/src/falconpy/d4c_registration.py b/src/falconpy/d4c_registration.py index 7ab21fe71..6c444fee0 100644 --- a/src/falconpy/d4c_registration.py +++ b/src/falconpy/d4c_registration.py @@ -200,8 +200,16 @@ def get_aws_account_scripts(self: object, *args, parameters: dict = None, **kwar """Return a script for customer to run in their cloud environment to grant CrowdStrike access. Keyword arguments: + accounts -- List of accounts to register. String or list of strings. + aws_profile -- The AWS profile to be used during registration. String. + behavior_assessment_enabled -- Enable behavior assessment. String. Allowed values: true, false + custom_role_name -- The custom IAM role to be used during registration. String. ids -- List of AWS Account IDs to retrieve the script for. String or list of strings. + organization_id -- The AWS organization ID to be registered. String or list of strings. parameters -- full parameters payload, not required if ids is provided as a keyword. + sensor_management_enabled -- Enable sensor management. String. Allowed values: true, false + template -- Template to be rendered. String. Allowed values: aws-bash, aws-terraform + use_existing_cloudtrail -- Use the existing cloudtrail log. String. Allowed values: true, false Arguments: When not specified, the first argument to this method is assumed to be 'ids'. All others are ignored. From 53d474708aff575d369cb7471e67cd060feef290 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Tue, 4 Jun 2024 09:00:35 -0400 Subject: [PATCH 21/30] Add new parameters to the GetCSPMAwsAccountScriptsAttachment operation --- src/falconpy/_endpoint/_cspm_registration.py | 68 ++++++++++++++++++++ src/falconpy/cspm_registration.py | 12 +++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/falconpy/_endpoint/_cspm_registration.py b/src/falconpy/_endpoint/_cspm_registration.py index 1c2c5e69c..d9237c548 100644 --- a/src/falconpy/_endpoint/_cspm_registration.py +++ b/src/falconpy/_endpoint/_cspm_registration.py @@ -251,6 +251,74 @@ "description": "AWS account IDs", "name": "ids", "in": "query" + }, + { + "enum": [ + "aws-bash", + "aws-terraform" + ], + "type": "string", + "description": "Template to be rendered", + "name": "template", + "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "description": "The list of accounts to register", + "name": "accounts", + "in": "query" + }, + { + "enum": [ + "true", + "false" + ], + "type": "string", + "name": "behavior_assessment_enabled", + "in": "query" + }, + { + "enum": [ + "true", + "false" + ], + "type": "string", + "name": "sensor_management_enabled", + "in": "query" + }, + { + "enum": [ + "true", + "false" + ], + "type": "string", + "name": "use_existing_cloudtrail", + "in": "query" + }, + { + "pattern": ".*", + "type": "string", + "description": "The AWS organization ID to be registered", + "name": "organization_id", + "in": "query" + }, + { + "pattern": ".*", + "type": "string", + "description": "The AWS profile to be used during registration", + "name": "aws_profile", + "in": "query" + }, + { + "pattern": ".*", + "type": "string", + "description": "The custom IAM role to be used during registration", + "name": "custom_role_name", + "in": "query" } ] ], diff --git a/src/falconpy/cspm_registration.py b/src/falconpy/cspm_registration.py index 542d61090..03728b943 100644 --- a/src/falconpy/cspm_registration.py +++ b/src/falconpy/cspm_registration.py @@ -271,8 +271,16 @@ def get_aws_account_scripts_attachment(self: object, parameters: dict = None, ** to grant access to CrowdStrike for their AWS environment. Keyword arguments: - ids -- AWS Account IDs to retrieve script attachments for. String or list of strings. - parameters -- full parameters payload, not required if using other keywords. + accounts -- List of accounts to register. String or list of strings. + aws_profile -- The AWS profile to be used during registration. String. + behavior_assessment_enabled -- Enable behavior assessment. String. Allowed values: true, false + custom_role_name -- The custom IAM role to be used during registration. String. + ids -- List of AWS Account IDs to retrieve the script for. String or list of strings. + organization_id -- The AWS organization ID to be registered. String or list of strings. + parameters -- full parameters payload, not required if ids is provided as a keyword. + sensor_management_enabled -- Enable sensor management. String. Allowed values: true, false + template -- Template to be rendered. String. Allowed values: aws-bash, aws-terraform + use_existing_cloudtrail -- Use the existing cloudtrail log. String. Allowed values: true, false This method only supports keywords for providing arguments. From 75ddcf094f8099f82645a1e2d3374d1c1830c61b Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Tue, 4 Jun 2024 09:13:37 -0400 Subject: [PATCH 22/30] Update sort parameter description (query_rulesMixin0) --- src/falconpy/_endpoint/_custom_ioa.py | 8 ++++---- src/falconpy/_endpoint/deprecated/_custom_ioa.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/falconpy/_endpoint/_custom_ioa.py b/src/falconpy/_endpoint/_custom_ioa.py index ac36fd61f..a32d94ca3 100644 --- a/src/falconpy/_endpoint/_custom_ioa.py +++ b/src/falconpy/_endpoint/_custom_ioa.py @@ -466,10 +466,10 @@ "rules.ruletype_name" ], "type": "string", - "description": "Possible order by fields: {rules.current_version.description, " - "rules.current_version.action_label, rules.current_version.modified_on, rules.created_on, " - "rules.current_version.name, rules.created_by, rules.current_version.pattern_severity, " - "rules.current_version.modified_by, rules.ruletype_name, rules.enabled}", + "description": "Possible order by fields: {rules.current_version.name, " + "rules.current_version.description, rules.current_version.pattern_severity, rules.current_version.modified_by, " + "rules.current_version.modified_on, rules.ruletype_name, rules.enabled, rules.current_version.action_label, " + "rules.created_by, rules.created_on}", "name": "sort", "in": "query" }, diff --git a/src/falconpy/_endpoint/deprecated/_custom_ioa.py b/src/falconpy/_endpoint/deprecated/_custom_ioa.py index 0ffc268f7..55be3e491 100644 --- a/src/falconpy/_endpoint/deprecated/_custom_ioa.py +++ b/src/falconpy/_endpoint/deprecated/_custom_ioa.py @@ -452,10 +452,10 @@ "rules.ruletype_name" ], "type": "string", - "description": "Possible order by fields: {rules.current_version.description, " - "rules.current_version.action_label, rules.current_version.modified_on, rules.created_on, " - "rules.current_version.name, rules.created_by, rules.current_version.pattern_severity, " - "rules.current_version.modified_by, rules.ruletype_name, rules.enabled}", + "description": "Possible order by fields: {rules.current_version.name, " + "rules.current_version.description, rules.current_version.pattern_severity, rules.current_version.modified_by, " + "rules.current_version.modified_on, rules.ruletype_name, rules.enabled, rules.current_version.action_label, " + "rules.created_by, rules.created_on}", "name": "sort", "in": "query" }, From 70e02b5a2dc7e777631e4523f26f69975012d97c Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Tue, 4 Jun 2024 09:31:44 -0400 Subject: [PATCH 23/30] Add update_rules_v2 operation --- src/falconpy/_endpoint/_custom_ioa.py | 17 ++++ .../_endpoint/deprecated/_custom_ioa.py | 17 ++++ src/falconpy/custom_ioa.py | 90 +++++++++++++++++++ tests/test_custom_ioa.py | 6 ++ 4 files changed, 130 insertions(+) diff --git a/src/falconpy/_endpoint/_custom_ioa.py b/src/falconpy/_endpoint/_custom_ioa.py index a32d94ca3..89ab11e0c 100644 --- a/src/falconpy/_endpoint/_custom_ioa.py +++ b/src/falconpy/_endpoint/_custom_ioa.py @@ -268,6 +268,23 @@ } ] ], + [ + "update_rules_v2", + "PATCH", + "/ioarules/entities/rules/v2", + "Update name, description, enabled or field_values for individual rules within a rule group. The v1 flavor " + " of this call requires the caller to specify the complete state for all the rules in the rule group, instead " + "the v2 flavor will accept the subset of rules in the rule group and apply the attribute updates to the subset " + "of rules in the rule group.Return the updated rules.", + "custom_ioa", + [ + { + "name": "body", + "in": "body", + "required": True + } + ] + ], [ "validate", "POST", diff --git a/src/falconpy/_endpoint/deprecated/_custom_ioa.py b/src/falconpy/_endpoint/deprecated/_custom_ioa.py index 55be3e491..b5449b998 100644 --- a/src/falconpy/_endpoint/deprecated/_custom_ioa.py +++ b/src/falconpy/_endpoint/deprecated/_custom_ioa.py @@ -268,6 +268,23 @@ } ] ], + [ + "update-rules-v2", + "PATCH", + "/ioarules/entities/rules/v2", + "Update name, description, enabled or field_values for individual rules within a rule group. The v1 flavor " + " of this call requires the caller to specify the complete state for all the rules in the rule group, instead " + "the v2 flavor will accept the subset of rules in the rule group and apply the attribute updates to the subset " + "of rules in the rule group.Return the updated rules.", + "custom_ioa", + [ + { + "name": "body", + "in": "body", + "required": True + } + ] + ], [ "query-patterns", "GET", diff --git a/src/falconpy/custom_ioa.py b/src/falconpy/custom_ioa.py index b739cd73b..6cde21485 100644 --- a/src/falconpy/custom_ioa.py +++ b/src/falconpy/custom_ioa.py @@ -526,6 +526,96 @@ def update_rules(self: object, body=body ) + @force_default(defaults=["body"], default_types=["dict"]) + def update_rules_v2(self: object, + body: dict = None, + **kwargs + ) -> Dict[str, Union[int, dict]]: + """Update rules within a rule group. Return the updated rules. + + Keyword arguments: + body -- full body payload in JSON format, not required if using other keywords. + { + "comment": "string", + "rule_updates": [ + { + "description": "string", + "disposition_id": 0, + "enabled": true, + "field_values": [ + { + "final_value": "string", + "label": "string", + "name": "string", + "type": "string", + "value": "string", + "values": [ + { + "label": "string", + "value": "string" + } + ] + } + ], + "instance_id": "string", + "name": "string", + "pattern_severity": "string", + "rulegroup_version": 0 + } + ], + "rulegroup_id": "string", + "rulegroup_version": 0 + } + comment -- Comment related to this update. String. + rulegroup_id -- ID of the rule group. String. + rule_updates -- JSON dictionary representing the rule updates to + be performed. Only one rule update can be done + in this manner. Dictionary. + { + "description": "string", + "disposition_id": 0, + "enabled": true, + "field_values": [ + { + "final_value": "string", + "label": "string", + "name": "string", + "type": "string", + "value": "string", + "values": [ + { + "label": "string", + "value": "string" + } + ] + } + ], + "instance_id": "string", + "name": "string", + "pattern_severity": "string", + "rulegroup_version": 0 + } + rulegroup_version -- Version of the rule group. Integer. + + This method only supports keywords for providing arguments. + + Returns: dict object containing API response. + + HTTP Method: PATCH + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/custom-ioa/update-rules-v2 + """ + if not body: + body = ioa_custom_payload(passed_keywords=kwargs) + + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="update_rules_v2", + body=body + ) + @force_default(defaults=["body"], default_types=["dict"]) def validate(self: object, body: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: """Validate field values and check for matches if a test string is provided. diff --git a/tests/test_custom_ioa.py b/tests/test_custom_ioa.py index 31cdb6290..86f2674ae 100644 --- a/tests/test_custom_ioa.py +++ b/tests/test_custom_ioa.py @@ -49,6 +49,12 @@ def ioa_generate_errors(): disposition_id=1, ruletype_id="12345678" ), + "update_rules_v2": falcon.update_rules_v2(enabled=True, + rulegroup_version=1, + rule_updates={"something": "something-darkside"}, + disposition_id=1, + ruletype_id="12345678" + ), "validate": falcon.validate(), "query_patterns": falcon.query_patterns(), "query_platforms": falcon.query_platformsMixin0(), From 99fbe6631c78e2d0f0118538a6904111e79e0427 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Tue, 4 Jun 2024 09:42:04 -0400 Subject: [PATCH 24/30] Remove three operations from KubernetesProtection service collection --- .../_endpoint/_kubernetes_protection.py | 78 ------------------ src/falconpy/kubernetes_protection.py | 81 ------------------- tests/test_kubernetes_protection.py | 3 - 3 files changed, 162 deletions(-) diff --git a/src/falconpy/_endpoint/_kubernetes_protection.py b/src/falconpy/_endpoint/_kubernetes_protection.py index 9966a3e7f..31452ecc5 100644 --- a/src/falconpy/_endpoint/_kubernetes_protection.py +++ b/src/falconpy/_endpoint/_kubernetes_protection.py @@ -382,58 +382,6 @@ } ] ], - [ - "ReadContainerEnrichment", - "GET", - "/container-security/aggregates/enrichment/containers/entities/v1", - "Retrieve container enrichment data", - "kubernetes_protection", - [ - { - "type": "array", - "items": { - "type": "string" - }, - "collectionFormat": "csv", - "description": "One or more container ids for which to retrieve enrichment info", - "name": "container_id", - "in": "query", - "required": True - }, - { - "type": "string", - "description": "Supported filters: last_seen", - "name": "filter", - "in": "query" - } - ] - ], - [ - "ReadDeploymentEnrichment", - "GET", - "/container-security/aggregates/enrichment/deployments/entities/v1", - "Retrieve deployment enrichment data", - "kubernetes_protection", - [ - { - "type": "array", - "items": { - "type": "string" - }, - "collectionFormat": "csv", - "description": "One or more deployment ids for which to retrieve enrichment info", - "name": "deployment_id", - "in": "query", - "required": True - }, - { - "type": "string", - "description": "Supported filters: last_seen", - "name": "filter", - "in": "query" - } - ] - ], [ "ReadNodeEnrichment", "GET", @@ -460,32 +408,6 @@ } ] ], - [ - "ReadPodEnrichment", - "GET", - "/container-security/aggregates/enrichment/pods/entities/v1", - "Retrieve pod enrichment data", - "kubernetes_protection", - [ - { - "type": "array", - "items": { - "type": "string" - }, - "collectionFormat": "csv", - "description": "One or more pod ids for which to retrieve enrichment info", - "name": "pod_id", - "in": "query", - "required": True - }, - { - "type": "string", - "description": "Supported filters: last_seen", - "name": "filter", - "in": "query" - } - ] - ], [ "ReadDistinctContainerImageCount", "GET", diff --git a/src/falconpy/kubernetes_protection.py b/src/falconpy/kubernetes_protection.py index 5265cd7da..638972a56 100644 --- a/src/falconpy/kubernetes_protection.py +++ b/src/falconpy/kubernetes_protection.py @@ -739,58 +739,6 @@ def read_cluster_enrichment(self: object, parameters: dict = None, **kwargs) -> params=parameters ) - @force_default(defaults=["parameters"], default_types=["dict"]) - def read_container_enrichment(self: object, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: - """Retrieve container enrichment data. - - Keyword arguments: - container_id -- One or more container ids for which to retrieve enrichment info - filter -- Supported filters: last_seen - parameters -- Full parameters payload dictionary. Not required if using other keywords. - - This method only supports keywords for providing arguments. - - Returns: dict object containing API response. - - HTTP Method: GET - - Swagger URL - https://assets.falcon.crowdstrike.com/support/api/swagger.html#/kubernetes-protection/ReadContainerEnrichment - """ - return process_service_request( - calling_object=self, - endpoints=Endpoints, - operation_id="ReadContainerEnrichment", - keywords=kwargs, - params=parameters - ) - - @force_default(defaults=["parameters"], default_types=["dict"]) - def read_deployment_enrichment(self: object, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: - """Retrieve deployment enrichment data. - - Keyword arguments: - deployment_id -- One or more deployment ids for which to retrieve enrichment info - filter -- Supported filters: last_seen - parameters -- Full parameters payload dictionary. Not required if using other keywords. - - This method only supports keywords for providing arguments. - - Returns: dict object containing API response. - - HTTP Method: GET - - Swagger URL - https://assets.falcon.crowdstrike.com/support/api/swagger.html#/kubernetes-protection/ReadDeploymentEnrichment - """ - return process_service_request( - calling_object=self, - endpoints=Endpoints, - operation_id="ReadDeploymentEnrichment", - keywords=kwargs, - params=parameters - ) - @force_default(defaults=["parameters"], default_types=["dict"]) def read_node_enrichment(self: object, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: """Retrieve node enrichment data. @@ -817,32 +765,6 @@ def read_node_enrichment(self: object, parameters: dict = None, **kwargs) -> Dic params=parameters ) - @force_default(defaults=["parameters"], default_types=["dict"]) - def read_pod_enrichment(self: object, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: - """Retrieve pod enrichment data. - - Keyword arguments: - pod_id -- One or more pod ids for which to retrieve enrichment info - filter -- Supported filters: last_seen - parameters -- Full parameters payload dictionary. Not required if using other keywords. - - This method only supports keywords for providing arguments. - - Returns: dict object containing API response. - - HTTP Method: GET - - Swagger URL - https://assets.falcon.crowdstrike.com/support/api/swagger.html#/kubernetes-protection/ReadPodEnrichment - """ - return process_service_request( - calling_object=self, - endpoints=Endpoints, - operation_id="ReadPodEnrichment", - keywords=kwargs, - params=parameters - ) - @force_default(defaults=["parameters"], default_types=["dict"]) def read_distinct_image_count(self: object, *args, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: """Retrieve count of distinct images running on containers. @@ -2086,10 +2008,7 @@ def update_azure_service_principal(self: object, *args, parameters: dict = None, ReadDeploymentsByDateRangeCount = read_deployment_counts_by_date_range ReadDeploymentCount = read_deployment_count ReadClusterEnrichment = read_cluster_enrichment - ReadContainerEnrichment = read_container_enrichment - ReadDeploymentEnrichment = read_deployment_enrichment ReadNodeEnrichment = read_node_enrichment - ReadPodEnrichment = read_pod_enrichment ReadDistinctContainerImageCount = read_distinct_image_count ReadContainerImagesByMostUsed = read_images_by_most_used ReadKubernetesIomByDateRange = read_iom_count_by_date_range diff --git a/tests/test_kubernetes_protection.py b/tests/test_kubernetes_protection.py index a76649c43..a65a37c71 100644 --- a/tests/test_kubernetes_protection.py +++ b/tests/test_kubernetes_protection.py @@ -60,10 +60,7 @@ def serviceKubeProtect_RunAllTests(self): "ReadDeploymentsByDateRangeCount": falcon.read_deployment_counts_by_date_range(), "ReadDeploymentCount": falcon.read_deployment_count(filter="whatever"), "ReadClusterEnrichment": falcon.read_cluster_enrichment(filter="whatever"), - "ReadContainerEnrichment": falcon.read_container_enrichment(filter="whatever"), - "ReadDeploymentEnrichment": falcon.read_deployment_enrichment(filter="whatever"), "ReadNodeEnrichment": falcon.read_node_enrichment(filter="whatever"), - "ReadPodEnrichment": falcon.read_pod_enrichment(filter="whatever"), "ReadDistinctContainerImageCount": falcon.read_distinct_image_count(filter="whatever"), "ReadContainerImagesByMostUsed": falcon.read_images_by_most_used(filter="whatever"), "ReadKubernetesIomByDateRange": falcon.read_iom_count_by_date_range(filter="whatever"), From 0c29d638d4043fb5d21693109508ac29c7db5809 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Tue, 4 Jun 2024 09:43:27 -0400 Subject: [PATCH 25/30] Update filter parameter description for ReadRunningContainerImages operation --- src/falconpy/_endpoint/_kubernetes_protection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/falconpy/_endpoint/_kubernetes_protection.py b/src/falconpy/_endpoint/_kubernetes_protection.py index 31452ecc5..3681a1f1f 100644 --- a/src/falconpy/_endpoint/_kubernetes_protection.py +++ b/src/falconpy/_endpoint/_kubernetes_protection.py @@ -627,8 +627,8 @@ { "type": "string", "description": "Retrieve list of images on running containers using a query in Falcon Query Language " - "(FQL). Supported filters: cid,hosts,image_digest,image_has_been_assessed,image_id,image_name,image_registry,i" - "mage_repository,image_tag,last_seen,running_status", + "(FQL). Supported filters: cid,cluster_id,cluster_name,hosts,image_digest,image_has_been_assessed,image_id,ima" + "ge_name,image_registry,image_repository,image_tag,last_seen,running_status", "name": "filter", "in": "query" }, From 1e4e349620d797b9eb132f924d6577c1135ea185 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Tue, 4 Jun 2024 10:00:14 -0400 Subject: [PATCH 26/30] Remove ReadDriftIndicatorEntities operation --- src/falconpy/_endpoint/_drift_indicators.py | 19 -------------- src/falconpy/drift_indicators.py | 29 +-------------------- tests/test_drift_indicators.py | 1 - 3 files changed, 1 insertion(+), 48 deletions(-) diff --git a/src/falconpy/_endpoint/_drift_indicators.py b/src/falconpy/_endpoint/_drift_indicators.py index 1663901b1..a9484f3bf 100644 --- a/src/falconpy/_endpoint/_drift_indicators.py +++ b/src/falconpy/_endpoint/_drift_indicators.py @@ -112,25 +112,6 @@ } ] ], - [ - "ReadDriftIndicatorEntities", - "GET", - "/container-security/entities/drift-indicators/v1", - "Retrieve Drift Indicator entities identified by the provided IDs", - "drift_indicators", - [ - { - "type": "array", - "items": { - "type": "string" - }, - "collectionFormat": "csv", - "description": "Search Drift Indicators by ids - The maximum amount is 100 IDs", - "name": "ids", - "in": "query" - } - ] - ], [ "SearchDriftIndicators", "GET", diff --git a/src/falconpy/drift_indicators.py b/src/falconpy/drift_indicators.py index d321aa9c7..851e22925 100644 --- a/src/falconpy/drift_indicators.py +++ b/src/falconpy/drift_indicators.py @@ -36,7 +36,7 @@ For more information, please refer to """ from typing import Dict, Union -from ._util import force_default, process_service_request, handle_single_argument +from ._util import force_default, process_service_request from ._service_class import ServiceClass from ._endpoint._drift_indicators import _drift_indicators_endpoints as Endpoints @@ -160,32 +160,6 @@ def search_and_read_drift_indicators(self: object, parameters: dict = None, **kw params=parameters ) - @force_default(defaults=["parameters"], default_types=["dict"]) - def read_drift_indicators(self: object, *args, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: - """Retrieve Drift Indicator entities identified by the provided IDs. - - Keyword arguments: - ids -- Search Drift Indicators by IDs. String or list of strings. [Max: 100] - parameters -- Full parameters payload dictionary. Not required if using other keywords. - - Arguments: When not specified, the first argument to this method is assumed to be 'ids'. - All others are ignored. - - Returns: dict object containing API response. - - HTTP Method: GET - - Swagger URL - https://assets.falcon.crowdstrike.com/support/api/swagger.html#/drift-indicators/ReadDriftIndicatorEntities - """ - return process_service_request( - calling_object=self, - endpoints=Endpoints, - operation_id="ReadDriftIndicatorEntities", - keywords=kwargs, - params=handle_single_argument(args, parameters, "ids") - ) - @force_default(defaults=["parameters"], default_types=["dict"]) def search_drift_indicators(self: object, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: """Retrieve all drift indicators that match the given query. @@ -226,5 +200,4 @@ def search_drift_indicators(self: object, parameters: dict = None, **kwargs) -> GetDriftIndicatorsValuesByDate = get_drift_indicators_by_date ReadDriftIndicatorsCount = read_drift_indicator_counts SearchAndReadDriftIndicatorEntities = search_and_read_drift_indicators - ReadDriftIndicatorEntities = read_drift_indicators SearchDriftIndicators = search_drift_indicators diff --git a/tests/test_drift_indicators.py b/tests/test_drift_indicators.py index 7a1e5b429..06ab7f1a6 100644 --- a/tests/test_drift_indicators.py +++ b/tests/test_drift_indicators.py @@ -26,7 +26,6 @@ def test_all_code_paths(self): "GetDriftIndicatorsValuesByDate": falcon.get_drift_indicators_by_date(filter="cid:'12345678901234567890123456789012'"), "ReadDriftIndicatorsCount": falcon.read_drift_indicator_counts(filter="cid:'12345678901234567890123456789012'"), "SearchAndReadDriftIndicatorEntities": falcon.search_and_read_drift_indicators(filter="cid:'12345678901234567890123456789012'"), - "ReadDriftIndicatorEntities": falcon.read_drift_indicators(ids="1234567890"), "SearchDriftIndicators": falcon.search_drift_indicators(filter="cid:'12345678901234567890123456789012'"), } for key in tests: From 6037cc3d8d47d1241636a12fee6a0c8c6376eeeb Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Tue, 4 Jun 2024 10:55:38 -0400 Subject: [PATCH 27/30] Add five new FileVantage operations --- src/falconpy/_constant/__init__.py | 2 +- src/falconpy/_endpoint/_filevantage.py | 113 ++++++++++++++++ src/falconpy/_payload/__init__.py | 6 +- src/falconpy/_payload/_filevantage.py | 26 ++++ src/falconpy/filevantage.py | 178 ++++++++++++++++++++++++- tests/test_filevantage.py | 7 +- 6 files changed, 327 insertions(+), 5 deletions(-) diff --git a/src/falconpy/_constant/__init__.py b/src/falconpy/_constant/__init__.py index c9d44ffde..94907529a 100644 --- a/src/falconpy/_constant/__init__.py +++ b/src/falconpy/_constant/__init__.py @@ -46,7 +46,7 @@ "GetDeviceDetails", "PostDeviceDetailsV2", "GetVulnerabilities", "GetIntelIndicatorEntities", "getChildrenV2", "cancel-scans", "GetDetectSummaries", "UpdateQuarantinedDetectsByIds", "GetQuarantineFiles", "PostEntitiesAlertsV1", "CreateSavedSearchesDeployV1", - "WorkflowExecutionsAction" + "WorkflowExecutionsAction", "signalChangesExternal" ] MOCK_OPERATIONS: List[str] = [ "GetImageAssessmentReport", "DeleteImageDetails", "ImageMatchesPolicy" diff --git a/src/falconpy/_endpoint/_filevantage.py b/src/falconpy/_endpoint/_filevantage.py index 7b69ffe91..6aa905d39 100644 --- a/src/falconpy/_endpoint/_filevantage.py +++ b/src/falconpy/_endpoint/_filevantage.py @@ -37,6 +37,66 @@ """ _filevantage_endpoints = [ + [ + "getActionsMixin0", + "GET", + "/filevantage/entities/actions/v1", + "Retrieves the processing results for 1 or more actions.", + "filevantage", + [ + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "description": "One or more actions ids in the form of `ids=ID1&ids=ID2`", + "name": "ids", + "in": "query", + "required": True + } + ] + ], + [ + "startActions", + "POST", + "/filevantage/entities/actions/v1", + "Initiates the specified action on the provided change ids", + "filevantage", + [ + { + "description": "Create a new action.\n\n * `operation` must be one of the `suppress`, `unsuppress`, or " + " `purge`\n\n * `change_ids` represent the ids of the changes the operation will perform; limited to 100 ids " + "per action\n\n * `comment` optional comment to describe the reason for the action", + "name": "body", + "in": "body", + "required": True + } + ] + ], + [ + "getContents", + "GET", + "/filevantage/entities/change-content/v1", + "Retrieves the content captured for the provided change id", + "filevantage", + [ + { + "type": "string", + "description": "ID of the change in the form of id=ID1", + "name": "id", + "in": "query", + "required": True + }, + { + "type": "string", + "description": "Providing the value of `gzip` compresses the response, otherwise the content is " + "returned uncompressed.", + "name": "Accept-Encoding", + "in": "header" + } + ] + ], [ "getChanges", "GET", @@ -607,6 +667,59 @@ } ] ], + [ + "signalChangesExternal", + "POST", + "/filevantage/entities/workflow/v1", + "Initiates workflows for the provided change ids", + "filevantage", + [ + { + "description": "Change ids to initiate the workflows; limited to 100 per request.", + "name": "body", + "in": "body", + "required": True + } + ] + ], + [ + "queryActionsMixin0", + "GET", + "/filevantage/queries/actions/v1", + "Returns one or more action ids", + "filevantage", + [ + { + "minimum": 0, + "type": "integer", + "description": "The first action index to return in the response. If not provided it will default to " + "'0'. Use with the `limit` parameter to manage pagination of results.", + "name": "offset", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of actions to return in the response (default: 100; max: 500). Use " + "with the `offset` parameter to manage pagination of results", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "description": "The sort expression that should be used to sort the results (e.g. created_date|desc)", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "description": "Filter changes using a query in Falcon Query Language (FQL). \n\nCommon filter options " + " include:\n\n - `status`\n - `operation_type`\n\n The full list of allowed filter parameters can be reviewed " + "in our API documentation.", + "name": "filter", + "in": "query" + } + ] + ], [ "queryChanges", "GET", diff --git a/src/falconpy/_payload/__init__.py b/src/falconpy/_payload/__init__.py index a4659cfbd..b5940530c 100644 --- a/src/falconpy/_payload/__init__.py +++ b/src/falconpy/_payload/__init__.py @@ -72,7 +72,8 @@ filevantage_rule_group_payload, filevantage_rule_payload, filevantage_policy_payload, - filevantage_scheduled_exclusion_payload + filevantage_scheduled_exclusion_payload, + filevantage_start_payload ) from ._mssp import mssp_payload from ._firewall import ( @@ -127,5 +128,6 @@ "workflow_template_payload", "foundry_execute_search_payload", "foundry_dynamic_search_payload", "image_policy_payload", "image_exclusions_payload", "image_group_payload", "workflow_definition_payload", "workflow_human_input", "workflow_mock_payload", - "cspm_service_account_validate_payload", "api_plugin_command_payload", "mobile_enrollment_payload" + "cspm_service_account_validate_payload", "api_plugin_command_payload", "mobile_enrollment_payload", + "filevantage_start_payload" ] diff --git a/src/falconpy/_payload/_filevantage.py b/src/falconpy/_payload/_filevantage.py index b821a44a8..b361b23a2 100644 --- a/src/falconpy/_payload/_filevantage.py +++ b/src/falconpy/_payload/_filevantage.py @@ -79,6 +79,32 @@ def filevantage_policy_payload(passed_keywords: dict) -> dict: return returned +def filevantage_start_payload(passed_keywords: dict) -> dict: + """Craft a properly formatted FileVantage policy body payload. + + { + "change_ids": [ + "string" + ], + "comment": "string", + "operation": "string" + } + """ + returned = {} + keys = ["change_ids", "comment", "operation"] + for key in keys: + if passed_keywords.get(key, None): + if key == "change_ids": + changes = passed_keywords.get(key, None) + if isinstance(changes, str): + changes = changes.split(",") + returned[key] = changes + else: + returned[key] = passed_keywords.get(key, None) + + return returned + + def filevantage_scheduled_exclusion_payload(passed_keywords: dict) -> dict: """Craft a properly formatted FileVantage scheduled exclusion body payload. diff --git a/src/falconpy/filevantage.py b/src/falconpy/filevantage.py index 88222cba9..041627f4b 100644 --- a/src/falconpy/filevantage.py +++ b/src/falconpy/filevantage.py @@ -36,18 +36,22 @@ For more information, please refer to """ # pylint: disable=C0302 +import json from typing import Dict, Union from ._payload import ( filevantage_rule_group_payload, filevantage_rule_payload, filevantage_policy_payload, - filevantage_scheduled_exclusion_payload + filevantage_scheduled_exclusion_payload, + filevantage_start_payload, + generic_payload_list ) from ._util import process_service_request, force_default, handle_single_argument from ._service_class import ServiceClass from ._endpoint._filevantage import _filevantage_endpoints as Endpoints +# pylint: disable=R0904 # Aligning to the number of operations within this API class FileVantage(ServiceClass): """The only requirement to instantiate an instance of this class is one of the following. @@ -61,6 +65,102 @@ class FileVantage(ServiceClass): - a valid token provided by the authentication service class (oauth2.py) """ + @force_default(defaults=["parameters"], default_types=["dict"]) + def get_actions(self: object, *args, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Retrieve the processing result for one or more actions. + + Keyword arguments: + ids -- Action IDs to retrieve. String or list of strings. + parameters - full parameters payload, not required if ids is provided as a keyword. + + Arguments: When not specified, the first argument to this method is assumed to be 'ids'. + All others are ignored. + + Returns: dict object containing API response. + + HTTP Method: GET + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/filevantage/getActionsMixin0 + """ + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="getActionsMixin0", + keywords=kwargs, + params=handle_single_argument(args, parameters, "ids") + ) + + @force_default(defaults=["body"], default_types=["dict"]) + def start_actions(self: object, body: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Initiate the specified action on the provided change IDs. + + Keyword arguments: + body - full body payload in JSON format, not required if using other keywords. + { + "change_ids": [ + "string" + ], + "comment": "string", + "operation": "string" + } + change_ids -- Represents the IDs of the changes the operation will perform. + String or list of strings. Limited to 100 IDs per action. + comment -- OPtional comment to describe the reason for the action. String. + operation -- Operation to perform. String. Allowed values: suppress, unsuppress, or purge. + + This method only supports keywords for providing arguments. + + Returns: dict object containing API response. + + HTTP Method: POST + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/filevantage/startActions + """ + if not body: + body = filevantage_start_payload(passed_keywords=kwargs) + + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="startActions", + keywords=kwargs, + body=body + ) + + @force_default(defaults=["parameters"], default_types=["dict"]) + def get_contents(self: object, *args, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Retrieve the content captured for the provided change ID. + + Keyword arguments: + id -- Change IDs to retrieve. String. + compress -- Compress the response using gzip. Boolean. Defaults to False. + parameters - full parameters payload, not required if ids is provided as a keyword. + + Arguments: When not specified, the first argument to this method is assumed to be 'id'. + All others are ignored. + + Returns: dict object containing API response. + + HTTP Method: GET + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/filevantage/getChanges + """ + header_payload = json.loads(json.dumps(self.headers)) + if kwargs.get("compress", None): + header_payload["Accept-Encoding"] = "gzip" + + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="getContents", + keywords=kwargs, + params=handle_single_argument(args, parameters, "id"), + headers=header_payload + ) + @force_default(defaults=["parameters"], default_types=["dict"]) def get_changes(self: object, *args, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: """Retrieve information on changes. @@ -977,6 +1077,77 @@ def update_rule_group(self: object, body: dict = None, **kwargs) -> Dict[str, Un body=body ) + @force_default(defaults=["body"], default_types=["dict"]) + def signal_changes(self: object, *args, body: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Initiate a workflow for the provided change IDs. + + Keyword arguments: + body - full body payload, not required if ids is provided as a keyword. + { + "ids": [ + "string" + ] + } + ids -- Action IDs to retrieve. String or list of strings. + + Arguments: When not specified, the first argument to this method is assumed to be 'ids'. + All others are ignored. + + Returns: dict object containing API response. + + HTTP Method: POST + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/filevantage/signalChangesExternal + """ + parameters = handle_single_argument(args, kwargs, "ids") + + if not body: + body = generic_payload_list(submitted_keywords=kwargs, payload_value="ids") + # Try to gracefully catch IDs passed incorrectly as a query string parameter + if parameters: + if "ids" in parameters and "ids" not in body: + body["ids"] = parameters["ids"] + + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="signalChangesExternal", + keywords=kwargs, + body=body + ) + + @force_default(defaults=["parameters"], default_types=["dict"]) + def query_actions(self: object, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Search for actions within your environment. Returns one or more action IDs. + + Keyword arguments: + filter -- The filter expression that should be used to limit the results. FQL syntax. String. + limit -- The maximum number of records to return. [Integer, 1-500, Default: 100] + offset -- The integer offset to start retrieving records from. Integer. + parameters - full parameters payload, not required if using other keywords. + sort -- The property to sort by. FQL syntax (e.g. status.desc or hostname.asc). String. + Available sort fields + action_timestamp ingestion_timestamp + + + This method only supports keywords for providing arguments. + + Returns: dict object containing API response. + + HTTP Method: GET + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/filevantage/queryActionsMixin0 + """ + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="queryActionsMixin0", + keywords=kwargs, + params=parameters + ) + @force_default(defaults=["parameters"], default_types=["dict"]) def query_changes(self: object, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: """Search for changes within your environment. Returns one or more change IDs. @@ -1154,6 +1325,9 @@ def query_rule_groups(self: object, parameters: dict = None, **kwargs) -> Dict[s # This method name aligns to the operation ID in the API but # does not conform to snake_case / PEP8 and is defined here # for backwards compatibility / ease of use purposes + getActionsMixin0 = get_actions + startActions = start_actions + getContents = get_contents updatePolicyHostGroups = update_policy_host_groups updatePolicyPrecedence = update_policy_precedence updatePolicyRuleGroups = update_policy_rule_groups @@ -1175,6 +1349,8 @@ def query_rule_groups(self: object, parameters: dict = None, **kwargs) -> Dict[s deleteRuleGroups = delete_rule_groups updateRuleGroups = update_rule_group getChanges = get_changes + signalChangesExternal = signal_changes + queryActionsMixin0 = query_actions queryChanges = query_changes highVolumeQueryChanges = query_changes_scroll queryRuleGroups = query_rule_groups diff --git a/tests/test_filevantage.py b/tests/test_filevantage.py index 0c1779777..bfd6059df 100644 --- a/tests/test_filevantage.py +++ b/tests/test_filevantage.py @@ -72,7 +72,12 @@ def service_filevantage_remaining_tests(self): "update_rule_group" : falcon.update_rule_group(), "query_policies": falcon.query_policies(), "query_scheduled_exclusions": falcon.query_scheduled_exclusions(), - "query_rule_groups": falcon.query_rule_groups(type="WindowsFiles") + "query_rule_groups": falcon.query_rule_groups(type="WindowsFiles"), + "getActionsMixin0": falcon.get_actions(ids="123456"), + "startActions": falcon.start_actions(change_ids="123456", comment="whatever", operation="whatever"), + "getContents": falcon.get_contents(id="123456", compress=True), + "signalChangesExternal": falcon.signal_changes("123456"), + "queryActionsMixin0": falcon.query_actions() } for key in tests: if tests[key]["status_code"] not in AllowedResponses: From d92c7da975f94dac57cc97d866d0a74c6a98afff Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Tue, 4 Jun 2024 12:02:23 -0400 Subject: [PATCH 28/30] Add ExposureManagement service collection --- src/falconpy/__init__.py | 3 +- src/falconpy/_endpoint/__init__.py | 2 + .../_endpoint/_exposure_management.py | 236 ++++++++++++++ src/falconpy/_endpoint/deprecated/__init__.py | 2 + .../deprecated/_exposure_management.py | 236 ++++++++++++++ src/falconpy/_payload/__init__.py | 3 +- src/falconpy/_payload/_exposure_management.py | 78 +++++ src/falconpy/exposure_management.py | 290 ++++++++++++++++++ tests/test_exposure_management.py | 65 ++++ 9 files changed, 913 insertions(+), 2 deletions(-) create mode 100644 src/falconpy/_endpoint/_exposure_management.py create mode 100644 src/falconpy/_endpoint/deprecated/_exposure_management.py create mode 100644 src/falconpy/_payload/_exposure_management.py create mode 100644 src/falconpy/exposure_management.py create mode 100644 tests/test_exposure_management.py diff --git a/src/falconpy/__init__.py b/src/falconpy/__init__.py index 1105000af..066336d0f 100644 --- a/src/falconpy/__init__.py +++ b/src/falconpy/__init__.py @@ -108,6 +108,7 @@ from .discover import Discover from .drift_indicators import DriftIndicators from .event_streams import EventStreams +from .exposure_management import ExposureManagement from .falcon_complete_dashboard import CompleteDashboard from .falcon_container import FalconContainer from .falconx_sandbox import FalconXSandbox @@ -197,7 +198,7 @@ "SDKDeprecationWarning", "ConfigurationAssessmentEvaluationLogic", "ConfigurationAssessment", "ContainerAlerts", "ContainerDetections", "ContainerImages", "ContainerPackages", "ContainerVulnerabilities", "DriftIndicators", "UnidentifiedContainers", - "ImageAssessmentPolicies", "APIIntegrations", "ThreatGraph" + "ImageAssessmentPolicies", "APIIntegrations", "ThreatGraph", "ExposureManagement" ] """ This is free and unencumbered software released into the public domain. diff --git a/src/falconpy/_endpoint/__init__.py b/src/falconpy/_endpoint/__init__.py index 7481db82f..45dad8fee 100644 --- a/src/falconpy/_endpoint/__init__.py +++ b/src/falconpy/_endpoint/__init__.py @@ -59,6 +59,7 @@ from ._discover import _discover_endpoints from ._drift_indicators import _drift_indicators_endpoints from ._event_streams import _event_streams_endpoints +from ._exposure_management import _exposure_management_endpoints from ._falcon_complete_dashboard import _falcon_complete_dashboard_endpoints from ._falcon_container import _falcon_container_endpoints from ._falconx_sandbox import _falconx_sandbox_endpoints @@ -129,6 +130,7 @@ api_endpoints.extend(_discover_endpoints) api_endpoints.extend(_drift_indicators_endpoints) api_endpoints.extend(_event_streams_endpoints) +api_endpoints.extend(_exposure_management_endpoints) api_endpoints.extend(_falcon_complete_dashboard_endpoints) api_endpoints.extend(_falcon_container_endpoints) api_endpoints.extend(_falconx_sandbox_endpoints) diff --git a/src/falconpy/_endpoint/_exposure_management.py b/src/falconpy/_endpoint/_exposure_management.py new file mode 100644 index 000000000..8bbc6bb8c --- /dev/null +++ b/src/falconpy/_endpoint/_exposure_management.py @@ -0,0 +1,236 @@ +"""Internal API endpoint constant library. + + _______ __ _______ __ __ __ +| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----. +|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__| +|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____| +|: 1 | |: 1 | +|::.. . | CROWDSTRIKE FALCON |::.. . | FalconPy +`-------' `-------' + +OAuth2 API - Customer SDK + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +""" + +_exposure_management_endpoints = [ + [ + "aggregate_external_assets", + "POST", + "/fem/aggregates/external-assets/v1", + "Returns external assets aggregates.", + "exposure_management", + [ + { + "description": "Aggregation specification.", + "name": "body", + "in": "body", + "required": True + } + ] + ], + [ + "blob_download_external_assets", + "GET", + "/fem/entities/blobs-download/v1", + "Download the entire contents of the blob. The relative link to this endpoint is returned in the GET " + "/entities/external-assets/v1 request.", + "exposure_management", + [ + { + "type": "string", + "description": "The Asset ID", + "name": "assetId", + "in": "query", + "required": True + }, + { + "type": "string", + "description": "The File Hash", + "name": "hash", + "in": "query", + "required": True + } + ] + ], + [ + "blob_preview_external_assets", + "GET", + "/fem/entities/blobs-preview/v1", + "Download a preview of the blob. The relative link to this endpoint is returned in the GET " + "/entities/external-assets/v1 request.", + "exposure_management", + [ + { + "type": "string", + "description": "The Asset ID", + "name": "assetId", + "in": "query", + "required": True + }, + { + "type": "string", + "description": "The File Hash", + "name": "hash", + "in": "query", + "required": True + } + ] + ], + [ + "get_external_assets", + "GET", + "/fem/entities/external-assets/v1", + "Get details on external assets by providing one or more IDs.", + "exposure_management", + [ + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "description": "One or more asset IDs (max: 100). Find asset IDs with GET `/fem/queries/external-assets/v1`", + "name": "ids", + "in": "query", + "required": True + } + ] + ], + [ + "patch_external_assets", + "PATCH", + "/fem/entities/external-assets/v1", + "Update the details of external assets.", + "exposure_management", + [ + { + "name": "body", + "in": "body", + "required": True + } + ] + ], + [ + "query_external_assets", + "GET", + "/fem/queries/external-assets/v1", + "Get a list of external asset IDs that match the provided filter conditions. Use these IDs with the " + "/entities/external-assets/v1 endpoints", + "exposure_management", + [ + { + "type": "string", + "description": "Starting index of result set from which to return IDs.", + "name": "offset", + "in": "query" + }, + { + "type": "integer", + "description": "Number of IDs to return.", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "description": "Order by fields.", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "description": "Filter assets using an FQL query. Common filter options " + "include:
    • asset_type:'ip'
    • last_seen_timestamp:>'now-7d'
    \n\t\t\t
    Available filter " + "fields that support exact match: asset_id, asset_type, confidence, connectivity_status, criticality, " + "criticality_description, criticality_timestamp, criticality_username, data_providers, discovered_by, " + "dns_domain.fqdn, dns_domain.isps, dns_domain.parent_domain, dns_domain.resolved_ips, " + "dns_domain.services.applications.category, dns_domain.services.applications.cpe, " + "dns_domain.services.applications.name, dns_domain.services.applications.vendor, " + "dns_domain.services.applications.version, dns_domain.services.cloud_provider, dns_domain.services.cpes, " + "dns_domain.services.hosting_provider, dns_domain.services.last_seen, dns_domain.services.platform_name, " + "dns_domain.services.port, dns_domain.services.protocol, dns_domain.services.protocol_port, " + "dns_domain.services.status, dns_domain.services.status_code, dns_domain.services.transport, dns_domain.type, " + "first_seen, id, internet_exposure, ip.asn, ip.cloud_vm.description, ip.cloud_vm.instance_id, " + "ip.cloud_vm.lifecycle, ip.cloud_vm.mac_address, ip.cloud_vm.owner_id, ip.cloud_vm.platform, " + "ip.cloud_vm.private_ip, ip.cloud_vm.public_ip, ip.cloud_vm.region, ip.cloud_vm.security_groups, " + "ip.cloud_vm.source, ip.cloud_vm.status, ip.fqdns, ip.ip_address, ip.isp, ip.location.area_code, " + "ip.location.city, ip.location.country_code, ip.location.country_name, ip.location.postal_code, " + "ip.location.region_code, ip.location.region_name, ip.location.timezone, ip.ptr, ip.aid, " + "ip.services.applications.category, ip.services.applications.cpe, ip.services.applications.name, " + "ip.services.applications.vendor, ip.services.applications.version, ip.services.cloud_provider, " + "ip.services.cpes, ip.services.first_seen, ip.services.last_seen, ip.services.platform_name, ip.services.port, " + "ip.services.protocol, ip.services.protocol_port, ip.services.status, ip.services.status_code, " + "ip.services.transport, last_seen, manual, perimeter, subsidiaries.id, subsidiaries.name, triage.action, " + "triage.assigned_to, triage.status, triage.updated_by, triage.updated_timestamp\n\t\t\t
    Available filter " + "fields that supports wildcard (*): asset_id, asset_type, confidence, connectivity_status, criticality, " + "criticality_username, data_providers, discovered_by, dns_domain.fqdn, dns_domain.isps, " + "dns_domain.parent_domain, dns_domain.resolved_ips, dns_domain.services.applications.category, " + "dns_domain.services.applications.cpe, dns_domain.services.applications.name, " + "dns_domain.services.applications.vendor, dns_domain.services.applications.version, " + "dns_domain.services.cloud_provider, dns_domain.services.cpes, dns_domain.services.hosting_provider, " + "dns_domain.services.id, dns_domain.services.platform_name, dns_domain.services.port, " + "dns_domain.services.protocol, dns_domain.services.protocol_port, dns_domain.services.status, " + "dns_domain.services.status_code, dns_domain.services.transport, dns_domain.type, id, internet_exposure, " + "ip.asn, ip.cloud_vm.instance_id, ip.cloud_vm.lifecycle, ip.cloud_vm.mac_address, ip.cloud_vm.owner_id, " + "ip.cloud_vm.platform, ip.cloud_vm.private_ip, ip.cloud_vm.public_ip, ip.cloud_vm.region, " + "ip.cloud_vm.security_groups, ip.cloud_vm.source, ip.cloud_vm.status, ip.fqdns, ip.ip_address, ip.isp, " + "ip.location.area_code, ip.location.city, ip.location.country_code, ip.location.country_name, " + "ip.location.postal_code, ip.location.region_code, ip.location.region_name, ip.location.timezone, ip.ptr, " + "ip.aid, ip.services.applications.category, ip.services.applications.cpe, ip.services.applications.name, " + "ip.services.applications.vendor, ip.services.applications.version, ip.services.cloud_provider, " + "ip.services.cpes, ip.services.platform_name, ip.services.port, ip.services.protocol, " + "ip.services.protocol_port, ip.services.status, ip.services.status_code, ip.services.transport, manual, " + "perimeter, subsidiaries.id, subsidiaries.name, triage.action, triage.assigned_to, triage.status, " + "triage.updated_by\n\t\t\t
    Available filter fields that supports in ([v1, v2]): asset_id, asset_type, " + "confidence, connectivity_status, criticality, criticality_username, data_providers, discovered_by, " + "dns_domain.fqdn, dns_domain.isps, dns_domain.parent_domain, dns_domain.services.applications.category, " + "dns_domain.services.applications.cpe, dns_domain.services.applications.name, " + "dns_domain.services.applications.vendor, dns_domain.services.applications.version, " + "dns_domain.services.cloud_provider, dns_domain.services.cpes, dns_domain.services.id, " + "dns_domain.services.platform_name, dns_domain.services.port, dns_domain.services.protocol, " + "dns_domain.services.protocol_port, dns_domain.services.status, dns_domain.services.status_code, " + "dns_domain.services.transport, dns_domain.type, id, internet_exposure, ip.asn, ip.cloud_vm.instance_id, " + "ip.cloud_vm.lifecycle, ip.cloud_vm.mac_address, ip.cloud_vm.owner_id, ip.cloud_vm.platform, " + "ip.cloud_vm.region, ip.cloud_vm.security_groups, ip.cloud_vm.source, ip.cloud_vm.status, ip.fqdns, ip.isp, " + "ip.location.area_code, ip.location.city, ip.location.country_code, ip.location.country_name, " + "ip.location.postal_code, ip.location.region_code, ip.location.region_name, ip.location.timezone, ip.ptr, " + "ip.aid, ip.services.applications.category, ip.services.applications.cpe, ip.services.applications.name, " + "ip.services.applications.vendor, ip.services.applications.version, ip.services.cloud_provider, " + "ip.services.cpes, ip.services.platform_name, ip.services.port, ip.services.protocol, " + "ip.services.protocol_port, ip.services.status, ip.services.status_code, ip.services.transport, manual, " + "perimeter, subsidiaries.id, subsidiaries.name, triage.action, triage.assigned_to, triage.status, " + "triage.updated_by\n\t\t\t
    Available filter fields that supports range comparisons (>, <, >=, <=): " + "criticality_timestamp, dns_domain.resolved_ips, dns_domain.services.first_seen, dns_domain.services.last_seen, " + " dns_domain.services.port, dns_domain.services.status_code, first_seen, ip.cloud_vm.private_ip, " + "ip.cloud_vm.public_ip, ip.ip_address, ip.services.first_seen, ip.services.last_seen, ip.services.port, " + "ip.services.status_code, last_seen, triage.updated_timestamp\n\t\t\t
    All filter fields and operations " + "supports negation (!).", + "name": "filter", + "in": "query" + } + ] + ] +] diff --git a/src/falconpy/_endpoint/deprecated/__init__.py b/src/falconpy/_endpoint/deprecated/__init__.py index 0c86c921a..82d7c9bf7 100644 --- a/src/falconpy/_endpoint/deprecated/__init__.py +++ b/src/falconpy/_endpoint/deprecated/__init__.py @@ -34,6 +34,7 @@ from ._custom_ioa import _custom_ioa_endpoints from ._d4c_registration import _d4c_registration_endpoints from ._discover import _discover_endpoints +from ._exposure_management import _exposure_management_endpoints from ._fdr import _fdr_endpoints from ._firewall_management import _firewall_management_endpoints from ._hosts import _hosts_endpoints @@ -52,6 +53,7 @@ _custom_ioa_deprecated = _custom_ioa_endpoints _d4c_registration_deprecated = _d4c_registration_endpoints _discover_deprecated = _discover_endpoints +_exposure_management_deprecated = _exposure_management_endpoints _fdr_deprecated = _fdr_endpoints _firewall_management_deprecated = _firewall_management_endpoints _hosts_deprecated = _hosts_endpoints diff --git a/src/falconpy/_endpoint/deprecated/_exposure_management.py b/src/falconpy/_endpoint/deprecated/_exposure_management.py new file mode 100644 index 000000000..9a2326d2e --- /dev/null +++ b/src/falconpy/_endpoint/deprecated/_exposure_management.py @@ -0,0 +1,236 @@ +"""Internal API endpoint constant library (deprecated operations). + + _______ __ _______ __ __ __ +| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----. +|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__| +|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____| +|: 1 | |: 1 | +|::.. . | CROWDSTRIKE FALCON |::.. . | FalconPy +`-------' `-------' + +OAuth2 API - Customer SDK + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +""" + +_exposure_management_endpoints = [ + [ + "aggregate-external-assets", + "POST", + "/fem/aggregates/external-assets/v1", + "Returns external assets aggregates.", + "exposure_management", + [ + { + "description": "Aggregation specification.", + "name": "body", + "in": "body", + "required": True + } + ] + ], + [ + "blob-download-external-assets", + "GET", + "/fem/entities/blobs-download/v1", + "Download the entire contents of the blob. The relative link to this endpoint is returned in the GET " + "/entities/external-assets/v1 request.", + "exposure_management", + [ + { + "type": "string", + "description": "The Asset ID", + "name": "assetId", + "in": "query", + "required": True + }, + { + "type": "string", + "description": "The File Hash", + "name": "hash", + "in": "query", + "required": True + } + ] + ], + [ + "blob-preview-external-assets", + "GET", + "/fem/entities/blobs-preview/v1", + "Download a preview of the blob. The relative link to this endpoint is returned in the GET " + "/entities/external-assets/v1 request.", + "exposure_management", + [ + { + "type": "string", + "description": "The Asset ID", + "name": "assetId", + "in": "query", + "required": True + }, + { + "type": "string", + "description": "The File Hash", + "name": "hash", + "in": "query", + "required": True + } + ] + ], + [ + "get-external-assets", + "GET", + "/fem/entities/external-assets/v1", + "Get details on external assets by providing one or more IDs.", + "exposure_management", + [ + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "description": "One or more asset IDs (max: 100). Find asset IDs with GET `/fem/queries/external-assets/v1`", + "name": "ids", + "in": "query", + "required": True + } + ] + ], + [ + "patch-external-assets", + "PATCH", + "/fem/entities/external-assets/v1", + "Update the details of external assets.", + "exposure_management", + [ + { + "name": "body", + "in": "body", + "required": True + } + ] + ], + [ + "query-external-assets", + "GET", + "/fem/queries/external-assets/v1", + "Get a list of external asset IDs that match the provided filter conditions. Use these IDs with the " + "/entities/external-assets/v1 endpoints", + "exposure_management", + [ + { + "type": "string", + "description": "Starting index of result set from which to return IDs.", + "name": "offset", + "in": "query" + }, + { + "type": "integer", + "description": "Number of IDs to return.", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "description": "Order by fields.", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "description": "Filter assets using an FQL query. Common filter options " + "include:
    • asset_type:'ip'
    • last_seen_timestamp:>'now-7d'
    \n\t\t\t
    Available filter " + "fields that support exact match: asset_id, asset_type, confidence, connectivity_status, criticality, " + "criticality_description, criticality_timestamp, criticality_username, data_providers, discovered_by, " + "dns_domain.fqdn, dns_domain.isps, dns_domain.parent_domain, dns_domain.resolved_ips, " + "dns_domain.services.applications.category, dns_domain.services.applications.cpe, " + "dns_domain.services.applications.name, dns_domain.services.applications.vendor, " + "dns_domain.services.applications.version, dns_domain.services.cloud_provider, dns_domain.services.cpes, " + "dns_domain.services.hosting_provider, dns_domain.services.last_seen, dns_domain.services.platform_name, " + "dns_domain.services.port, dns_domain.services.protocol, dns_domain.services.protocol_port, " + "dns_domain.services.status, dns_domain.services.status_code, dns_domain.services.transport, dns_domain.type, " + "first_seen, id, internet_exposure, ip.asn, ip.cloud_vm.description, ip.cloud_vm.instance_id, " + "ip.cloud_vm.lifecycle, ip.cloud_vm.mac_address, ip.cloud_vm.owner_id, ip.cloud_vm.platform, " + "ip.cloud_vm.private_ip, ip.cloud_vm.public_ip, ip.cloud_vm.region, ip.cloud_vm.security_groups, " + "ip.cloud_vm.source, ip.cloud_vm.status, ip.fqdns, ip.ip_address, ip.isp, ip.location.area_code, " + "ip.location.city, ip.location.country_code, ip.location.country_name, ip.location.postal_code, " + "ip.location.region_code, ip.location.region_name, ip.location.timezone, ip.ptr, ip.aid, " + "ip.services.applications.category, ip.services.applications.cpe, ip.services.applications.name, " + "ip.services.applications.vendor, ip.services.applications.version, ip.services.cloud_provider, " + "ip.services.cpes, ip.services.first_seen, ip.services.last_seen, ip.services.platform_name, ip.services.port, " + "ip.services.protocol, ip.services.protocol_port, ip.services.status, ip.services.status_code, " + "ip.services.transport, last_seen, manual, perimeter, subsidiaries.id, subsidiaries.name, triage.action, " + "triage.assigned_to, triage.status, triage.updated_by, triage.updated_timestamp\n\t\t\t
    Available filter " + "fields that supports wildcard (*): asset_id, asset_type, confidence, connectivity_status, criticality, " + "criticality_username, data_providers, discovered_by, dns_domain.fqdn, dns_domain.isps, " + "dns_domain.parent_domain, dns_domain.resolved_ips, dns_domain.services.applications.category, " + "dns_domain.services.applications.cpe, dns_domain.services.applications.name, " + "dns_domain.services.applications.vendor, dns_domain.services.applications.version, " + "dns_domain.services.cloud_provider, dns_domain.services.cpes, dns_domain.services.hosting_provider, " + "dns_domain.services.id, dns_domain.services.platform_name, dns_domain.services.port, " + "dns_domain.services.protocol, dns_domain.services.protocol_port, dns_domain.services.status, " + "dns_domain.services.status_code, dns_domain.services.transport, dns_domain.type, id, internet_exposure, " + "ip.asn, ip.cloud_vm.instance_id, ip.cloud_vm.lifecycle, ip.cloud_vm.mac_address, ip.cloud_vm.owner_id, " + "ip.cloud_vm.platform, ip.cloud_vm.private_ip, ip.cloud_vm.public_ip, ip.cloud_vm.region, " + "ip.cloud_vm.security_groups, ip.cloud_vm.source, ip.cloud_vm.status, ip.fqdns, ip.ip_address, ip.isp, " + "ip.location.area_code, ip.location.city, ip.location.country_code, ip.location.country_name, " + "ip.location.postal_code, ip.location.region_code, ip.location.region_name, ip.location.timezone, ip.ptr, " + "ip.aid, ip.services.applications.category, ip.services.applications.cpe, ip.services.applications.name, " + "ip.services.applications.vendor, ip.services.applications.version, ip.services.cloud_provider, " + "ip.services.cpes, ip.services.platform_name, ip.services.port, ip.services.protocol, " + "ip.services.protocol_port, ip.services.status, ip.services.status_code, ip.services.transport, manual, " + "perimeter, subsidiaries.id, subsidiaries.name, triage.action, triage.assigned_to, triage.status, " + "triage.updated_by\n\t\t\t
    Available filter fields that supports in ([v1, v2]): asset_id, asset_type, " + "confidence, connectivity_status, criticality, criticality_username, data_providers, discovered_by, " + "dns_domain.fqdn, dns_domain.isps, dns_domain.parent_domain, dns_domain.services.applications.category, " + "dns_domain.services.applications.cpe, dns_domain.services.applications.name, " + "dns_domain.services.applications.vendor, dns_domain.services.applications.version, " + "dns_domain.services.cloud_provider, dns_domain.services.cpes, dns_domain.services.id, " + "dns_domain.services.platform_name, dns_domain.services.port, dns_domain.services.protocol, " + "dns_domain.services.protocol_port, dns_domain.services.status, dns_domain.services.status_code, " + "dns_domain.services.transport, dns_domain.type, id, internet_exposure, ip.asn, ip.cloud_vm.instance_id, " + "ip.cloud_vm.lifecycle, ip.cloud_vm.mac_address, ip.cloud_vm.owner_id, ip.cloud_vm.platform, " + "ip.cloud_vm.region, ip.cloud_vm.security_groups, ip.cloud_vm.source, ip.cloud_vm.status, ip.fqdns, ip.isp, " + "ip.location.area_code, ip.location.city, ip.location.country_code, ip.location.country_name, " + "ip.location.postal_code, ip.location.region_code, ip.location.region_name, ip.location.timezone, ip.ptr, " + "ip.aid, ip.services.applications.category, ip.services.applications.cpe, ip.services.applications.name, " + "ip.services.applications.vendor, ip.services.applications.version, ip.services.cloud_provider, " + "ip.services.cpes, ip.services.platform_name, ip.services.port, ip.services.protocol, " + "ip.services.protocol_port, ip.services.status, ip.services.status_code, ip.services.transport, manual, " + "perimeter, subsidiaries.id, subsidiaries.name, triage.action, triage.assigned_to, triage.status, " + "triage.updated_by\n\t\t\t
    Available filter fields that supports range comparisons (>, <, >=, <=): " + "criticality_timestamp, dns_domain.resolved_ips, dns_domain.services.first_seen, dns_domain.services.last_seen, " + " dns_domain.services.port, dns_domain.services.status_code, first_seen, ip.cloud_vm.private_ip, " + "ip.cloud_vm.public_ip, ip.ip_address, ip.services.first_seen, ip.services.last_seen, ip.services.port, " + "ip.services.status_code, last_seen, triage.updated_timestamp\n\t\t\t
    All filter fields and operations " + "supports negation (!).", + "name": "filter", + "in": "query" + } + ] + ] +] diff --git a/src/falconpy/_payload/__init__.py b/src/falconpy/_payload/__init__.py index b5940530c..6ec88e10f 100644 --- a/src/falconpy/_payload/__init__.py +++ b/src/falconpy/_payload/__init__.py @@ -67,6 +67,7 @@ cspm_service_account_validate_payload ) from ._device_control_policy import device_policy_payload, default_device_policy_config_payload +from ._exposure_management import fem_asset_payload from ._falconx import falconx_payload from ._filevantage import ( filevantage_rule_group_payload, @@ -129,5 +130,5 @@ "image_policy_payload", "image_exclusions_payload", "image_group_payload", "workflow_definition_payload", "workflow_human_input", "workflow_mock_payload", "cspm_service_account_validate_payload", "api_plugin_command_payload", "mobile_enrollment_payload", - "filevantage_start_payload" + "filevantage_start_payload", "fem_asset_payload" ] diff --git a/src/falconpy/_payload/_exposure_management.py b/src/falconpy/_payload/_exposure_management.py new file mode 100644 index 000000000..f5de5a2cd --- /dev/null +++ b/src/falconpy/_payload/_exposure_management.py @@ -0,0 +1,78 @@ +"""Internal payload handling library - Exposure Management. + + _______ __ _______ __ __ __ +| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----. +|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__| +|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____| +|: 1 | |: 1 | +|::.. . | CROWDSTRIKE FALCON |::.. . | FalconPy +`-------' `-------' + +OAuth2 API - Customer SDK + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +""" + + +def fem_asset_payload(passed_keywords: dict) -> dict: + """Craft a properly formatted exposure management asset update payload. + + { + "assets": [ + { + "cid": "string", + "criticality": "string", + "criticality_description": "string", + "id": "string", + "triage": { + "action": "string", + "assigned_to": "string", + "description": "string", + "status": "string" + } + } + ] + } + """ + returned = { + "assets": [] + } + keys = ["cid", "criticality", "criticality_description", "id", + "action", "assigned_to", "description", "status" + ] + triage_keys = ["action", "assigned_to", "description", "status"] + item = {} + for key in keys: + if passed_keywords.get(key, None): + if key in triage_keys: + if "triage" not in item: + item["triage"] = {} + item["triage"][key] = passed_keywords.get(key, None) + else: + item[key] = passed_keywords.get(key, None) + returned["assets"].append(item) + + return returned diff --git a/src/falconpy/exposure_management.py b/src/falconpy/exposure_management.py new file mode 100644 index 000000000..5c4e3f49d --- /dev/null +++ b/src/falconpy/exposure_management.py @@ -0,0 +1,290 @@ +"""CrowdStrike Falcon ExposureManagement API interface class. + + _______ __ _______ __ __ __ +| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----. +|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__| +|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____| +|: 1 | |: 1 | +|::.. . | CROWDSTRIKE FALCON |::.. . | FalconPy +`-------' `-------' + +OAuth2 API - Customer SDK + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +""" +from typing import Dict, Union +from ._util import force_default, process_service_request, handle_single_argument +from ._payload import aggregate_payload, fem_asset_payload +from ._service_class import ServiceClass +from ._endpoint._exposure_management import _exposure_management_endpoints as Endpoints + + +class ExposureManagement(ServiceClass): + """The only requirement to instantiate an instance of this class is one of the following. + + - a valid client_id and client_secret provided as keywords. + - a credential dictionary with client_id and client_secret containing valid API credentials + { + "client_id": "CLIENT_ID_HERE", + "client_secret": "CLIENT_SECRET_HERE" + } + - a previously-authenticated instance of the authentication service class (oauth2.py) + - a valid token provided by the authentication service class (oauth2.py) + """ + + @force_default(defaults=["body"], default_types=["list"]) + def aggregate_assets(self: object, body: list = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Get detect aggregates as specified via json in request body. + + Keyword arguments: + body -- full body payload, not required when using other keywords. + [ + { + "date_ranges": [ + { + "from": "string", + "to": "string" + } + ], + "exclude": "string", + "field": "string", + "filter": "string", + "from": 0, + "include": "string", + "interval": "string", + "max_doc_count": 0, + "min_doc_count": 0, + "missing": "string", + "name": "string", + "q": "string", + "ranges": [ + { + "From": 0, + "To": 0 + } + ], + "size": 0, + "sort": "string", + "sub_aggregates": [ + null + ], + "time_zone": "string", + "type": "string" + } + ] + date_ranges -- If peforming a date range query specify the from and to date ranges. + These can be in common date formats like 2019-07-18 or now. + List of dictionaries. + exclude -- Fields to exclude. String. + field -- Term you want to aggregate on. If doing a date_range query, + this is the date field you want to apply the date ranges to. String. + filter -- Optional filter criteria in the form of an FQL query. + For more information about FQL queries, see our FQL documentation in Falcon. + String. + from -- Integer. + include -- Fields to include. String. + interval -- String. + max_doc_count -- Maximum number of documents. Integer. + min_doc_count -- Minimum number of documents. Integer. + missing -- String. + name -- Scan name. String. + q -- FQL syntax. String. + ranges -- List of dictionaries. + size -- Integer. + sort -- FQL syntax. String. + sub_aggregates -- List of strings. + time_zone -- String. + type -- String. + + This method only supports keywords for providing arguments. + + Returns: dict object containing API response. + + HTTP Method: POST + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/exposure-management/aggregate-external-assets + """ + if not body: + body = [aggregate_payload(submitted_keywords=kwargs)] + + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="aggregate_external_assets", + body=body + ) + + @force_default(defaults=["parameters"], default_types=["dict"]) + def download_assets(self: object, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Download the entire contents of the blob. The relative link to this endpoint is returned from query_external_assets. + + Keyword arguments: + assetId -- The Asset ID. String. + hash -- The File Hash. String. + parameters -- Full parameters payload dictionary. Not required if using other keywords. + + This method only supports keywords for providing arguments. + + Returns: dict object containing API response. + + HTTP Method: GET + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/exposure-management/blob-download-external-assets + """ + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="blob_download_external_assets", + keywords=kwargs, + params=parameters + ) + + @force_default(defaults=["parameters"], default_types=["dict"]) + def preview_assets(self: object, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Download a preview of the blob. The relative link to this endpoint is returned from query_external_assets. + + Keyword arguments: + assetId -- The Asset ID. String. + hash -- The File Hash. String. + parameters -- Full parameters payload dictionary. Not required if using other keywords. + + This method only supports keywords for providing arguments. + + Returns: dict object containing API response. + + HTTP Method: GET + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/exposure-management/blob-preview-external-assets + """ + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="blob_preview_external_assets", + keywords=kwargs, + params=parameters + ) + + @force_default(defaults=["parameters"], default_types=["dict"]) + def get_assets(self: object, *args, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Get details on external assets by providing one or more IDs. + + Keyword arguments: + ids -- One or more asset IDs (max: 100). Find asset IDs with query_external_assets. + String or list of strings. + parameters -- Full parameters payload dictionary. Not required if using other keywords. + + This method only supports keywords for providing arguments. + + Returns: dict object containing API response. + + HTTP Method: GET + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/exposure-management/get-external-assets + """ + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="get_external_assets", + keywords=kwargs, + params=handle_single_argument(args, parameters, "ids") + ) + + @force_default(defaults=["body"], default_types=["dict"]) + def update_assets(self: object, body: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Update the details of external assets. + + Keyword arguments: + action -- The asset triage action. String. + assigned_to -- The user assigned to triage the asset. String. + body -- Full body payload as a dictionary. Not required when using other keywords. + cid -- Falcon Customer ID. String. + criticality -- The criticality level manually assigned to this asset. String. + criticality_description -- The criticality description assigned to this asset. String. + description -- The asset triage description. String. + id -- The unique ID of the asset. String. + status -- The asset trriage status. String. + + This method only supports keywords for providing arguments. + + Returns: dict object containing API response. + + HTTP Method: PATCH + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/exposure-management/patch-external-assets + """ + if not body: + body = fem_asset_payload(passed_keywords=kwargs) + + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="patch_external_assets", + keywords=kwargs, + body=body + ) + + @force_default(defaults=["parameters"], default_types=["dict"]) + def query_assets(self: object, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Get a list of external asset IDs that match the provided filter conditions. + + Keyword arguments: + offset -- Starting index of result set from which to return IDs. Integer. + limit -- Number of IDs to return. Integer. + sort -- Order by fields. String. + filter -- Filter assets using an FQL query. String. + parameters -- Full parameters payload dictionary. Not required if using other keywords. + + This method only supports keywords for providing arguments. + + Returns: dict object containing API response. + + HTTP Method: GET + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/exposure-management/query-external-assets + """ + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="query_external_assets", + keywords=kwargs, + params=parameters + ) + + # These method names align to the operation IDs in the API but + # does not conform to snake_case / PEP8 and are defined here + # for backwards compatibility / ease of use purposes + aggregate_external_assets = aggregate_assets + blob_download_external_assets = download_assets + blob_preview_external_assets = preview_assets + get_external_assets = get_assets + patch_external_assets = update_assets + query_external_assets = query_assets diff --git a/tests/test_exposure_management.py b/tests/test_exposure_management.py new file mode 100644 index 000000000..20e546609 --- /dev/null +++ b/tests/test_exposure_management.py @@ -0,0 +1,65 @@ +# test_exposure_management.py +# This class tests the exposure management service class + +# import json +import os +import sys + +# Authentication via the test_authorization.py +from tests import test_authorization as Authorization + +# Import our sibling src folder into the path +sys.path.append(os.path.abspath('src')) +# Classes to test - manually imported from sibling folder +from falconpy import ExposureManagement + +auth = Authorization.TestAuthorization() +config = auth.getConfigObject() +falcon = ExposureManagement(auth_object=config) +AllowedResponses = [200, 201, 207, 400, 403, 404, 429] + + +class TestExposureManagement: + def test_all_code_paths(self): + error_checks = True + tests = { + "aggregate_assets": falcon.aggregate_assets( + date_ranges=[ + { + "from": "string", + "to": "string" + } + ], + field="string", + filter="string", + interval="string", + min_doc_count=0, + missing="string", + name="string", + q="string", + ranges=[ + { + "From": 0, + "To": 0 + } + ], + size=0, + sort="string", + sub_aggregates=[ + "string" + ], + time_zone="string", + type="string" + ), + "blob_download_external_assets": falcon.download_assets(assetId="whatever", hash="whatever"), + "blob_preview_external_assets": falcon.preview_assets(assetId="whatever", hash="whatever"), + "get_external_assets": falcon.get_assets("123456"), + "patch_external_assets": falcon.update_assets(cid="whatever", action="whatever"), + "query_external_assets": falcon.query_assets(), + } + for key in tests: + if tests[key]["status_code"] not in AllowedResponses: + error_checks = False + # print(key) + # print(tests[key]) + assert error_checks From b9abb05276a7a2d8b4358da70001768e1617e602 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Tue, 4 Jun 2024 07:26:52 -0400 Subject: [PATCH 29/30] Update CHANGELOG.md --- CHANGELOG.md | 153 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 521f840ff..a8670ad42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,156 @@ +# Version 1.4.4 +## Added features and functionality ++ Added: Added new __API Integrations__ service collection with two new operations, __GetCombinedPluginConfigs__ and __ExecuteCommand__. + - `__init__.py` + - `_endpoint/__init__.py` + - `_endpoint/_api_integrations.py` + - `_payload/__init__.py` + - `_payload/_api_integrations.py` + - `api_integrations.py` + > Unit testing expanded to complete code coverage. + - `tests/test_api_integrations.py` + - `tests/test_uber.py` + ++ Added: Added new allowed parameters for the _GetCSPMAwsAccountScriptsAttachment_ operation within the __CSPM Registration__ service collection. + - `_endpoint/_cspm_registration.py` + - `cspm_registration.py` + ++ Added: Added one new operation (_update_rules_v2_) to the __Custom IOA__ service collection. + - `_endpoint/_custom_ioa.py` + - `_endpoint/deprecated/_custom_ioa.py` + - `custom_ioa.py` + > Unit testing expanded to complete code coverage. + - `tests/test_custom_ioa.py` + ++ Added: Added new allowed parameters for the _GetD4CAWSAccountScriptsAttachment_ operation within the __D4C Registration__ service collection. + - `_endpoint/_d4c_registration.py` + - `d4c_registration.py` + ++ Added: Added new __Exposure Management__ service collection with 6 new operations. + - _aggregate_external_assets_ + - _blob_download_external_assets_ + - _blob_preview_external_assets_ + - _get_external_assets_ + - _patch_external_assets_ + - _query_external_assets_ + - `_endpoint/__init__.py` + - `_endpoint/_exposure_management.py` + - `_endpoint/deprecated/__init__.py` + - `_endpoint/deprecated/_exposure_management.py` + - `_payload/__init__.py` + - `_payload/_exposure_management.py` + - `__init__.py` + - `exposure_management.py` + > Unit testing expanded to complete code coverage. + - `tests/test_exposure_management.py` + ++ Added: Added five new operations to the __FileVantage__ service collection. + - _getActionsMixin0_ + - _startActions_ + - _getContents_ + - _signalChangesExternal_ + - _queryActionsMixin0_ + - `_constant/__init__.py` + - `_endpoint/_filevantage.py` + - `_payload/__init__.py` + - `_payload/_filevantage.py` + - `filevantage.py` + > Unit testing expanded to complete code coverage. + - `tests/test_filevantage.py` + ++ Added: Added `cql-master`, `cql-update`, and `cql-changelog` as allowed options for the `type` keyword within the _GetLatestIntelRuleFile_ and _QueryIntelRuleIds_ operations (__Intel__ service collection). + - `_endpoint/_intel.py` + - `intel.py` + ++ Added: Added one new operation (_RequestDeviceEnrollmentV4_) to the __Mobile Enrollment__ service collection. + - `_endpoint/_mobile_enrollment.py` + - `_payload/__init__.py` + - `_payload/_mobile_enrollment.py` + - `mobile_enrollment.py` + > Unit testing expanded to complete code coverage. + - `tests/test_mobile_enrollment.py` + ++ Added: Added new __ThreatGraph__ service collection with 5 new operations. + - `__init__.py` + - `_endpoint/__init__.py` + - `_endpoint/_threatgraph.py` + - `_util/_functions.py` + - `_util/uber.py` + - `threatgraph.py` + > Unit testing expanded to complete code coverage. + - `tests/test_threatgraph.py` + ++ Added: Added two new operations (_WorkflowActivitiesCombined_ and _WorkflowTriggersCombined_) to the __Workflows__ service collection. + - `_endpoint/_workflows.py` + - `workflows.py` + > Unit testing expanded to complete code coverage. + - `tests/test_workflows.py` + +## Issues resolved ++ Fixed: Resolved parameter abstraction issue when leveraging form data payloads with certain API operations. Closes #1160. + - `_util/__init__.py` + - `_util/_functions.py` + - `falconx_sandbox.py` + - `foundry_logscale.py` + - `message_center.py` + - `sample_uploads.py` + - `workflows.py` + > Unit testing expanding to complete code coverage. + - `test_falconx_sandbox.py` + - `test_message_center.py` + - `test_sample_uploads.py` + - `test_workflows.py` + - Thanks go out to @Destom for reporting this issue! 🙇 + ++ Fixed: Resolved collision with the `action` keyword argument within the Uber Class and API operations using this string as a key. Closes #1161. + - `_util/_uber.py` + - `api_complete/_advanced.py` + - Thanks go out to @Don-Swanson-Adobe for identifying and reporting this issue! 🙇 + ++ Fixed: Resolved potential ValueError when providing invalid values to version comparison method. + - `_version.py` + > Unit testing expanded to complete code coverage. + - `test_timeout.py` + +## Other ++ Adjusted: Unit testing adjusted to allow 204 responses from _DeletePolicy_ operation testing. + - `test_image_assessment_policies.py` + ++ Expanded: Unit testing expanded to test context authentication when `base_url` is not specified. + - `test_zero_trust_assessment.py` + ++ Updated: Updated enumerator for the `sort` parameter definition for the _QueryCasesIdsByFilter_ operation (__Message Center__ service collection). + - `_endpoint/_message_center.py` + ++ Updated: Updated `filter` parameter description for the _query_iot_hosts_ operation within the __Discover__ service collection. + - `_endpoint/_discover.py` + - `_endpoint/deprecated/_discover.py` + ++ Removed: Removed one operation from the __Drift Indicators__ service collection. + - _ReadDriftIndicatorEntities_ + - `_endpoint/_drift_indicators.py` + - `drift_indicators.py` + > Unit testing revised to complete code coverage. + - `tests/test_drift_indicators.py` + ++ Updated: Updated `sort` parameter description for the _query_rulesMixin0_ operation within the __Custom IOA__ service collection. + - `_endpoint/_custom_ioa.py` + - `_endpoint/deprecated/_custom_ioa.py` + ++ Removed: Removed three operations from the __Kubernetes Protection__ service collection. + - _ReadContainerEnrichment_ + - _ReadDeploymentEnrichment_ + - _ReadPodEnrichment_ + - `_endpoint/_kubernetes_protection.py` + - `kubernetes_protection.py` + > Unit testing revised to complete code coverage. + - `tests/test_kubernetes_protection.py` + ++ Updated: Updated `filter` parameter description for the _ReadRunningContainerImages_ operation within the __Kubernetes Protection__ service collection. + - `_endpoint/_kubernetes_protection.py` + +--- + # Version 1.4.3 ## Added features and functionality + Added: Context Authentication (supports Foundry execution environments). From f13eb5b80c9bfa64d8ed65ccdbaef52a7e4fd88c Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Tue, 4 Jun 2024 12:40:53 -0400 Subject: [PATCH 30/30] Update wordlist.txt --- .github/wordlist.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/wordlist.txt b/.github/wordlist.txt index 979600b5e..9638e59db 100644 --- a/.github/wordlist.txt +++ b/.github/wordlist.txt @@ -1365,3 +1365,17 @@ instantiation UpdateCSPMGCPServiceAccountsExt UpdateD RoemIko +GetCombinedPluginConfigs +CAWSAccountScriptsAttachment +getActionsMixin +startActions +getContents +signalChangesExternal +queryActionsMixin +RequestDeviceEnrollmentV +ThreatGraph +WorkflowActivitiesCombined +WorkflowTriggersCombined +Destom +ValueError +QueryCasesIdsByFilter