diff --git a/README.md b/README.md index b2c6c2d03..f6fc920c8 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # VMware Carbon Black Cloud Python SDK -**Latest Version:** 1.5.1 +**Latest Version:** 1.5.2
-**Release Date:** January 30, 2024 +**Release Date:** May 1, 2024 [![Coverage Status](https://coveralls.io/repos/github/carbonblack/carbon-black-cloud-sdk-python/badge.svg?t=Id6Baf)](https://coveralls.io/github/carbonblack/carbon-black-cloud-sdk-python) [![Codeship Status for carbonblack/carbon-black-cloud-sdk-python](https://app.codeship.com/projects/9e55a370-a772-0138-aae4-129773225755/status?branch=develop)](https://app.codeship.com/projects/402767) diff --git a/VERSION b/VERSION index 26ca59460..4cda8f19e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.1 +1.5.2 diff --git a/docs/audit-log.rst b/docs/audit-log.rst index e21b8dec4..f17f8a9b6 100644 --- a/docs/audit-log.rst +++ b/docs/audit-log.rst @@ -8,45 +8,58 @@ In the Carbon Black Cloud, *audit logs* are records of various organization-wide * Creation of connectors * LiveResponse events -The Audit Log API allows these records to be retrieved in JSON format, sorted by time in ascending order -(oldest records come first). The API call returns only *new* audit log records that have been added since -the last time the call was made using the same API Key ID. Once records have been returned, they are *cleared* -and will not be included in future responses. - -When reading audit log records using a *new* API key, the queue for reading audit logs will begin three days -earlier. This may lead to duplicate data if audit log records were previously read with a different API key. - -.. note:: - Future versions of the Carbon Black Cloud and this SDK will support a more flexible API for finding and retrieving - audit log records. This Guide will be rewritten to cover this when it is incorporated into the SDK. +The Audit Log API allows these records to be retrieved as objects, either by getting the most recent audit logs, or +through a flexible search API. API Permissions --------------- -To call this API function, use a custom API key created with a role containing the ``READ`` permission on +To call the Audit Log APIs, use a custom API key created with a role containing the ``READ`` permission on ``org.audits``. -Example of API Usage --------------------- +Retrieving Queued Audit Log Events +---------------------------------- + +The Carbon Black Cloud maintains a queue of audit log events for each API key, which is initialized with the last three +days of audit logs when the API key is created. This demonstrates how to read audit log events from the queue:: + + >>> from cbc_sdk import CBCloudAPI + >>> from cbc_sdk.platform import AuditLog + >>> api = CBCloudAPI(profile='sample') + >>> events = AuditLog.get_queued_auditlogs(api) + >>> for event in events: + ... print(f"{event.create_time}: {event.actor} {event.description}") + +Once audit log events have been retrieved from the queue, they are "cleared" and will not be included in future +responses to a ``get_queued_auditlogs()`` call. + +.. note:: + Reading queued audit log events using *different* API keys may lead to duplicate data. + +Searching for Audit Log Events +------------------------------ + +Audit log events may be searched for in a manner similar to other objects within the SDK:: -.. code-block:: python + # assume "api" contains our CBCloudAPI reference as above + >>> query = api.select(AuditLog).where("description:Logged in") + >>> query.sort_by("create_time") + >>> for event in query: + ... print(f"{event.create_time}: {event.actor} {event.description}") - import time - from cbc_sdk import CBCloudAPI - from cbc_sdk.platform import AuditLog +See also the :ref:`searching-guide` guide page for a more detailed discussion of searching. - cb = CBCloudAPI(profile='yourprofile') - running = True +Exporting Audit Log Events +-------------------------- - while running: - events_list = AuditLog.get_auditlogs(cb) - for event in events_list: - print(f"Event {event['eventId']}:") - for (k, v) in event.items(): - print(f"\t{k}: {v}") - # omitted: decide whether running should be set to False - if running: - time.sleep(5) +Any search query may also be used to export audit log data, in either CSV or JSON format:: + # assume "api" contains our CBCloudAPI reference as above + >>> query = api.select(AuditLog).where("description:Logged in") + >>> query.sort_by("create_time") + >>> job = query.export("csv") + >>> result = job.await_completion().result() + >>> print(result) -Check out the example script ``audit_log.py`` in the examples/platform directory on `GitHub `_. +Note that the ``export()`` call returns a ``Job`` object, as exports can take some time to complete. The results may +be obtained from the ``Job`` when the export process is completed. diff --git a/docs/authentication.rst b/docs/authentication.rst index 5405884f6..9ee343e9d 100644 --- a/docs/authentication.rst +++ b/docs/authentication.rst @@ -3,7 +3,6 @@ Authentication ============== - Carbon Black Cloud APIs require authentication to secure your data. There are several methods for authentication listed below. Every method requires @@ -52,9 +51,6 @@ Store the credential with a profile name, and reference the profile when creatin For more examples on Live Response, check :doc:`live-response` - - - Authentication Methods ---------------------- @@ -117,8 +113,9 @@ Authentication Methods With a File ^^^^^^^^^^^ Credentials may be supplied in a file that resembles a Windows ``.INI`` file in structure, which allows for -multiple "profiles" or sets of credentials to be supplied in a single file. The file format is backwards compatible with -CBAPI, so older files can continue to be used. +multiple "profiles" or sets of credentials to be supplied in a single file. The file format is backwards compatible +with CBAPI, so older files can continue to be used. The file must be encoded as UTF-8, or as UTF-16 using either +big-endian or little-endian format. **Example of a credentials file containing two profiles** diff --git a/docs/cbc_sdk.workload.rst b/docs/cbc_sdk.workload.rst index d2b369406..ed43d8d57 100644 --- a/docs/cbc_sdk.workload.rst +++ b/docs/cbc_sdk.workload.rst @@ -1,6 +1,14 @@ Workload Package ***************** +CIS Benchmarks +----------------------------------------- + +.. automodule:: cbc_sdk.workload.compliance_assessment + :members: + :inherited-members: + :show-inheritance: + NSX Remediation Module ----------------------------------------- diff --git a/docs/changelog.rst b/docs/changelog.rst index ae9707f40..2c05d4d9f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,5 +1,43 @@ Changelog ================================ +CBC SDK 1.5.2 - Released May 1, 2024 +------------------------------------ + +New Features: + +* Enhanced Audit Log support with search and export capabilities +* CIS Benchmarking: + + * Schedule compliance scans + * Search, create, update, and delete benchmark sets + * Search and modify benchmark rules within a benchmark set + * Search and export device summaries for benchmark sets + * Enable, disable, and trigger reassessment on benchmark sets or individual devices + * Search benchmark set summaries + * Search and export device compliance summaries + * Search and export rule compliance summaries + * Search rule results for devices + * Get and acknowledge compliance bundle version updates, show differences, get rule info + +Updates: + +* Added `collapse_field` parameter for process searches +* Added an exponential backoff for polling of Job completion status +* Added rule configurations for event reporting and sensor operation exclusions + +Bug Fixes: + +* Fixed implementation of iterable queries for consistency across the SDK +* Fixed parsing of credential files that are encoded in UTF-16 +* Fixed processing of Job so that it doesn't rely on an API call that doesn't give proper answers +* Fixed missing properties in Process + +Documentation: + +* Fixed documentation for Alert and Process to include links to the Developer Network field descriptions +* New example script for identifying devices that have checked in but have not sent any events +* Added guide page for Devices including searching and actions + CBC SDK 1.5.1 - Released January 30, 2024 ----------------------------------------- diff --git a/docs/compliance.rst b/docs/compliance.rst new file mode 100644 index 000000000..86fa3a037 --- /dev/null +++ b/docs/compliance.rst @@ -0,0 +1,114 @@ +Compliance Benchmarks +====== + +CIS benchmarks are configuration guidelines published by the Center for Internet Security. +The CIS Benchmark enable configuration and retrieval of Benchmark Sets and Rules in Carbon Black Cloud, and +retrieval of the results from scans performed using these Rules. + +For more information on CIS Benchmarks, see the `Center for Internet Security `_. +CIS benchmarks contain over 100 configuration guidelines created by a global community of cybersecurity experts to safeguard +various systems against attacks targeting configuration vulnerabilities. + +You can use all the operations shown in the API, such as retrieving, filtering, reaccessing and enabling/disabling the benchmark rules. +You can locate the full list of operations and attributes in the :py:mod:`ComplianceBenchmark() ` class. + +Resources +--------- +* `API Documentation `_ on Developer Network +* `User Guide `_ + +Retrieve Compliance Benchmarks +--------------- + +By using the following the example, you can retrieve the list of supported benchmarks + +.. code-block:: python + + >>> from cbc_sdk import CBCloudAPI + >>> from cbc_sdk.workload import ComplianceBenchmark + >>> api = CBCloudAPI(profile='sample') + >>> benchmark_query = api.select(ComplianceBenchmark) + >>> for benchmark in benchmark_query: + >>> print(benchmark) + ComplianceBenchmark object, bound to https://defense-test03.cbdtest.io. + ------------------------------------------------------------------------------- + + bundle_name: CIS Compliance - Microsoft Windows Server + create_time: 2023-03-20T13:44:10.923039Z + created_by: emuthu+csr@carbonblack.com + enabled: True + id: b7d1b266-d899-4e28-bae6-7619019447ba + name: CIS Windows Server Retail application Prod + os_family: WINDOWS_SERVER + release_time: 2023-07-10T13:55:59.274881Z + supported_os_info: [list:5 items]: + [0]: {'os_metadata_id': '1', 'os_type': 'WINDOWS', '... + [1]: {'os_metadata_id': '2', 'os_type': 'WINDOWS', '... + [2]: {'os_metadata_id': '3', 'os_type': 'WINDOWS', '... + [...] + type: Custom + update_time: 2024-04-15T21:24:43.283032Z + updated_by: + version: 1.0.0.4 + + +Modify Compliance Benchmarks Schedule +--------------- + +By using the following the example, you can get and set the benchmark assessment schedule + +.. code-block:: python + + >>> from cbc_sdk import CBCloudAPI + >>> from cbc_sdk.workload import ComplianceBenchmark + >>> api = CBCloudAPI(profile='sample') + >>> ComplianceBenchmark.set_compliance_schedule(api, "RRULE:FREQ=DAILY;BYHOUR=17;BYMINUTE=30;BYSECOND=0", "UTC") + >>> schedule = ComplianceBenchmark.get_compliance_schedule(api) + >>> print(schedule) + { + "scan_schedule": "FREQ=WEEKLY;BYDAY=TU;BYHOUR=11;BYMINUTE=30;BYSECOND=0", + "scan_timezone": "UTC" + } + + +Reassess Compliance Benchmarks +--------------- + +By using the following the example, you can reasses a benchmark + +.. code-block:: python + + >>> from cbc_sdk import CBCloudAPI + >>> from cbc_sdk.workload import ComplianceBenchmark + >>> api = CBCloudAPI(profile='sample') + >>> benchmark = api.select(ComplianceBenchmark).first() + >>> # Execute for all devices matching benchmark + >>> benchmark.execute_action("REASSESS") + >>> # Execute for a specific set of devices + >>> benchmark.execute_action("REASSESS", [ 1, 2, 3 ]) + + +Device Compliance Summary +--------------- + +By using the following the example, you can fetch the compliance percentage for each device assessed by the Compliance Benchmark + +.. code-block:: python + + >>> from cbc_sdk import CBCloudAPI + >>> from cbc_sdk.workload import ComplianceBenchmark + >>> api = CBCloudAPI(profile='sample') + >>> benchmark = api.select(ComplianceBenchmark).first() + >>> summaries = benchmark.get_device_compliances() + >>> print(summaries[0]) + { + "device_id": 39074613, + "device_name": "Example\\Win2022", + "os_version": "Windows Server 2022 x64", + "compliance_percentage": 93, + "last_assess_time": "2024-04-16T00:00:00.014765Z", + "excluded_on": None, + "excluded_by": None, + "reason": None, + "deployment_type": "WORKLOAD" + } diff --git a/docs/conf.py b/docs/conf.py index 1cc03df75..6c87a30e2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,11 +19,11 @@ # -- Project information ----------------------------------------------------- project = 'Carbon Black Cloud Python SDK' -copyright = '2020-2023 VMware Carbon Black' +copyright = '2020-2024 VMware Carbon Black' author = 'Developer Relations' # The full version, including alpha/beta/rc tags -release = '1.5.1' +release = '1.5.2' # -- General configuration --------------------------------------------------- diff --git a/docs/devices.rst b/docs/devices.rst new file mode 100644 index 000000000..b577b8401 --- /dev/null +++ b/docs/devices.rst @@ -0,0 +1,164 @@ +Devices +======= + +*Devices*, also known as *endpoints*, are at the heart of Carbon Black Cloud's functionality. Each device has a +Carbon Black Cloud sensor installed on it, which communicates with Carbon Black analytics and the Carbon Black Cloud +back end. + +Using the Carbon Black Cloud SDK, you can search for devices with a wide range of criteria, filtering on many different +fields. You can also perform actions on individual devices, such as setting quarantine status, setting bypass status, +or upgrading to a new sensor version. + +Searching for Devices +--------------------- + +Using a query of the ``Device`` object, you can list the devices configured for your organization:: + + >>> from cbc_sdk import CBCloudAPI + >>> api = CBCloudAPI(profile='sample') + >>> from cbc_sdk.platform import Device + >>> query = api.select(Device).where("os:WINDOWS") + >>> query.add_criteria('target_priority', ['LOW']).add_criteria('virtualization_provider', ['VirtualBox']) + >>> for d in query: + ... print(f"{d.name} - {d.last_internal_ip_address}") + DESKTOP-A19 - 10.0.2.44 + DESKTOP-Q210 - 10.10.25.169 + DESKTOP-Q211 - 10.10.25.170 + DESKTOP-Q211B - 10.10.25.180 + EVALUATION-1 - 10.0.2.51 + EVALUATION-2 - 10.0.2.52 + STAGING-1A - 192.168.1.99 + ZZIGNORE-1 - 10.0.3.74 + +The criteria supported in the ``where()`` and ``add_criteria()`` query methods are too numerous to enumerate here; +please see +`the Developer Network documentation `_ +for more details. + +The results of a search query can also be exported:: + + >>> from cbc_sdk import CBCloudAPI + >>> api = CBCloudAPI(profile='sample') + >>> from cbc_sdk.platform import Device + >>> query = api.select(Device).where("os:WINDOWS") + >>> query.add_criteria('target_priority', ['LOW']).add_criteria('virtualization_provider', ['VirtualBox']) + >>> job = query.export() + >>> csv_report = job.get_output_as_string() + >>> # can also get the output as a file or as enumerated lines of text + +Faceting +++++++++ + +Facet search queries return statistical information indicating the relative weighting of the requested values as per +the specified criteria. Device queries support faceting:: + + >>> from cbc_sdk import CBCloudAPI + >>> api = CBCloudAPI(profile='sample') + >>> from cbc_sdk.platform import Device + >>> query = api.select(Device).where("os:WINDOWS") + >>> query.add_criteria('target_priority', ['LOW']).add_criteria('virtualization_provider', ['VirtualBox']) + >>> facets = query.facets(['policy_id']) + >>> for value in facets[0].values_: + ... print(f"Policy ID {value.id}: {value.total} device(s)") + Policy ID 8801: 4 device(s) + Policy ID 81664: 3 device(s) + Policy ID 82804: 1 device(s) + +Note that you can facet on multiple fields by passing more than one field name to the ``facets()`` call. It returns +one ``DeviceFacet`` object per field name, each of which may contain multiple ``DeviceFacetValue`` objects. + +Search Scrolling +++++++++++++++++ + +A Device Search request can return no more than 10,000 items at a time. Some customers may have more endpoints than +that; to return *all* devices, you can use the ``scroll()`` method on the query to continue searching after all devices +that have been previously returned. This snippet illustrates the technique:: + + # assume "api" is your CBCloudAPI reference + query = api.select(Device) + # add search terms and/or criteria to the query (not shown here) + while query.num_remaining is None or query.num_remaining > 0: + devicelist = query.scroll() # fetch the batch - 10,000 is default + for d in devicelist: + do_something_with_device(d) # whatever you need for each device + +Device Actions +-------------- + +Most device actions in the Carbon Black Cloud can be performed on a single device through the ``Device`` object, +on multiple devices specified by ID, or on the results of a device query. + +Bypass Enable/Disable ++++++++++++++++++++++ + +Setting a device to *bypass* disables all enforcement on the device; its sensor stops sending data to the Carbon Black +Cloud. + +Setting bypass on a single device:: + + >>> # assume "api" is your CBCloudAPI reference + >>> d = api.select(Device, 12345) + >>> d.bypass(True) + +Setting bypass on multiple devices:: + + >>> # assume "api" is your CBCloudAPI reference + api.device_bypass([1001, 1002, 1003], True) + +Setting bypass on the results of a device search:: + + >>> # assume "api" is your CBCloudAPI reference + query = api.select(Device) + # add search terms and/or criteria to the query (not shown here) + query.bypass(True) + +Quarantine +++++++++++ + +A device that has been *quarantined* has its outbound traffic limited, and all inbound traffic to it stopped, except +for communication with the Carbon Black Cloud back end. This would be used on any device determined to be interacting +maliciously. + +Setting quarantine on a single device:: + + >>> # assume "api" is your CBCloudAPI reference + >>> d = api.select(Device, 12345) + >>> d.quarantine(True) + +Setting quarantine on multiple devices:: + + >>> # assume "api" is your CBCloudAPI reference + api.device_quarantine([1001, 1002, 1003], True) + +Setting quarantine on the results of a device search:: + + >>> # assume "api" is your CBCloudAPI reference + query = api.select(Device) + # add search terms and/or criteria to the query (not shown here) + query.quarantine(True) + +Background Scan ++++++++++++++++ + +Enabling *background scan* causes a one-time inventory scan on the device to identify any malware files already present +there. Disabling background scan causes any background scan currently running on the device to be temporarily +suspended; it will restart when background scan is enabled again, or when the endpoint restarts. + +Enabling background scan on a single device:: + + >>> # assume "api" is your CBCloudAPI reference + >>> d = api.select(Device, 12345) + >>> d.background_scan(True) + +Enabling background scan on multiple devices:: + + >>> # assume "api" is your CBCloudAPI reference + api.device_background_scan([1001, 1002, 1003], True) + +Enabling background scan on the results of a device search:: + + >>> # assume "api" is your CBCloudAPI reference + query = api.select(Device) + # add search terms and/or criteria to the query (not shown here) + query.background_scan(True) + diff --git a/docs/guides.rst b/docs/guides.rst index dd19bcce5..1f2d9ad7b 100755 --- a/docs/guides.rst +++ b/docs/guides.rst @@ -29,7 +29,9 @@ Feature Guides alerts asset-groups audit-log + compliance developing-credential-providers + devices device-control differential-analysis live-query @@ -48,6 +50,8 @@ Feature Guides * :doc:`asset-groups` - Create and modify Asset Groups, and preview the impact changes to policy ranking or asset group definition will have. * :doc:`alerts-migration` - Update from SDK 1.4.3 or earlier to SDK 1.5.0 or later to get the benefits of the Alerts v7 API. * :doc:`audit-log` - Retrieve audit log events indicating various "system" events. +* :doc:`compliance` - Search and validate Compliance Benchmarks. +* :doc:`devices` - Search for, get information about, and act on endpoints. * :doc:`device-control` - Control the blocking of USB devices on endpoints. * :doc:`differential-analysis` - Provides the ability to compare and understand the changes between two Live Query runs * :doc:`live-query` - Live Query allows operators to ask questions of endpoints diff --git a/examples/platform/audit_log.py b/examples/platform/audit_log.py index 25e984713..f48b50b71 100644 --- a/examples/platform/audit_log.py +++ b/examples/platform/audit_log.py @@ -12,11 +12,17 @@ """Example script which collects audit logs -The Audit log API provides a read-once queue so no search parameters are requied. -The command line takes the command (get_audit_logs), the total time to run for in seconds and -the polling period, also in seconds. This command will run for 180 seconds (3 minutes) polling for new audit -logs every 30 seconds. -> python examples/platform/audit_log.py --profile DEMO_PROFILE get_audit_logs -r 180 -p 30 +This script demonstrates how to use Audit Logs in the SDK and the three different APIs. + * Search + * Export + * Queue + + This example has minimal command line parsing in order to reduce complexity and focus on the SDK functions. + Review the Authentication section of the Read the Docs for information about Authentication in the SDK + https://carbon-black-cloud-python-sdk.readthedocs.io/en/latest/authentication/ + + This command line will use "DEMO PROFILE" from the credentials file and poll every 30 seconds for three minutes. + > python examples/platform/audit_log.py --profile DEMO_PROFILE get_audit_logs -r 180 -p 30 """ import sys @@ -24,34 +30,122 @@ from cbc_sdk.helpers import build_cli_parser, get_cb_cloud_object from cbc_sdk.platform import AuditLog +# To see the http requests being made, and the structure of the search requests enable debug logging +import logging +logging.basicConfig(level=logging.DEBUG) + + +def deprecated_get_audit_logs(cb, args): + """Deprecated: Polls for audit logs for the period set on input at the specified interval. -def get_audit_logs(cb, args): - """Polls for audit logs for the period set on input at the specified interval.""" + Uses the deprecated queue endpoint. Use AuditLog.get_queued_auditlogs(), shown in get_audit_logs_from_queue + for equivalent functionality using an updated API signature. + """ + print("The AuditLog.get_auditlogs() method is deprecated.") + print("You should have a look at and change to a new method.") + print("Field names have changed from CamelCase to snake_case.") poll_interval = args.poll_interval run_period = args.run_period while run_period > 0: - events_list = AuditLog.get_auditlogs(cb) + audit_log_records = AuditLog.get_auditlogs(cb) print("Runtime remaining: {0} seconds".format(run_period)) - if len(events_list) == 0: + if len(audit_log_records) == 0: print("No audit logs available") - for event in events_list: - print(f"Event {event['eventId']}:") - for (k, v) in event.items(): + for audit_log in audit_log_records: + print(f"Event {audit_log['eventId']}:") + for (k, v) in audit_log.items(): print(f"\t{k}: {v}") time.sleep(poll_interval) run_period = run_period - poll_interval - print("Run time completed") + print("deprecated_get_audit_logs completed") + + +def get_audit_logs_from_queue(cb, args): + """Polls for audit logs for the period set on input at the specified interval. + + Uses the queue endpoint. + """ + print("Starting get_audit_logs_from_queue") + poll_interval = args.poll_interval + run_period = args.run_period + + while run_period > 0: + audit_log_records = AuditLog.get_queued_auditlogs(cb) + print("Runtime remaining: {0} seconds".format(run_period)) + if len(audit_log_records) == 0: + print("No audit logs available") + for audit_log in audit_log_records: + print("New Event:") + print("{}".format(audit_log)) + time.sleep(poll_interval) + run_period = run_period - poll_interval + + print("get_audit_logs_from_queue completed") + + +def search_audit_logs(cb): + """Shows requests for audit logs exercising search criteria. Uses the /_search endpoint.""" + print("Starting search_audit_logs") + # add_time_criteria can stake a start and end time, or a range + # add_time_criteria(start="2024-04-23T09:00:00Z", end="2024-04-23T10:40:00Z") + audit_log_records = cb.select(AuditLog).add_time_criteria(range="-3d").add_boolean_criteria("verbose", True)\ + .add_criteria("description", ["logged in"]) + print("Found {} alert records".format(len(audit_log_records))) + + # Instead of the criteria function, a lucene style query can be used in a where clause for comparable behaviour + # to the search on the Audit Log page of the Carbon Black Cloud console. + audit_log_records = cb.select(AuditLog).add_time_criteria(range="-3d").where("description:login") + print("Found {} alert records".format(len(audit_log_records))) + + for a in audit_log_records: + print("{}".format(a)) + + print("search_audit_logs completed") + + +def export_audit_logs(cb): + """Does one request for audit logs exercising search criteria and then exports via the Job Service. + + Uses the /_export endpoint. + """ + print("Starting export_audit_logs") + audit_log_query = cb.select(AuditLog).add_time_criteria(range="-1d") + audit_log_export_job = audit_log_query.export(format="csv") + results = audit_log_export_job.await_completion().result() + print(results) + results = audit_log_export_job.get_output_as_string() + print(results) + + print("Async Export in json format") + audit_log_export_job = cb.select(AuditLog).add_time_criteria(range="-1d").export(format="json") + results = audit_log_export_job.get_output_as_file("/my/home/directory/audit_results.json") + print("export_audit_logs complete") def main(): - """Main function for Audit Logs example script.""" + """Main function for Audit Logs example script. + + This script demonstrates how to use Audit Logs in the SDK and the three different APIs. + * Search + * Export + * Queue + + This is written for clarity of explanation, not perfect coding practices. + """ + # CBCloudAPI is the connection to the cloud. It holds the credentials for connectivity. + # To execute this script, the profile must have an API key with the following permissions. + # If you are restricted in the actions you're allowed to perform, expect a 403 response for missing permissions. + # Permissions are set on Settings -> API Access -> Access Level and then assigned to an API Key + # Audit Logs - org.audits - READ: View and Export Audits + # Background tasks - Status - jobs.status - READ: To get the status and results of an asynchronous export + + # command line parameters are used for the PROFILE to get connection credentials and polling information. parser = build_cli_parser("Get Audit Logs") subparsers = parser.add_subparsers(dest="command", required=True) - get = subparsers.add_parser("get_audit_logs", help="Get available audit logs") - + get = subparsers.add_parser("get", help="Gets audit logs using Queue, Search, Export and Deprecated queue") get.add_argument('-r', '--run_period', type=int, default=180, help="Time in seconds to continue polling for") # For production use, a longer poll interval of at least one minute should be used get.add_argument('-p', '--poll_interval', type=int, default=30, help="Time in seconds between calling the api") @@ -59,8 +153,11 @@ def main(): args = parser.parse_args() cb = get_cb_cloud_object(args) - if args.command == "get_audit_logs": - get_audit_logs(cb, args) + export_audit_logs(cb) + search_audit_logs(cb) + get_audit_logs_from_queue(cb, args) + deprecated_get_audit_logs(cb, args) + print("The End") if __name__ == "__main__": diff --git a/examples/platform/identify_silent_devices.py b/examples/platform/identify_silent_devices.py new file mode 100644 index 000000000..84ee8821b --- /dev/null +++ b/examples/platform/identify_silent_devices.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# ******************************************************* +# Copyright (c) VMware, Inc. 2020-2024. All Rights Reserved. +# SPDX-License-Identifier: MIT +# ******************************************************* +# * +# * DISCLAIMER. THIS PROGRAM IS PROVIDED TO YOU "AS IS" WITHOUT +# * WARRANTIES OR CONDITIONS OF ANY KIND, WHETHER ORAL OR WRITTEN, +# * EXPRESS OR IMPLIED. THE AUTHOR SPECIFICALLY DISCLAIMS ANY IMPLIED +# * WARRANTIES OR CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, +# * NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE. + +""" +This script identifies silent devices in the current organization. + +A "silent device" is one which has checked in with the Carbon Black Cloud Server during a recent period of time, +the "checkin window," but has not sent any events within a certain period of time, the "event threshold." The script +allows configuration of the checkin window (in days) and the event threshold (in minutes), as well as specifying to +only report on devices running selected operating systems. +""" + +import sys +from cbc_sdk.helpers import build_cli_parser, get_cb_cloud_object +from cbc_sdk.platform import Device +from dateutil import parser + + +def main(): + """Main function for the script.""" + cmdparser = build_cli_parser("Identify silent devices") + cmdparser.add_argument("-w", "--window", type=int, default=1, + help="The checkin window for devices (specified in days, default 1)") + cmdparser.add_argument("-t", "--threshold", type=int, default=60, + help="The event threshold for devices (specified in minutes, default 60)") + cmdparser.add_argument("-o", "--os", action='append', nargs='+', + help="Restrict query to these operating systems (multiple values permitted)") + + args = cmdparser.parse_args() + cb = get_cb_cloud_object(args) + + device_query = cb.select(Device).set_last_contact_time(range=f"-{args.window}d").set_status(["ACTIVE"]) + if args.os: + for sublist in args.os: + device_query.add_criteria("os", sublist) + devices = list(device_query) + print(f"{len(devices)} device(s) have checked in during the last {args.window} day(s)") + + for device in devices: + delta = parser.parse(device.last_contact_time) - parser.parse(device.last_reported_time) + delta_minutes = round(delta.total_seconds() / 60) + if delta_minutes >= args.threshold: + print(f"Device {device.name} (ID={device.id}, OS={device.os}) " + f"last checked in = '{device.last_contact_time}', last reported data = '{device.last_reported_time}, " + f"delta = {delta_minutes} minutes") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/cbc_sdk/__init__.py b/src/cbc_sdk/__init__.py index af0fe893a..3fd4917bb 100644 --- a/src/cbc_sdk/__init__.py +++ b/src/cbc_sdk/__init__.py @@ -4,7 +4,7 @@ __author__ = 'Carbon Black Developer Network' __license__ = 'MIT' __copyright__ = 'Copyright 2020-2024 VMware Carbon Black' -__version__ = '1.5.1' +__version__ = '1.5.2' from .rest_api import CBCloudAPI from .cache import lru diff --git a/src/cbc_sdk/audit_remediation/base.py b/src/cbc_sdk/audit_remediation/base.py index 9a676407a..d98f97f28 100644 --- a/src/cbc_sdk/audit_remediation/base.py +++ b/src/cbc_sdk/audit_remediation/base.py @@ -1000,13 +1000,13 @@ def _count(self): return self._total_results - def _perform_query(self, start=0, rows=0): + def _perform_query(self, from_row=0, max_rows=0): """ Performs the query and returns the results of the query in an iterable fashion. Args: - start (int): The row to start the query at (default 0). - rows (int): The maximum number of rows to be returned (default 0, meaning "all"). + from_row (int): The row to start the query at (default 0). + max_rows (int): The maximum number of rows to be returned (default 0, meaning "all"). Returns: Iterable: The iterated query. @@ -1014,11 +1014,11 @@ def _perform_query(self, start=0, rows=0): url = self._doc_class.urlobject_history.format( self._cb.credentials.org_key ) - current = start + current = from_row numrows = 0 still_querying = True while still_querying: - request = self._build_request(start, rows) + request = self._build_request(from_row, max_rows) resp = self._cb.post_object(url, body=request) result = resp.json() @@ -1031,11 +1031,11 @@ def _perform_query(self, start=0, rows=0): current += 1 numrows += 1 - if rows and numrows == rows: + if max_rows and numrows == max_rows: still_querying = False break - start = current + from_row = current if current >= self._total_results: still_querying = False break @@ -1312,13 +1312,13 @@ def _count(self): return self._total_results - def _perform_query(self, start=0, rows=0): + def _perform_query(self, from_row=0, max_rows=0): """ Performs the query and returns the results of the query in an iterable fashion. Args: - start (int): The row to start the query at (default 0). - rows (int): The maximum number of rows to be returned (default 0, meaning "all"). + from_row (int): The row to start the query at (default 0). + max_rows (int): The maximum number of rows to be returned (default 0, meaning "all"). Returns: Iterable: The iterated query. @@ -1329,11 +1329,11 @@ def _perform_query(self, start=0, rows=0): url = self._doc_class.urlobject.format( self._cb.credentials.org_key, self._run_id ) - current = start + current = from_row numrows = 0 still_querying = True while still_querying: - request = self._build_request(start, rows) + request = self._build_request(from_row, max_rows) resp = self._cb.post_object(url, body=request) result = resp.json() @@ -1348,11 +1348,11 @@ def _perform_query(self, start=0, rows=0): current += 1 numrows += 1 - if rows and numrows == rows: + if max_rows and numrows == max_rows: still_querying = False break - start = current + from_row = current if current >= self._total_results: still_querying = False break @@ -1722,12 +1722,13 @@ def _build_request(self, rows): request["criteria"] = self._criteria return request - def _perform_query(self, rows=0): + def _perform_query(self, from_row=0, max_rows=0): """ Performs the query and returns the results of the query in an iterable fashion. Args: - rows (int): The maximum number of rows to be returned (default 0, meaning "all"). + from_row (int): Not used, inserted for compatibility. + max_rows (int): The maximum number of rows to be returned (default 0, meaning "all"). Returns: Iterable: The iterated query. @@ -1738,7 +1739,7 @@ def _perform_query(self, rows=0): url = self._doc_class.urlobject.format( self._cb.credentials.org_key, self._run_id ) - request = self._build_request(rows) + request = self._build_request(max_rows) resp = self._cb.post_object(url, body=request) result = resp.json() results = result.get("terms", []) @@ -1857,13 +1858,13 @@ def _count(self): return self._total_results - def _perform_query(self, start=0, rows=0): + def _perform_query(self, from_row=0, max_rows=0): """ Performs the query and returns the results of the query in an iterable fashion. Args: - start (int): The row to start the query at (default 0). - rows (int): The maximum number of rows to be returned (default 0, meaning "all"). + from_row (int): The row to start the query at (default 0). + max_rows (int): The maximum number of rows to be returned (default 0, meaning "all"). Returns: Iterable: The iterated query. @@ -1871,11 +1872,11 @@ def _perform_query(self, start=0, rows=0): url = self._doc_class.urlobject_history.format( self._cb.credentials.org_key ) - current = start + current = from_row numrows = 0 still_querying = True while still_querying: - request = self._build_request(start, rows) + request = self._build_request(from_row, max_rows) resp = self._cb.post_object(url, body=request) result = resp.json() @@ -1888,11 +1889,11 @@ def _perform_query(self, start=0, rows=0): current += 1 numrows += 1 - if rows and numrows == rows: + if max_rows and numrows == max_rows: still_querying = False break - start = current + from_row = current if current >= self._total_results: still_querying = False break diff --git a/src/cbc_sdk/base.py b/src/cbc_sdk/base.py index 40c7f115b..c61a7f805 100644 --- a/src/cbc_sdk/base.py +++ b/src/cbc_sdk/base.py @@ -147,7 +147,7 @@ def __new__(mcs, name, bases, clsdict): setattr(cls, field_name, FieldDescriptor(field_name)) for fk_name, fk_info in iter(foreign_keys.items()): - setattr(cls, fk_name, ForeignKeyFieldDescriptor(fk_name, fk_info[0], fk_info[1])) + setattr(cls, fk_name, ForeignKeyFieldDescriptor(fk_name, fk_info[0], fk_info[1])) # pragma: no cover return cls @@ -790,6 +790,10 @@ def refresh(self): # pragma: no cover """Reload this object from the server.""" raise ApiError("refresh() called on an unrefreshable model") + def _refresh(self): + """Override protected refresh""" + pass + class MutableBaseModel(NewBaseModel): """Base model for objects that can have properties changed and then saved back to the server.""" @@ -1022,7 +1026,7 @@ def __init__(self, query=None): def _clone(self): return self.__class__(self._query) - def _perform_query(self): + def _perform_query(self, from_row=0, max_rows=-1): # This has the effect of generating an empty iterator. yield from () @@ -1046,10 +1050,7 @@ def first(self): Returns: obj: First query item """ - res = self[:1] - if res is None or not len(res): - return None - return res[0] + return self.__getitem__(0) def one(self): """ @@ -1062,13 +1063,11 @@ def one(self): MoreThanOneResultError: If the query returns more than one item ObjectNotFoundError: If the query returns zero items """ - res = self[:2] - if res is None: - return None + res = list(self._perform_query(from_row=0, max_rows=2)) label = str(self._query) if self._query else "" if len(res) == 0: raise ObjectNotFoundError("query_uri", message="0 results for query {0:s}".format(label)) - if len(res) > 1: + if len(res) > 1: # pragma: no cover raise MoreThanOneResultError( message="{0:d} results found for query {1:s}".format(len(self), label), results=self.all() @@ -1111,8 +1110,8 @@ def __getitem__(self, item): return [results[ii] for ii in range(*item.indices(len(results)))] elif isinstance(item, int): results = list(self._perform_query(from_row=item, max_rows=1)) - return results[item] - else: + return results[0] + else: # pragma: no cover raise TypeError("Invalid argument type") def __iter__(self): @@ -1258,9 +1257,15 @@ def and_(self, new_query): raise ApiError("Cannot have multiple 'where' clauses") return self.where(new_query) - def _perform_query(self): - for item in self.results: + def _perform_query(self, from_row=0, max_rows=-1): + returned_rows = 0 + for index, item in enumerate(self.results): + if index < from_row: + continue yield item + returned_rows += 1 + if 0 < max_rows <= returned_rows: + break def sort(self, new_sort): """ @@ -1369,8 +1374,8 @@ def __getitem__(self, item): else: raise TypeError("invalid type") - def _perform_query(self, start=0, numrows=0): - for item in self._search(start=start, rows=numrows): + def _perform_query(self, from_row=0, max_rows=0): + for item in self._search(start=from_row, rows=max_rows): yield self._doc_class._new_object(self._cb, item) def batch_size(self, new_batch_size): @@ -1865,6 +1870,7 @@ def __init__(self, doc_class, cb): self._time_range = {} self._fields = ["*"] self._default_args = {} + self._collapse_field = [] def _add_exclusions(self, key, newlist): """ @@ -1968,6 +1974,8 @@ def _get_query_parameters(self): if 'process_guid:' in args.get('query', ''): q = args['query'].split('process_guid:', 1)[1].split(' ', 1)[0] args["process_guid"] = q + if self._collapse_field: + args['collapse_field'] = self._collapse_field if args.get("sort", None) is not None and args.get("fields", None) is None: # Add default fields if only sort is specified @@ -2408,8 +2416,13 @@ def _search(self, start=0, rows=0): result = self._cb.get_object(result_url, query_parameters=query_parameters) return self._doc_class(self._cb, model_unique_id=self._query_token, initial_data=result) - def _perform_query(self): - return self.results + def _perform_query(self, from_row=0, max_rows=-1): + if max_rows > 0: + return self.results[from_row:from_row + max_rows] + elif from_row > 0: + return self.results[from_row:] + else: + return self.results @property def results(self): diff --git a/src/cbc_sdk/credential_providers/file_credential_provider.py b/src/cbc_sdk/credential_providers/file_credential_provider.py index 746e7070d..ef97b4eb5 100755 --- a/src/cbc_sdk/credential_providers/file_credential_provider.py +++ b/src/cbc_sdk/credential_providers/file_credential_provider.py @@ -11,6 +11,7 @@ """Credentials provider that reads the credentials from a file.""" +import codecs import configparser import logging import os @@ -64,7 +65,7 @@ def _file_stat(self, path): Returns: os.stat_result: The resulting status. """ - return path.stat() + return path.stat() # pragma: no cover def _security_check(self, path): """ @@ -112,6 +113,31 @@ def _security_check(self, path): log.warning("Security warning: " + failmsg) return True + @staticmethod + def _get_encoding(file): + """ + Detects which encoding a file is in. + + Args: + file (Path): The file to be tested. + + Returns: + str: The (possibly-guessed) encoding for the file. + """ + try: + with open(file, "rb") as f: + prefix = bytearray(f.read(5)) + if prefix.startswith(codecs.BOM_UTF8): + return "utf_8_sig" + if prefix.startswith(codecs.BOM_UTF16_LE): + return "utf_16_le" + if prefix.startswith(codecs.BOM_UTF16_BE): + return "utf_16_be" + return "utf_8" + except OSError: # pragma: no cover + log.warning(f"unable to read encoding of file {file}, assuming utf_8 encoding") + return "utf_8" + def get_credentials(self, section=None): """ Return a Credentials object containing the configured credentials. @@ -132,14 +158,23 @@ def get_credentials(self, section=None): cred_files = [p for p in self._search_path if self._security_check(p)] if not cred_files: raise CredentialError(f"Unable to locate credential file(s) from {self._search_path}") - raw_cred_files = [str(p) for p in cred_files] # needed to support 3.6.0 correctly & for error message try: parser = configparser.ConfigParser() - parser.read(raw_cred_files) + for file in cred_files: + encoding = self._get_encoding(file) + if encoding.startswith("utf_16"): + with open(file, 'rt', encoding=encoding) as f: + # skip the BOM at the start of the file, because that seems to break ConfigParser + ch = f.read(1) + assert ch == "\ufeff" + parser.read_file(f, source=str(file)) + else: + # use string as filename parameter to maintain compatibility + parser.read(str(file), encoding=encoding) for sect in parser.sections(): new_creds[sect] = Credentials({name: value for (name, value) in parser.items(sect)}) except configparser.Error as e: - raise CredentialError(f"Unable to read credential file(s) {raw_cred_files}") from e + raise CredentialError(f"Unable to read credential file(s) {cred_files}") from e self._cached_credentials = new_creds if section in self._cached_credentials: return self._cached_credentials[section] diff --git a/src/cbc_sdk/platform/alerts.py b/src/cbc_sdk/platform/alerts.py index d66aaf26c..78c3dfe7e 100644 --- a/src/cbc_sdk/platform/alerts.py +++ b/src/cbc_sdk/platform/alerts.py @@ -11,7 +11,27 @@ # * WARRANTIES OR CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, # * NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE. -"""Model and Query Classes for Platform Alerts and Workflows""" +""" +The model and query classes for supporting alerts and alert workflows. + +*Alerts* indicate suspicious behavior and known threats in the monitored environment. They should be regularly +reviewed to determine whether action must be taken or policies should be modified. The Carbon Black Cloud Python +SDK may be used to retrieve alerts, as well as manage the workflow by modifying alert status or closing alerts. + +The Carbon Black Cloud Python SDK currently implements the Alerts v7 API, as documented on +`the Developer Network `_. +It works with any Carbon Black Cloud product, although certain alert types are only generated by specific products. + +Typical usage example:: + + # assume "cb" is an instance of CBCloudAPI + query = cb.select(Alert).add_criteria("device_os", ["WINDOWS"]).set_minimum_severity(3) + query.set_time_range(range="-1d").set_rows(1000).add_exclusions("type", ["WATCHLIST"]) + for alert in query: + print(f"Alert ID {alert.id} with severity {alert.severity} at {alert.detection_timestamp}") + +""" + import time import datetime @@ -40,7 +60,16 @@ class Alert(PlatformModel): - """Represents a basic alert.""" + """ + Represents a basic alert within the Carbon Black Cloud. + + ``Alert`` objects are typically located through a search (using ``AlertSearchQuery``) before they can be + operated on. + + The complete list of alert fields is too large to be reproduced here; please see the list of available fields + for each alert type on `the Developer Network + `_. + """ REMAPPED_ALERTS_V6_TO_V7 = { "alert_classification.user_feedback": "determination_value", "cluster_name": "k8s_cluster", @@ -177,7 +206,6 @@ class Alert(PlatformModel): urlobject_single = "/api/alerts/v7/orgs/{0}/alerts/{1}" threat_urlobject_single = "/api/alerts/v7/orgs/{0}/threats/{1}" primary_key = "id" - swagger_meta_file = "platform/models/alert.yaml" def __init__(self, cb, model_unique_id, initial_data=None): """ @@ -196,11 +224,18 @@ def get_process(self, async_mode=False): """ Gets the process corresponding with the alert. + Required Permissions: + org.search.events (CREATE. READ) + Args: - async_mode: True to request process in an asynchronous manner. + async_mode: ``True`` to request process in an asynchronous manner. Returns: Process: The process corresponding to the alert. + + Note: + - When using asynchronous mode, this method returns a Python ``Future``. + You can call ``result()`` on the ``Future`` object to wait for completion and get the results. """ process_guid = self._info.get("process_guid") if not process_guid: @@ -211,10 +246,13 @@ def get_process(self, async_mode=False): def _get_process(self, *args, **kwargs): """ - Implementation of the get_process. + Implementation of the ``get_process`` call. + + Required Permissions: + org.search.events (CREATE. READ) Returns: - Process: The process corresponding to the alert. May return None if no process is found. + Process: The process corresponding to the alert. May return ``None`` if no process is found. """ process_guid = self._info.get("process_guid") try: @@ -224,16 +262,16 @@ def _get_process(self, *args, **kwargs): return process def get_observations(self, timeout=0): - """Requests observations that are associated with the Alert. + """ + Requests observations that are associated with the ``Alert``. - Uses Observations bulk get details. + Uses ``Observation.bulk_get_details``. - Returns: - list: Observations associated with the alert + Required Permissions: + org.search.events (READ, CREATE) - Note: - - When using asynchronous mode, this method returns a python future. - You can call result() on the future object to wait for completion and get the results. + Returns: + list[Observation]: Observations associated with the ``Alert``. """ alert_id = self.get("id") if not alert_id: @@ -244,14 +282,16 @@ def get_observations(self, timeout=0): def get_history(self, threat=False): """ - Get the actions taken on an Alert such as Notes added and workflow state changes. + Get the actions taken on an ``Alert`` such as ``Note``s added and workflow state changes. + + Required Permissions: + org.alerts (READ) Args: - threat (bool): Whether to return the Alert or Threat history + threat (bool): If ``True``, the threat history is returned; if ``False``, the alert history is returned. Returns: - list: The dicts of each determination, note or workflow change - + list: The ``dict``s of each determination, note or workflow change. """ if threat: url = Alert.threat_urlobject_single.format(self._cb.credentials.org_key, self.threat_id) @@ -264,13 +304,13 @@ def get_history(self, threat=False): def get_threat_tags(self): """ - Gets the threat's tags + Gets the threat's tags. Required Permissions: org.alerts.tags (READ) Returns: - (list[str]): The list of current tags + list[str]: The list of current tags """ url = Alert.threat_urlobject_single.format(self._cb.credentials.org_key, self.threat_id) url = f"{url}/tags" @@ -279,19 +319,19 @@ def get_threat_tags(self): def add_threat_tags(self, tags): """ - Adds tags to the threat + Adds tags to the threat. Required Permissions: org.alerts.tags (CREATE) Args: - tags (list[str]): List of tags to add to the threat + tags (list[str]): List of tags to add to the threat. Raises: - ApiError: If tags is not a list of strings + ApiError: If ``tags`` is not a list of strings. Returns: - (list[str]): The list of current tags + list[str]: The list of current tags. """ if not isinstance(tags, list) or not isinstance(tags[0], str): raise ApiError("Tags must be a list of strings") @@ -304,16 +344,16 @@ def add_threat_tags(self, tags): def delete_threat_tag(self, tag): """ - Delete a threat tag + Delete a threat tag. Required Permissions: org.alerts.tags (DELETE) Args: - tag (str): The tag to delete + tag (str): The tag to delete. Returns: - (list[str]): The list of current tags + (list[str]): The list of current tags. """ url = Alert.threat_urlobject_single.format(self._cb.credentials.org_key, self.threat_id) url = f"{url}/tags/{tag}" @@ -322,7 +362,7 @@ def delete_threat_tag(self, tag): return resp_json.get("tags", []) class Note(PlatformModel): - """Represents a note within an alert.""" + """Represents a note placed on an alert.""" REMAPPED_NOTES_V6_TO_V7 = { "create_time": "create_timestamp", } @@ -339,13 +379,13 @@ class Note(PlatformModel): def __init__(self, cb, alert, model_unique_id, threat_note=False, initial_data=None): """ - Initialize the Note object. + Initialize the ``Note`` object. Args: cb (BaseAPI): Reference to API object used to communicate with the server. alert (Alert): The alert where the note is saved. model_unique_id (str): ID of the note represented. - threat_note (bool): Whether the note is an Alert or Threat note + threat_note (bool): ``True`` if the note is a threat note, ``False`` if the note is an alert note.`` initial_data (dict): Initial data used to populate the note. """ super(Alert.Note, self).__init__(cb, model_unique_id, initial_data) @@ -358,8 +398,11 @@ def _refresh(self): """ Rereads the alert data from the server. + Required Permissions: + org.alerts.notes (READ) + Returns: - bool: True if refresh was successful, False if not. + bool: ``True`` if refresh was successful, ``False`` if not. """ _exists_in_list = False if self._is_deleted: @@ -400,7 +443,12 @@ def _query_implementation(cls, cb, **kwargs): raise NonQueryableModel("Notes cannot be queried directly") def delete(self): - """Deletes a note from an alert.""" + """ + Deletes a note from an alert. + + Required Permissions: + org.alerts.notes (DELETE) + """ if self._threat_note: url = self.threat_urlobject.format(self._cb.credentials.org_key, self._alert.threat_id) else: @@ -453,10 +501,16 @@ def __getattr__(self, item): def notes_(self, threat_note=False): """ - Retrieves all notes for an alert. + Retrieves all notes for this alert. + + Required Permissions: + org.alerts.notes (READ) Args: - threat_note (bool): Whether to return the Alert notes or Threat notes + threat_note (bool): ``True`` to retrieve threat notes, ``False`` to retrieve alert notes. + + Returns: + list[Note]: The list of notes for the alert. """ if threat_note: url = Alert.Note.threat_urlobject.format(self._cb.credentials.org_key, self.threat_id) @@ -470,11 +524,17 @@ def notes_(self, threat_note=False): def create_note(self, note, threat_note=False): """ - Creates a new note. + Creates a new note for this alert. + + Required Permissions: + org.alerts.notes (CREATE) Args: - note (str): Note content to add - threat_note (bool): Whether to add the note to the Alert or Threat + note (str): Note content to add. + threat_note (bool): ``True`` to add this alert to the treat, ``False`` to add this note to the alert. + + Returns: + Note: The newly-added note. """ request = {"note": note} if threat_note: @@ -503,8 +563,11 @@ def _refresh(self): """ Rereads the alert data from the server. + Required Permissions: + org.alerts (READ) + Returns: - bool: True if refresh was successful, False if not. + bool: ``True`` if refresh was successful, ``False`` if not. """ url = self.urlobject_single.format(self._cb.credentials.org_key, self._model_unique_id) resp = self._cb.get_object(url) @@ -527,10 +590,11 @@ def deobfuscate_cmdline(self): Deobfuscates the command line of the process pointed to by the alert and returns the deobfuscated result. Required Permissions: - script.deobfuscation(EXECUTE) + script.deobfuscation (EXECUTE) Returns: - dict: A dict containing information about the obfuscated command line, including the deobfuscated result. + dict: A ``dict`` containing information about the obfuscated command line, including the + deobfuscated result. """ body = {"input": self.process_cmdline} result = self._cb.post_object(f"/tau/v2/orgs/{self._cb.credentials.org_key}/reveal", body) @@ -540,6 +604,14 @@ def close(self, closure_reason=None, determination=None, note=None): """ Closes this alert. + Note: + - This is an asynchronous call that returns a ``Job``. If you want to wait and block on the results + you can call ``await_completion()`` to get a ``Future`` then ``result()`` on the ``future`` object + to wait for completion and get the results. + + Required Permissions: + org.alerts.close (EXECUTE), jobs.status (READ) + Args: closure_reason (str): the closure reason for this alert, either "NO_REASON", "RESOLVED", \ "RESOLVED_BENIGN_KNOWN_GOOD", "DUPLICATE_CLEANUP", "OTHER" @@ -547,22 +619,17 @@ def close(self, closure_reason=None, determination=None, note=None): "FALSE_POSITIVE", or "NONE" note (str): The comment to set for the alert. - Note: - - This is an asynchronus call that returns a Job. If you want to wait and block on the results - you can call await_completion() to get a Futre then result() on the future object to wait for - completion and get the results. + Returns: + Job: The ``Job`` object for the alert workflow action. Example: >>> alert = cb.select(Alert, "708d7dbf-2020-42d4-9cbc-0cddd0ffa31a") >>> job = alert.close("RESOLVED", "FALSE_POSITIVE", "Normal behavior") >>> completed_job = job.await_completion().result() >>> alert.refresh() - - Returns: - Job: The Job object for the alert workflow action. """ job = self._cb.select(Alert).add_criteria("id", [self.get("id")]) \ - ._update_status("CLOSED", closure_reason, note, determination) + ._update_status("CLOSED", closure_reason, note, determination) self._last_refresh_time = time.time() return job @@ -571,30 +638,33 @@ def update(self, status, closure_reason=None, determination=None, note=None): """ Update the Alert with optional closure_reason, determination, note, or status. + Note: + - This is an asynchronous call that returns a ``Job``. If you want to wait and block on the results + you can call ``await_completion()`` to get a ``Future`` then ``result()`` on the ``future`` object + to wait for completion and get the results. + + Required Permissions: + org.alerts.close (EXECUTE), jobs.status (READ) + Args: status (str): The status to set for this alert, either "OPEN", "IN_PROGRESS", or "CLOSED". - closure_reason (str): the closure reason for this alert, either "NO_REASON", "RESOLVED", \ - "RESOLVED_BENIGN_KNOWN_GOOD", "DUPLICATE_CLEANUP", "OTHER" - determination (str): The determination status to set for the alert, either "TRUE_POSITIVE", \ - "FALSE_POSITIVE", or "NONE" + closure_reason (str): the closure reason for this alert, either "NO_REASON", "RESOLVED", + "RESOLVED_BENIGN_KNOWN_GOOD", "DUPLICATE_CLEANUP", "OTHER" + determination (str): The determination status to set for the alert, either "TRUE_POSITIVE", + "FALSE_POSITIVE", or "NONE" note (str): The comment to set for the alert. - Note: - - This is an asynchronus call that returns a Job. If you want to wait and block on the results - you can call await_completion() to get a Futre then result() on the future object to wait for - completion and get the results. + Returns: + Job: The ``Job`` object for the alert workflow action. Example: >>> alert = cb.select(Alert, "708d7dbf-2020-42d4-9cbc-0cddd0ffa31a") >>> job = alert.update("IN_PROGESS", "NO_REASON", "NONE", "Starting Investigation") >>> completed_job = job.await_completion().result() >>> alert.refresh() - - Returns: - Job: The Job object for the alert workflow action. """ job = self._cb.select(Alert).add_criteria("id", [self.get("id")]) \ - ._update_status(status, closure_reason, note, determination) + ._update_status(status, closure_reason, note, determination) self._last_refresh_time = time.time() return job @@ -603,6 +673,9 @@ def _update_threat_workflow_status(self, state, remediation, comment): """ Updates the workflow status of all future alerts with the same threat ID. + Required Permissions: + org.alerts.dismiss (EXECUTE) + Args: state (str): The state to set for this alert, either "OPEN" or "DISMISSED". remediation (str): The remediation status to set for the alert. @@ -621,6 +694,9 @@ def dismiss_threat(self, remediation=None, comment=None): """ Dismisses all future alerts assigned to the threat_id. + Required Permissions: + org.alerts.dismiss (EXECUTE) + Args: remediation (str): The remediation status to set for the alert. comment (str): The comment to set for the alert. @@ -635,6 +711,9 @@ def update_threat(self, remediation=None, comment=None): """ Updates all future alerts assigned to the threat_id to the OPEN state. + Required Permissions: + org.alerts.dismiss (EXECUTE) + Args: remediation (str): The remediation status to set for the alert. comment (str): The comment to set for the alert. @@ -650,6 +729,9 @@ def search_suggestions(cb, query): """ Returns suggestions for keys and field values that can be used in a search. + Required Permissions: + org.alerts (READ) + Args: cb (CBCloudAPI): A reference to the CBCloudAPI object. query (str): A search query to use. @@ -707,13 +789,14 @@ def __getattr__(self, item): raise FunctionalityDecommissioned( "Attribute '{0}' does not exist in object '{1}' because it was deprecated in " "Alerts v7. In SDK 1.5.0 the".format(item, self.__class__.__name__)) - if item in Alert.DEPRECATED_FIELDS_NOT_IN_V7_CONTAINER_ONLY and self.type == "CONTAINER_RUNTIME": + if (item in Alert.DEPRECATED_FIELDS_NOT_IN_V7_CONTAINER_ONLY + and self._info.get('type', None) == "CONTAINER_RUNTIME"): raise FunctionalityDecommissioned( "Attribute '{0}' does not exist in object '{1}' because it was deprecated in " "Alerts v7. In SDK 1.5.0 the".format(item, self.__class__.__name__)) item = Alert.REMAPPED_ALERTS_V6_TO_V7.get(item, item) - if self.get("type") == "CONTAINER_RUNTIME": + if self._info.get('type', None) == "CONTAINER_RUNTIME": item = Alert.REMAPPED_CONTAINER_ALERTS_V6_TO_V7.get(original_item, item) return super(Alert, self).__getattr__(item) except AttributeError: @@ -788,10 +871,15 @@ def get(self, item, default_val=None): class WatchlistAlert(Alert): - """Represents watch list alerts.""" + """ + A specialization of the base ``Alert`` class that represents a watchlist alert. + + The complete list of alert fields is too large to be reproduced here; please see the list of available fields + for each alert type on `the Developer Network + `_. + """ urlobject = "/api/alerts/v7/orgs/{0}/alerts" type = ["WATCHLIST"] - swagger_meta_file = "platform/models/alert_watchlist.yaml" @classmethod def _query_implementation(cls, cb, **kwargs): @@ -819,17 +907,22 @@ def get_watchlist_objects(self): list[Watchlist]: A list of Watchlist objects. """ watchlist_objects = [] - for watchlist in self.get("watchlists"): + for watchlist in self._info.get("watchlists"): watchlist_id = watchlist.get("id") watchlist_objects.append(self._cb.select(Watchlist, watchlist_id)) return watchlist_objects class CBAnalyticsAlert(Alert): - """Represents CB Analytics alerts.""" + """ + A specialization of the base ``Alert`` class that represents a CB Analytics alert. + + The complete list of alert fields is too large to be reproduced here; please see the list of available fields + for each alert type on `the Developer Network + `_. + """ urlobject = "/api/alerts/v7/orgs/{0}/alerts" type = ["CB_ANALYTICS"] - swagger_meta_file = "platform/models/alert_cb_analytic.yaml" @classmethod def _query_implementation(cls, cb, **kwargs): @@ -846,7 +939,8 @@ def _query_implementation(cls, cb, **kwargs): return AlertSearchQuery(cls, cb).add_criteria("type", ["CB_ANALYTICS"]) def get_events(self, timeout=0, async_mode=False): - """Removed in CBC SDK 1.5.0 because Enriched Events are deprecated. + """ + Removed in CBC SDK 1.5.0 because Enriched Events are deprecated. Previously requested enriched events detailed results. Update to use get_observations() instead. See `Developer Network Observations Migration @@ -872,9 +966,14 @@ def get_events(self, timeout=0, async_mode=False): class DeviceControlAlert(Alert): - """Represents Device Control alerts.""" + """ + A specialization of the base ``Alert`` class that represents a Device Control alert. + + The complete list of alert fields is too large to be reproduced here; please see the list of available fields + for each alert type on `the Developer Network + `_. + """ urlobject = "/api/alerts/v7/orgs/{0}/alerts" - swagger_meta_file = "platform/models/alert_device_control.yaml" @classmethod def _query_implementation(cls, cb, **kwargs): @@ -892,9 +991,14 @@ def _query_implementation(cls, cb, **kwargs): class ContainerRuntimeAlert(Alert): - """Represents Container Runtime alerts.""" + """ + A specialization of the base ``Alert`` class that represents a Container Runtime alert. + + The complete list of alert fields is too large to be reproduced here; please see the list of available fields + for each alert type on `the Developer Network + `_. + """ urlobject = "/api/alerts/v7/orgs/{0}/alerts" - swagger_meta_file = "platform/models/alert_container_runtime.yaml" type = ["CONTAINER_RUNTIME"] @classmethod @@ -913,9 +1017,14 @@ def _query_implementation(cls, cb, **kwargs): class HostBasedFirewallAlert(Alert): - """Represents Host Based Firewall alerts.""" + """ + A specialization of the base ``Alert`` class that represents a host-based firewall alert. + + The complete list of alert fields is too large to be reproduced here; please see the list of available fields + for each alert type on `the Developer Network + `_. + """ urlobject = "/api/alerts/v7/orgs/{0}/alerts" - swagger_meta_file = "platform/models/alert_host_based_firewall.yaml" type = ["HOST_BASED_FIREWALL"] @classmethod @@ -924,7 +1033,6 @@ def _query_implementation(cls, cb, **kwargs): Returns the appropriate query object for this alert type. Args: - cb (BaseAPI): Reference to API object used to communicate with the server. cb (BaseAPI): Reference to API object used to communicate with the server. **kwargs (dict): Not used, retained for compatibility. @@ -935,9 +1043,14 @@ def _query_implementation(cls, cb, **kwargs): class IntrusionDetectionSystemAlert(Alert): - """Represents Intrusion Detection System alerts.""" + """ + A specialization of the base ``Alert`` class that represents an intrusion detection system alert. + + The complete list of alert fields is too large to be reproduced here; please see the list of available fields + for each alert type on `the Developer Network + `_. + """ urlobject = "/api/alerts/v7/orgs/{0}/alerts" - swagger_meta_file = "platform/models/alert_intrusion_detection_system.yaml" type = ["INTRUSION_DETECTION_SYSTEM"] @classmethod @@ -956,7 +1069,7 @@ def _query_implementation(cls, cb, **kwargs): def get_network_threat_metadata(self): """ - The NetworkThreatMetadata associated with this IDS alert if it exists. + Retrun the ``NetworkThreatMetadata`` associated with this IDS alert if it exists. Example: >>> alert_threat_metadata = ids_alert.get_network_threat_metadata() @@ -971,7 +1084,11 @@ def get_network_threat_metadata(self): class GroupedAlert(PlatformModel): - """Represents Grouped alerts.""" + """ + Represents alerts that have been grouped together based on a common characteristic. + + This allows viewing of similar alerts across multiple endpoints. + """ urlobject = "/api/alerts/v7/orgs/{0}/grouped_alerts" swagger_meta_file = "platform/models/grouped_alert.yaml" @@ -1062,13 +1179,18 @@ def get_alerts(self): class AlertSearchQuery(BaseQuery, QueryBuilderSupportMixin, IterableQueryMixin, LegacyAlertSearchQueryCriterionMixin, CriteriaBuilderSupportMixin, ExclusionBuilderSupportMixin): - """Represents a query that is used to locate Alert objects.""" + """ + Query object that is used to locate ``Alert`` objects. + + The ``AlertSearchQuery`` is constructed via SDK functions like the ``select()`` method on ``CBCloudAPI``. + The user would then add a query and/or criteria to it before iterating over the results. + """ DEPRECATED_FACET_FIELDS = ["ALERT_TYPE", "CATEGORY", "REPUTATION", "WORKFLOW", "TAG", "POLICY_ID", "POLICY_NAME", "APPLICATION_HASH", "APPLICATION_NAME", "STATUS", "POLICY_APPLIED_STATE"] def __init__(self, doc_class, cb): """ - Initialize the AlertSearchQuery. + Initialize the ``AlertSearchQuery``. Args: doc_class (class): The model class that will be returned by this query. @@ -1250,9 +1372,9 @@ def _create_valid_time_filter(self, kwargs): Args: kwargs (dict): Used to specify start= for start time, end= for end time, and range= for range. Values are - either timestamp ISO 8601 strings or datetime objects for start and end time. For range the time range to - execute the result search, ending on the current time. Should be in the form "-2w", - where y=year, w=week, d=day, h=hour, m=minute, s=second. + either timestamp ISO 8601 strings or datetime objects for start and end time. For range the time + range to execute the result search, ending on the current time. Should be in the form "-2w", + where y=year, w=week, d=day, h=hour, m=minute, s=second. Returns: filter object to be applied to the global time range or a specific field @@ -1376,6 +1498,9 @@ def _count(self): """ Returns the number of results from the run of this query. + Required Permissions: + org.alerts (READ) + Returns: int: The number of results from the run of this query. """ @@ -1392,21 +1517,24 @@ def _count(self): return self._total_results - def _perform_query(self, from_row=1, max_rows=-1): + def _perform_query(self, from_row=0, max_rows=-1): """ Performs the query and returns the results of the query in an iterable fashion. Alerts v6 API uses base 1 instead of 0. + Required Permissions: + org.alerts (READ) + Args: - from_row (int): The row to start the query at (default 1). + from_row (int): The row to start the query at (default 0). max_rows (int): The maximum number of rows to be returned (default -1, meaning "all"). Returns: Iterable: The iterated query. """ url = self._build_url("/_search") - current = from_row + current = from_row + 1 numrows = 0 still_querying = True while still_querying: @@ -1449,7 +1577,7 @@ def _perform_query(self, from_row=1, max_rows=-1): still_querying = False break - from_row = current + from_row = current - 1 if current >= self._total_results: still_querying = False break @@ -1458,6 +1586,9 @@ def facets(self, fieldlist, max_rows=0): """ Return information about the facets for this alert by search, using the defined criteria. + Required Permissions: + org.alerts (READ) + Args: fieldlist (list): List of facet field names. max_rows (int): The maximum number of rows to return. 0 means return all rows. @@ -1490,15 +1621,18 @@ def _update_status(self, status, closure_reason, note, determination): """ Updates the status of all alerts matching the given query. + Required Permissions: + org.alerts.close (EXECUTE), jobs.status (READ) + Args: status (str): The status to set for this alert, either "OPEN", "IN_PROGRESS", or "CLOSED". closure_reason (str): the closure reason for this alert, either "TRUE_POSITIVE", "FALSE_POSITIVE", or "NONE" note (str): The comment to set for the alert. - determination (str): The determination status to set for the alert, either "NO_REASON", "RESOLVED", \ - "RESOLVED_BENIGN_KNOWN_GOOD", "DUPLICATE_CLEANUP", "OTHER" + determination (str): The determination status to set for the alert, either "NO_REASON", "RESOLVED", + "RESOLVED_BENIGN_KNOWN_GOOD", "DUPLICATE_CLEANUP", "OTHER" Returns: - Job: The Job object for the bulk workflow action. + Job: The ``Job`` object for the bulk workflow action. """ request = self._build_request(0, -1) del request["rows"] @@ -1519,6 +1653,9 @@ def update(self, status, closure_reason=None, determination=None, note=None): """ Update all alerts matching the given query. + Required Permissions: + org.alerts.close (EXECUTE), jobs.status (READ) + Args: status (str): The status to set for this alert, either "OPEN", "IN_PROGRESS", or "CLOSED". closure_reason (str): the closure reason for this alert, either "NO_REASON", "RESOLVED", \ @@ -1528,12 +1665,12 @@ def update(self, status, closure_reason=None, determination=None, note=None): note (str): The comment to set for the alert. Returns: - Job: The Job object for the bulk workflow action. + Job: The ``Job`` object for the bulk workflow action. Note: - - This is an asynchronus call that returns a Job. If you want to wait and block on the results - you can call await_completion() to get a Futre then result() on the future object to wait for - completion and get the results. + - This is an asynchronous call that returns a ``Job``. If you want to wait and block on the results + you can call ``await_completion()`` to get a ``Future`` then ``result()`` on the ``Future`` object + to wait for completion and get the results. Example: >>> alert_query = cb.select(Alert).add_criteria("threat_id", ["19261158DBBF00775959F8AA7F7551A1"]) @@ -1546,6 +1683,9 @@ def close(self, closure_reason=None, determination=None, note=None, ): """ Close all alerts matching the given query. The alerts will be left in a CLOSED state after this request. + Required Permissions: + org.alerts.close (EXECUTE), jobs.status (READ) + Args: closure_reason (str): the closure reason for this alert, either "NO_REASON", "RESOLVED", \ "RESOLVED_BENIGN_KNOWN_GOOD", "DUPLICATE_CLEANUP", "OTHER" @@ -1554,12 +1694,12 @@ def close(self, closure_reason=None, determination=None, note=None, ): note (str): The comment to set for the alert. Returns: - Job: The Job object for the bulk workflow action. + Job: The ``Job`` object for the bulk workflow action. Note: - - This is an asynchronus call that returns a Job. If you want to wait and block on the results - you can call await_completion() to get a Futre then result() on the future object to wait for - completion and get the results. + - This is an asynchronous call that returns a ``Job``. If you want to wait and block on the results + you can call ``await_completion()`` to get a ``Future`` then ``result()`` on the ``Future`` object + to wait for completion and get the results. Example: >>> alert_query = cb.select(Alert).add_criteria("threat_id", ["19261158DBBF00775959F8AA7F7551A1"]) @@ -1635,13 +1775,13 @@ def set_remote_is_private(self, is_private, exclude=False): def set_group_by(self, field): """ - Converts the AlertSearchQuery to a GroupAlertSearchQuery grouped by the argument + Converts the ``AlertSearchQuery`` to a ``GroupAlertSearchQuery`` grouped by the argument. Args: - field (string): The field to group by, defaults to "threat_id" + field (string): The field to group by, defaults to "threat_id." Returns: - AlertSearchQuery + GroupedAlertSearchQuery: New query instance. Note: Does not preserve sort criterion @@ -1656,7 +1796,13 @@ def set_group_by(self, field): class GroupedAlertSearchQuery(AlertSearchQuery): - """Represents a query that is used to group Alert objects by a given field.""" + """ + Query object that is used to locate ``Alert`` objects. + + This query is constructed by using the ``select()`` method on ``CBCloudAPI`` to create an ``AlertSearchQuery,`` + then using that query's ``set_group_by()`` method to specify grouping. + """ + def __init__(self, *args, **kwargs): """Initialize the GroupAlertSearchQuery.""" super().__init__(*args, **kwargs) @@ -1691,11 +1837,13 @@ def _build_request(self, from_row, max_rows, add_sort=True): def get_alert_search_query(self): """ - Converts the GroupedAlertSearchQuery into a nongrouped AlertSearchQuery + Converts the ``GroupedAlertSearchQuery`` into a nongrouped ``AlertSearchQuery``. - Returns: AlertSearchQuery + Returns: + AlertSearchQuery: New query instance. - Note: Does not preserve sort criterion + Note: + Does not preserve sort criterion. """ alert_search_query = self._cb.select(Alert) for key, value in vars(alert_search_query).items(): @@ -1704,19 +1852,22 @@ def get_alert_search_query(self): return alert_search_query - def _perform_query(self, from_row=1, max_rows=-1): + def _perform_query(self, from_row=0, max_rows=-1): """ Performs the query and returns the results of the query in an iterable fashion. + Required Permissions: + org.alerts (READ) + Args: - from_row (int): The row to start the query at (default 1). + from_row (int): The row to start the query at (default 0). max_rows (int): The maximum number of rows to be returned (default -1, meaning "all"). Returns: Iterable: The iterated query. """ url = self._build_url("/_search") - current = from_row + current = from_row + 1 numrows = 0 still_querying = True while still_querying: @@ -1744,7 +1895,7 @@ def _perform_query(self, from_row=1, max_rows=-1): still_querying = False break - from_row = current + from_row = current - 1 if current >= self._total_results: still_querying = False break @@ -1782,15 +1933,17 @@ def facets(self, fieldlist, max_rows=0, filter_values=False): """ Return information about the facets for this alert by search, using the defined criteria. + Required Permissions: + org.alerts (READ) + Args: fieldlist (list): List of facet field names. max_rows (int): The maximum number of rows to return. 0 means return all rows. filter_values (boolean): A flag to indicate whether any filters on a term should be applied to facet - calculation. When false (default), a filter on the term is ignored while calculating facets + calculation. When ``False`` (default), a filter on the term is ignored while calculating facets. Returns: - list: A list of facet information specified as dicts. - error: invalid enum + list: A list of facet information specified as ``dict``s. Raises: FunctionalityDecommissioned: If the requested attribute is no longer available. diff --git a/src/cbc_sdk/platform/audit.py b/src/cbc_sdk/platform/audit.py index 4d9cee3ae..117c3fb5b 100644 --- a/src/cbc_sdk/platform/audit.py +++ b/src/cbc_sdk/platform/audit.py @@ -11,23 +11,68 @@ # * WARRANTIES OR CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, # * NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE. -"""Model and Query Classes for Platform Auditing""" +""" +Model and query classes for platform audit logs. -from cbc_sdk.base import UnrefreshableModel +``AuditLog`` can be used to monitor your Carbon Black Cloud organization for actions performed by Carbon Black Cloud +console users and API keys. Audit logs are recorded for most CREATE, UPDATE and DELETE actions as well as a few READ +actions. Audit logs will include a description of the action and indicate the actor who performed the action along +with their IP to help determine if the User/API key are from an expected source. + +""" + +import datetime +from cbc_sdk.base import (UnrefreshableModel, BaseQuery, QueryBuilder, QueryBuilderSupportMixin, + CriteriaBuilderSupportMixin, ExclusionBuilderSupportMixin, IterableQueryMixin, + AsyncQueryMixin) +from cbc_sdk.errors import ApiError +from cbc_sdk.platform.jobs import Job + +from backports._datetime_fromisoformat import datetime_fromisoformat + + +"""Model Class""" class AuditLog(UnrefreshableModel): - """Model class which represents audit log events. Mostly for future implementation.""" + """ + The model class which represents individual audit log entries. - def __init__(self, cb, model_unique_id, initial_data=None): - """Creation of AuditLog objects is not yet implemented.""" - raise NotImplementedError("AuditLog creation will be in a future implementation") + Each entry includes the actor performing the action, the IP address of the actor, a description, and a request URL + where available. + """ + urlobject = "/audit_log/v1/orgs/{0}/logs" + swagger_meta_file = "platform/models/audit_log.yaml" + + def __init__(self, cb, initial_data=None): + """ + Creates a new ``AuditLog`` object. + + Args: + cb (BaseAPI): Reference to API object used to communicate with the server. + initial_data (dict): Initial data to fill in the audit log record details. + """ + super(AuditLog, self).__init__(cb, -1, initial_data, force_init=False, full_doc=True) + + @classmethod + def _query_implementation(cls, cb, **kwargs): + """ + Returns the appropriate query object for the ``AuditLog`` type. + + Args: + cb (BaseAPI): Reference to API object used to communicate with the server. + **kwargs (dict): Not used, retained for compatibility. + """ + return AuditLogQuery(cls, cb) @staticmethod def get_auditlogs(cb): """ Retrieve queued audit logs from the Carbon Black Cloud server. + Deprecated: + This method uses an outdated API. Use ``get_queued_auditlogs()`` instead. + Required Permissions: org.audits (READ) @@ -39,3 +84,333 @@ def get_auditlogs(cb): """ res = cb.get_object("/integrationServices/v3/auditlogs") return res.get("notifications", []) + + @staticmethod + def get_queued_auditlogs(cb): + """ + Retrieve queued audit logs from the Carbon Black Cloud server. + + Required Permissions: + org.audits (READ) + + Args: + cb (BaseAPI): Reference to API object used to communicate with the server. + + Returns: + list[AuditLog]: List of objects representing the audit logs, or an empty list if none available. + """ + res = cb.get_object(AuditLog.urlobject.format(cb.credentials.org_key) + "/_queue") + return [AuditLog(cb, data) for data in res.get("results", [])] + + +"""Query Class""" + + +class AuditLogQuery(BaseQuery, QueryBuilderSupportMixin, CriteriaBuilderSupportMixin, + ExclusionBuilderSupportMixin, IterableQueryMixin, AsyncQueryMixin): + """ + Query object that is used to locate ``AuditLog`` objects. + + The ``AuditLogQuery`` is constructed via SDK functions like the ``select()`` method on ``CBCloudAPI``. + The user would then add a query and/or criteria to it before iterating over the results. + + The following criteria may be added to the query via the standard ``add_criteria()`` method, or added to query + exclusions via the standard ``add_exclusions()`` method: + + * ``actor_ip`` - IP address of the entity that caused the creation of this audit log. + * ``actor`` - Name of the entity that caused the creation of this audit log. + * ``request_url`` - URL of the request that caused the creation of this audit log. + * ``description`` - Text description of this audit log. + """ + VALID_EXPORT_FORMATS = ("csv", "json") + + def __init__(self, doc_class, cb): + """ + Initialize the ``AuditLogQuery``. + + Args: + doc_class (class): The model class that will be returned by this query. + cb (BaseAPI): Reference to API object used to communicate with the server. + """ + self._doc_class = doc_class + self._cb = cb + self._count_valid = False + super(AuditLogQuery, self).__init__() + + self._query_builder = QueryBuilder() + self._criteria = {} + self._exclusions = {} + self._sortcriteria = {} + self._search_after = None + self.num_remaining = None + self.num_found = None + self.max_rows = -1 + + @staticmethod + def _create_valid_time_filter(kwargs): + """ + Creates the time range used for a "create_time" criteria value. + + Args: + kwargs (dict): Used to specify start= for start time, end= for end time, and range= for range. Values are + either timestamp ISO 8601 strings or datetime objects for start and end time. For range, the time range + to execute the result search, ending on the current time. Should be in the form "-2w", + where y=year, w=week, d=day, h=hour, m=minute, s=second. + + Returns: + dict: A new filter object. + + Raises: + ApiError: If the argument format is incorrect. + """ + time_filter = {} + if kwargs.get("start", None) and kwargs.get("end", None): + if kwargs.get("range", None): + raise ApiError("cannot specify range= in addition to start= and end=") + stime = kwargs["start"] + etime = kwargs["end"] + try: + if isinstance(stime, str): + stime = datetime_fromisoformat(stime) + if isinstance(etime, str): + etime = datetime_fromisoformat(etime) + if isinstance(stime, datetime.datetime) and isinstance(etime, datetime.datetime): + time_filter = {"start": stime.strftime("%Y-%m-%dT%H:%M:%S.%fZ"), + "end": etime.strftime("%Y-%m-%dT%H:%M:%S.%fZ")} + except: + raise ApiError(f"Start and end time must be a string in ISO 8601 format or an object of datetime. " + f"Start time {stime} is a {type(stime)}. End time {etime} is a {type(etime)}.") + elif kwargs.get("range", None): + if kwargs.get("start", None) or kwargs.get("end", None): + raise ApiError("cannot specify start= or end= in addition to range=") + time_filter = {"range": kwargs["range"]} + else: + raise ApiError("must specify either start= and end= or range=") + return time_filter + + def add_time_criteria(self, **kwargs): + """ + Adds a ``create_time`` value to either criteria or exclusions. + + Examples: + >>> query_specify_start_and_end = api.select(AuditLog). + ... add_time_criteria(start="2023-10-20T20:34:07Z", end="2023-10-30T20:34:07Z") + >>> query_specify_exclude_range = api.select(AuditLog).add_time_criteria(range='-3d', exclude=True) + + Args: + kwargs (dict): Keyword arguments to this method. + + Keyword Args: + start (str/datetime): Starting time for the time interval to include in the criteria. Must be either a + ``datetime`` object or a string in ISO 8601 format. Both ``start`` and ``end`` must be specified + if they are to be used. + end (str/datetime): Ending time for the time interval to include in the criteria. Must be either a + ``datetime`` object or a string in ISO 8601 format. Both ``start`` and ``end`` must be specified + if they are to be used. + range (str): Range for the time interval, to be measured backwards from the current time. Cannot + be specified if ``start`` or ``end`` are specified. Must be in the format "-NX", where ``N`` is an + integer value, and ``X`` is a single character specifying the time unit: "y" for years, "w" for weeks, + "d" for days, "h" for hours, "m" for minutes, or "s" for seconds. + exclude (bool): ``True`` if this value is to be applied to exclusions, ``False`` if this value is to be + applied to search criteria. Default ``False.`` + + Returns: + AuditLogQuery: This instance. + + Raises: + ApiError: If the argument format is incorrect. + """ + if kwargs.get("exclude", False): + self._exclusions['create_time'] = self._create_valid_time_filter(kwargs) + else: + self._criteria['create_time'] = self._create_valid_time_filter(kwargs) + return self + + def add_boolean_criteria(self, criteria_name, value, exclude=False): + """ + Adds a Boolean value to either the criteria or exclusions. + + Args: + criteria_name (str): The criteria name to set. May be either "flagged" (to set whether or not the audit + record has been flagged) or "verbose" (so set whether or not the audit record has been marked verbose). + value (bool): The value of the criteria to be set. + exclude (bool): ``True`` if this value is to be applied to exclusions, ``False`` if this value is to be + applied to search criteria. Default ``False.`` + + Returns: + AuditLogQuery: This instance. + """ + if exclude: + self._exclusions[criteria_name] = value + else: + self._criteria[criteria_name] = value + return self + + def sort_by(self, key, direction="ASC"): + """ + Sets the sorting behavior on a query's results. + + Example: + >>> cb.select(AuditLog).sort_by("name") + + Args: + key (str): The key in the schema to sort by. + direction (str): The sort order, either "ASC" or "DESC". + + Returns: + AuditLogQuery: This instance. + """ + if direction not in CriteriaBuilderSupportMixin.VALID_DIRECTIONS: + raise ApiError("invalid sort direction specified") + self._sortcriteria = {"field": key, "order": direction} + return self + + def _build_request(self, from_row, max_rows): + """ + Creates the request body for an API call. + + Args: + from_row (int): The row to start the query at. + max_rows (int): The maximum number of rows to be returned. + + Returns: + dict: The complete request body. + """ + request = {} + if self._criteria: + request['criteria'] = self._criteria + if self._exclusions: + request['exclusions'] = self._exclusions + query = self._query_builder._collapse() + if query: + request['query'] = query + if max_rows > 0: + request['rows'] = max_rows + if from_row > 0: + request['start'] = from_row + if self._sortcriteria: + request['sort'] = [self._sortcriteria] + return request + + def _build_url(self, tail_end): + """ + Creates the URL to be used for an API call. + + Args: + tail_end (str): String to be appended to the end of the generated URL. + + Returns: + str: The complete URL. + """ + url = self._doc_class.urlobject.format(self._cb.credentials.org_key) + tail_end + return url + + def _count(self): + """ + Returns the number of results from the run of this query. + + Required Permissions: + org.audits (READ) + + Returns: + int: The number of results from the run of this query. + """ + if self._count_valid: + return self._total_results + + url = self._build_url("/_search") + request = self._build_request(0, -1) + resp = self._cb.post_object(url, body=request) + result = resp.json() + + self._total_results = result["num_found"] + self._count_valid = True + + return self._total_results + + def _perform_query(self, from_row=0, max_rows=-1): + """ + Performs the query and returns the results of the query in an iterable fashion. + + Required Permissions: + org.audits (READ) + + Args: + from_row (int): The row to start the query at (default 0). + max_rows (int): The maximum number of rows to be returned (default -1, meaning "all"). + + Yields: + AuditLog: The audit log records resulting from the search. + """ + url = self._build_url("/_search") + current = from_row + numrows = 0 + still_querying = True + while still_querying: + request = self._build_request(current, max_rows) + resp = self._cb.post_object(url, body=request) + result = resp.json() + + self._total_results = result["num_found"] + self._count_valid = True + + results = result.get("results", []) + for item in results: + yield self._doc_class(self._cb, item) + current += 1 + numrows += 1 + + if max_rows > 0 and numrows == max_rows: + still_querying = False + break + + from_row = current + if current >= self._total_results: + still_querying = False + break + + def _run_async_query(self, context): + """ + Executed in the background to run an asynchronous query. + + Args: + context (object): Not used. + + Returns: + list[AuditLog]: The results of the query. + """ + url = self._build_url("/_search") + request = self._build_request(0, -1) + resp = self._cb.post_object(url, body=request) + result = resp.json() + results = result.get("results", []) + return [self._doc_class(self._cb, item) for item in results] + + def export(self, format="csv"): + """ + Export audit logs using the Job service. + + The actual results are retrieved by waiting for the resulting job to complete, then calling one of the methods + on ``Job`` to retrieve the results. + + Example: + >>> audit_log_query = cb.select(AuditLog).add_time_criteria(range="-1d") + >>> audit_log_export_job = audit_log_query.export(format="csv") + >>> results = audit_log_export_job.await_completion().result() + + Args: + format (str): Format in which to return results, either "csv" or "json". Default is "csv". + + Returns: + Job: The object representing the export job. + """ + if format not in AuditLogQuery.VALID_EXPORT_FORMATS: + raise ApiError(f"invalid export format '{format}'") + url = self._build_url("/_export") + request = self._build_request(0, -1) + request["format"] = format + resp = self._cb.post_object(url, body=request) + result = resp.json() + if "job_id" in result: + return Job(self._cb, result["job_id"]) + return None # pragma: no cover diff --git a/src/cbc_sdk/platform/devices.py b/src/cbc_sdk/platform/devices.py index 30d62d823..0d3446a52 100644 --- a/src/cbc_sdk/platform/devices.py +++ b/src/cbc_sdk/platform/devices.py @@ -999,25 +999,22 @@ def _count(self): return self._total_results - def _perform_query(self, from_row=1, max_rows=-1): + def _perform_query(self, from_row=0, max_rows=-1): """ Performs the query and returns the results of the query in an iterable fashion. - Note: - Device v6 API uses base 1 instead of 0. - Required Permissions: device(READ) Args: - from_row (int): The row to start the query at (default 1). + from_row (int): The row to start the query at (default 0). max_rows (int): The maximum number of rows to be returned (default -1, meaning "all"). Yields: Device: The individual devices which match the query. """ url = self._build_url("/_search") - current = from_row + current = from_row + 1 numrows = 0 still_querying = True while still_querying: @@ -1038,7 +1035,7 @@ def _perform_query(self, from_row=1, max_rows=-1): still_querying = False break - from_row = current + from_row = current - 1 if current >= self._total_results: still_querying = False break diff --git a/src/cbc_sdk/platform/events.py b/src/cbc_sdk/platform/events.py index c41ea9e78..1251a44e3 100644 --- a/src/cbc_sdk/platform/events.py +++ b/src/cbc_sdk/platform/events.py @@ -217,7 +217,8 @@ def _search(self, start=0, rows=0): self._total_segments = result.get("total_segments", 0) self._processed_segments = result.get("processed_segments", 0) self._count_valid = True - if self._processed_segments != self._total_segments: + if self._processed_segments != self._total_segments \ + and len(result.get('results', [])) != self._total_results: retry_counter = 0 if self._processed_segments > last_processed_segments else retry_counter + 1 last_processed_segments = max(last_processed_segments, self._processed_segments) if retry_counter == MAX_EVENT_SEARCH_RETRIES: @@ -280,8 +281,13 @@ def _get_query_parameters(self): args["process_guid"] = q return args - def _perform_query(self): - return self.results + def _perform_query(self, from_row=0, max_rows=-1): + if max_rows > 0: + return self.results[from_row:from_row + max_rows] + elif from_row > 0: + return self.results[from_row:] + else: + return self.results def _submit(self): args = self._get_query_parameters() diff --git a/src/cbc_sdk/platform/jobs.py b/src/cbc_sdk/platform/jobs.py index 598fca737..03bd4e26c 100644 --- a/src/cbc_sdk/platform/jobs.py +++ b/src/cbc_sdk/platform/jobs.py @@ -17,7 +17,8 @@ import logging import time from cbc_sdk.base import NewBaseModel, BaseQuery, IterableQueryMixin, AsyncQueryMixin -from cbc_sdk.errors import ObjectNotFoundError, ServerError +from cbc_sdk.errors import ObjectNotFoundError, ServerError, ApiError +from cbc_sdk.utils import BackoffHandler log = logging.getLogger(__name__) @@ -60,7 +61,12 @@ def _query_implementation(cls, cb, **kwargs): return JobQuery(cls, cb) def _refresh(self): - """Reload this object from the server.""" + """ + Reload this object from the server. + + Required Permissions: + jobs.status (READ) + """ url = self.urlobject_single.format(self._cb.credentials.org_key, self._model_unique_id) resp = self._cb.get_object(url) self._info = resp @@ -73,7 +79,7 @@ def get_progress(self): Get and return the current progress information for the job. Required Permissions: - jobs.status(READ) + jobs.status (READ) Returns: int: Total number of items to be operated on by this job. @@ -85,54 +91,67 @@ def get_progress(self): self._info['progress'] = resp return resp['num_total'], resp['num_completed'], resp.get('message', None) - def _await_completion(self): + def _await_completion(self, timeout=0): """ Waits for this job to complete by examining the progress data. Required Permissions: - jobs.status(READ) + jobs.status (READ) + + Args: + timeout (int): The timeout for this wait in milliseconds. If this is 0, the default value will be used. Returns: Job: This object. + + Raises: + TimeoutError: If the wait times out. """ - progress_data = (1, 0, '') - do_sleep = False - errorcount = 0 - while progress_data[1] < progress_data[0]: - if do_sleep: - time.sleep(0.5) - try: - progress_data = self.get_progress() - except (ServerError, ObjectNotFoundError): - errorcount += 1 - if errorcount == 3: - raise - progress_data = (1, 0, '') - do_sleep = True + backoff = BackoffHandler(self._cb, timeout=timeout) + with backoff as b: + errorcount = 0 + status = "" + while status not in ("FAILED", "COMPLETED"): + b.pause() + try: + self._refresh() + if self.status != status: + status = self.status + b.reset() + except (ServerError, ObjectNotFoundError): + errorcount += 1 + if errorcount == 3: + raise + status = "" + if status == "FAILED": + raise ApiError(f"Job {self.id} reports failure") return self - def await_completion(self): + def await_completion(self, timeout=0): """ - Create a Python Future to check for job completion and return results when available. + Create a Python ``Future`` to check for job completion and return results when available. - Returns a Future object which can be used to await results that are ready to fetch. This function call + Returns a ``Future`` object which can be used to await results that are ready to fetch. This function call does not block. Required Permissions: - jobs.status(READ) + jobs.status (READ) + + Args: + timeout (int): The timeout for this wait in milliseconds. If this is 0, the default value will be used. Returns: - Future: A future which can be used to wait for this job's completion. When complete, the result of the - Future will be this object. + Future: A ``Future`` which can be used to wait for this job's completion. When complete, the result of the + ``Future`` will be this object. """ - return self._cb._async_submit(lambda arg, kwarg: arg[0]._await_completion(), self) + return self._cb._async_submit(lambda arg, kwarg: arg[0]._await_completion(timeout), self) def get_output_as_stream(self, output): """ Export the results from the job, writing the results to the given stream. Required Permissions: - jobs.status(READ) + jobs.status (READ) Args: output (RawIOBase): Stream to write the CSV data from the request to. @@ -145,7 +164,7 @@ def get_output_as_string(self): Export the results from the job, returning the results as a string. Required Permissions: - jobs.status(READ) + jobs.status (READ) Returns: str: The results from the job. @@ -159,7 +178,7 @@ def get_output_as_file(self, filename): Export the results from the job, writing the results to the given file. Required Permissions: - jobs.status(READ) + jobs.status (READ) Args: filename (str): Name of the file to write the results to. @@ -175,7 +194,7 @@ def get_output_as_lines(self): CSV. If a job outputs structured text like JSON or XML, this method should not be used. Required Permissions: - jobs.status(READ) + jobs.status (READ) Returns: iterable: An iterable that can be used to get each line of text in turn as a string. diff --git a/src/cbc_sdk/platform/models/alert.yaml b/src/cbc_sdk/platform/models/alert.yaml deleted file mode 100644 index 619ca1c96..000000000 --- a/src/cbc_sdk/platform/models/alert.yaml +++ /dev/null @@ -1,683 +0,0 @@ -type: object -properties: - additional_events_present: - type: boolean - description: Indicator to let API and forwarder users know that they should look up other associated events related to this alert - alert_notes_present: - type: boolean - description: True if notes are present on the alert ID. False if notes are not present. - alert_url: - type: string - description: Link to the alerts page for this alert. Does not vary by alert type - attack_tactic: - type: string - description: A tactic from the MITRE ATT&CK framework; defines a reason for an adversary’s action, such as achieving credential access - attack_technique: - type: string - description: A technique from the MITRE ATT&CK framework; defines an action an adversary takes to accomplish a goal, such as dumping credentials to achieve credential access - backend_timestamp: - type: string - format: date-time - description: Timestamp when the Carbon Black Cloud processed and enabled the alert for searching. Corresponds to the Created column on the Alerts page. - backend_update_timestamp: - type: string - format: date-time - description: Timestamp when the Carbon Black Cloud initiated and processed an update to an alert. Corresponds to the Updated column on the Alerts page. Note that changes made by users do not change this date; those changes are reflected on `user_update_timestamp` - blocked_effective_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Effective reputation of the blocked file or process; applied by the sensor at the time the block occurred - blocked_md5: - type: string - description: MD5 hash of the child process binary; for any process terminated by the sensor - blocked_name: - type: string - description: Tokenized file path of the files blocked by sensor action - blocked_sha256: - type: string - description: SHA-256 hash of the child process binary; for any process terminated by the sensor - category: - type: string - description: Alert category - Monitored vs Threat - enum: - - THREAT - - MONITORED - - INFO - - MINOR - - SERIOUS - - CRITICAL - childproc_cmdline: - type: string - description: Command line for the child process - childproc_effective_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Effective reputation of the child process; applied by the sensor at the time the event occurred - childproc_guid: - type: string - description: Unique process identifier assigned to the child process - childproc_md5: - type: string - description: Hash of the child process' binary (Enterprise EDR) - childproc_name: - type: string - description: Filesystem path of the child process' binary - childproc_sha256: - type: string - description: Hash of the child process' binary (Endpoint Standard) - childproc_username: - type: string - description: User context in which the child process was executed - connection_type: - type: string - enum: - - INTERNAL_INBOUND - - INTERNAL_OUTBOUND - - INGRESS - - EGRESS - description: Connection Type - detection_timestamp: - type: string - format: date-time - description: For sensor-sent alerts, this is the time of the event on the sensor. For alerts generated on the backend, this is the time the backend system triggered the alert. - determination: - description: User-updatable determination of the alert - type: object - properties: - change_timestamp: - type: string - format: date-time - description: When the determination was updated. - changed_by: - type: string - description: User the determination was changed by. - changed_by_type: - type: string - description: - enum: - - SYSTEM - - USER - - API - - AUTOMATION - value: - type: string - description: Determination of the alert set by a user - enum: - - NONE - - TRUE_POSITIVE - - FALSE_POSITIVE - device_external_ip: - type: string - description: IP address of the endpoint according to the Carbon Black Cloud; can differ from device_internal_ip due to network proxy or NAT; either IPv4 (dotted decimal notation) or IPv6 (proprietary format) - device_id: - type: integer - format: int64 - description: ID of devices - device_internal_ip: - type: string - description: IP address of the endpoint reported by the sensor; either IPv4 (dotted decimal notation) or IPv6 (proprietary format) - device_location: - type: string - enum: - - ONSITE - - OFFSITE - - UNKNOWN - description: Whether the device was on or off premises when the alert started, based on the current IP address and the device’s registered DNS domain suffix - device_name: - type: string - description: Device name - device_os: - type: string - enum: - - WINDOWS - - ANDROID - - MAC - - LINUX - - OTHER - description: Device Operating Systems - device_os_version: - type: string - example: Windows 10 x64 - description: The operating system and version of the endpoint. Requires Windows CBC sensor version 3.5 or later. - device_policy: - type: string - description: Device policy - device_policy_id: - type: integer - format: int64 - description: Device policy id - device_target_value: - type: string - enum: - - LOW - - MEDIUM - - HIGH - - MISSION_CRITICAL - description: Target value assigned to the device, set from the policy - device_uem_id: - type: string - description: Device correlation with WS1/EUC, required for our Workspace ONE Intelligence integration to function - device_username: - type: string - description: Logged on user during the alert. This is filled on a best-effort - approach. If the user is not available it may be populated with the device - owner (empty for Container Runtime alerts) - egress_group_id: - type: string - description: Unique identifier for the egress group - egress_group_name: - type: string - description: Name of the egress group - external_device_friendly_name: - type: string - description: Human-readable external device names - first_event_timestamp: - type: string - format: date-time - description: Timestamp when the first event in the alert occurred - id: - type: string - description: Unique IDs of alerts - ioc_field: - type: string - description: The field the indicator of comprise (IOC) hit contains - ioc_hit: - type: string - description: IOC field value or IOC query that matches - ioc_id: - type: string - description: Unique identifier of the IOC that generated the watchlist hit - ip_reputation: - type: integer - format: int64 - description: Range of reputations to accept for the remote IP 0- unknown 1-20 high risk 21-40 suspicious 41-60 moderate 61-80 low risk 81-100 trustworthy There must be two values in this list. The first is the lower end of the range (inclusive) the second is the upper end of the range (inclusive) - is_updated: - type: boolean - description: Boolean that describes whether or not this is the original copy of the alert - k8s_cluster: - type: string - description: K8s Cluster name - k8s_kind: - type: string - description: K8s Workload kind - k8s_namespace: - type: string - description: K8s namespace - k8s_pod_name: - type: string - description: Name of the pod within a workload - k8s_policy: - type: string - description: Name of the K8s policy - k8s_policy_id: - type: string - description: Unique identifier for the K8s policy - k8s_rule: - type: string - description: Name of the K8s policy rule - k8s_rule_id: - type: string - description: Unique identifier for the K8s policy rule - k8s_workload_name: - type: string - description: K8s Workload Name - last_event_timestamp: - type: string - format: date-time - description: Timestamp when the last event in the alert occurred - mdr_alert: - type: boolean - description: Is Mdr alert - mdr_alert_notes_present: - type: boolean - description: Customer visible notes at the alert level that were added by a MDR analyst - mdr_determination: - type: object - description: Mdr updatable classification of the alert - properties: - change_timestamp: - type: string - description: When the last MDR classification change occurred - format: date-time - value: - type: string - description: A record that identifies the whether the alert was determined to represent a likely or unlikely threat. - enum: - - NOT_ENOUGH_INFO - - NOT_REVIEWED - - NONE - - UNLIKELY_THREAT - - LIKELY_THREAT - mdr_threat_notes_present: - type: boolean - description: Customer visible notes at the threat level that were added by a MDR analyst - mdr_workflow: - type: object - description: MDR-updatable workflow of the alert - properties: - change_timestamp: - description: When the last MDR status change occurred - type: string - format: date-time - is_assigned: - type: boolean - description: - status: - type: string - description: Primary value used to capture status change during MD Analyst's alert triage - enum: - - UNCLAIMED - - IN_PROGRESS - - TRIAGE_COMPLETE - - ACTION_REQUESTED - - PENDING_RESPONSE - - RESPONCE_RECEIVED - ml_classification_final_verdict: - type: string - enum: - - NOT_CLASSIFIED - - NOT_ANOMALOUS - - ANOMALOUS - description: Final verdict of the alert, based on the ML models that were used to make the prediction. - ml_classification_global_prevalence: - type: string - enum: - - UNKNOWN - - LOW - - MEDIUM - - HIGH - description: Categories (low/medium/high) used to describe the prevalence of alerts across all regional organizations. - ml_classification_org_prevalence: - type: string - enum: - - UNKNOWN - - LOW - - MEDIUM - - HIGH - description: Categories (low/medium/high) used to describe the prevalence of alerts within an organization. - netconn_local_ip: - type: string - description: IP address of the remote side of the network connection; stored as dotted decimal - netconn_local_ipv4: - type: string - description: IPv4 address of the local side of the network connection; stored as a dotted decimal. Only one of ipv4 and ipv6 fields will be populated. - netconn_local_ipv6: - type: string - description: IPv6 address of the local side of the network connection; stored as a string without octet-separating colon characters. Only one of ipv4 and ipv6 fields will be populated. - netconn_local_port: - type: integer - format: int64 - description: TCP or UDP port used by the local side of the network connection - netconn_protocol: - type: string - description: Network protocol of the network connection - netconn_remote_domain: - type: string - description: Domain name (FQDN) associated with the remote end of the network connection, if available - netconn_remote_ip: - type: string - description: IP address of the local side of the network connection; stored as dotted decimal - netconn_remote_ipv4: - type: string - description: IPv4 address of the remote side of the network connection; stored as dotted decimal. Only one of ipv4 and ipv6 fields will be populated. - netconn_remote_ipv6: - type: string - description: IPv6 address of the remote side of the network connection; stored as a string without octet-separating colon characters. Only one of ipv4 and ipv6 fields will be populated. - netconn_remote_port: - type: integer - format: int64 - description: TCP or UDP port used by the remote side of the network connection; same as netconn_port and event_network_remote_port - org_key: - type: string - description: Unique alphanumeric string that identifies your organization in the Carbon Black Cloud - parent_cmdline: - type: string - description: Command line of the parent process - parent_effective_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Effective reputation of the parent process; applied by the sensor when the event occurred - parent_guid: - type: string - description: Unique process identifier assigned to the parent process - parent_md5: - type: string - description: MD5 hash of the parent process binary - parent_name: - type: string - description: Filesystem path of the parent process binary - parent_pid: - type: integer - format: int64 - description: Identifier assigned by the operating system to the parent process - parent_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Reputation of the parent process; applied by the Carbon Black Cloud when the event is initially processed - parent_sha256: - type: string - description: SHA-256 hash of the parent process binary - parent_username: - type: string - description: User context in which the parent process was executed - policy_applied: - type: string - enum: - - APPLIED - - NOT_APPLIED - description: Indicates whether or not a policy has been applied to any event associated with this alert - primary_event_id: - type: string - description: ID of the primary event in the alert - process_cmdline: - type: string - description: Command line executed by the actor process - process_effective_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Effective reputation of the actor hash - process_guid: - type: string - description: Guid of the process that has fired the alert (optional) - process_issuer: - type: string - description: - process_md5: - type: string - description: MD5 hash of the actor process binary - process_name: - type: string - description: Process names of an alert - process_pid: - type: integer - format: int64 - description: PID of the process that has fired the alert (optional) - process_publisher: - type: string - description: - process_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Reputation of the actor process; applied when event is processed by the Carbon Black Cloud - process_sha256: - type: string - description: SHA-256 hash of the actor process binary - process_username: - type: string - description: User context in which the actor process was executed. MacOS - all users for the PID for fork() and exec() transitions. Linux - process user for exec() events, but in a future sensor release can be multi-valued due to setuid(). - product_id: - type: string - description: IDs of the product that identifies USB devices - product_name: - type: string - description: Names of the product that identifies USB devices - reason: - type: string - description: A spoken language written explanation of the what and why the alert occurred and any action taken, usually consisting of 1 to 3 sentences. - reason_code: - type: string - description: A unique short-hand code or GUID identifying the particular alert reason - remote_is_private: - type: boolean - description: Is the remote information private - remote_k8s_kind: - type: string - description: Kind of remote workload; set if the remote side is another workload in the same cluster - remote_k8s_namespace: - type: string - description: Namespace within the remote workload’s cluster; set if the remote side is another workload in the same cluster - remote_k8s_pod_name: - type: string - description: Remote workload pod name; set if the remote side is another workload in the same cluster - remote_k8s_workload_name: - type: string - description: Name of the remote workload; set if the remote side is another workload in the same cluster - report_description: - type: string - description: Description of the watchlist report associated with the alert - report_id: - type: string - description: Report IDs that contained the IOC that caused a hit - report_link: - type: string - description: Link of reports that contained the IOC that caused a hit - report_name: - type: string - description: Name of the watchlist report - report_tags: - type: string[] - description: Tags associated with the watchlist report - rule_category_id: - type: string - description: ID representing the category of the rule_id for certain alert types - rule_config_category: - type: string - description: Types of rule configs - rule_id: - type: string - description: ID of the rule that triggered an alert; applies to Intrusion Detection System, Host-Based Firewall, TAU Intelligence, and USB Device Control alerts - run_state: - type: string - enum: - - DID_NOT_RUN - - RAN - - UNKNOWN - description: Whether the threat in the alert actually ran - sensor_action: - type: string - enum: - - ALLOW - - ALLOW_AND_LOG - - DENY - - TERMINATE - description: Actions taken by the sensor, according to the rules of a policy - serial_number: - type: string - description: Serial numbers of the specific devices - severity: - type: integer - format: int64 - description: integer representation of the impact of alert if true positive - tags: - type: array - description: Tags added to the threat ID of the alert - items: - type: string - threat_id: - type: string - description: ID assigned to a group of alerts with common criteria, based on alert type - threat_name: - type: string - description: Name of the threat - threat_notes_present: - type: boolean - description: True if notes are present on the threat ID. False if notes are not present. - tms_rule_id: - type: string - description: Detection id - ttps: - type: string - description: Other potential malicious activities involved in a threat - type: - type: string - enum: - - CB_ANALYTICS - - WATCHLIST - - DEVICE_CONTROL - - CONTAINER_RUNTIME - - HOST_BASED_FIREWALL - - INTRUSION_DETECTION_SYSTEM - - NETWORK_TRAFFIC_ANALYSIS - description: Type of alert generated - user_update_timestamp: - type: string - format: date-time - description: Timestamp of the last property of an alert changed by a user, such as the alert workflow or determination - vendor_id: - type: string - description: IDs of the vendors who produced the devices - vendor_name: - type: string - description: Names of the vendors who produced the devices - watchlists: - type: object - description: List of watchlists associated with an alert. Alerts are batched hourly - properties: - id: - type: string - description: - name: - type: string - description: - workflow: - type: object - description: Current workflow state of an alert. The workflow represents the flow from `OPEN` to `IN_PROGRESS` to `CLOSED` and captures who moved the alert into the current state. The history of these state transitions is available via the alert history route. - properties: - change_timestamp: - type: string - format: date-time - description: When the last status change occurred - changed_by: - type: string - description: Who (or what) made the last status change - workflow_changed_by_rule_id: - type: string - description: - changed_by_type: - type: string - enum: - - SYSTEM - - USER - - API - - AUTOMATION - description: - closure_reason: - type: string `NO_REASON`, `RESOLVED`, `RESOLVED_BENIGN_KNOWN_GOOD`, `DUPLICATE_CLEANUP`, `OTHER` - description: A more detailed description of why the alert was resolved - status: - type: string - enum: - - OPEN - - IN_PROGRESS - - CLOSED - description: primary value used to determine if the alert is active or inactive and displayed in the UI by default \ No newline at end of file diff --git a/src/cbc_sdk/platform/models/alert_cb_analytic.yaml b/src/cbc_sdk/platform/models/alert_cb_analytic.yaml deleted file mode 100644 index a054c63e8..000000000 --- a/src/cbc_sdk/platform/models/alert_cb_analytic.yaml +++ /dev/null @@ -1,492 +0,0 @@ -type: object -properties: - additional_events_present: - type: boolean - description: Indicator to let API and forwarder users know that they should look up other associated events related to this alert - alert_notes_present: - type: boolean - description: True if notes are present on the alert ID. False if notes are not present. - alert_url: - type: string - description: Link to the alerts page for this alert. Does not vary by alert type - attack_tactic: - type: string - description: A tactic from the MITRE ATT&CK framework; defines a reason for an adversary’s action, such as achieving credential access - attack_technique: - type: string - description: A technique from the MITRE ATT&CK framework; defines an action an adversary takes to accomplish a goal, such as dumping credentials to achieve credential access - backend_timestamp: - type: string - format: date-time - description: Timestamp when the Carbon Black Cloud processed and enabled the alert for searching. Corresponds to the Created column on the Alerts page. - backend_update_timestamp: - type: string - format: date-time - description: Timestamp when the Carbon Black Cloud initiated and processed an update to an alert. Corresponds to the Updated column on the Alerts page. Note that changes made by users do not change this date; those changes are reflected on `user_update_timestamp` - blocked_effective_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Effective reputation of the blocked file or process; applied by the sensor at the time the block occurred - blocked_md5: - type: string - description: MD5 hash of the child process binary; for any process terminated by the sensor - blocked_name: - type: string - description: Tokenized file path of the files blocked by sensor action - blocked_sha256: - type: string - description: SHA-256 hash of the child process binary; for any process terminated by the sensor - category: - type: string - description: Alert category - Monitored vs Threat - enum: - - THREAT - - MONITORED - - INFO - - MINOR - - SERIOUS - - CRITICAL - childproc_cmdline: - type: string - description: Command line for the child process - childproc_effective_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Effective reputation of the child process; applied by the sensor at the time the event occurred - childproc_guid: - type: string - description: Unique process identifier assigned to the child process - childproc_md5: - type: string - description: Hash of the child process' binary (Enterprise EDR) - childproc_name: - type: string - description: Filesystem path of the child process' binary - childproc_sha256: - type: string - description: Hash of the child process' binary (Endpoint Standard) - childproc_username: - type: string - description: User context in which the child process was executed - detection_timestamp: - type: string - format: date-time - description: For sensor-sent alerts, this is the time of the event on the sensor. For alerts generated on the backend, this is the time the backend system triggered the alert. - determination: - description: User-updatable determination of the alert - type: object - properties: - change_timestamp: - type: string - format: date-time - description: When the determination was updated. - changed_by: - type: string - description: User the determination was changed by. - changed_by_type: - type: string - description: - enum: - - SYSTEM - - USER - - API - - AUTOMATION - value: - type: string - description: Determination of the alert set by a user - enum: - - NONE - - TRUE_POSITIVE - - FALSE_POSITIVE - device_external_ip: - type: string - description: IP address of the endpoint according to the Carbon Black Cloud; can differ from device_internal_ip due to network proxy or NAT; either IPv4 (dotted decimal notation) or IPv6 (proprietary format) - device_id: - type: integer - format: int64 - description: ID of devices - device_internal_ip: - type: string - description: IP address of the endpoint reported by the sensor; either IPv4 (dotted decimal notation) or IPv6 (proprietary format) - device_location: - type: string - enum: - - ONSITE - - OFFSITE - - UNKNOWN - description: Whether the device was on or off premises when the alert started, based on the current IP address and the device’s registered DNS domain suffix - device_name: - type: string - description: Device name - device_os: - type: string - enum: - - WINDOWS - - ANDROID - - MAC - - LINUX - - OTHER - description: Device Operating Systems - device_os_version: - type: string - example: Windows 10 x64 - description: The operating system and version of the endpoint. Requires Windows CBC sensor version 3.5 or later. - device_policy: - type: string - description: Device policy - device_policy_id: - type: integer - format: int64 - description: Device policy id - device_target_value: - type: string - enum: - - LOW - - MEDIUM - - HIGH - - MISSION_CRITICAL - description: Target value assigned to the device, set from the policy - device_uem_id: - type: string - description: Device correlation with WS1/EUC, required for our Workspace ONE Intelligence integration to function - device_username: - type: string - description: Logged on user during the alert. This is filled on a best-effort - approach. If the user is not available it may be populated with the device - owner (empty for Container Runtime alerts) - first_event_timestamp: - type: string - format: date-time - description: Timestamp when the first event in the alert occurred - id: - type: string - description: Unique IDs of alerts - is_updated: - type: boolean - description: Boolean that describes whether or not this is the original copy of the alert - last_event_timestamp: - type: string - format: date-time - description: Timestamp when the last event in the alert occurred - netconn_local_ip: - type: string - description: IP address of the remote side of the network connection; stored as dotted decimal - netconn_local_ipv4: - type: string - description: IPv4 address of the local side of the network connection; stored as a dotted decimal. Only one of ipv4 and ipv6 fields will be populated. - netconn_local_ipv6: - type: string - description: IPv6 address of the local side of the network connection; stored as a string without octet-separating colon characters. Only one of ipv4 and ipv6 fields will be populated. - netconn_local_port: - type: integer - format: int64 - description: TCP or UDP port used by the local side of the network connection - netconn_protocol: - type: string - description: Network protocol of the network connection - netconn_remote_domain: - type: string - description: Domain name (FQDN) associated with the remote end of the network connection, if available - netconn_remote_ip: - type: string - description: IP address of the local side of the network connection; stored as dotted decimal - netconn_remote_ipv4: - type: string - description: IPv4 address of the remote side of the network connection; stored as dotted decimal. Only one of ipv4 and ipv6 fields will be populated. - netconn_remote_ipv6: - type: string - description: IPv6 address of the remote side of the network connection; stored as a string without octet-separating colon characters. Only one of ipv4 and ipv6 fields will be populated. - netconn_remote_port: - type: integer - format: int64 - description: TCP or UDP port used by the remote side of the network connection; same as netconn_port and event_network_remote_port - org_key: - type: string - description: Unique alphanumeric string that identifies your organization in the Carbon Black Cloud - parent_cmdline: - type: string - description: Command line of the parent process - parent_effective_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Effective reputation of the parent process; applied by the sensor when the event occurred - parent_guid: - type: string - description: Unique process identifier assigned to the parent process - parent_md5: - type: string - description: MD5 hash of the parent process binary - parent_name: - type: string - description: Filesystem path of the parent process binary - parent_pid: - type: integer - format: int64 - description: Identifier assigned by the operating system to the parent process - parent_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Reputation of the parent process; applied by the Carbon Black Cloud when the event is initially processed - parent_sha256: - type: string - description: SHA-256 hash of the parent process binary - parent_username: - type: string - description: User context in which the parent process was executed - policy_applied: - type: string - enum: - - APPLIED - - NOT_APPLIED - description: Indicates whether or not a policy has been applied to any event associated with this alert - primary_event_id: - type: string - description: ID of the primary event in the alert - process_cmdline: - type: string - description: Command line executed by the actor process - process_effective_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Effective reputation of the actor hash - process_guid: - type: string - description: Guid of the process that has fired the alert (optional) - process_issuer: - type: string - description: - process_md5: - type: string - description: MD5 hash of the actor process binary - process_name: - type: string - description: Process names of an alert - process_pid: - type: integer - format: int64 - description: PID of the process that has fired the alert (optional) - process_publisher: - type: string - description: - process_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Reputation of the actor process; applied when event is processed by the Carbon Black Cloud - process_sha256: - type: string - description: SHA-256 hash of the actor process binary - process_username: - type: string - description: User context in which the actor process was executed. MacOS - all users for the PID for fork() and exec() transitions. Linux - process user for exec() events, but in a future sensor release can be multi-valued due to setuid(). - reason: - type: string - description: A spoken language written explanation of the what and why the alert occurred and any action taken, usually consisting of 1 to 3 sentences. - reason_code: - type: string - description: A unique short-hand code or GUID identifying the particular alert reason - rule_category_id: - type: string - description: ID representing the category of the rule_id for certain alert types - rule_id: - type: string - description: ID of the rule that triggered an alert; applies to Intrusion Detection System, Host-Based Firewall, TAU Intelligence, and USB Device Control alerts - run_state: - type: string - enum: - - DID_NOT_RUN - - RAN - - UNKNOWN - description: Whether the threat in the alert actually ran - sensor_action: - type: string - enum: - - ALLOW - - ALLOW_AND_LOG - - DENY - - TERMINATE - description: Actions taken by the sensor, according to the rules of a policy - severity: - type: integer - format: int64 - description: integer representation of the impact of alert if true positive - tags: - type: array - description: Tags added to the threat ID of the alert - items: - type: string - threat_id: - type: string - description: ID assigned to a group of alerts with common criteria, based on alert type - threat_notes_present: - type: boolean - description: True if notes are present on the threat ID. False if notes are not present. - ttps: - type: string - description: Other potential malicious activities involved in a threat - type: - type: string - enum: - - CB_ANALYTICS - - WATCHLIST - - DEVICE_CONTROL - - CONTAINER_RUNTIME - - HOST_BASED_FIREWALL - - INTRUSION_DETECTION_SYSTEM - - NETWORK_TRAFFIC_ANALYSIS - description: Type of alert generated - user_update_timestamp: - type: string - format: date-time - description: Timestamp of the last property of an alert changed by a user, such as the alert workflow or determination - workflow: - type: object - description: Current workflow state of an alert. The workflow represents the flow from `OPEN` to `IN_PROGRESS` to `CLOSED` and captures who moved the alert into the current state. The history of these state transitions is available via the alert history route. - properties: - change_timestamp: - type: string - format: date-time - description: When the last status change occurred - workflow_changed_by: - type: string - description: Who (or what) made the last status change - workflow_changed_by_rule_id: - type: string - description: - workflow_changed_by_type: - type: string - enum: - - SYSTEM - - USER - - API - - AUTOMATION - description: - workflow_closure_reason: - type: string `NO_REASON`, `RESOLVED`, `RESOLVED_BENIGN_KNOWN_GOOD`, `DUPLICATE_CLEANUP`, `OTHER` - description: A more detailed description of why the alert was resolved - workflow_status: - type: string - enum: - - OPEN - - IN_PROGRESS - - CLOSED - description: primary value used to determine if the alert is active or inactive and displayed in the UI by default \ No newline at end of file diff --git a/src/cbc_sdk/platform/models/alert_container_runtime.yaml b/src/cbc_sdk/platform/models/alert_container_runtime.yaml deleted file mode 100644 index 054d9497a..000000000 --- a/src/cbc_sdk/platform/models/alert_container_runtime.yaml +++ /dev/null @@ -1,247 +0,0 @@ -type: object -properties: - alert_notes_present: - type: boolean - description: True if notes are present on the alert ID. False if notes are not present. - alert_url: - type: string - description: Link to the alerts page for this alert. Does not vary by alert type - backend_timestamp: - type: string - format: date-time - description: Timestamp when the Carbon Black Cloud processed and enabled the alert for searching. Corresponds to the Created column on the Alerts page. - backend_update_timestamp: - type: string - format: date-time - description: Timestamp when the Carbon Black Cloud initiated and processed an update to an alert. Corresponds to the Updated column on the Alerts page. Note that changes made by users do not change this date; those changes are reflected on `user_update_timestamp` - connection_type: - type: string - enum: - - INTERNAL_INBOUND - - INTERNAL_OUTBOUND - - INGRESS - - EGRESS - description: Connection Type - detection_timestamp: - type: string - format: date-time - description: For sensor-sent alerts, this is the time of the event on the sensor. For alerts generated on the backend, this is the time the backend system triggered the alert. - determination: - description: User-updatable determination of the alert - type: object - properties: - change_timestamp: - type: string - format: date-time - description: When the determination was updated. - changed_by: - type: string - description: User the determination was changed by. - changed_by_type: - type: string - description: - enum: - - SYSTEM - - USER - - API - - AUTOMATION - value: - type: string - description: Determination of the alert set by a user - enum: - - NONE - - TRUE_POSITIVE - - FALSE_POSITIVE - egress_group_id: - type: string - description: Unique identifier for the egress group - egress_group_name: - type: string - description: Name of the egress group - first_event_timestamp: - type: string - format: date-time - description: Timestamp when the first event in the alert occurred - id: - type: string - description: Unique IDs of alerts - ip_reputation: - type: integer - format: int64 - description: Range of reputations to accept for the remote IP 0- unknown 1-20 high risk 21-40 suspicious 41-60 moderate 61-80 low risk 81-100 trustworthy There must be two values in this list. The first is the lower end of the range (inclusive) the second is the upper end of the range (inclusive) - is_updated: - type: boolean - description: Boolean that describes whether or not this is the original copy of the alert - k8s_cluster: - type: string - description: K8s Cluster name - k8s_kind: - type: string - description: K8s Workload kind - k8s_namespace: - type: string - description: K8s namespace - k8s_pod_name: - type: string - description: Name of the pod within a workload - k8s_policy: - type: string - description: Name of the K8s policy - k8s_policy_id: - type: string - description: Unique identifier for the K8s policy - k8s_rule: - type: string - description: Name of the K8s policy rule - k8s_rule_id: - type: string - description: Unique identifier for the K8s policy rule - k8s_workload_name: - type: string - description: K8s Workload Name - last_event_timestamp: - type: string - format: date-time - description: Timestamp when the last event in the alert occurred - netconn_local_ip: - type: string - description: IP address of the remote side of the network connection; stored as dotted decimal - netconn_local_ipv4: - type: string - description: IPv4 address of the local side of the network connection; stored as a dotted decimal. Only one of ipv4 and ipv6 fields will be populated. - netconn_local_ipv6: - type: string - description: IPv6 address of the local side of the network connection; stored as a string without octet-separating colon characters. Only one of ipv4 and ipv6 fields will be populated. - netconn_local_port: - type: integer - format: int64 - description: TCP or UDP port used by the local side of the network connection - netconn_protocol: - type: string - description: Network protocol of the network connection - netconn_remote_domain: - type: string - description: Domain name (FQDN) associated with the remote end of the network connection, if available - netconn_remote_ip: - type: string - description: IP address of the local side of the network connection; stored as dotted decimal - netconn_remote_ipv4: - type: string - description: IPv4 address of the remote side of the network connection; stored as dotted decimal. Only one of ipv4 and ipv6 fields will be populated. - netconn_remote_ipv6: - type: string - description: IPv6 address of the remote side of the network connection; stored as a string without octet-separating colon characters. Only one of ipv4 and ipv6 fields will be populated. - netconn_remote_port: - type: integer - format: int64 - description: TCP or UDP port used by the remote side of the network connection; same as netconn_port and event_network_remote_port - org_key: - type: string - description: Unique alphanumeric string that identifies your organization in the Carbon Black Cloud - policy_applied: - type: string - enum: - - APPLIED - - NOT_APPLIED - description: Indicates whether or not a policy has been applied to any event associated with this alert - primary_event_id: - type: string - description: ID of the primary event in the alert - reason: - type: string - description: A spoken language written explanation of the what and why the alert occurred and any action taken, usually consisting of 1 to 3 sentences. - reason_code: - type: string - description: A unique short-hand code or GUID identifying the particular alert reason - remote_is_private: - type: boolean - description: Is the remote information private - remote_k8s_kind: - type: string - description: Kind of remote workload; set if the remote side is another workload in the same cluster - remote_k8s_namespace: - type: string - description: Namespace within the remote workload’s cluster; set if the remote side is another workload in the same cluster - remote_k8s_pod_name: - type: string - description: Remote workload pod name; set if the remote side is another workload in the same cluster - remote_k8s_workload_name: - type: string - description: Name of the remote workload; set if the remote side is another workload in the same cluster - run_state: - type: string - enum: - - DID_NOT_RUN - - RAN - - UNKNOWN - description: Whether the threat in the alert actually ran - sensor_action: - type: string - enum: - - ALLOW - - ALLOW_AND_LOG - - DENY - - TERMINATE - description: Actions taken by the sensor, according to the rules of a policy - severity: - type: integer - format: int64 - description: integer representation of the impact of alert if true positive - tags: - type: array - description: Tags added to the threat ID of the alert - items: - type: string - threat_id: - type: string - description: ID assigned to a group of alerts with common criteria, based on alert type - threat_notes_present: - type: boolean - description: True if notes are present on the threat ID. False if notes are not present. - type: - type: string - enum: - - CB_ANALYTICS - - WATCHLIST - - DEVICE_CONTROL - - CONTAINER_RUNTIME - - HOST_BASED_FIREWALL - - INTRUSION_DETECTION_SYSTEM - - NETWORK_TRAFFIC_ANALYSIS - description: Type of alert generated - user_update_timestamp: - type: string - format: date-time - description: Timestamp of the last property of an alert changed by a user, such as the alert workflow or determination - workflow: - type: object - description: Current workflow state of an alert. The workflow represents the flow from `OPEN` to `IN_PROGRESS` to `CLOSED` and captures who moved the alert into the current state. The history of these state transitions is available via the alert history route. - properties: - change_timestamp: - type: string - format: date-time - description: When the last status change occurred - workflow_changed_by: - type: string - description: Who (or what) made the last status change - workflow_changed_by_rule_id: - type: string - description: - workflow_changed_by_type: - type: string - enum: - - SYSTEM - - USER - - API - - AUTOMATION - description: - workflow_closure_reason: - type: string `NO_REASON`, `RESOLVED`, `RESOLVED_BENIGN_KNOWN_GOOD`, `DUPLICATE_CLEANUP`, `OTHER` - description: A more detailed description of why the alert was resolved - workflow_status: - type: string - enum: - - OPEN - - IN_PROGRESS - - CLOSED - description: primary value used to determine if the alert is active or inactive and displayed in the UI by default \ No newline at end of file diff --git a/src/cbc_sdk/platform/models/alert_device_control.yaml b/src/cbc_sdk/platform/models/alert_device_control.yaml deleted file mode 100644 index c9bad81a4..000000000 --- a/src/cbc_sdk/platform/models/alert_device_control.yaml +++ /dev/null @@ -1,229 +0,0 @@ -type: object -properties: - alert_notes_present: - type: boolean - description: True if notes are present on the alert ID. False if notes are not present. - alert_url: - type: string - description: Link to the alerts page for this alert. Does not vary by alert type - backend_timestamp: - type: string - format: date-time - description: Timestamp when the Carbon Black Cloud processed and enabled the alert for searching. Corresponds to the Created column on the Alerts page. - backend_update_timestamp: - type: string - format: date-time - description: Timestamp when the Carbon Black Cloud initiated and processed an update to an alert. Corresponds to the Updated column on the Alerts page. Note that changes made by users do not change this date; those changes are reflected on `user_update_timestamp` - detection_timestamp: - type: string - format: date-time - description: For sensor-sent alerts, this is the time of the event on the sensor. For alerts generated on the backend, this is the time the backend system triggered the alert. - determination: - description: User-updatable determination of the alert - type: object - properties: - change_timestamp: - type: string - format: date-time - description: When the determination was updated. - changed_by: - type: string - description: User the determination was changed by. - changed_by_type: - type: string - description: - enum: - - SYSTEM - - USER - - API - - AUTOMATION - value: - type: string - description: Determination of the alert set by a user - enum: - - NONE - - TRUE_POSITIVE - - FALSE_POSITIVE - device_external_ip: - type: string - description: IP address of the endpoint according to the Carbon Black Cloud; can differ from device_internal_ip due to network proxy or NAT; either IPv4 (dotted decimal notation) or IPv6 (proprietary format) - device_id: - type: integer - format: int64 - description: ID of devices - device_internal_ip: - type: string - description: IP address of the endpoint reported by the sensor; either IPv4 (dotted decimal notation) or IPv6 (proprietary format) - device_location: - type: string - enum: - - ONSITE - - OFFSITE - - UNKNOWN - description: Whether the device was on or off premises when the alert started, based on the current IP address and the device’s registered DNS domain suffix - device_name: - type: string - description: Device name - device_os: - type: string - enum: - - WINDOWS - - ANDROID - - MAC - - LINUX - - OTHER - description: Device Operating Systems - device_os_version: - type: string - example: Windows 10 x64 - description: The operating system and version of the endpoint. Requires Windows CBC sensor version 3.5 or later. - device_policy: - type: string - description: Device policy - device_policy_id: - type: integer - format: int64 - description: Device policy id - device_target_value: - type: string - enum: - - LOW - - MEDIUM - - HIGH - - MISSION_CRITICAL - description: Target value assigned to the device, set from the policy - device_uem_id: - type: string - description: Device correlation with WS1/EUC, required for our Workspace ONE Intelligence integration to function - device_username: - type: string - description: Logged on user during the alert. This is filled on a best-effort - approach. If the user is not available it may be populated with the device - owner (empty for Container Runtime alerts) - external_device_friendly_name: - type: string - description: Human-readable external device names - first_event_timestamp: - type: string - format: date-time - description: Timestamp when the first event in the alert occurred - id: - type: string - description: Unique IDs of alerts - is_updated: - type: boolean - description: Boolean that describes whether or not this is the original copy of the alert - last_event_timestamp: - type: string - format: date-time - description: Timestamp when the last event in the alert occurred - org_key: - type: string - description: Unique alphanumeric string that identifies your organization in the Carbon Black Cloud - policy_applied: - type: string - enum: - - APPLIED - - NOT_APPLIED - description: Indicates whether or not a policy has been applied to any event associated with this alert - primary_event_id: - type: string - description: ID of the primary event in the alert - product_id: - type: string - description: IDs of the product that identifies USB devices - product_name: - type: string - description: Names of the product that identifies USB devices - reason: - type: string - description: A spoken language written explanation of the what and why the alert occurred and any action taken, usually consisting of 1 to 3 sentences. - reason_code: - type: string - description: A unique short-hand code or GUID identifying the particular alert reason - run_state: - type: string - enum: - - DID_NOT_RUN - - RAN - - UNKNOWN - description: Whether the threat in the alert actually ran - sensor_action: - type: string - enum: - - ALLOW - - ALLOW_AND_LOG - - DENY - - TERMINATE - description: Actions taken by the sensor, according to the rules of a policy - serial_number: - type: string - description: Serial numbers of the specific devices - severity: - type: integer - format: int64 - description: integer representation of the impact of alert if true positive - tags: - type: array - description: Tags added to the threat ID of the alert - items: - type: string - threat_id: - type: string - description: ID assigned to a group of alerts with common criteria, based on alert type - threat_notes_present: - type: boolean - description: True if notes are present on the threat ID. False if notes are not present. - type: - type: string - enum: - - CB_ANALYTICS - - WATCHLIST - - DEVICE_CONTROL - - CONTAINER_RUNTIME - - HOST_BASED_FIREWALL - - INTRUSION_DETECTION_SYSTEM - - NETWORK_TRAFFIC_ANALYSIS - description: Type of alert generated - user_update_timestamp: - type: string - format: date-time - description: Timestamp of the last property of an alert changed by a user, such as the alert workflow or determination - vendor_id: - type: string - description: IDs of the vendors who produced the devices - vendor_name: - type: string - description: Names of the vendors who produced the devices - workflow: - type: object - description: Current workflow state of an alert. The workflow represents the flow from `OPEN` to `IN_PROGRESS` to `CLOSED` and captures who moved the alert into the current state. The history of these state transitions is available via the alert history route. - properties: - change_timestamp: - type: string - format: date-time - description: When the last status change occurred - workflow_changed_by: - type: string - description: Who (or what) made the last status change - workflow_changed_by_rule_id: - type: string - description: - workflow_changed_by_type: - type: string - enum: - - SYSTEM - - USER - - API - - AUTOMATION - description: - workflow_closure_reason: - type: string `NO_REASON`, `RESOLVED`, `RESOLVED_BENIGN_KNOWN_GOOD`, `DUPLICATE_CLEANUP`, `OTHER` - description: A more detailed description of why the alert was resolved - workflow_status: - type: string - enum: - - OPEN - - IN_PROGRESS - - CLOSED - description: primary value used to determine if the alert is active or inactive and displayed in the UI by default \ No newline at end of file diff --git a/src/cbc_sdk/platform/models/alert_host_based_firewall.yaml b/src/cbc_sdk/platform/models/alert_host_based_firewall.yaml deleted file mode 100644 index 4ccba400d..000000000 --- a/src/cbc_sdk/platform/models/alert_host_based_firewall.yaml +++ /dev/null @@ -1,483 +0,0 @@ -type: object -properties: - additional_events_present: - type: boolean - description: Indicator to let API and forwarder users know that they should look up other associated events related to this alert - alert_notes_present: - type: boolean - description: True if notes are present on the alert ID. False if notes are not present. - alert_url: - type: string - description: Link to the alerts page for this alert. Does not vary by alert type - backend_timestamp: - type: string - format: date-time - description: Timestamp when the Carbon Black Cloud processed and enabled the alert for searching. Corresponds to the Created column on the Alerts page. - backend_update_timestamp: - type: string - format: date-time - description: Timestamp when the Carbon Black Cloud initiated and processed an update to an alert. Corresponds to the Updated column on the Alerts page. Note that changes made by users do not change this date; those changes are reflected on `user_update_timestamp` - blocked_effective_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Effective reputation of the blocked file or process; applied by the sensor at the time the block occurred - blocked_md5: - type: string - description: MD5 hash of the child process binary; for any process terminated by the sensor - blocked_name: - type: string - description: Tokenized file path of the files blocked by sensor action - blocked_sha256: - type: string - description: SHA-256 hash of the child process binary; for any process terminated by the sensor - category: - type: string - description: Alert category - Monitored vs Threat - enum: - - THREAT - - MONITORED - - INFO - - MINOR - - SERIOUS - - CRITICAL - childproc_cmdline: - type: string - description: Command line for the child process - childproc_effective_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Effective reputation of the child process; applied by the sensor at the time the event occurred - childproc_guid: - type: string - description: Unique process identifier assigned to the child process - childproc_md5: - type: string - description: Hash of the child process' binary (Enterprise EDR) - childproc_name: - type: string - description: Filesystem path of the child process' binary - childproc_sha256: - type: string - description: Hash of the child process' binary (Endpoint Standard) - childproc_username: - type: string - description: User context in which the child process was executed - detection_timestamp: - type: string - format: date-time - description: For sensor-sent alerts, this is the time of the event on the sensor. For alerts generated on the backend, this is the time the backend system triggered the alert. - determination: - description: User-updatable determination of the alert - type: object - properties: - change_timestamp: - type: string - format: date-time - description: When the determination was updated. - changed_by: - type: string - description: User the determination was changed by. - changed_by_type: - type: string - description: - enum: - - SYSTEM - - USER - - API - - AUTOMATION - value: - type: string - description: Determination of the alert set by a user - enum: - - NONE - - TRUE_POSITIVE - - FALSE_POSITIVE - device_external_ip: - type: string - description: IP address of the endpoint according to the Carbon Black Cloud; can differ from device_internal_ip due to network proxy or NAT; either IPv4 (dotted decimal notation) or IPv6 (proprietary format) - device_id: - type: integer - format: int64 - description: ID of devices - device_internal_ip: - type: string - description: IP address of the endpoint reported by the sensor; either IPv4 (dotted decimal notation) or IPv6 (proprietary format) - device_location: - type: string - enum: - - ONSITE - - OFFSITE - - UNKNOWN - description: Whether the device was on or off premises when the alert started, based on the current IP address and the device’s registered DNS domain suffix - device_name: - type: string - description: Device name - device_os: - type: string - enum: - - WINDOWS - - ANDROID - - MAC - - LINUX - - OTHER - description: Device Operating Systems - device_os_version: - type: string - example: Windows 10 x64 - description: The operating system and version of the endpoint. Requires Windows CBC sensor version 3.5 or later. - device_policy: - type: string - description: Device policy - device_policy_id: - type: integer - format: int64 - description: Device policy id - device_target_value: - type: string - enum: - - LOW - - MEDIUM - - HIGH - - MISSION_CRITICAL - description: Target value assigned to the device, set from the policy - device_uem_id: - type: string - description: Device correlation with WS1/EUC, required for our Workspace ONE Intelligence integration to function - device_username: - type: string - description: Logged on user during the alert. This is filled on a best-effort - approach. If the user is not available it may be populated with the device - owner (empty for Container Runtime alerts) - first_event_timestamp: - type: string - format: date-time - description: Timestamp when the first event in the alert occurred - id: - type: string - description: Unique IDs of alerts - is_updated: - type: boolean - description: Boolean that describes whether or not this is the original copy of the alert - last_event_timestamp: - type: string - format: date-time - description: Timestamp when the last event in the alert occurred - netconn_local_ip: - type: string - description: IP address of the remote side of the network connection; stored as dotted decimal - netconn_local_ipv4: - type: string - description: IPv4 address of the local side of the network connection; stored as a dotted decimal. Only one of ipv4 and ipv6 fields will be populated. - netconn_local_ipv6: - type: string - description: IPv6 address of the local side of the network connection; stored as a string without octet-separating colon characters. Only one of ipv4 and ipv6 fields will be populated. - netconn_local_port: - type: integer - format: int64 - description: TCP or UDP port used by the local side of the network connection - netconn_protocol: - type: string - description: Network protocol of the network connection - netconn_remote_domain: - type: string - description: Domain name (FQDN) associated with the remote end of the network connection, if available - netconn_remote_ip: - type: string - description: IP address of the local side of the network connection; stored as dotted decimal - netconn_remote_ipv4: - type: string - description: IPv4 address of the remote side of the network connection; stored as dotted decimal. Only one of ipv4 and ipv6 fields will be populated. - netconn_remote_ipv6: - type: string - description: IPv6 address of the remote side of the network connection; stored as a string without octet-separating colon characters. Only one of ipv4 and ipv6 fields will be populated. - netconn_remote_port: - type: integer - format: int64 - description: TCP or UDP port used by the remote side of the network connection; same as netconn_port and event_network_remote_port - org_key: - type: string - description: Unique alphanumeric string that identifies your organization in the Carbon Black Cloud - parent_cmdline: - type: string - description: Command line of the parent process - parent_effective_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Effective reputation of the parent process; applied by the sensor when the event occurred - parent_guid: - type: string - description: Unique process identifier assigned to the parent process - parent_md5: - type: string - description: MD5 hash of the parent process binary - parent_name: - type: string - description: Filesystem path of the parent process binary - parent_pid: - type: integer - format: int64 - description: Identifier assigned by the operating system to the parent process - parent_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Reputation of the parent process; applied by the Carbon Black Cloud when the event is initially processed - parent_sha256: - type: string - description: SHA-256 hash of the parent process binary - parent_username: - type: string - description: User context in which the parent process was executed - policy_applied: - type: string - enum: - - APPLIED - - NOT_APPLIED - description: Indicates whether or not a policy has been applied to any event associated with this alert - primary_event_id: - type: string - description: ID of the primary event in the alert - process_cmdline: - type: string - description: Command line executed by the actor process - process_effective_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Effective reputation of the actor hash - process_guid: - type: string - description: Guid of the process that has fired the alert (optional) - process_issuer: - type: string - description: - process_md5: - type: string - description: MD5 hash of the actor process binary - process_name: - type: string - description: Process names of an alert - process_pid: - type: integer - format: int64 - description: PID of the process that has fired the alert (optional) - process_publisher: - type: string - description: - process_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Reputation of the actor process; applied when event is processed by the Carbon Black Cloud - process_sha256: - type: string - description: SHA-256 hash of the actor process binary - process_username: - type: string - description: User context in which the actor process was executed. MacOS - all users for the PID for fork() and exec() transitions. Linux - process user for exec() events, but in a future sensor release can be multi-valued due to setuid(). - reason: - type: string - description: A spoken language written explanation of the what and why the alert occurred and any action taken, usually consisting of 1 to 3 sentences. - reason_code: - type: string - description: A unique short-hand code or GUID identifying the particular alert reason - rule_category_id: - type: string - description: ID representing the category of the rule_id for certain alert types - rule_id: - type: string - description: ID of the rule that triggered an alert; applies to Intrusion Detection System, Host-Based Firewall, TAU Intelligence, and USB Device Control alerts - run_state: - type: string - enum: - - DID_NOT_RUN - - RAN - - UNKNOWN - description: Whether the threat in the alert actually ran - sensor_action: - type: string - enum: - - ALLOW - - ALLOW_AND_LOG - - DENY - - TERMINATE - description: Actions taken by the sensor, according to the rules of a policy - severity: - type: integer - format: int64 - description: integer representation of the impact of alert if true positive - tags: - type: array - description: Tags added to the threat ID of the alert - items: - type: string - threat_id: - type: string - description: ID assigned to a group of alerts with common criteria, based on alert type - threat_notes_present: - type: boolean - description: True if notes are present on the threat ID. False if notes are not present. - type: - type: string - enum: - - CB_ANALYTICS - - WATCHLIST - - DEVICE_CONTROL - - CONTAINER_RUNTIME - - HOST_BASED_FIREWALL - - INTRUSION_DETECTION_SYSTEM - - NETWORK_TRAFFIC_ANALYSIS - description: Type of alert generated - user_update_timestamp: - type: string - format: date-time - description: Timestamp of the last property of an alert changed by a user, such as the alert workflow or determination - workflow: - type: object - description: Current workflow state of an alert. The workflow represents the flow from `OPEN` to `IN_PROGRESS` to `CLOSED` and captures who moved the alert into the current state. The history of these state transitions is available via the alert history route. - properties: - change_timestamp: - type: string - format: date-time - description: When the last status change occurred - workflow_changed_by: - type: string - description: Who (or what) made the last status change - workflow_changed_by_rule_id: - type: string - description: - workflow_changed_by_type: - type: string - enum: - - SYSTEM - - USER - - API - - AUTOMATION - description: - workflow_closure_reason: - type: string `NO_REASON`, `RESOLVED`, `RESOLVED_BENIGN_KNOWN_GOOD`, `DUPLICATE_CLEANUP`, `OTHER` - description: A more detailed description of why the alert was resolved - workflow_status: - type: string - enum: - - OPEN - - IN_PROGRESS - - CLOSED - description: primary value used to determine if the alert is active or inactive and displayed in the UI by default \ No newline at end of file diff --git a/src/cbc_sdk/platform/models/alert_intrusion_detection_system.yaml b/src/cbc_sdk/platform/models/alert_intrusion_detection_system.yaml deleted file mode 100644 index c10c1ce33..000000000 --- a/src/cbc_sdk/platform/models/alert_intrusion_detection_system.yaml +++ /dev/null @@ -1,498 +0,0 @@ -type: object -properties: - additional_events_present: - type: boolean - description: Indicator to let API and forwarder users know that they should look up other associated events related to this alert - alert_notes_present: - type: boolean - description: True if notes are present on the alert ID. False if notes are not present. - alert_url: - type: string - description: Link to the alerts page for this alert. Does not vary by alert type - attack_tactic: - type: string - description: A tactic from the MITRE ATT&CK framework; defines a reason for an adversary’s action, such as achieving credential access - attack_technique: - type: string - description: A technique from the MITRE ATT&CK framework; defines an action an adversary takes to accomplish a goal, such as dumping credentials to achieve credential access - backend_timestamp: - type: string - format: date-time - description: Timestamp when the Carbon Black Cloud processed and enabled the alert for searching. Corresponds to the Created column on the Alerts page. - backend_update_timestamp: - type: string - format: date-time - description: Timestamp when the Carbon Black Cloud initiated and processed an update to an alert. Corresponds to the Updated column on the Alerts page. Note that changes made by users do not change this date; those changes are reflected on `user_update_timestamp` - blocked_effective_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Effective reputation of the blocked file or process; applied by the sensor at the time the block occurred - blocked_md5: - type: string - description: MD5 hash of the child process binary; for any process terminated by the sensor - blocked_name: - type: string - description: Tokenized file path of the files blocked by sensor action - blocked_sha256: - type: string - description: SHA-256 hash of the child process binary; for any process terminated by the sensor - category: - type: string - description: Alert category - Monitored vs Threat - enum: - - THREAT - - MONITORED - - INFO - - MINOR - - SERIOUS - - CRITICAL - childproc_cmdline: - type: string - description: Command line for the child process - childproc_effective_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Effective reputation of the child process; applied by the sensor at the time the event occurred - childproc_guid: - type: string - description: Unique process identifier assigned to the child process - childproc_md5: - type: string - description: Hash of the child process' binary (Enterprise EDR) - childproc_name: - type: string - description: Filesystem path of the child process' binary - childproc_sha256: - type: string - description: Hash of the child process' binary (Endpoint Standard) - childproc_username: - type: string - description: User context in which the child process was executed - detection_timestamp: - type: string - format: date-time - description: For sensor-sent alerts, this is the time of the event on the sensor. For alerts generated on the backend, this is the time the backend system triggered the alert. - determination: - description: User-updatable determination of the alert - type: object - properties: - change_timestamp: - type: string - format: date-time - description: When the determination was updated. - changed_by: - type: string - description: User the determination was changed by. - changed_by_type: - type: string - description: - enum: - - SYSTEM - - USER - - API - - AUTOMATION - value: - type: string - description: Determination of the alert set by a user - enum: - - NONE - - TRUE_POSITIVE - - FALSE_POSITIVE - device_external_ip: - type: string - description: IP address of the endpoint according to the Carbon Black Cloud; can differ from device_internal_ip due to network proxy or NAT; either IPv4 (dotted decimal notation) or IPv6 (proprietary format) - device_id: - type: integer - format: int64 - description: ID of devices - device_internal_ip: - type: string - description: IP address of the endpoint reported by the sensor; either IPv4 (dotted decimal notation) or IPv6 (proprietary format) - device_location: - type: string - enum: - - ONSITE - - OFFSITE - - UNKNOWN - description: Whether the device was on or off premises when the alert started, based on the current IP address and the device’s registered DNS domain suffix - device_name: - type: string - description: Device name - device_os: - type: string - enum: - - WINDOWS - - ANDROID - - MAC - - LINUX - - OTHER - description: Device Operating Systems - device_os_version: - type: string - example: Windows 10 x64 - description: The operating system and version of the endpoint. Requires Windows CBC sensor version 3.5 or later. - device_policy: - type: string - description: Device policy - device_policy_id: - type: integer - format: int64 - description: Device policy id - device_target_value: - type: string - enum: - - LOW - - MEDIUM - - HIGH - - MISSION_CRITICAL - description: Target value assigned to the device, set from the policy - device_uem_id: - type: string - description: Device correlation with WS1/EUC, required for our Workspace ONE Intelligence integration to function - device_username: - type: string - description: Logged on user during the alert. This is filled on a best-effort - approach. If the user is not available it may be populated with the device - owner (empty for Container Runtime alerts) - first_event_timestamp: - type: string - format: date-time - description: Timestamp when the first event in the alert occurred - id: - type: string - description: Unique IDs of alerts - is_updated: - type: boolean - description: Boolean that describes whether or not this is the original copy of the alert - last_event_timestamp: - type: string - format: date-time - description: Timestamp when the last event in the alert occurred - netconn_local_ip: - type: string - description: IP address of the remote side of the network connection; stored as dotted decimal - netconn_local_ipv4: - type: string - description: IPv4 address of the local side of the network connection; stored as a dotted decimal. Only one of ipv4 and ipv6 fields will be populated. - netconn_local_ipv6: - type: string - description: IPv6 address of the local side of the network connection; stored as a string without octet-separating colon characters. Only one of ipv4 and ipv6 fields will be populated. - netconn_local_port: - type: integer - format: int64 - description: TCP or UDP port used by the local side of the network connection - netconn_protocol: - type: string - description: Network protocol of the network connection - netconn_remote_domain: - type: string - description: Domain name (FQDN) associated with the remote end of the network connection, if available - netconn_remote_ip: - type: string - description: IP address of the local side of the network connection; stored as dotted decimal - netconn_remote_ipv4: - type: string - description: IPv4 address of the remote side of the network connection; stored as dotted decimal. Only one of ipv4 and ipv6 fields will be populated. - netconn_remote_ipv6: - type: string - description: IPv6 address of the remote side of the network connection; stored as a string without octet-separating colon characters. Only one of ipv4 and ipv6 fields will be populated. - netconn_remote_port: - type: integer - format: int64 - description: TCP or UDP port used by the remote side of the network connection; same as netconn_port and event_network_remote_port - org_key: - type: string - description: Unique alphanumeric string that identifies your organization in the Carbon Black Cloud - parent_cmdline: - type: string - description: Command line of the parent process - parent_effective_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Effective reputation of the parent process; applied by the sensor when the event occurred - parent_guid: - type: string - description: Unique process identifier assigned to the parent process - parent_md5: - type: string - description: MD5 hash of the parent process binary - parent_name: - type: string - description: Filesystem path of the parent process binary - parent_pid: - type: integer - format: int64 - description: Identifier assigned by the operating system to the parent process - parent_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Reputation of the parent process; applied by the Carbon Black Cloud when the event is initially processed - parent_sha256: - type: string - description: SHA-256 hash of the parent process binary - parent_username: - type: string - description: User context in which the parent process was executed - policy_applied: - type: string - enum: - - APPLIED - - NOT_APPLIED - description: Indicates whether or not a policy has been applied to any event associated with this alert - primary_event_id: - type: string - description: ID of the primary event in the alert - process_cmdline: - type: string - description: Command line executed by the actor process - process_effective_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Effective reputation of the actor hash - process_guid: - type: string - description: Guid of the process that has fired the alert (optional) - process_issuer: - type: string - description: - process_md5: - type: string - description: MD5 hash of the actor process binary - process_name: - type: string - description: Process names of an alert - process_pid: - type: integer - format: int64 - description: PID of the process that has fired the alert (optional) - process_publisher: - type: string - description: - process_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Reputation of the actor process; applied when event is processed by the Carbon Black Cloud - process_sha256: - type: string - description: SHA-256 hash of the actor process binary - process_username: - type: string - description: User context in which the actor process was executed. MacOS - all users for the PID for fork() and exec() transitions. Linux - process user for exec() events, but in a future sensor release can be multi-valued due to setuid(). - reason: - type: string - description: A spoken language written explanation of the what and why the alert occurred and any action taken, usually consisting of 1 to 3 sentences. - reason_code: - type: string - description: A unique short-hand code or GUID identifying the particular alert reason - rule_category_id: - type: string - description: ID representing the category of the rule_id for certain alert types - rule_id: - type: string - description: ID of the rule that triggered an alert; applies to Intrusion Detection System, Host-Based Firewall, TAU Intelligence, and USB Device Control alerts - run_state: - type: string - enum: - - DID_NOT_RUN - - RAN - - UNKNOWN - description: Whether the threat in the alert actually ran - sensor_action: - type: string - enum: - - ALLOW - - ALLOW_AND_LOG - - DENY - - TERMINATE - description: Actions taken by the sensor, according to the rules of a policy - severity: - type: integer - format: int64 - description: integer representation of the impact of alert if true positive - tags: - type: array - description: Tags added to the threat ID of the alert - items: - type: string - threat_id: - type: string - description: ID assigned to a group of alerts with common criteria, based on alert type - threat_name: - type: string - description: Name of the threat - threat_notes_present: - type: boolean - description: True if notes are present on the threat ID. False if notes are not present. - tms_rule_id: - type: string - description: Detection id - ttps: - type: string - description: Other potential malicious activities involved in a threat - type: - type: string - enum: - - CB_ANALYTICS - - WATCHLIST - - DEVICE_CONTROL - - CONTAINER_RUNTIME - - HOST_BASED_FIREWALL - - INTRUSION_DETECTION_SYSTEM - - NETWORK_TRAFFIC_ANALYSIS - description: Type of alert generated - user_update_timestamp: - type: string - format: date-time - description: Timestamp of the last property of an alert changed by a user, such as the alert workflow or determination - workflow: - type: object - description: Current workflow state of an alert. The workflow represents the flow from `OPEN` to `IN_PROGRESS` to `CLOSED` and captures who moved the alert into the current state. The history of these state transitions is available via the alert history route. - properties: - change_timestamp: - type: string - format: date-time - description: When the last status change occurred - workflow_changed_by: - type: string - description: Who (or what) made the last status change - workflow_changed_by_rule_id: - type: string - description: - workflow_changed_by_type: - type: string - enum: - - SYSTEM - - USER - - API - - AUTOMATION - description: - workflow_closure_reason: - type: string `NO_REASON`, `RESOLVED`, `RESOLVED_BENIGN_KNOWN_GOOD`, `DUPLICATE_CLEANUP`, `OTHER` - description: A more detailed description of why the alert was resolved - workflow_status: - type: string - enum: - - OPEN - - IN_PROGRESS - - CLOSED - description: primary value used to determine if the alert is active or inactive and displayed in the UI by default \ No newline at end of file diff --git a/src/cbc_sdk/platform/models/alert_watchlist.yaml b/src/cbc_sdk/platform/models/alert_watchlist.yaml deleted file mode 100644 index 3951694f0..000000000 --- a/src/cbc_sdk/platform/models/alert_watchlist.yaml +++ /dev/null @@ -1,543 +0,0 @@ -type: object -properties: - additional_events_present: - type: boolean - description: Indicator to let API and forwarder users know that they should look up other associated events related to this alert - alert_notes_present: - type: boolean - description: True if notes are present on the alert ID. False if notes are not present. - alert_url: - type: string - description: Link to the alerts page for this alert. Does not vary by alert type - attack_tactic: - type: string - description: A tactic from the MITRE ATT&CK framework; defines a reason for an adversary’s action, such as achieving credential access - attack_technique: - type: string - description: A technique from the MITRE ATT&CK framework; defines an action an adversary takes to accomplish a goal, such as dumping credentials to achieve credential access - backend_timestamp: - type: string - format: date-time - description: Timestamp when the Carbon Black Cloud processed and enabled the alert for searching. Corresponds to the Created column on the Alerts page. - backend_update_timestamp: - type: string - format: date-time - description: Timestamp when the Carbon Black Cloud initiated and processed an update to an alert. Corresponds to the Updated column on the Alerts page. Note that changes made by users do not change this date; those changes are reflected on `user_update_timestamp` - blocked_effective_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Effective reputation of the blocked file or process; applied by the sensor at the time the block occurred - blocked_md5: - type: string - description: MD5 hash of the child process binary; for any process terminated by the sensor - blocked_name: - type: string - description: Tokenized file path of the files blocked by sensor action - blocked_sha256: - type: string - description: SHA-256 hash of the child process binary; for any process terminated by the sensor - category: - type: string - description: Alert category - Monitored vs Threat - enum: - - THREAT - - MONITORED - - INFO - - MINOR - - SERIOUS - - CRITICAL - childproc_cmdline: - type: string - description: Command line for the child process - childproc_effective_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Effective reputation of the child process; applied by the sensor at the time the event occurred - childproc_guid: - type: string - description: Unique process identifier assigned to the child process - childproc_md5: - type: string - description: Hash of the child process' binary (Enterprise EDR) - childproc_name: - type: string - description: Filesystem path of the child process' binary - childproc_sha256: - type: string - description: Hash of the child process' binary (Endpoint Standard) - childproc_username: - type: string - description: User context in which the child process was executed - detection_timestamp: - type: string - format: date-time - description: For sensor-sent alerts, this is the time of the event on the sensor. For alerts generated on the backend, this is the time the backend system triggered the alert. - determination: - description: User-updatable determination of the alert - type: object - properties: - change_timestamp: - type: string - format: date-time - description: When the determination was updated. - changed_by: - type: string - description: User the determination was changed by. - changed_by_type: - type: string - description: - enum: - - SYSTEM - - USER - - API - - AUTOMATION - value: - type: string - description: Determination of the alert set by a user - enum: - - NONE - - TRUE_POSITIVE - - FALSE_POSITIVE - device_external_ip: - type: string - description: IP address of the endpoint according to the Carbon Black Cloud; can differ from device_internal_ip due to network proxy or NAT; either IPv4 (dotted decimal notation) or IPv6 (proprietary format) - device_id: - type: integer - format: int64 - description: ID of devices - device_internal_ip: - type: string - description: IP address of the endpoint reported by the sensor; either IPv4 (dotted decimal notation) or IPv6 (proprietary format) - device_location: - type: string - enum: - - ONSITE - - OFFSITE - - UNKNOWN - description: Whether the device was on or off premises when the alert started, based on the current IP address and the device’s registered DNS domain suffix - device_name: - type: string - description: Device name - device_os: - type: string - enum: - - WINDOWS - - ANDROID - - MAC - - LINUX - - OTHER - description: Device Operating Systems - device_os_version: - type: string - example: Windows 10 x64 - description: The operating system and version of the endpoint. Requires Windows CBC sensor version 3.5 or later. - device_policy: - type: string - description: Device policy - device_policy_id: - type: integer - format: int64 - description: Device policy id - device_target_value: - type: string - enum: - - LOW - - MEDIUM - - HIGH - - MISSION_CRITICAL - description: Target value assigned to the device, set from the policy - device_uem_id: - type: string - description: Device correlation with WS1/EUC, required for our Workspace ONE Intelligence integration to function - device_username: - type: string - description: Logged on user during the alert. This is filled on a best-effort - approach. If the user is not available it may be populated with the device - owner (empty for Container Runtime alerts) - first_event_timestamp: - type: string - format: date-time - description: Timestamp when the first event in the alert occurred - id: - type: string - description: Unique IDs of alerts - ioc_field: - type: string - description: The field the indicator of comprise (IOC) hit contains - ioc_hit: - type: string - description: IOC field value or IOC query that matches - ioc_id: - type: string - description: Unique identifier of the IOC that generated the watchlist hit - is_updated: - type: boolean - description: Boolean that describes whether or not this is the original copy of the alert - last_event_timestamp: - type: string - format: date-time - description: Timestamp when the last event in the alert occurred - ml_classification_final_verdict: - type: string - enum: - - NOT_CLASSIFIED - - NOT_ANOMALOUS - - ANOMALOUS - description: Final verdict of the alert, based on the ML models that were used to make the prediction. - ml_classification_global_prevalence: - type: string - enum: - - UNKNOWN - - LOW - - MEDIUM - - HIGH - description: Categories (low/medium/high) used to describe the prevalence of alerts across all regional organizations. - ml_classification_org_prevalence: - type: string - enum: - - UNKNOWN - - LOW - - MEDIUM - - HIGH - description: Categories (low/medium/high) used to describe the prevalence of alerts within an organization. - netconn_local_ip: - type: string - description: IP address of the remote side of the network connection; stored as dotted decimal - netconn_local_ipv4: - type: string - description: IPv4 address of the local side of the network connection; stored as a dotted decimal. Only one of ipv4 and ipv6 fields will be populated. - netconn_local_ipv6: - type: string - description: IPv6 address of the local side of the network connection; stored as a string without octet-separating colon characters. Only one of ipv4 and ipv6 fields will be populated. - netconn_local_port: - type: integer - format: int64 - description: TCP or UDP port used by the local side of the network connection - netconn_protocol: - type: string - description: Network protocol of the network connection - netconn_remote_domain: - type: string - description: Domain name (FQDN) associated with the remote end of the network connection, if available - netconn_remote_ip: - type: string - description: IP address of the local side of the network connection; stored as dotted decimal - netconn_remote_ipv4: - type: string - description: IPv4 address of the remote side of the network connection; stored as dotted decimal. Only one of ipv4 and ipv6 fields will be populated. - netconn_remote_ipv6: - type: string - description: IPv6 address of the remote side of the network connection; stored as a string without octet-separating colon characters. Only one of ipv4 and ipv6 fields will be populated. - netconn_remote_port: - type: integer - format: int64 - description: TCP or UDP port used by the remote side of the network connection; same as netconn_port and event_network_remote_port - org_key: - type: string - description: Unique alphanumeric string that identifies your organization in the Carbon Black Cloud - parent_cmdline: - type: string - description: Command line of the parent process - parent_effective_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Effective reputation of the parent process; applied by the sensor when the event occurred - parent_guid: - type: string - description: Unique process identifier assigned to the parent process - parent_md5: - type: string - description: MD5 hash of the parent process binary - parent_name: - type: string - description: Filesystem path of the parent process binary - parent_pid: - type: integer - format: int64 - description: Identifier assigned by the operating system to the parent process - parent_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Reputation of the parent process; applied by the Carbon Black Cloud when the event is initially processed - parent_sha256: - type: string - description: SHA-256 hash of the parent process binary - parent_username: - type: string - description: User context in which the parent process was executed - policy_applied: - type: string - enum: - - APPLIED - - NOT_APPLIED - description: Indicates whether or not a policy has been applied to any event associated with this alert - primary_event_id: - type: string - description: ID of the primary event in the alert - process_cmdline: - type: string - description: Command line executed by the actor process - process_effective_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Effective reputation of the actor hash - process_guid: - type: string - description: Guid of the process that has fired the alert (optional) - process_issuer: - type: string - description: - process_md5: - type: string - description: MD5 hash of the actor process binary - process_name: - type: string - description: Process names of an alert - process_pid: - type: integer - format: int64 - description: PID of the process that has fired the alert (optional) - process_publisher: - type: string - description: - process_reputation: - type: string - enum: - - ADAPTIVE_WHITE_LIST - - COMMON_WHITE_LIST - - COMPANY_BLACK_LIST - - COMPANY_WHITE_LIST - - PUP - - TRUSTED_WHITE_LIST - - RESOLVING - - COMPROMISED_OBSOLETE - - DLP_OBSOLETE - - IGNORE - - ADWARE - - HEURISTIC - - SUSPECT_MALWARE - - KNOWN_MALWARE - - ADMIN_RESTRICT_OBSOLETE - - NOT_LISTED - - GRAY_OBSOLETE - - NOT_COMPANY_WHITE_OBSOLETE - - LOCAL_WHITE - - NOT_SUPPORTED - description: Reputation of the actor process; applied when event is processed by the Carbon Black Cloud - process_sha256: - type: string - description: SHA-256 hash of the actor process binary - process_username: - type: string - description: User context in which the actor process was executed. MacOS - all users for the PID for fork() and exec() transitions. Linux - process user for exec() events, but in a future sensor release can be multi-valued due to setuid(). - reason: - type: string - description: A spoken language written explanation of the what and why the alert occurred and any action taken, usually consisting of 1 to 3 sentences. - reason_code: - type: string - description: A unique short-hand code or GUID identifying the particular alert reason - report_description: - type: string - description: Description of the watchlist report associated with the alert - report_id: - type: string - description: Report IDs that contained the IOC that caused a hit - report_link: - type: string - description: Link of reports that contained the IOC that caused a hit - report_name: - type: string - description: Name of the watchlist report - report_tags: - type: string[] - description: Tags associated with the watchlist report - run_state: - type: string - enum: - - DID_NOT_RUN - - RAN - - UNKNOWN - description: Whether the threat in the alert actually ran - sensor_action: - type: string - enum: - - ALLOW - - ALLOW_AND_LOG - - DENY - - TERMINATE - description: Actions taken by the sensor, according to the rules of a policy - severity: - type: integer - format: int64 - description: integer representation of the impact of alert if true positive - tags: - type: array - description: Tags added to the threat ID of the alert - items: - type: string - threat_id: - type: string - description: ID assigned to a group of alerts with common criteria, based on alert type - threat_notes_present: - type: boolean - description: True if notes are present on the threat ID. False if notes are not present. - ttps: - type: string - description: Other potential malicious activities involved in a threat - type: - type: string - enum: - - CB_ANALYTICS - - WATCHLIST - - DEVICE_CONTROL - - CONTAINER_RUNTIME - - HOST_BASED_FIREWALL - - INTRUSION_DETECTION_SYSTEM - - NETWORK_TRAFFIC_ANALYSIS - description: Type of alert generated - user_update_timestamp: - type: string - format: date-time - description: Timestamp of the last property of an alert changed by a user, such as the alert workflow or determination - watchlists: - type: object - description: List of watchlists associated with an alert. Alerts are batched hourly - properties: - id: - type: string - description: - name: - type: string - description: - workflow: - type: object - description: Current workflow state of an alert. The workflow represents the flow from `OPEN` to `IN_PROGRESS` to `CLOSED` and captures who moved the alert into the current state. The history of these state transitions is available via the alert history route. - properties: - change_timestamp: - type: string - format: date-time - description: When the last status change occurred - workflow_changed_by: - type: string - description: Who (or what) made the last status change - workflow_changed_by_rule_id: - type: string - description: - workflow_changed_by_type: - type: string - enum: - - SYSTEM - - USER - - API - - AUTOMATION - description: - workflow_closure_reason: - type: string `NO_REASON`, `RESOLVED`, `RESOLVED_BENIGN_KNOWN_GOOD`, `DUPLICATE_CLEANUP`, `OTHER` - description: A more detailed description of why the alert was resolved - workflow_status: - type: string - enum: - - OPEN - - IN_PROGRESS - - CLOSED - description: primary value used to determine if the alert is active or inactive and displayed in the UI by default \ No newline at end of file diff --git a/src/cbc_sdk/platform/models/audit_log.yaml b/src/cbc_sdk/platform/models/audit_log.yaml new file mode 100644 index 000000000..842ad0916 --- /dev/null +++ b/src/cbc_sdk/platform/models/audit_log.yaml @@ -0,0 +1,27 @@ +type: object +properties: + actor_ip: + type: string + description: IP address of the entity that caused the creation of this audit log + actor: + type: string + description: Name of the entity that caused the creation of this audit log + create_time: + type: string + format: date-time + description: Timestamp when this audit log was created in ISO-8601 string format + description: + type: string + description: Text description of this audit log + flagged: + type: boolean + description: Whether the audit has been flagged + org_key: + type: string + description: Organization key + request_url: + type: string + description: URL of the request that caused the creation of this audit log + verbose: + type: boolean + description: Whether the audit has been marked verbose diff --git a/src/cbc_sdk/platform/models/policy_ruleconfig.yaml b/src/cbc_sdk/platform/models/policy_ruleconfig.yaml index 50b72db6a..ea7a14054 100644 --- a/src/cbc_sdk/platform/models/policy_ruleconfig.yaml +++ b/src/cbc_sdk/platform/models/policy_ruleconfig.yaml @@ -18,3 +18,6 @@ properties: parameters: type: object description: The parameters associated with this rule config + exclusions: + type: object + description: The exclusions associated with this rule config diff --git a/src/cbc_sdk/platform/observations.py b/src/cbc_sdk/platform/observations.py index 7e455f860..38143e166 100644 --- a/src/cbc_sdk/platform/observations.py +++ b/src/cbc_sdk/platform/observations.py @@ -269,6 +269,9 @@ def search_suggestions(cb, query, count=None): def bulk_get_details(cb, alert_id=None, observation_ids=None, timeout=0): """Bulk get details + Required Permissions: + org.search.events (READ, CREATE) + Args: cb (CBCloudAPI): A reference to the CBCloudAPI object. alert_id (str): An alert id to fetch associated observations diff --git a/src/cbc_sdk/platform/policies.py b/src/cbc_sdk/platform/policies.py index d7a67e11a..2f3f9291f 100644 --- a/src/cbc_sdk/platform/policies.py +++ b/src/cbc_sdk/platform/policies.py @@ -18,7 +18,8 @@ from cbc_sdk.base import MutableBaseModel, BaseQuery, IterableQueryMixin, AsyncQueryMixin from cbc_sdk.platform.devices import Device from cbc_sdk.platform.policy_ruleconfigs import (PolicyRuleConfig, CorePreventionRuleConfig, - HostBasedFirewallRuleConfig, DataCollectionRuleConfig) + HostBasedFirewallRuleConfig, DataCollectionRuleConfig, + BypassRuleConfig) from cbc_sdk.platform.previewer import DevicePolicyChangePreview from cbc_sdk.errors import ApiError, ServerError, InvalidObjectError @@ -26,7 +27,8 @@ SPECIFIC_RULECONFIGS = MappingProxyType({ "core_prevention": CorePreventionRuleConfig, "host_based_firewall": HostBasedFirewallRuleConfig, - "data_collection": DataCollectionRuleConfig + "data_collection": DataCollectionRuleConfig, + "bypass": BypassRuleConfig }) @@ -696,6 +698,28 @@ def object_rule_configs_list(self): """ return [rconf for rconf in self.object_rule_configs.values()] + @property + def bypass_rule_configs(self): + """ + Returns a dictionary of bypass rule configuration IDs and objects for this Policy. + + Returns: + dict: A dictionary with bypass rule configuration IDs as keys and BypassRuleConfig objects + as values. + """ + return {key: rconf for (key, rconf) in self.object_rule_configs.items() + if isinstance(rconf, BypassRuleConfig)} + + @property + def bypass_rule_configs_list(self): + """ + Returns a list of bypass rule configuration objects for this Policy. + + Returns: + list: A list of BypassRuleConfig objects. + """ + return [rconf for rconf in self.object_rule_configs.values() if isinstance(rconf, BypassRuleConfig)] + @property def core_prevention_rule_configs(self): """ diff --git a/src/cbc_sdk/platform/policy_ruleconfigs.py b/src/cbc_sdk/platform/policy_ruleconfigs.py index 6a17bf74e..a0f8d1fa6 100644 --- a/src/cbc_sdk/platform/policy_ruleconfigs.py +++ b/src/cbc_sdk/platform/policy_ruleconfigs.py @@ -263,8 +263,10 @@ def _refresh(self): def _update_ruleconfig(self): """Perform the internal update of the rule configuration object.""" url = self.urlobject_single.format(self._cb.credentials.org_key, self._parent._model_unique_id) - body = [{"id": self.id, "parameters": self.parameters}] - self._cb.put_object(url, body) + body = {"id": self.id, "parameters": self.parameters} + if "exclusions" in self._info: + body["exclusions"] = self.exclusions + self._cb.put_object(url, [body]) def _delete_ruleconfig(self): """Perform the internal delete of the rule configuration object.""" @@ -292,6 +294,16 @@ def set_assignment_mode(self, mode): raise ApiError(f"invalid assignment mode: {mode}") self.set_parameter("WindowsAssignmentMode", mode) + def replace_exclusions(self, exclusions): + """ + Replaces all the exclusions for a bypasss rule configuration + + Args: + exclusions(dict): The entire exclusion set to be replaced + """ + self._mark_changed(True) + self._info['exclusions'] = exclusions + class HostBasedFirewallRuleConfig(PolicyRuleConfig): """Represents a host-based firewall rule configuration in the policy.""" @@ -301,7 +313,7 @@ class HostBasedFirewallRuleConfig(PolicyRuleConfig): def __init__(self, cb, parent, model_unique_id=None, initial_data=None, force_init=False, full_doc=False): """ - Initialize the CorePreventionRuleConfig object. + Initialize the HostBasedFirewallRuleConfig object. Args: cb (BaseAPI): Reference to API object used to communicate with the server. @@ -643,7 +655,7 @@ class DataCollectionRuleConfig(PolicyRuleConfig): To update a DataCollectionRuleConfig, change the values of its property fields, then call its save() method. This requires the org.policies(UPDATE) permission. - To delete an existing CorePreventionRuleConfig, call its delete() method. This requires the org.policies(DELETE) + To delete an existing DataCollectionRuleConfig, call its delete() method. This requires the org.policies(DELETE) permission. """ urlobject_single = "/policyservice/v1/orgs/{0}/policies/{1}/rule_configs/data_collection" @@ -651,7 +663,7 @@ class DataCollectionRuleConfig(PolicyRuleConfig): def __init__(self, cb, parent, model_unique_id=None, initial_data=None, force_init=False, full_doc=False): """ - Initialize the CorePreventionRuleConfig object. + Initialize the DataCollectionRuleConfig object. Args: cb (BaseAPI): Reference to API object used to communicate with the server. @@ -696,3 +708,94 @@ def _delete_ruleconfig(self): """Perform the internal delete of the rule configuration object.""" url = self.urlobject_single.format(self._cb.credentials.org_key, self._parent._model_unique_id) + f"/{self.id}" self._cb.delete_object(url) + + +class BypassRuleConfig(PolicyRuleConfig): + """ + Represents a bypass rule configuration in the policy. + + Create one of these objects, associating it with a Policy, and set its properties, then call its save() method to + add the rule configuration to the policy. This requires the org.policies(UPDATE) permission. + + To update a BypassRuleConfig, change the values of its property fields, then call its save() method. This + requires the org.policies(UPDATE) permission. + + To delete an existing BypassRuleConfig, call its delete() method. This requires the org.policies(DELETE) + permission. + """ + urlobject_single = "/policyservice/v1/orgs/{0}/policies/{1}/rule_configs/bypass" + swagger_meta_file = "platform/models/policy_ruleconfig.yaml" + + def __init__(self, cb, parent, model_unique_id=None, initial_data=None, force_init=False, full_doc=False): + """ + Initialize the BypassRuleConfig object. + + Args: + cb (BaseAPI): Reference to API object used to communicate with the server. + parent (Policy): The "parent" policy of this rule configuration. + model_unique_id (str): ID of the rule configuration. + initial_data (dict): Initial data used to populate the rule configuration. + force_init (bool): If True, forces the object to be refreshed after constructing. Default False. + full_doc (bool): If True, object is considered "fully" initialized. Default False. + """ + super(BypassRuleConfig, self).__init__(cb, parent, model_unique_id, initial_data, force_init, full_doc) + + def _refresh(self): + """ + Refreshes the rule configuration object from the server. + + Required Permissions: + org.policies (READ) + + Returns: + bool: True if the refresh was successful. + + Raises: + InvalidObjectError: If the object is unparented or its ID is invalid. + """ + url = self.urlobject_single.format(self._cb.credentials.org_key, self._parent._model_unique_id) + return_data = self._cb.get_object(url) + ruleconfig_data = [d for d in return_data.get("results", []) if d.get("id", "") == self._model_unique_id] + if ruleconfig_data: + self._info = ruleconfig_data[0] + self._mark_changed(False) + else: + raise InvalidObjectError(f"invalid data collection ID: {self._model_unique_id}") + return True + + def _update_ruleconfig(self): + """Perform the internal update of the rule configuration object.""" + url = self.urlobject_single.format(self._cb.credentials.org_key, self._parent._model_unique_id) + body = {"id": self.id} + if "exclusions" in self._info: + body["exclusions"] = self.exclusions + + self._cb.put_object(url, body) + + def _delete_ruleconfig(self): + """Perform the internal delete of the rule configuration object.""" + url = self.urlobject_single.format(self._cb.credentials.org_key, self._parent._model_unique_id) + self._cb.delete_object(url) + + def replace_exclusions(self, exclusions): + """ + Replaces all the exclusions for a bypasss rule configuration + + Args: + exclusions(dict): The entire exclusion set to be replaced + """ + self._mark_changed(True) + self._info['exclusions'] = exclusions + + @property + def parameter_names(self): + """Not Supported""" + raise Exception("Not Suppported") + + def get_parameter(self, name, default_value=None): + """Not Supported""" + raise Exception("Not Suppported") + + def set_parameter(self, name, value): + """Not Supported""" + raise Exception("Not Suppported") diff --git a/src/cbc_sdk/platform/processes.py b/src/cbc_sdk/platform/processes.py index ec2030fc2..4093ab7f6 100644 --- a/src/cbc_sdk/platform/processes.py +++ b/src/cbc_sdk/platform/processes.py @@ -54,6 +54,11 @@ class Process(UnrefreshableModel): Objects of this type are retrieved through queries to the Carbon Black Cloud server, such as via ``AsyncProcessQuery``. + Processes have many fields, too many to list here; for a complete list of available fields, visit + `the Search Fields page + `_ + on the Carbon Black Developer Network, and filter on the ``PROCESS`` route. + Examples: >>> # use the Process GUID directly >>> process = api.select(Process, "WNEXFKQ7-00050603-0000066c-00000000-1d6c9acb43e29bb") @@ -662,6 +667,18 @@ def set_rows(self, rows): self._batch_size = rows return self + def set_collapse_field(self, field): + """ + Sets the 'collapse_field' query parameter, which queries the file name depending on field. + + Args: + field (list): query parameters to get file details. + """ + if not isinstance(field, list): + raise ApiError(f"Field must be list. {field} is a {type(field)}.") + self._collapse_field = field + return self + def _submit(self): """ Submits the query to the server. @@ -1023,15 +1040,28 @@ def _search(self, start=0, rows=0): else: raise ApiError(f"Failed to get Process Tree: {result['exception']}") - def _perform_query(self): + def _perform_query(self, from_row=0, max_rows=-1): """ Iterate over the results of the query. Required Permissions: org.search.events(CREATE, READ) + + Args: + from_row (int): Row to start iterating from (default 0). + max_rows(int): Number of rows to enumerate (default -1, meaning "all rows"). + + Yields: + Process.Summary or Process.Tree: The enumerated results. """ - for item in self.results: + returned_rows = 0 + for ndx, item in enumerate(self.results): + if ndx < from_row: + continue yield item + returned_rows += 1 + if 0 < max_rows <= returned_rows: + break @property def results(self): diff --git a/src/cbc_sdk/platform/vulnerability_assessment.py b/src/cbc_sdk/platform/vulnerability_assessment.py index 5f102cec0..0a67b81b4 100644 --- a/src/cbc_sdk/platform/vulnerability_assessment.py +++ b/src/cbc_sdk/platform/vulnerability_assessment.py @@ -294,10 +294,14 @@ def set_severity(self, severity): self._severity = severity return self - def _perform_query(self): + def _perform_query(self, from_row=0, max_rows=-1): """ Performs the query and returns the Vulnerability.OrgSummary + Args: + from_row (int): Not used, retained for compatibility. + max_rows (int): Not used, retained for compatibility. + Returns: Vulnerability.OrgSummary: The vulnerabilty summary for an organization """ diff --git a/src/cbc_sdk/utils.py b/src/cbc_sdk/utils.py index e1ae7de5f..2dbfcffa6 100755 --- a/src/cbc_sdk/utils.py +++ b/src/cbc_sdk/utils.py @@ -15,6 +15,8 @@ from __future__ import absolute_import import dateutil.parser +import time +from cbc_sdk.errors import TimeoutError cb_datetime_format = "%Y-%m-%d %H:%M:%S.%f" @@ -47,3 +49,141 @@ def convert_to_cb(dt): str: The date and time as a string. """ return dt.strftime(cb_datetime_format) + + +class BackoffHandler: + """ + Logic for handling exponential backoff of multiple communications requests. + + The logic also handles timeouts of operations that go on too long. + + Example:: + + backoff = BackoffHandler(timeout=600000) # 10 minutes = 600 seconds + with backoff as b: + while operation_continues(): + b.pause() + do_operation() + """ + def __init__(self, cb, timeout=0, initial=0.1, multiplier=2.0, threshold=2.0): + """ + Initialize the ``BackoffHandler``. + + Args: + cb (BaseAPI): The API object for the operation. + timeout (int): The timeout for the operation, in milliseconds. If this is 0, the default timeout as + configured in the credentials will be used. The default is 0. + initial (float): The initial value for the exponential backoff pause, in seconds. The default is 0.1. + multiplier (float): The value by which the exponential backoff pause will be multiplied each time + a pause happens. The default is 2.0. + threshold (float): The maximum value for the exponential backoff pause, in seconds. The default is 2.0. + """ + self._cb = cb + self._timeout = cb.credentials.default_timeout if timeout == 0 else timeout + self._initial = initial + self._multiplier = multiplier + self._threshold = threshold + + class BackoffOperation: + """ + Handler for a single operation requiring exponential backoff between communication attempts. + + This is returned by ``BackoffHandler`` as part of the ``with`` operation, and is stored in the variable + referred to in its ``as`` clause. + """ + def __init__(self, timeout, initial, multiplier, threshold): + """ + Initialize the ``BackoffOperation``. + + Args: + timeout (int): The timeout for the operation, in milliseconds. + initial (float): The initial value for the exponential backoff pause, in seconds. + multiplier (float): The value by which the exponential backoff pause will be multiplied each time + a pause happens. + threshold (float): The maximum value for the exponential backoff pause, in seconds. + """ + self._initial = initial + self._multiplier = multiplier + self._threshold = threshold + self._timeout_time = time.time() * 1000 + timeout + self._pausetime = 0.0 + self._first = True + + def pause(self): + """ + Pauses operation for a determined amount of time. + + The method also checks for a timeout and raises ``TimeoutError`` if it happens, and computes the amount + of time to pause the next time this method is called. + + Raises: + TimeoutError: If the timeout value is reached. + """ + if time.time() * 1000 > self._timeout_time: + raise TimeoutError(message="Operation timed out") + if self._pausetime > 0.0: + time.sleep(self._pausetime) + if time.time() * 1000 > self._timeout_time: + raise TimeoutError(message="Operation timed out") + if self._first: + self._pausetime = self._initial + self._first = False + else: + self._pausetime = min(self._pausetime * self._multiplier, self._threshold) + + def reset(self, full=False): + """ + Resets the state of the operation so that the pause time is reset. + + Does not affect the timeout value. This should be used, for instance, after a successful operation to + minimize the pause before the next operation is started. + + Args: + full (bool): If this is ``True``, the next pause time will be reset to 0. If this is ``False``, the + next pause time will be reset to the initial pause time. + """ + if full: + self._pausetime = 0.0 + self._first = True + else: + self._pausetime = self._initial + + def __enter__(self): + """ + Called at entry of the context specified by this object. + + Returns: + BackoffHandler.BackoffOperation: A new ``BackoffOperation`` object to manage the current operation. + """ + return BackoffHandler.BackoffOperation(self._timeout, self._initial, self._multiplier, self._threshold) + + def __exit__(self, exc_type, exc_val, exc_tb): + """ + Called at exit of the context specified by this object. + + This context does not suppress exceptions. + + Args: + exc_type (Any): Exception type (not used). + exc_val (Any): Exception value (not used). + exc_tb (Any): Exception traceback (not used). + + Returns: + bool: Always ``False``. + """ + return False + + @property + def timeout(self): + """Returns the current timeout associated with this handler, in milliseconds.""" + return self._timeout + + @timeout.setter + def timeout(self, val): + """ + Sets the the current timeout associated with this handler + + Args: + val (int): New timeout value to set, in milliseconds. + """ + self._timeout = self._cb.credentials.default_timeout if val == 0 else val diff --git a/src/cbc_sdk/workload/__init__.py b/src/cbc_sdk/workload/__init__.py index 2039ff707..e92bc689c 100644 --- a/src/cbc_sdk/workload/__init__.py +++ b/src/cbc_sdk/workload/__init__.py @@ -3,6 +3,8 @@ from cbc_sdk.workload.vm_workloads_search import VCenterComputeResource, AWSComputeResource from cbc_sdk.workload.sensor_lifecycle import SensorKit from cbc_sdk.workload.nsx_remediation import NSXRemediationJob +from cbc_sdk.workload.compliance_assessment import ComplianceBenchmark + # Maintain link for easier migration from cbc_sdk.platform.vulnerability_assessment import Vulnerability diff --git a/src/cbc_sdk/workload/compliance_assessment.py b/src/cbc_sdk/workload/compliance_assessment.py new file mode 100644 index 000000000..42d9f9f72 --- /dev/null +++ b/src/cbc_sdk/workload/compliance_assessment.py @@ -0,0 +1,615 @@ +#!/usr/bin/env python3 +# ******************************************************* +# Copyright (c) VMware, Inc. 2021-2024. All Rights Reserved. +# SPDX-License-Identifier: MIT +# ******************************************************* +# * +# * DISCLAIMER. THIS PROGRAM IS PROVIDED TO YOU "AS IS" WITHOUT +# * WARRANTIES OR CONDITIONS OF ANY KIND, WHETHER ORAL OR WRITTEN, +# * EXPRESS OR IMPLIED. THE AUTHOR SPECIFICALLY DISCLAIMS ANY IMPLIED +# * WARRANTIES OR CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, +# * NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE. + +"""Model and Query Classes for Compliance Assessment API""" + +from cbc_sdk.base import (BaseQuery, QueryBuilder, QueryBuilderSupportMixin, CriteriaBuilderSupportMixin, + IterableQueryMixin, AsyncQueryMixin, UnrefreshableModel) +from cbc_sdk.errors import ApiError +import logging + +log = logging.getLogger(__name__) + +""" Compliance models """ + + +class ComplianceBenchmark(UnrefreshableModel): + """Class representing Compliance Benchmarks.""" + urlobject = '/compliance/assessment/api/v1/orgs/{}/benchmark_sets/' + swagger_meta_file = "workload/models/compliance_benchmark.yaml" + primary_key = "id" + + def __init__(self, cb, model_unique_id, initial_data=None): + """ + Initialize a ComplianceBenchmark instance. + + Args: + cb (CBCloudAPI): Instance of CBCloudAPI. + initial_data (dict): Initial data for the instance. + model_unique_id (str): Unique identifier for the model. + + Returns: + ComplianceBenchmark: An instance of ComplianceBenchmark. + """ + super(ComplianceBenchmark, self).__init__(cb, model_unique_id, initial_data) + + if model_unique_id is not None and initial_data is None: + benchmark = cb.select(ComplianceBenchmark).add_criteria("id", [model_unique_id]).first() + if benchmark is None: + raise ApiError(f"Benchmark {model_unique_id} not found") + self._info = benchmark._info + self._full_init = True + + @classmethod + def _query_implementation(cls, cb, **kwargs): + """ + Get the query implementation for ComplianceBenchmark. + + Args: + cb (CBCloudAPI): Instance of CBCloudAPI. + + Returns: + ComplianceBenchmarkQuery: Query implementation for ComplianceBenchmark. + """ + return ComplianceBenchmarkQuery(cls, cb) + + @staticmethod + def get_compliance_schedule(cb): + """ + Gets the compliance scan schedule and timezone configured for the Organization. + + Args: + cb (CBCloudAPI): An instance of CBCloudAPI representing the Carbon Black Cloud API. + + Required Permissions: + complianceAssessment.data(READ) + + Raises: + ApiError: If cb is not an instance of CBCloudAPI. + + Returns: + dict: The configured organization settings for Compliance Assessment. + + Example: + >>> cb = CBCloudAPI(profile="example_profile") + >>> schedule = ComplianceBenchmark.get_compliance_schedule(cb) + >>> print(schedule) + """ + if cb.__class__.__name__ != "CBCloudAPI": + raise ApiError("cb argument should be instance of CBCloudAPI") + + url = f"/compliance/assessment/api/v1/orgs/{cb.credentials.org_key}/settings" + + return cb.get_object(url) + + @staticmethod + def set_compliance_schedule(cb, scan_schedule, scan_timezone): + """ + Sets the compliance scan schedule and timezone for the organization. + + Required Permissions: + complianceAssessment.data(UPDATE) + + Args: + cb (CBCloudAPI): An instance of CBCloudAPI representing the Carbon Black Cloud API. + scan_schedule (str): The scan schedule, specified in RFC 5545 format. + Example: "RRULE:FREQ=DAILY;BYHOUR=17;BYMINUTE=30;BYSECOND=0". + scan_timezone (str): The timezone in which the scan will run, + specified as a timezone string. Example: "UTC". + + Raises: + ApiError: If cb is not an instance of CBCloudAPI, or if scan_schedule or scan_timezone are not provided. + + Returns: + dict: The configured organization settings for Compliance Assessment. + + Example: + >>> cb = CBCloudAPI(profile="example_profile") + >>> schedule = ComplianceBenchmark.set_compliance_schedule(cb, + scan_schedule="RRULE:FREQ=DAILY;BYHOUR=17;BYMINUTE=30;BYSECOND=0", + scan_timezone="UTC") + >>> print(schedule) + """ + if cb.__class__.__name__ != "CBCloudAPI": + raise ApiError("cb argument should be instance of CBCloudAPI") + if not scan_schedule or not scan_timezone or scan_schedule == "" or scan_timezone == "": + raise ApiError("scan_schedule and scan_timezone are required") + + args = {"scan_schedule": scan_schedule, "scan_timezone": scan_timezone} + url = f"/compliance/assessment/api/v1/orgs/{cb.credentials.org_key}/settings" + + return cb.put_object(url, body=args).json() + + def get_sections(self): + """ + Get Sections of the Benchmark Set. + + Required Permissions: + complianceAssessment.data(READ) + + Returns: + list[dict]: List of sections within the Benchmark Set. + + Example: + >>> cb = CBCloudAPI(profile="example_profile") + >>> benchmark = cb.select(ComplianceBenchmark).first() + >>> for section in benchmark.get_sections(): + ... print(section.section_name, section.section_id) + """ + url = self.urlobject.format(self._cb.credentials.org_key) + f"{self.id}/sections" + results = self._cb.get_object(url) + return results + + def get_rules(self, rule_id=None): + """ + Fetches compliance rules associated with the benchmark set. + + Required Permissions: + complianceAssessment.data(READ) + + Args: + rule_id (str, optional): The rule ID to fetch a specific rule. Defaults to None. + + Returns: + [dict]: List of Benchmark Rules + + Example: + >>> cb = CBCloudAPI(profile="example_profile") + >>> benchmark_set = cb.select(ComplianceBenchmark).first() + >>> # To return all rules within a benchmark set, leave get_rules empty. + >>> rules = benchmark_set.get_rules() + """ + if rule_id is not None: + self._rule_id = rule_id + url = self.urlobject.format(self._cb.credentials.org_key) + f"{self.id}/rules/{rule_id}" + return [self._cb.get_object(url)] + + url = self.urlobject.format(self._cb.credentials.org_key) + f"{self.id}/rules/_search" + current = 0 + rules = [] + while True: + resp = self._cb.post_object(url, body={ + "rows": 10000, + "start": current, + "sort": [ + { + "field": "section_name", + "order": "DESC" + } + ] + }) + result = resp.json() + + rules.extend(result.get("results", [])) + current = len(rules) + if current >= result["num_found"]: + break + + return rules + + def update_rules(self, rule_ids, enabled): + """ + Update compliance rules associated with the benchmark set. + + Required Permissions: + complianceAssessment.data(UPDATE) + + Args: + rule_ids (list[str]): The rule IDs to update their enabled/disabled status. + enabled (bool): Whether the rule is enabled or disabled. + + Returns: + [dict]: List of Updated Benchmark Rules + + Example: + >>> cb = CBCloudAPI(profile="example_profile") + >>> benchmark_set = cb.select(ComplianceBenchmark).first() + >>> # To return all rules within a benchmark set, leave get_rules empty. + >>> rules = benchmark_set.update_rules(["2A65B63E-89D9-4844-8290-5042FDF2A27B"], True) + """ + request = [] + for rule_id in rule_ids: + request.append({ + "rule_id": rule_id, + "enabled": enabled + }) + url = self.urlobject.format(self._cb.credentials.org_key) + f"{self.id}/rules" + return self._cb.put_object(url, body=request).json() + + def execute_action(self, action, device_ids=None): + """ + Execute a specified action for the Benchmark Set for all devices or a specified subset. + + Required Permissions: + complianceAssessment.data(EXECUTE) + + Args: + action (str): The action to be executed. Options: ENABLE, DISABLE, REASSESS + + device_ids (str or list, optional): IDs of devices on which the action will be executed. + If specified as a string, only one device will be targeted. If specified as a list, + the action will be executed on multiple devices. Default is None. + + Returns: + dict: JSON response containing information about the executed action. + + Raises: + ApiError: If the provided action is not one of the allowed actions. + + Example: + To reassess an object: + benchmark_sets = cb.select(ComplianceBenchmark) + benchmark_sets[0].execute_action('REASSESS') + """ + ACTIONS = ('ENABLE', 'DISABLE', 'REASSESS') + + if action.upper() not in ACTIONS: + raise ApiError("Action is not supported. Options: ENABLE, DISABLE, REASSESS") + + args = {"action": action.upper()} + url = "" + if device_ids: + args['device_ids'] = [device_ids] if isinstance(device_ids, str) else device_ids + url = self.urlobject.format(self._cb.credentials.org_key) + f"{self.id}/compliance/device_actions" + else: + url = self.urlobject.format(self._cb.credentials.org_key) + f"{self.id}/actions" + + return self._cb.post_object(url, body=args).json() + + def get_device_compliances(self, query=""): + """ + Fetches devices compliance summaries associated with the benchmark set. + + Required Permissions: + complianceAssessment.data(READ) + + Args: + query (str, optional): The query to filter results. + + Returns: + [dict]: List of Device Compliances + + Example: + >>> cb = CBCloudAPI(profile="example_profile") + >>> benchmark_set = cb.select(ComplianceBenchmark).first() + >>> device_compliances = benchmark_set.get_device_compliance() + """ + url = self.urlobject.format(self._cb.credentials.org_key) + f"{self.id}/compliance/devices/_search" + current = 0 + device_compliances = [] + while True: + resp = self._cb.post_object(url, body={ + "query": query, + "rows": 10000, + "start": current, + "sort": [ + { + "field": "device_name", + "order": "DESC" + } + ] + }) + result = resp.json() + + device_compliances.extend(result.get("results", [])) + current = len(device_compliances) + + if current >= result["num_found"]: + break + + return device_compliances + + def get_rule_compliances(self, query=""): + """ + Fetches rule compliance summaries associated with the benchmark set. + + Required Permissions: + complianceAssessment.data(READ) + + Args: + query (str, optional): The query to filter results. + + Returns: + [dict]: List of Rule Compliances + + Example: + >>> cb = CBCloudAPI(profile="example_profile") + >>> benchmark_set = cb.select(ComplianceBenchmark).first() + >>> rules = benchmark_set.get_rule_compliance() + """ + url = self.urlobject.format(self._cb.credentials.org_key) + f"{self.id}/compliance/rules/_search" + current = 0 + rule_compliances = [] + while True: + resp = self._cb.post_object(url, body={ + "query": query, + "rows": 10000, + "start": current, + "sort": [ + { + "field": "section_name", + "order": "DESC" + } + ] + }) + result = resp.json() + + rule_compliances.extend(result.get("results", [])) + current = len(rule_compliances) + + if current >= result["num_found"]: + break + + return rule_compliances + + def get_device_rule_compliances(self, device_id, query=""): + """ + Fetches rule compliances for specific device. + + Required Permissions: + complianceAssessment.data(READ) + + Args: + device_id (int): Device id to fetch benchmark rule compliance + query (str, optional): The query to filter results. + + Returns: + [dict]: List of Rule Compliances + + Example: + >>> cb = CBCloudAPI(profile="example_profile") + >>> benchmark_set = cb.select(ComplianceBenchmark).first() + >>> rules = benchmark_set.get_device_rule_compliance(123) + """ + url = self.urlobject.format(self._cb.credentials.org_key) + url += f"{self.id}/compliance/devices/{device_id}/rules/_search" + + current = 0 + rule_compliances = [] + while True: + resp = self._cb.post_object(url, body={ + "query": query, + "rows": 10000, + "start": current, + "sort": [ + { + "field": "section_name", + "order": "DESC" + } + ] + }) + result = resp.json() + + rule_compliances.extend(result.get("results", [])) + current = len(rule_compliances) + + if current >= result["num_found"]: + break + + return rule_compliances + + def get_rule_compliance_devices(self, rule_id, query=""): + """ + Fetches device compliances for a specific rule. + + Required Permissions: + complianceAssessment.data(READ) + + Args: + rule_id (str): Rule id to fetch device compliances + query (str, optional): The query to filter results. + + Returns: + [dict]: List of Device Compliances + + Example: + >>> cb = CBCloudAPI(profile="example_profile") + >>> benchmark_set = cb.select(ComplianceBenchmark).first() + >>> rules = benchmark_set.get_rule_compliance_devices("BCCAAACA-F0BE-4C0F-BE0A-A09FC1641EE2") + """ + url = self.urlobject.format(self._cb.credentials.org_key) + \ + f"{self.id}/compliance/rules/{rule_id}/devices/_search" + + current = 0 + device_compliances = [] + while True: + resp = self._cb.post_object(url, body={ + "query": query, + "rows": 10000, + "start": current, + "sort": [ + { + "field": "device_name", + "order": "DESC" + } + ] + }) + result = resp.json() + + device_compliances.extend(result.get("results", [])) + current = len(device_compliances) + + if current >= result["num_found"]: + break + + return device_compliances + + +class ComplianceBenchmarkQuery(BaseQuery, QueryBuilderSupportMixin, CriteriaBuilderSupportMixin, + IterableQueryMixin, AsyncQueryMixin): + """A class representing a query for Compliance Benchmark.""" + + def __init__(self, doc_class, cb): + """ + Initialize a ComplianceBenchmarkQuery instance. + + Args: + doc_class (class): The document class for this query. + cb (CBCloudAPI): An instance of CBCloudAPI. + + Returns: + ComplianceBenchmarkQuery: An instance of ComplianceBenchmarkQuery. + """ + self._doc_class = doc_class + self._cb = cb + self._count_valid = False + super(BaseQuery, self).__init__() + + self._query_builder = QueryBuilder() + self._criteria = {} + self._sortcriteria = {} + self._total_results = 0 + + self._benchmark_set_id = None + self._rule_id = None + + def sort_by(self, key, direction='ASC'): + """ + Sets the sorting behavior on a query's results. + + Arguments: + key (str): The key in the schema to sort by. + direction (str): The sort order, either "ASC" or "DESC". + + Returns: + Query: The query with sorting parameters. + + Raises: + ApiError: If an invalid sort direction is specified. + + Example: + To sort by a field in descending order: + + >>> cb = CBCloudAPI(profile="example_profile") + >>> benchmark_sets = cb.select(ComplianceBenchmark).sort_by("name", direction="DESC") + >>> print(*benchmark_sets) + """ + if direction.upper() not in ('ASC', 'DESC'): + raise ApiError('invalid sort direction specified') + self._sortcriteria = {'field': key, 'order': direction} + return self + + def _build_request(self, from_row, max_rows, add_sort=True): + """ + Build the request dictionary for the API query. + + Args: + from_row (int): The starting row for the query. + max_rows (int): The maximum number of rows to retrieve. + add_sort (bool): Flag indicating whether to add sorting criteria to the request. + + Returns: + dict: The constructed request dictionary. + """ + request = { + "criteria": self._criteria, + "query": self._query_builder._collapse(), + "rows": 1000 if max_rows < 0 else max_rows + } + + if from_row > 0: + request["start"] = from_row + + if add_sort and self._sortcriteria != {}: + request["sort"] = [self._sortcriteria] + + return request + + def _build_url(self, tail_end): + """ + Build the URL for the API request. + + Args: + tail_end (str): The tail end of the URL to be appended. + + Returns: + str: The constructed URL. + """ + return self._doc_class.urlobject.format(self._cb.credentials.org_key) + tail_end + + def _count(self): + """ + Get the total number of results. + + Returns: + int: The total number of results. + """ + if self._count_valid: + return self._total_results + + url = self._build_url("_search") + request = self._build_request(0, -1) + result = self._cb.post_object(url, body=request).json() + + self._total_results = result["num_found"] + self._count_valid = True + + return self._total_results + + def _perform_query(self, from_row=0, max_rows=-1): + """ + Perform a query and retrieve the results. + + Args: + from_row (int): The starting row index for the query (default is 0). + max_rows (int): The maximum number of rows to retrieve (-1 retrieves all rows). + + Yields: + obj: An instance of the document class containing each query result. + """ + url = self._build_url("_search") + current = from_row + numrows = 0 + while True: + request = self._build_request(current, max_rows) + resp = self._cb.post_object(url, body=request) + result = resp.json() + + self._total_results = result["num_found"] + self._count_valid = True + + results = result.get("results", []) + for item in results: + yield self._doc_class(self._cb, item[self._doc_class.primary_key], initial_data=item) + current += 1 + numrows += 1 + + if max_rows > 0 and numrows == max_rows: + break + + if current >= self._total_results: + break + + def _run_async_query(self, context): + """ + Run an asynchronous query and retrieve the results. + + Args: + context: The context for the query. + + Returns: + dict: The JSON response containing the query results. + """ + url = self._build_url("_search") + output = [] + while not self._count_valid or len(output) < self._total_results: + request = self._build_request(len(output), -1) + resp = self._cb.post_object(url, body=request) + result = resp.json() + + if not self._count_valid: + self._total_results = result["num_found"] + self._count_valid = True + + results = result.get("results", []) + output += [self._doc_class(self._cb, item[self._doc_class.primary_key], item) for item in results] + return output diff --git a/src/cbc_sdk/workload/models/compliance_benchmark.yaml b/src/cbc_sdk/workload/models/compliance_benchmark.yaml new file mode 100644 index 000000000..bf248f567 --- /dev/null +++ b/src/cbc_sdk/workload/models/compliance_benchmark.yaml @@ -0,0 +1,53 @@ +type: object +properties: + id: + type: string + description: Unique identifier for the benchmark set. + name: + type: string + description: Name of the benchmark set. + version: + type: string + description: Version of the benchmark set. + os_family: + type: string + description: Operating system family associated with the benchmark set (e.g., WINDOWS_SERVER). + enabled: + type: boolean + description: Indicates whether the benchmark set is enabled or not. + type: + type: string + description: Type of the benchmark set (e.g., Custom). + supported_os_info: + type: array + description: Array of supported operating system information. + items: + type: object + properties: + os_metadata_id: + type: string + description: Unique identifier for the OS metadata. + os_type: + type: string + description: Type of the operating system (e.g., WINDOWS). + os_name: + type: string + description: Name of the operating system. + cis_version: + type: string + description: CIS (Center for Internet Security) version associated with the OS. + created_by: + type: string + description: Name of the user who created the benchmark set. + updated_by: + type: string + description: Email of the user who last updated the benchmark set. + create_time: + type: string + description: Timestamp indicating when the benchmark set was created (in ISO 8601 format). + update_time: + type: string + description: Timestamp indicating when the benchmark set was last updated (in ISO 8601 format). + release_time: + type: string + description: Timestamp indicating when the benchmark set was released (in ISO 8601 format). diff --git a/src/tests/unit/credential_providers/test_file.py b/src/tests/unit/credential_providers/test_file.py index 519f84e14..c2f0dceb2 100755 --- a/src/tests/unit/credential_providers/test_file.py +++ b/src/tests/unit/credential_providers/test_file.py @@ -127,9 +127,15 @@ def mock_stat(path): assert last_failmsg.endswith(suffix) -def test_read_single_file(): +@pytest.mark.parametrize("filename", [ + "config_valid.cbc", + "config_valid_utf8sig.cbc", + "config_valid_utf16be.cbc", + "config_valid_utf16le.cbc" +]) +def test_read_single_file(filename): """Test the basic reading of multiple credential sets from a single file.""" - sut = FileCredentialProvider(path_of("config_valid.cbc")) + sut = FileCredentialProvider(path_of(filename)) creds = sut.get_credentials("default") assert creds.url == "http://example.com" assert creds.token == "ABCDEFGH" @@ -192,3 +198,11 @@ def test_file_with_parsing_error(): sut = FileCredentialProvider(path_of("config_parseerror.cbc")) with pytest.raises(CredentialError): sut.get_credentials("default") + + +def test_no_search_path_error(): + """Tests that an error is thrown if no files can be found in the search path.""" + sut = FileCredentialProvider() + sut._search_path = [] + with pytest.raises(CredentialError): + sut.get_credentials() diff --git a/src/tests/unit/credential_providers/test_file_data/config_valid_utf16be.cbc b/src/tests/unit/credential_providers/test_file_data/config_valid_utf16be.cbc new file mode 100644 index 000000000..f06be7089 Binary files /dev/null and b/src/tests/unit/credential_providers/test_file_data/config_valid_utf16be.cbc differ diff --git a/src/tests/unit/credential_providers/test_file_data/config_valid_utf16le.cbc b/src/tests/unit/credential_providers/test_file_data/config_valid_utf16le.cbc new file mode 100644 index 000000000..aa0f69908 Binary files /dev/null and b/src/tests/unit/credential_providers/test_file_data/config_valid_utf16le.cbc differ diff --git a/src/tests/unit/credential_providers/test_file_data/config_valid_utf8sig.cbc b/src/tests/unit/credential_providers/test_file_data/config_valid_utf8sig.cbc new file mode 100644 index 000000000..b289cac93 --- /dev/null +++ b/src/tests/unit/credential_providers/test_file_data/config_valid_utf8sig.cbc @@ -0,0 +1,15 @@ +[default] +url=http://example.com +token=ABCDEFGH +org_key=A1B2C3D4 +ssl_verify=false +ssl_verify_hostname=no +ssl_cert_file=foo.certs +ssl_force_tls_1_2=1 +proxy=proxy.example +ignore_system_proxy=on +integration=Covax + +[partial] +url=http://example.com +ssl_verify=False diff --git a/src/tests/unit/fixtures/platform/mock_audit.py b/src/tests/unit/fixtures/platform/mock_audit.py index 11f065e92..84e58841c 100644 --- a/src/tests/unit/fixtures/platform/mock_audit.py +++ b/src/tests/unit/fixtures/platform/mock_audit.py @@ -61,3 +61,110 @@ "success": True, "message": "Success", } + +AUDIT_SEARCH_REQUEST = { + "criteria": { + "actor_ip": ["10.29.99.1"], + "actor": ["ABCDEFGHIJ"], + "request_url": ["https://inclusiveladyship.com"], + "description": ["FOOBAR"], + "flagged": True, + "verbose": False, + "create_time": { + "start": "2024-03-01T00:00:00.000000Z", + "end": "2024-03-31T22:00:00.000000Z" + } + }, + "exclusions": { + "actor_ip": ["10.29.99.254"], + "actor": ["JIHGFEDCBA"], + "request_url": ["https://links.inclusiveladyship.com"], + "description": ["BLORT"], + "flagged": False, + "verbose": True, + "create_time": { + "range": "-5d" + } + }, + "query": "description:FOO", + "sort": [ + { + "field": "actor_ip", + "order": "ASC" + } + ] +} + +AUDIT_SEARCH_RESPONSE = { + "num_found": 5, + "num_available": 5, + "results": [ + { + "org_key": "test", + "actor_ip": "192.168.0.5", + "actor": "DEFGHIJKLM", + "request_url": None, + "description": "Connector DEFGHIJKLM logged in successfully", + "flagged": False, + "verbose": False, + "create_time": "2024-04-17T19:18:37.480Z" + }, + { + "org_key": "test", + "actor_ip": "192.168.3.5", + "actor": "BELTALOWDA", + "request_url": None, + "description": "Updated report, 'MCRN threat feed'", + "flagged": False, + "verbose": False, + "create_time": "2024-04-17T19:13:01.528Z" + }, + { + "org_key": "test", + "actor_ip": "192.168.3.8", + "actor": "BELTALOWDA", + "request_url": None, + "description": "Updated report, 'Reported by DOP'", + "flagged": False, + "verbose": False, + "create_time": "2024-04-17T19:13:01.042Z" + }, + { + "org_key": "test", + "actor_ip": "192.168.3.11", + "actor": "BELTALOWDA", + "request_url": None, + "description": "Updated report, 'Reported by Mao-Kwikowski'", + "flagged": False, + "verbose": False, + "create_time": "2024-04-17T19:13:00.235Z" + }, + { + "org_key": "test", + "actor_ip": "192.168.3.14", + "actor": "BELTALOWDA", + "request_url": None, + "description": "Updated report, 'Malware SSL Certificate Fingerprint'", + "flagged": False, + "verbose": False, + "create_time": "2024-04-17T19:12:59.693Z" + } + ] +} + +AUDIT_EXPORT_REQUEST = { + "query": "description:FOO", + "format": "csv" +} + +MOCK_AUDIT_EXPORT_JOB = { + "id": 4805565, + "type": "EXTERNAL", + "job_parameters": { + "job_parameters": None + }, + "org_key": "test", + "status": "COMPLETED", + "create_time": "2023-02-02T23:16:25.625583Z", + "last_update_time": "2023-02-02T23:16:29.079184Z" +} diff --git a/src/tests/unit/fixtures/platform/mock_jobs.py b/src/tests/unit/fixtures/platform/mock_jobs.py index b5525becc..5a6e55ad6 100644 --- a/src/tests/unit/fixtures/platform/mock_jobs.py +++ b/src/tests/unit/fixtures/platform/mock_jobs.py @@ -56,20 +56,6 @@ "message": "Foo" } -AWAIT_COMPLETION_PROGRESS = [ - { - "num_total": 18, - "num_completed": 0, - "message": "" - }, - { - "num_total": 18, - "num_completed": 10, - "message": "" - }, - { - "num_total": 18, - "num_completed": 18, - "message": "" - }, -] +AWAIT_COMPLETION_DETAILS_PROGRESS_1 = ["CREATED", "CREATED", "CREATED", "COMPLETED"] + +AWAIT_COMPLETION_DETAILS_PROGRESS_2 = ["CREATED", "CREATED", "CREATED", "FAILED"] diff --git a/src/tests/unit/fixtures/platform/mock_policies.py b/src/tests/unit/fixtures/platform/mock_policies.py index ed6b70290..cd7ee2d32 100644 --- a/src/tests/unit/fixtures/platform/mock_policies.py +++ b/src/tests/unit/fixtures/platform/mock_policies.py @@ -315,6 +315,64 @@ }, "enable_host_based_firewall": False } + }, + { + "id": "1664f2e6-645f-4d6e-98ec-0c80485cbe0f", + "name": "Event Reporting Exclusions", + "description": "Allows customers to exclude specific processes from reporting events to CBC", + "inherited_from": "psc:region", + "category": "bypass", + "parameters": {} + }, + { + "id": "1c03d653-eca4-4adc-81a1-04b17b6cbffc", + "name": "Event Reporting and Sensor Operation Exclusions", + "description": "Allows customers to exclude specific processes and process events from reporting to CBC", + "inherited_from": "psc:region", + "category": "bypass", + "parameters": {}, + "exclusions": { + "windows": [ + { + "id": 8090, + "criteria": [ + { + "id": 13426, + "type": "initiator_process", + "attributes": [ + { + "id": 93774, + "name": "process_name", + "values": [ + "**\\explorer.exe" + ] + } + ] + }, + { + "id": 13427, + "type": "operation", + "attributes": [ + { + "id": 93775, + "name": "operation_type", + "values": [ + "ALL" + ] + } + ] + } + ], + "comments": "", + "type": "ENDPOINT_STANDARD_PROCESS_BYPASS", + "apply_to_descendent_processes": True, + "created_by": "ABCD1234", + "created_at": "2024-01-27T13:29:44.839Z", + "modified_by": "ABCD1234", + "modified_at": "2024-01-27T13:29:44.839Z" + } + ] + } } ] } @@ -1738,22 +1796,107 @@ POLICY_CONFIG_PRESENTATION = { "configs": [ + { + "id": "cc075469-8d1e-4056-84b6-0e6f437c4010", + "name": "XDR", + "description": "Turns on XDR network data collection at the sensor", + "presentation": { + "category": "data_collection" + }, + "parameters": [] + }, { "id": "91c919da-fb90-4e63-9eac-506255b0a0d0", "name": "Authentication Events", - "description": "Authentication Events", + "description": "Turns on Windows authentication events at the sensor", "presentation": { "category": "data_collection" }, "parameters": [] }, + { + "id": "1c03d653-eca4-4adc-81a1-04b17b6cbffc", + "name": "Event Reporting and Sensor Operation Exclusions", + "description": "Allows customers to exclude specific processes and process events from reporting to CBC", + "presentation": { + "name": "process_exclusion.name", + "category": "bypass", + "description": [ + "process_exclusion.description" + ], + "platforms": [ + { + "platform": "WINDOWS", + "exclusions": { + "criteria": [ + "initiator_process", + "operations" + ], + "additional_attributes": [ + "type", + "inheritence" + ] + } + } + ] + }, + "parameters": [] + }, + { + "id": "0aa2b31a-f938-4cf9-acee-7cf7b810eb79", + "name": "Background Scan", + "description": "This rapid config handles DRE rules and sensor settings associated with Background Scan", + "presentation": { + "category": "sensor_settings" + }, + "parameters": [] + }, + { + "id": "1664f2e6-645f-4d6e-98ec-0c80485cbe0f", + "name": "Event Reporting Exclusions", + "description": "Allows customers to exclude specific processes from reporting events to CBC", + "presentation": { + "name": "event_reporting_exclusion.name", + "category": "bypass", + "description": [ + "event_reporting_exclusion.description" + ], + "platforms": [ + { + "platform": "WINDOWS", + "exclusions": { + "criteria": [ + "initiator_process", + "operations" + ], + "additional_attributes": [ + "type", + "inheritence" + ] + } + } + ] + }, + "parameters": [] + }, + { + "id": "df181779-f623-415d-879e-91c40246535d", + "name": "Host Based Firewall", + "description": "These are the Host based Firewall Rules which will be executed by the sensor." + " The Definition will be part of Main Policies.", + "presentation": { + "category": "hbfw" + }, + "parameters": [] + }, { "id": "1f8a5e4b-34f2-4d31-9f8f-87c56facaec8", "name": "Advanced Scripting Prevention", - "description": "Addresses malicious fileless and file-backed scripts that leverage native programs [...]", + "description": "Addresses malicious fileless and file-backed scripts that leverage native programs" + " and common scripting languages.", "presentation": { "name": "amsi.name", - "category": "core_prevention", + "category": "core-prevention", "description": [ "amsi.description" ], @@ -1794,10 +1937,11 @@ { "id": "ac67fa14-f6be-4df9-93f2-6de0dbd96061", "name": "Credential Theft", - "description": "Addresses threat actors obtaining credentials and relies on detecting the malicious [...]", + "description": "Addresses threat actors obtaining credentials and relies on detecting the malicious use of" + " TTPs/behaviors that indicate such activity.", "presentation": { "name": "cred_theft.name", - "category": "core_prevention", + "category": "core-prevention", "description": [ "cred_theft.description" ], @@ -1836,21 +1980,23 @@ ] }, { - "id": "df181779-f623-415d-879e-91c40246535d", - "name": "Host Based Firewall", - "description": "These are the Host based Firewall Rules which will be executed by the sensor. [...].", + "id": "491dd777-5a76-4f58-88bf-d29926d12778", + "name": "Prevalent Module Exclusions", + "description": "Collects events created when a process loads a common library. Enabling this will increase" + " the number of events reported for expected process behavior.", "presentation": { - "category": "hbfw" + "category": "data_collection" }, "parameters": [] }, { "id": "c4ed61b3-d5aa-41a9-814f-0f277451532b", "name": "Carbon Black Threat Intel", - "description": "Addresses common and pervasive TTPs used for malicious activity as well as [...]", + "description": "Addresses common and pervasive TTPs used for malicious activity as well as living off the" + " land TTPs/behaviors detected by Carbon Black’s Threat Analysis Unit.", "presentation": { "name": "cbti.name", - "category": "core_prevention", + "category": "core-prevention", "description": [ "cbti.description" ], @@ -1891,10 +2037,12 @@ { "id": "88b19232-7ebb-48ef-a198-2a75a282de5d", "name": "Privilege Escalation", - "description": "Addresses behaviors that indicate a threat actor has gained elevated access via [...]", + "description": "Addresses behaviors that indicate a threat actor has gained elevated access via a bug or" + " misconfiguration within an operating system, and leverages the detection of TTPs/behaviors to prevent" + " such activity.", "presentation": { "name": "privesc.name", - "category": "core_prevention", + "category": "core-prevention", "description": [ "privesc.description" ], @@ -1931,6 +2079,97 @@ ] } ] + }, + { + "id": "97a03cc2-5796-4864-b16d-790d06bea20d", + "name": "Defense Evasion", + "description": "Addresses common TTPs/behaviors that threat actors use to avoid detection such as" + " uninstalling or disabling security software, obfuscating or encrypting data/scripts and abusing" + " trusted processes to hide and disguise their malicious activity.", + "presentation": { + "name": "defense_evasion.name", + "category": "core-prevention", + "description": [ + "defense_evasion.description" + ], + "platforms": [ + { + "platform": "WINDOWS", + "header": "defense_evasion.windows.heading", + "subHeader": [ + "defense_evasion.windows.sub_heading" + ], + "actions": [ + { + "component": "assignment-mode-selector", + "parameter": "WindowsAssignmentMode" + } + ] + } + ] + }, + "parameters": [ + { + "default": "BLOCK", + "name": "WindowsAssignmentMode", + "description": "Used to change assignment mode to PREVENT or BLOCK", + "recommended": "BLOCK", + "validations": [ + { + "type": "enum", + "values": [ + "REPORT", + "BLOCK" + ] + } + ] + } + ] + }, + { + "id": "8a16234c-9848-473a-a803-f0f0ffaf5f29", + "name": "Persistence", + "description": "Addresses common TTPs/behaviors that threat actors use to retain access to systems across" + " restarts, changed credentials, and other interruptions that could cut off their access.", + "presentation": { + "name": "persistence.name", + "category": "core-prevention", + "description": [ + "persistence.description" + ], + "platforms": [ + { + "platform": "WINDOWS", + "header": "persistence.windows.heading", + "subHeader": [ + "persistence.windows.sub_heading" + ], + "actions": [ + { + "component": "assignment-mode-selector", + "parameter": "WindowsAssignmentMode" + } + ] + } + ] + }, + "parameters": [ + { + "default": "BLOCK", + "name": "WindowsAssignmentMode", + "description": "Used to change assignment mode to PREVENT or BLOCK", + "recommended": "BLOCK", + "validations": [ + { + "type": "enum", + "values": [ + "REPORT", + "BLOCK" + ] + } + ] + } + ] } ] } diff --git a/src/tests/unit/fixtures/platform/mock_policy_ruleconfigs.py b/src/tests/unit/fixtures/platform/mock_policy_ruleconfigs.py index 2c8066eb7..d56398ee1 100644 --- a/src/tests/unit/fixtures/platform/mock_policy_ruleconfigs.py +++ b/src/tests/unit/fixtures/platform/mock_policy_ruleconfigs.py @@ -1223,3 +1223,201 @@ ], "failed": [] } + +BYPASS_RULE_CONFIGS = { + "results": [ + { + "id": "1664f2e6-645f-4d6e-98ec-0c80485cbe0f", + "name": "Event Reporting Exclusions", + "description": "Allows customers to exclude specific processes from reporting events to CBC", + "inherited_from": "psc:region", + "category": "bypass", + "parameters": {} + }, + { + "id": "1c03d653-eca4-4adc-81a1-04b17b6cbffc", + "name": "Event Reporting and Sensor Operation Exclusions", + "description": "Allows customers to exclude specific processes and process events from reporting to CBC", + "inherited_from": "psc:region", + "category": "bypass", + "parameters": {}, + "exclusions": { + "windows": [ + { + "id": 8090, + "criteria": [ + { + "id": 13426, + "type": "initiator_process", + "attributes": [ + { + "id": 93774, + "name": "process_name", + "values": [ + "**\\explorer.exe" + ] + } + ] + }, + { + "id": 13427, + "type": "operation", + "attributes": [ + { + "id": 93775, + "name": "operation_type", + "values": [ + "ALL" + ] + } + ] + } + ], + "comments": "", + "type": "ENDPOINT_STANDARD_PROCESS_BYPASS", + "apply_to_descendent_processes": True, + "created_by": "ABCD1234", + "created_at": "2024-01-27T13:29:44.839Z", + "modified_by": "ABCD1234", + "modified_at": "2024-01-27T13:29:44.839Z" + } + ] + } + } + ] +} + +BYPASS_RULE_CONFIGS_UPDATE = { + "id": "1c03d653-eca4-4adc-81a1-04b17b6cbffc", + "name": "Event Reporting and Sensor Operation Exclusions", + "description": "Allows customers to exclude specific processes and process events from reporting to CBC", + "inherited_from": "psc:region", + "category": "bypass", + "parameters": {}, + "exclusions": { + "windows": [ + { + "criteria": [ + { + "type": "initiator_process", + "attributes": [ + { + "name": "process_name", + "values": [ + "**\\explorer.exe" + ] + } + ] + }, + { + "type": "operation", + "attributes": [ + { + "name": "operation_type", + "values": [ + "ALL" + ] + } + ] + } + ], + "comments": "", + "apply_to_descendent_processes": True, + "type": "ENDPOINT_STANDARD_PROCESS_BYPASS" + } + ] + } +} + +BYPASS_RULE_CONFIGS_UPDATE_REQUEST = { + "id": "1c03d653-eca4-4adc-81a1-04b17b6cbffc", + "exclusions": { + "windows": [ + { + "criteria": [ + { + "type": "initiator_process", + "attributes": [ + { + "name": "process_name", + "values": [ + "**\\explorer.exe" + ] + } + ] + }, + { + "type": "operation", + "attributes": [ + { + "name": "operation_type", + "values": [ + "ALL" + ] + } + ] + } + ], + "comments": "", + "apply_to_descendent_processes": True, + "type": "ENDPOINT_STANDARD_PROCESS_BYPASS" + } + ] + } +} + +BYPASS_RULE_CONFIGS_UPDATE_RESPONSE = { + "successful": [ + { + "id": "1c03d653-eca4-4adc-81a1-04b17b6cbffc", + "name": "Event Reporting and Sensor Operation Exclusions", + "description": "Allows customers to exclude specific processes and process events from reporting to CBC", + "inherited_from": "psc:region", + "category": "bypass", + "parameters": {}, + "exclusions": { + "windows": [ + { + "id": 8090, + "criteria": [ + { + "id": 13426, + "type": "initiator_process", + "attributes": [ + { + "id": 93774, + "name": "process_name", + "values": [ + "**\\explorer.exe" + ] + } + ] + }, + { + "id": 13427, + "type": "operation", + "attributes": [ + { + "id": 93775, + "name": "operation_type", + "values": [ + "ALL" + ] + } + ] + } + ], + "comments": "", + "type": "ENDPOINT_STANDARD_PROCESS_BYPASS", + "apply_to_descendent_processes": True, + "created_by": "ABCDEFD", + "created_at": "2024-01-27T13:29:44.839Z", + "modified_by": "ABCDEFD", + "modified_at": "2024-01-27T13:29:44.839Z" + } + ] + } + } + ], + "failed": [] +} diff --git a/src/tests/unit/fixtures/workload/mock_compliance.py b/src/tests/unit/fixtures/workload/mock_compliance.py new file mode 100644 index 000000000..f5f46865d --- /dev/null +++ b/src/tests/unit/fixtures/workload/mock_compliance.py @@ -0,0 +1,248 @@ +"""Mock data for ComplianceBenchmark""" + +SEARCH_COMPLIANCE_BENCHMARKS = { + "num_found": 1, + "results": [ + { + "id": "eee5e491-9c31-4a38-84d8-50c9163ef559", + "name": "CIS Compliance - Microsoft Windows Server", + "version": "1.0.0.4", + "os_family": "WINDOWS_SERVER", + "bundle_name": "CIS Compliance - Microsoft Windows Server", + "enabled": True, + "type": "Default", + "supported_os_info": [ + { + "os_metadata_id": "1", + "os_type": "WINDOWS", + "os_name": "Windows Server 2012 x64", + "cis_version": "2.3.0" + }, + { + "os_metadata_id": "2", + "os_type": "WINDOWS", + "os_name": "Windows Server 2012 R2 x64", + "cis_version": "2.5.0" + }, + { + "os_metadata_id": "3", + "os_type": "WINDOWS", + "os_name": "Windows Server 2016 x64", + "cis_version": "1.4.0" + }, + { + "os_metadata_id": "4", + "os_type": "WINDOWS", + "os_name": "Windows Server 2019 x64", + "cis_version": "1.3.0" + }, + { + "os_metadata_id": "71", + "os_type": "WINDOWS", + "os_name": "Windows Server 2022 x64", + "cis_version": "1.0.0" + } + ], + "created_by": "CB_ADMIN", + "updated_by": "user@vmware.com", + "create_time": "2023-03-20T13:04:38.557369Z", + "update_time": "2023-07-10T13:56:35.238166Z", + "release_time": "2023-07-10T13:55:59.274881Z" + } + ] +} + +COMPLIANCE_SCHEDULE = { + "scan_schedule": "FREQ=WEEKLY;BYDAY=TU;BYHOUR=11;BYMINUTE=30;BYSECOND=0", + "scan_timezone": "UTC" +} + + +GET_SECTIONS = [ + { + "section_id": "0ABA0288-8A68-83AF-3BAE-A7F45167564B", + "section_name": "Remote Assistance", + "parent_id": "8C180BA0-EE6F-FB08-18F5-8F5FFC9A3FFF" + }, + { + "section_id": "0D69B5D9-B931-5ADB-EB04-F3775256B445", + "section_name": "App runtime", + "parent_id": "F072A0E3-24F6-B29C-6F2E-254F42CA6DF6" + }, + { + "section_id": "0FD8844A-C679-3F8F-748D-CDEAFF892CD4", + "section_name": "System Services", + "parent_id": None + } +] + +SEARCH_RULES = { + "num_found": 4, + "results": [ + { + "id": "39D861A0-3631-442B-BF94-CC442C73C03E", + "rule_name": "(L1) Ensure 'Configure Offer Remote Assistance' is set to 'Disabled'", + "enabled": True, + "section_id": "0ABA0288-8A68-83AF-3BAE-A7F45167564B", + "section_name": "Remote Assistance" + }, + { + "id": "C5632571-24C4-430D-9CCE-542F30B6933A", + "rule_name": "(L1) Ensure 'Configure Solicited Remote Assistance' is set to 'Disabled'", + "enabled": True, + "section_id": "0ABA0288-8A68-83AF-3BAE-A7F45167564B", + "section_name": "Remote Assistance" + }, + { + "id": "6A530464-631B-43E4-AB8C-5B06A747B7D7", + "rule_name": "(L1) Ensure 'Allow Microsoft accounts to be optional' is set to 'Enabled'", + "enabled": True, + "section_id": "0D69B5D9-B931-5ADB-EB04-F3775256B445", + "section_name": "App runtime" + }, + { + "id": "1F65A756-338E-49A8-AA78-3EC07734B96D", + "rule_name": "(L1) Ensure 'Print Spooler (Spooler)' is set to 'Disabled' (DC only)", + "enabled": True, + "section_id": "0FD8844A-C679-3F8F-748D-CDEAFF892CD4", + "section_name": "System Services" + } + ] +} + +GET_RULE = { + "id": "39D861A0-3631-442B-BF94-CC442C73C03E", + "rule_name": "(L1) Ensure 'Configure Offer Remote Assistance' is set to 'Disabled'", + "enabled": True, + "section_id": "0ABA0288-8A68-83AF-3BAE-A7F45167564B", + "section_name": "Remote Assistance", + "supported_os_info": [ + { + "os_metadata_id": "1", + "os_type": "WINDOWS", + "os_name": "Windows Server 2012 x64", + "cis_version": "2.3.0" + }, + { + "os_metadata_id": "2", + "os_type": "WINDOWS", + "os_name": "Windows Server 2012 R2 x64", + "cis_version": "2.5.0" + }, + { + "os_metadata_id": "3", + "os_type": "WINDOWS", + "os_name": "Windows Server 2016 x64", + "cis_version": "1.4.0" + }, + { + "os_metadata_id": "4", + "os_type": "WINDOWS", + "os_name": "Windows Server 2019 x64", + "cis_version": "1.3.0" + }, + { + "os_metadata_id": "71", + "os_type": "WINDOWS", + "os_name": "Windows Server 2022 x64", + "cis_version": "1.0.0" + } + ], + "description": "This policy setting allows you to turn on or turn off Offer (Unsolicited) Remote Assistance on this" + " computer.\n\nHelp desk and support personnel will not be able to proactively offer assistance, although they can" + " still respond to user assistance requests.\n\nThe recommended state for this setting is: `Disabled`.", + "rationale": "A user might be tricked and accept an unsolicited Remote Assistance offer from a malicious user.", + "impact": "None - this is the default behavior.", + "remediation": { + "procedure": "To establish the recommended configuration via GP, set the following UI path to `Disabled`", + "steps": "\n\n```\nComputer Configuration\\Policies\\Administrative Templates\\System\\Remote" + " Assistance\\Configure Offer Remote Assistance\n```\n\n**Note" + }, + "profile": [ + "Level 1 Domain Controller", + "Level 1 Member Server" + ] +} + +RULE_COMPLIANCES = { + "num_found": 2, + "results": [ + { + "rule_id": "39D861A0-3631-442B-BF94-CC442C73C03E", + "rule_name": "(L1) Ensure 'Configure Offer Remote Assistance' is set to 'Disabled'", + "section_id": "0ABA0288-8A68-83AF-3BAE-A7F45167564B", + "section_name": "Remote Assistance", + "compliant_assets": 1, + "non_compliant_assets": 0, + "profile": [ + "Level 1 Domain Controller", + "Level 1 Member Server" + ] + }, + { + "rule_id": "C5632571-24C4-430D-9CCE-542F30B6933A", + "rule_name": "(L1) Ensure 'Configure Solicited Remote Assistance' is set to 'Disabled'", + "section_id": "0ABA0288-8A68-83AF-3BAE-A7F45167564B", + "section_name": "Remote Assistance", + "compliant_assets": 1, + "non_compliant_assets": 0, + "profile": [ + "Level 1 Domain Controller", + "Level 1 Member Server" + ] + } + ] +} + +DEVICE_SPECIFIC_RULE_COMPLIANCE = { + "num_found": 2, + "results": [ + { + "id": "39D861A0-3631-442B-BF94-CC442C73C03E", + "rule_name": "(L1) Ensure 'Configure Offer Remote Assistance' is set to 'Disabled'", + "enabled": True, + "section_id": "0ABA0288-8A68-83AF-3BAE-A7F45167564B", + "section_name": "Remote Assistance", + "compliance_result": True, + "message": "Registry_Terminal_Services_fAllowUnsolicited=0" + }, + { + "id": "C5632571-24C4-430D-9CCE-542F30B6933A", + "rule_name": "(L1) Ensure 'Configure Solicited Remote Assistance' is set to 'Disabled'", + "enabled": True, + "section_id": "0ABA0288-8A68-83AF-3BAE-A7F45167564B", + "section_name": "Remote Assistance", + "compliance_result": True, + "message": "Registry_Terminal_Services_fAllowToGetHelp=0" + } + ] +} + +RULE_COMPLIANCE_DEVICE_SEARCH = { + "num_found": 1, + "results": [ + { + "device_id": 1, + "device_name": "Example\\Win2022", + "os_version": "Windows Server 2022 x64", + "compliance_result": True + } + ] +} + +DEVICE_COMPLIANCES = { + "num_found": 1, + "results": [ + { + "device_id": 39074613, + "device_name": "Example\\Win2022", + "os_version": "Windows Server 2022 x64", + "compliance_percentage": 93, + "last_assess_time": "2024-04-16T00:00:00.014765Z", + "excluded_on": None, + "excluded_by": None, + "reason": None, + "deployment_type": "WORKLOAD" + } + ] +} diff --git a/src/tests/unit/platform/test_alertsv7_api.py b/src/tests/unit/platform/test_alertsv7_api.py index 9c3083887..42d32de52 100755 --- a/src/tests/unit/platform/test_alertsv7_api.py +++ b/src/tests/unit/platform/test_alertsv7_api.py @@ -2101,7 +2101,7 @@ def on_post(url, body, **kwargs): alert = alerts[0] assert isinstance(alerts, list) - assert alert.get("type") == "WATCHLIST" + # assert alert.get("type") == "WATCHLIST" assert alert.get("threat_id") == group_alert.most_recent_alert.get("threat_id") diff --git a/src/tests/unit/platform/test_audit.py b/src/tests/unit/platform/test_audit.py index 12722d8d4..49dbdc25a 100644 --- a/src/tests/unit/platform/test_audit.py +++ b/src/tests/unit/platform/test_audit.py @@ -12,10 +12,12 @@ """Tests for the audit logs APIs.""" import pytest +from cbc_sdk.errors import ApiError from cbc_sdk.rest_api import CBCloudAPI -from cbc_sdk.platform.audit import AuditLog +from cbc_sdk.platform import AuditLog, Job from tests.unit.fixtures.CBCSDKMock import CBCSDKMock -from tests.unit.fixtures.platform.mock_audit import AUDITLOGS_RESP +from tests.unit.fixtures.platform.mock_audit import (AUDITLOGS_RESP, AUDIT_SEARCH_REQUEST, AUDIT_SEARCH_RESPONSE, + AUDIT_EXPORT_REQUEST, MOCK_AUDIT_EXPORT_JOB) @pytest.fixture(scope="function") @@ -35,15 +37,115 @@ def cbcsdk_mock(monkeypatch, cb): # ==================================== UNIT TESTS BELOW ==================================== -def test_no_create_object_for_now(cb): - """Validates that we can't create an AuditLog object. Remove when we have a better implementation.""" - with pytest.raises(NotImplementedError): - AuditLog(cb, 0) - - def test_get_auditlogs(cbcsdk_mock): """Tests getting audit logs.""" cbcsdk_mock.mock_request("GET", "/integrationServices/v3/auditlogs", AUDITLOGS_RESP) api = cbcsdk_mock.api result = AuditLog.get_auditlogs(api) assert len(result) == 5 + + +def test_get_queued_auditlogs(cbcsdk_mock): + """Tests the get_queued_auditlogs function.""" + cbcsdk_mock.mock_request("GET", "/audit_log/v1/orgs/test/logs/_queue", AUDIT_SEARCH_RESPONSE) + api = cbcsdk_mock.api + result = AuditLog.get_queued_auditlogs(api) + assert len(result) == 5 + for v in result: + assert isinstance(v, AuditLog) + + +def test_search_audit_logs_with_all_bells_and_whistles(cbcsdk_mock): + """Tests the generation and execution of a search request.""" + + def on_post(url, body, **kwargs): + assert body == AUDIT_SEARCH_REQUEST + return AUDIT_SEARCH_RESPONSE + + cbcsdk_mock.mock_request("POST", "/audit_log/v1/orgs/test/logs/_search", on_post) + api = cbcsdk_mock.api + query = api.select(AuditLog).where("description:FOO").add_criteria("actor_ip", ["10.29.99.1"]) + query.add_criteria("actor", ["ABCDEFGHIJ"]).add_criteria("request_url", ["https://inclusiveladyship.com"]) + query.add_criteria("description", ["FOOBAR"]).add_boolean_criteria("flagged", True) + query.add_boolean_criteria("verbose", False) + query.add_time_criteria(start="2024-03-01T00:00:00", end="2024-03-31T22:00:00") + query.add_exclusions("actor_ip", ["10.29.99.254"]).add_exclusions("actor", ["JIHGFEDCBA"]) + query.add_exclusions("request_url", ["https://links.inclusiveladyship.com"]) + query.add_exclusions("description", ["BLORT"]).add_boolean_criteria("flagged", False, exclude=True) + query.add_boolean_criteria("verbose", True, exclude=True) + query.add_time_criteria(range="-5d", exclude=True).sort_by("actor_ip", "ASC") + result_list = list(query) + assert len(result_list) == 5 + assert query._count() == 5 + assert result_list[0].actor == "DEFGHIJKLM" + assert result_list[0].actor_ip == "192.168.0.5" + assert result_list[1].actor == "BELTALOWDA" + assert result_list[1].actor_ip == "192.168.3.5" + assert result_list[2].actor == "BELTALOWDA" + assert result_list[2].actor_ip == "192.168.3.8" + assert result_list[3].actor == "BELTALOWDA" + assert result_list[3].actor_ip == "192.168.3.11" + assert result_list[4].actor == "BELTALOWDA" + assert result_list[4].actor_ip == "192.168.3.14" + + +def test_criteria_errors(cb): + """Tests error handling in the criteria-setting functions on the query object.""" + query = cb.select(AuditLog) + with pytest.raises(ApiError): + query.add_time_criteria(start="2024-03-01T00:00:00", end="2024-03-31T22:00:00", range="-5d") + with pytest.raises(ApiError): + query.add_time_criteria(start="2024-03-01T00:00:00") + with pytest.raises(ApiError): + query.add_time_criteria(end="2024-03-31T22:00:00") + with pytest.raises(ApiError): + query.add_time_criteria(start="2024-03-01T00:00:00", range="-5d") + with pytest.raises(ApiError): + query.add_time_criteria(end="2024-03-31T22:00:00", range="-5d") + with pytest.raises(ApiError): + query.add_time_criteria(start="BOGUS", end="2024-03-31T22:00:00") + with pytest.raises(ApiError): + query.sort_by("actor_ip", "BOGUS") + + +def test_async_search_audit_logs(cbcsdk_mock): + """Tests async query of audit logs.""" + cbcsdk_mock.mock_request("POST", "/audit_log/v1/orgs/test/logs/_search", AUDIT_SEARCH_RESPONSE) + api = cbcsdk_mock.api + query = api.select(AuditLog) + future = query.execute_async() + result_list = future.result() + assert isinstance(result_list, list) + assert len(result_list) == 5 + assert result_list[0].actor == "DEFGHIJKLM" + assert result_list[0].actor_ip == "192.168.0.5" + assert result_list[1].actor == "BELTALOWDA" + assert result_list[1].actor_ip == "192.168.3.5" + assert result_list[2].actor == "BELTALOWDA" + assert result_list[2].actor_ip == "192.168.3.8" + assert result_list[3].actor == "BELTALOWDA" + assert result_list[3].actor_ip == "192.168.3.11" + assert result_list[4].actor == "BELTALOWDA" + assert result_list[4].actor_ip == "192.168.3.14" + + +def test_export_audit_logs(cbcsdk_mock): + """Tests the basic functionality of the export() function.""" + def on_post(url, body, **kwargs): + assert body == AUDIT_EXPORT_REQUEST + return {"job_id": 4805565} + + cbcsdk_mock.mock_request("POST", "/audit_log/v1/orgs/test/logs/_export", on_post) + cbcsdk_mock.mock_request("GET", "/jobs/v1/orgs/test/jobs/4805565", MOCK_AUDIT_EXPORT_JOB) + api = cbcsdk_mock.api + query = api.select(AuditLog).where("description:FOO") + job = query.export() + assert isinstance(job, Job) + assert job.id == 4805565 + + +def test_export_bad_format(cb): + """Tests calling export() with a bad format name.""" + query = cb.select(AuditLog) + with pytest.raises(ApiError): + query.export("bogusformat") diff --git a/src/tests/unit/platform/test_jobs.py b/src/tests/unit/platform/test_jobs.py index 6d6eadb63..e5ac173c6 100644 --- a/src/tests/unit/platform/test_jobs.py +++ b/src/tests/unit/platform/test_jobs.py @@ -1,4 +1,5 @@ """Tests for the Job object of the CBC SDK""" +import copy import pytest import logging @@ -6,11 +7,12 @@ import os from tempfile import mkstemp from cbc_sdk.platform import Job -from cbc_sdk.errors import ServerError +from cbc_sdk.errors import ServerError, ApiError from cbc_sdk.rest_api import CBCloudAPI from tests.unit.fixtures.CBCSDKMock import CBCSDKMock from tests.unit.fixtures.platform.mock_jobs import (FIND_ALL_JOBS_RESP, JOB_DETAILS_1, JOB_DETAILS_2, PROGRESS_1, - PROGRESS_2, AWAIT_COMPLETION_PROGRESS) + PROGRESS_2, AWAIT_COMPLETION_DETAILS_PROGRESS_1, + AWAIT_COMPLETION_DETAILS_PROGRESS_2) logging.basicConfig(format='%(asctime)s %(levelname)s:%(message)s', level=logging.DEBUG, filename='log.txt') @@ -98,45 +100,71 @@ def test_load_job_and_get_progress(cbcsdk_mock, jobid, total, completed, msg, lo def test_job_await_completion(cbcsdk_mock): - """Test the functionality of await_completion().""" - first_time = True + """Test that await_completion() functions.""" pr_index = 0 - def on_progress(url, query_params, default): - nonlocal first_time, pr_index - if first_time: - first_time = False - raise ServerError(400, "Not yet") - assert pr_index < len(AWAIT_COMPLETION_PROGRESS), "Too many progress calls made" - return_value = AWAIT_COMPLETION_PROGRESS[pr_index] + def on_details(url, query_params, default): + nonlocal pr_index + assert pr_index < len(AWAIT_COMPLETION_DETAILS_PROGRESS_1), "Too many status calls made" + return_value = copy.deepcopy(JOB_DETAILS_1) + return_value['status'] = AWAIT_COMPLETION_DETAILS_PROGRESS_1[pr_index] pr_index += 1 return return_value - cbcsdk_mock.mock_request('GET', '/jobs/v1/orgs/test/jobs/12345', JOB_DETAILS_1) - cbcsdk_mock.mock_request('GET', '/jobs/v1/orgs/test/jobs/12345/progress', on_progress) + cbcsdk_mock.mock_request('GET', '/jobs/v1/orgs/test/jobs/12345', on_details) api = cbcsdk_mock.api job = api.select(Job, 12345) + job._wait_status = True future = job.await_completion() result = future.result() assert result is job - assert first_time is False - assert pr_index == len(AWAIT_COMPLETION_PROGRESS) + assert pr_index == len(AWAIT_COMPLETION_DETAILS_PROGRESS_1) def test_job_await_completion_error(cbcsdk_mock): """Test that await_completion() throws a ServerError if it gets too many ServerErrors internally.""" - def on_progress(url, query_params, default): + first_time = True + + def on_details(url, query_params, default): + nonlocal first_time + if first_time: + first_time = False + return_value = copy.deepcopy(JOB_DETAILS_1) + return_value['status'] = "CREATED" + return return_value raise ServerError(400, "Ain't happening") - cbcsdk_mock.mock_request('GET', '/jobs/v1/orgs/test/jobs/12345', JOB_DETAILS_1) - cbcsdk_mock.mock_request('GET', '/jobs/v1/orgs/test/jobs/12345/progress', on_progress) + cbcsdk_mock.mock_request('GET', '/jobs/v1/orgs/test/jobs/12345', on_details) api = cbcsdk_mock.api job = api.select(Job, 12345) + job._wait_status = True future = job.await_completion() with pytest.raises(ServerError): future.result() +def test_job_await_completion_failure(cbcsdk_mock): + """Test that await_completion() throws an ApiError if it gets a FAILURE response.""" + pr_index = 0 + + def on_details(url, query_params, default): + nonlocal pr_index + assert pr_index < len(AWAIT_COMPLETION_DETAILS_PROGRESS_2), "Too many status calls made" + return_value = copy.deepcopy(JOB_DETAILS_1) + return_value['status'] = AWAIT_COMPLETION_DETAILS_PROGRESS_2[pr_index] + pr_index += 1 + return return_value + + cbcsdk_mock.mock_request('GET', '/jobs/v1/orgs/test/jobs/12345', on_details) + api = cbcsdk_mock.api + job = api.select(Job, 12345) + job._wait_status = True + future = job.await_completion() + with pytest.raises(ApiError): + future.result() + assert pr_index == len(AWAIT_COMPLETION_DETAILS_PROGRESS_2) + + def test_job_output_export_string(cbcsdk_mock): """Tests exporting the results of a job as a string.""" cbcsdk_mock.mock_request('GET', '/jobs/v1/orgs/test/jobs/12345', JOB_DETAILS_1) diff --git a/src/tests/unit/platform/test_platform_events.py b/src/tests/unit/platform/test_platform_events.py index 003086a3c..62a140590 100644 --- a/src/tests/unit/platform/test_platform_events.py +++ b/src/tests/unit/platform/test_platform_events.py @@ -100,7 +100,7 @@ def test_event_query_select_with_where(cbcsdk_mock): # test .where(process_guid=...) events = api.select(Event).where(process_guid=guid) - results = [res for res in events._perform_query(numrows=10)] + results = [res for res in events._perform_query(max_rows=10)] assert len(results) == 10 first_event = results[0] assert first_event.process_guid == guid @@ -117,7 +117,7 @@ def test_event_query_select_with_where(cbcsdk_mock): EVENT_SEARCH_VALIDATION_RESP) events = api.select(Event).where('process_guid:J7G6DTLN-006633e3-00000334-00000000-1d677bedfbb1c2e') - results = [res for res in events._perform_query(numrows=10)] + results = [res for res in events._perform_query(max_rows=10)] first_event = results[0] assert first_event.process_guid == guid @@ -125,7 +125,7 @@ def test_event_query_select_with_where(cbcsdk_mock): assert len(results) == 10 # test ._perform_query(numrows) - results = [result for result in events._perform_query(numrows=100)] + results = [result for result in events._perform_query(max_rows=100)] assert len(results) == 100 first_result = results[0] assert first_result.process_guid == guid diff --git a/src/tests/unit/platform/test_platform_process.py b/src/tests/unit/platform/test_platform_process.py index e4d64807d..d6308d448 100644 --- a/src/tests/unit/platform/test_platform_process.py +++ b/src/tests/unit/platform/test_platform_process.py @@ -816,6 +816,27 @@ def test_process_query_start_rows(cbcsdk_mock): assert process._batch_size == 102 +def test_process_query_collapse_field(cbcsdk_mock): + """Testing AsyncProcessQuery.set_collapse_field()""" + api = cbcsdk_mock.api + # use the update methods + process = api.select(Process).where("event_type:modload").add_criteria("device_id", [1234]).add_exclusions( + "crossproc_effective_reputation", ["REP_WHITE"]) + process = process.set_collapse_field(["process_sha256"]) + + process_q_params = process._get_query_parameters() + expected_params = {"query": "event_type:modload", + "criteria": { + "device_id": [1234] + }, + "exclusions": { + "crossproc_effective_reputation": ["REP_WHITE"] + }, + "collapse_field": ["process_sha256"] + } + assert process_q_params == expected_params + + def test_process_sort_by(cbcsdk_mock): """Testing AsyncProcessQuery.sort_by().""" api = cbcsdk_mock.api @@ -1448,3 +1469,30 @@ def on_validation_post(url, body, **kwargs): procTree = api.select(Process.Tree, "WNEXFKQ7-0002b226-000015bd-00000000-1d6225bbba74c00") assert procTree is not None + + +def test_process_missing_property(cbcsdk_mock): + """Testing Process missing property""" + def on_validation_post(url, body, **kwargs): + assert body == {"query": "process_guid:WNEXFKQ7\\-0002b226\\-000015bd\\-00000000\\-1d6225bbba74c00"} + return POST_PROCESS_VALIDATION_RESP + + # mock the search validation + cbcsdk_mock.mock_request("POST", "/api/investigate/v2/orgs/test/processes/search_validation", on_validation_post) + # mock the POST of a search + cbcsdk_mock.mock_request("POST", "/api/investigate/v2/orgs/test/processes/search_jobs", + POST_PROCESS_SEARCH_JOB_RESP) + # mock the GET to check search status + cbcsdk_mock.mock_request("GET", ("/api/investigate/v2/orgs/test/processes/" + "search_jobs/2c292717-80ed-4f0d-845f-779e09470920/results?start=0&rows=0"), + GET_PROCESS_SEARCH_JOB_RESP) + # mock the GET to get search results + cbcsdk_mock.mock_request("GET", ("/api/investigate/v2/orgs/test/processes/search_jobs/" + "2c292717-80ed-4f0d-845f-779e09470920/results?start=0&rows=500"), + GET_PROCESS_SEARCH_JOB_RESULTS_RESP) + api = cbcsdk_mock.api + guid = 'WNEXFKQ7-0002b226-000015bd-00000000-1d6225bbba74c00' + process = api.select(Process, guid) + + with pytest.raises(AttributeError): + process.invalid_property diff --git a/src/tests/unit/platform/test_policy_ruleconfigs.py b/src/tests/unit/platform/test_policy_ruleconfigs.py index 9639fe3fa..bfefcb6f8 100644 --- a/src/tests/unit/platform/test_policy_ruleconfigs.py +++ b/src/tests/unit/platform/test_policy_ruleconfigs.py @@ -18,7 +18,7 @@ from cbc_sdk.rest_api import CBCloudAPI from cbc_sdk.platform import Policy, PolicyRuleConfig from cbc_sdk.platform.policy_ruleconfigs import (CorePreventionRuleConfig, HostBasedFirewallRuleConfig, - DataCollectionRuleConfig) + DataCollectionRuleConfig, BypassRuleConfig) from cbc_sdk.errors import ApiError, InvalidObjectError, ServerError from tests.unit.fixtures.CBCSDKMock import CBCSDKMock from tests.unit.fixtures.platform.mock_policies import (FULL_POLICY_1, BASIC_CONFIG_TEMPLATE_RETURN, @@ -41,7 +41,11 @@ HBFW_EXPORT_RULE_CONFIGS_RESPONSE, HBFW_EXPORT_RULE_CONFIGS_RESPONSE_CSV, DATA_COLLECTION_RETURNS, DATA_COLLECTION_UPDATE_1, - DATA_COLLECTION_UPDATE_RETURNS_1) + DATA_COLLECTION_UPDATE_RETURNS_1, + BYPASS_RULE_CONFIGS, + BYPASS_RULE_CONFIGS_UPDATE, + BYPASS_RULE_CONFIGS_UPDATE_REQUEST, + BYPASS_RULE_CONFIGS_UPDATE_RESPONSE) logging.basicConfig(format='%(asctime)s %(levelname)s:%(message)s', level=logging.DEBUG, filename='log.txt') @@ -218,10 +222,13 @@ def test_rule_config_initialization_matches_categories(policy): assert isinstance(cfg, HostBasedFirewallRuleConfig) elif cfg.category == "data_collection": assert isinstance(cfg, DataCollectionRuleConfig) + elif cfg.category == "bypass": + assert isinstance(cfg, BypassRuleConfig) else: assert not isinstance(cfg, CorePreventionRuleConfig) assert not isinstance(cfg, HostBasedFirewallRuleConfig) assert not isinstance(cfg, DataCollectionRuleConfig) + assert not isinstance(cfg, BypassRuleConfig) def test_core_prevention_refresh(cbcsdk_mock, policy): @@ -752,3 +759,70 @@ def on_delete(url, body): assert rule_config.name == 'Authentication Events' rule_config.delete() assert delete_called + + +def test_bypass_refresh(cbcsdk_mock, policy): + """Tests the refresh operation for a BypassRuleConfig.""" + cbcsdk_mock.mock_request('GET', '/policyservice/v1/orgs/test/policies/65536/rule_configs/bypass', + BYPASS_RULE_CONFIGS) + for rule_config in policy.bypass_rule_configs_list: + rule_config.refresh() + + +def test_bypass_update_and_save(cbcsdk_mock, policy): + """Tests updating the bypass data and saving it.""" + put_called = False + + def on_put(url, body, **kwargs): + nonlocal put_called + assert body == BYPASS_RULE_CONFIGS_UPDATE_REQUEST + put_called = True + return copy.deepcopy(BYPASS_RULE_CONFIGS_UPDATE_RESPONSE) + + cbcsdk_mock.mock_request('GET', '/policyservice/v1/orgs/test/policies/65536/configs/presentation', + POLICY_CONFIG_PRESENTATION) + cbcsdk_mock.mock_request('PUT', '/policyservice/v1/orgs/test/policies/65536/rule_configs/bypass', on_put) + rule_config = policy.bypass_rule_configs['1c03d653-eca4-4adc-81a1-04b17b6cbffc'] + assert rule_config.name == 'Event Reporting and Sensor Operation Exclusions' + rule_config.replace_exclusions(BYPASS_RULE_CONFIGS_UPDATE_REQUEST["exclusions"]) + + rule_config.save() + assert put_called + + +def test_bypass_update_via_replace(cbcsdk_mock, policy): + """Tests updating the bypass data and saving it via replace_rule_config.""" + put_called = False + + def on_put(url, body, **kwargs): + nonlocal put_called + assert body == BYPASS_RULE_CONFIGS_UPDATE_REQUEST + put_called = True + return copy.deepcopy(BYPASS_RULE_CONFIGS_UPDATE_RESPONSE) + + cbcsdk_mock.mock_request('GET', '/policyservice/v1/orgs/test/policies/65536/configs/presentation', + POLICY_CONFIG_PRESENTATION) + cbcsdk_mock.mock_request('PUT', '/policyservice/v1/orgs/test/policies/65536/rule_configs/bypass', on_put) + rule_config = policy.bypass_rule_configs['1c03d653-eca4-4adc-81a1-04b17b6cbffc'] + assert rule_config.name == 'Event Reporting and Sensor Operation Exclusions' + + policy.replace_rule_config('1c03d653-eca4-4adc-81a1-04b17b6cbffc', BYPASS_RULE_CONFIGS_UPDATE) + assert put_called + + +def test_bypass_delete(cbcsdk_mock, policy): + """Tests delete of bypass rules.""" + delete_called = False + + def on_delete(url, body): + nonlocal delete_called + delete_called = True + return CBCSDKMock.StubResponse(None, scode=204) + + cbcsdk_mock.mock_request('GET', '/policyservice/v1/orgs/test/policies/65536/configs/presentation', + POLICY_CONFIG_PRESENTATION) + cbcsdk_mock.mock_request('DELETE', '/policyservice/v1/orgs/test/policies/65536/rule_configs/bypass', on_delete) + rule_config = policy.bypass_rule_configs['1664f2e6-645f-4d6e-98ec-0c80485cbe0f'] + assert rule_config.name == 'Event Reporting Exclusions' + rule_config.delete() + assert delete_called diff --git a/src/tests/unit/platform/test_vulnerability_assessment.py b/src/tests/unit/platform/test_vulnerability_assessment.py index 2d9018778..5030ccd18 100644 --- a/src/tests/unit/platform/test_vulnerability_assessment.py +++ b/src/tests/unit/platform/test_vulnerability_assessment.py @@ -93,8 +93,11 @@ def test_get_vulnerability_summary_per_severity_fail(cbcsdk_mock): cbcsdk_mock.mock_request("GET", "/vulnerability/assessment/api/v1/orgs/test/vulnerabilities/summary", GET_VULNERABILITY_SUMMARY_ORG_LEVEL_PER_SEVERITY) api = cbcsdk_mock.api + query = api.select(Vulnerability.OrgSummary) with pytest.raises(ApiError): - api.select(Vulnerability.OrgSummary).set_severity('ERROR') + query.set_severity('ERROR') + with pytest.raises(ApiError): + query.set_visibility('BOGUS') def test_get_vulnerability_summary_per_severity_per_vcenter(cbcsdk_mock): @@ -176,6 +179,13 @@ def test_get_all_vulnerabilities(cbcsdk_mock): assert query._count() == len(results) +def test_vulnerability_query_visibility_fail(cb): + """Test that setting visibility on vulnerability query to a bogus value returns an error.""" + query = cb.select(Vulnerability) + with pytest.raises(ApiError): + query.set_visibility('BOGUS') + + def test_export_vulnerabilities(cbcsdk_mock): """Test Export Vulnerabilities""" cbcsdk_mock.mock_request("POST", @@ -295,6 +305,7 @@ def post_validate(url, body, **kwargs): assert crits['highest_risk_score'] == {"value": 10, "operator": "LESS_THAN"} assert crits['last_sync_ts'] == {"value": "2020-01-02T03:04:05Z", "operator": "EQUALS"} assert crits['name'] == {"value": "test", "operator": "EQUALS"} + assert crits['deployment_type'] == {"value": "ENDPOINT", "operator": "EQUALS"} assert crits['os_arch'] == {"value": "x86_64", "operator": "EQUALS"} assert crits['os_name'] == {"value": "Red Hat Enterprise Linux Server", "operator": "EQUALS"} assert crits['os_type'] == {"value": "MAC", "operator": "EQUALS"} @@ -315,6 +326,7 @@ def post_validate(url, body, **kwargs): .set_highest_risk_score(10, 'LESS_THAN') \ .set_last_sync_ts('2020-01-02T03:04:05Z', 'EQUALS') \ .set_name('test', 'EQUALS') \ + .set_deployment_type('ENDPOINT', 'EQUALS') \ .set_os_arch('x86_64', 'EQUALS') \ .set_os_name('Red Hat Enterprise Linux Server', 'EQUALS') \ .set_os_type('MAC', 'EQUALS') \ @@ -357,6 +369,10 @@ def post_validate(url, body, **kwargs): api.select(Vulnerability).set_name('', 'EQUALS') assert ex.value.message == 'Invalid name' + with pytest.raises(ApiError) as ex: + api.select(Vulnerability).set_deployment_type('', 'EQUALS') + assert ex.value.message == 'Invalid deployment type' + with pytest.raises(ApiError) as ex: api.select(Vulnerability).set_os_arch('', 'EQUALS') assert ex.value.message == 'Invalid os architecture' diff --git a/src/tests/unit/test_utils.py b/src/tests/unit/test_utils.py index 3b0878ea2..90f7969e0 100755 --- a/src/tests/unit/test_utils.py +++ b/src/tests/unit/test_utils.py @@ -11,9 +11,12 @@ """Test code for the utility functions""" -# import pytest +import pytest +import time from datetime import datetime -from cbc_sdk.utils import convert_from_cb, convert_to_cb +from cbc_sdk.utils import convert_from_cb, convert_to_cb, BackoffHandler +from cbc_sdk.errors import TimeoutError +from cbc_sdk.connection import BaseAPI # ==================================== Unit TESTS BELOW ==================================== @@ -46,3 +49,45 @@ def test_convert_to_cb(): t = datetime(2020, 3, 11, 18, 34, 11, 123456) s = convert_to_cb(t) assert s == "2020-03-11 18:34:11.123456" + + +def test_backoff_handler_operation(): + """Test the operation of the BackoffHandler.""" + cb = BaseAPI(integration_name='test1', url='https://example.com', token='ABCDEFGHIJKLM', org_key='A1B2C3D4') + sut = BackoffHandler(cb, threshold=0.5) + assert sut.timeout == 300000 + assert sut._initial == 0.1 + assert sut._multiplier == 2.0 + assert sut._threshold == 0.5 + with sut as b: + assert b._pausetime == 0.0 + b.pause() + assert b._pausetime == 0.1 + b.pause() + assert b._pausetime == 0.2 + b.pause() + assert b._pausetime == 0.4 + b.pause() + assert b._pausetime == 0.5 + b.reset() + assert b._pausetime == 0.1 + b.pause() + assert b._pausetime == 0.2 + b.reset(True) + assert b._pausetime == 0.0 + + +def test_backoff_handler_timeouts(): + """Test the raising of TimeoutError by the BackoffHandler.""" + cb = BaseAPI(integration_name='test1', url='https://example.com', token='ABCDEFGHIJKLM', org_key='A1B2C3D4') + sut = BackoffHandler(cb, timeout=10) + with sut as b: + time.sleep(0.1) + with pytest.raises(TimeoutError): + b.pause() + sut.timeout = 250 + with sut as b: + b.pause() # no pause + b.pause() # pauses 0.1 sec + with pytest.raises(TimeoutError): + b.pause() diff --git a/src/tests/unit/workload/test_compliance.py b/src/tests/unit/workload/test_compliance.py new file mode 100644 index 000000000..38047fd7d --- /dev/null +++ b/src/tests/unit/workload/test_compliance.py @@ -0,0 +1,283 @@ +#!/usr/bin/env python3 + +# ******************************************************* +# Copyright (c) VMware, Inc. 2021-2024. All Rights Reserved. +# SPDX-License-Identifier: MIT +# ******************************************************* +# * +# * DISCLAIMER. THIS PROGRAM IS PROVIDED TO YOU "AS IS" WITHOUT +# * WARRANTIES OR CONDITIONS OF ANY KIND, WHETHER ORAL OR WRITTEN, +# * EXPRESS OR IMPLIED. THE AUTHOR SPECIFICALLY DISCLAIMS ANY IMPLIED +# * WARRANTIES OR CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, +# * NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE. + +"""Unit test code for ComplianceBenchmark""" + +import pytest +import logging +from cbc_sdk.rest_api import CBCloudAPI +from cbc_sdk.workload import ComplianceBenchmark +from tests.unit.fixtures.CBCSDKMock import CBCSDKMock + +from tests.unit.fixtures.workload.mock_compliance import (SEARCH_COMPLIANCE_BENCHMARKS, + COMPLIANCE_SCHEDULE, + GET_SECTIONS, + SEARCH_RULES, + GET_RULE, + RULE_COMPLIANCES, + DEVICE_SPECIFIC_RULE_COMPLIANCE, + RULE_COMPLIANCE_DEVICE_SEARCH, + DEVICE_COMPLIANCES) + + +logging.basicConfig(format="%(asctime)s %(levelname)s:%(message)s", level=logging.DEBUG, filename="log.txt") + + +@pytest.fixture(scope="function") +def cb(): + """Create CBCloudAPI singleton""" + return CBCloudAPI(url="https://example.com", + org_key="test", + token="abcd/1234", + ssl_verify=False) + + +@pytest.fixture(scope="function") +def cbcsdk_mock(monkeypatch, cb): + """Mocks CBC SDK for unit tests""" + return CBCSDKMock(monkeypatch, cb) + + +# ==================================== UNIT TESTS BELOW ==================================== + + +def test_compliance_benchmark(cbcsdk_mock): + """Tests a simple compliance benchmark query""" + + def post_validate(url, body, **kwargs): + assert body == { + "criteria": {"enabled": [True]}, + "query": "Windows Server", + "rows": 1, + "sort": [{"field": "name", "order": "DESC"}] + } + return SEARCH_COMPLIANCE_BENCHMARKS + + cbcsdk_mock.mock_request("POST", "/compliance/assessment/api/v1/orgs/test/benchmark_sets/_search", + post_validate) + + api = cbcsdk_mock.api + benchmark = api.select(ComplianceBenchmark) \ + .where("Windows Server") \ + .add_criteria("enabled", [True]) \ + .sort_by("name", "DESC").first() + + assert benchmark.name == "CIS Compliance - Microsoft Windows Server" + + +def test_compliance_benchmark_async(cbcsdk_mock): + """Test async compliance benchmark query""" + + def post_validate(url, body, **kwargs): + assert body == { + "criteria": {"enabled": [True]}, + "query": "Windows Server", + "rows": 1000, + "sort": [{"field": "name", "order": "DESC"}] + } + return SEARCH_COMPLIANCE_BENCHMARKS + + cbcsdk_mock.mock_request("POST", "/compliance/assessment/api/v1/orgs/test/benchmark_sets/_search", + post_validate) + + api = cbcsdk_mock.api + future_benchmark = api.select(ComplianceBenchmark) \ + .where("Windows Server") \ + .add_criteria("enabled", [True]) \ + .sort_by("name", "DESC").execute_async() + + benchmarks = future_benchmark.result() + + assert benchmarks[0].name == "CIS Compliance - Microsoft Windows Server" + + +def test_get_compliance_compliance(cbcsdk_mock): + """Test get_compliance_schedule""" + cbcsdk_mock.mock_request("GET", "/compliance/assessment/api/v1/orgs/test/settings", + COMPLIANCE_SCHEDULE) + + api = cbcsdk_mock.api + assert ComplianceBenchmark.get_compliance_schedule(api) == COMPLIANCE_SCHEDULE + + +def test_set_compliance_compliance(cbcsdk_mock): + """Test set_compliance_schedule""" + + def put_validate(url, body, **kwargs): + assert body == COMPLIANCE_SCHEDULE + return body + + cbcsdk_mock.mock_request("PUT", "/compliance/assessment/api/v1/orgs/test/settings", + put_validate) + + api = cbcsdk_mock.api + assert ComplianceBenchmark.set_compliance_schedule(api, + COMPLIANCE_SCHEDULE["scan_schedule"], + COMPLIANCE_SCHEDULE["scan_timezone"]) == COMPLIANCE_SCHEDULE + + +def test_get_sections(cbcsdk_mock): + """Test get_sections""" + cbcsdk_mock.mock_request("POST", "/compliance/assessment/api/v1/orgs/test/benchmark_sets/_search", + SEARCH_COMPLIANCE_BENCHMARKS) + cbcsdk_mock.mock_request("GET", "/compliance/assessment/api/v1/orgs/test/benchmark_sets/" + "eee5e491-9c31-4a38-84d8-50c9163ef559/sections", + GET_SECTIONS) + + api = cbcsdk_mock.api + benchmark = api.select(ComplianceBenchmark, "eee5e491-9c31-4a38-84d8-50c9163ef559") + assert benchmark.name == "CIS Compliance - Microsoft Windows Server" + + assert benchmark.get_sections() == GET_SECTIONS + + +def test_get_rules(cbcsdk_mock): + """Test get_rules""" + cbcsdk_mock.mock_request("POST", "/compliance/assessment/api/v1/orgs/test/benchmark_sets/_search", + SEARCH_COMPLIANCE_BENCHMARKS) + cbcsdk_mock.mock_request("POST", "/compliance/assessment/api/v1/orgs/test/benchmark_sets/" + "eee5e491-9c31-4a38-84d8-50c9163ef559/rules/_search", + SEARCH_RULES) + cbcsdk_mock.mock_request("GET", "/compliance/assessment/api/v1/orgs/test/benchmark_sets/" + "eee5e491-9c31-4a38-84d8-50c9163ef559/rules/39D861A0-3631-442B-BF94-CC442C73C03E", + GET_RULE) + + api = cbcsdk_mock.api + benchmark = api.select(ComplianceBenchmark, "eee5e491-9c31-4a38-84d8-50c9163ef559") + assert benchmark.name == "CIS Compliance - Microsoft Windows Server" + + assert benchmark.get_rules() == SEARCH_RULES["results"] + + assert benchmark.get_rules("39D861A0-3631-442B-BF94-CC442C73C03E") == [GET_RULE] + + +def test_update_rules(cbcsdk_mock): + """Test update_sections""" + def put_validate(url, body, **kwargs): + assert body == [{ + "rule_id": "39D861A0-3631-442B-BF94-CC442C73C03E", + "enabled": True + }] + return [{ + "id": "39D861A0-3631-442B-BF94-CC442C73C03E", + "rule_name": "(L1) Ensure 'Configure Offer Remote Assistance' is set to 'Disabled'", + "enabled": True, + "section_id": "0ABA0288-8A68-83AF-3BAE-A7F45167564B", + "section_name": "Remote Assistance" + }] + + cbcsdk_mock.mock_request("POST", "/compliance/assessment/api/v1/orgs/test/benchmark_sets/_search", + SEARCH_COMPLIANCE_BENCHMARKS) + cbcsdk_mock.mock_request("PUT", "/compliance/assessment/api/v1/orgs/test/benchmark_sets/" + "eee5e491-9c31-4a38-84d8-50c9163ef559/rules", + put_validate) + + api = cbcsdk_mock.api + benchmark = api.select(ComplianceBenchmark, "eee5e491-9c31-4a38-84d8-50c9163ef559") + assert benchmark.name == "CIS Compliance - Microsoft Windows Server" + + assert benchmark.update_rules(["39D861A0-3631-442B-BF94-CC442C73C03E"], True)[0]["enabled"] is True + + +def test_execute_action(cbcsdk_mock): + """Test execute_action""" + def post_validate(url, body, **kwargs): + assert body["action"] == "REASSESS" + if "device_ids" in body: + assert body["device_ids"] == [1] + return { + "status_code": "SUCCESS", + "message": "Action Successful" + } + return { + "status_code": "SUCCESS", + "message": "Successfully triggered Reassess for BenchmarkSet: eee5e491-9c31-4a38-84d8-50c9163ef559" + } + + cbcsdk_mock.mock_request("POST", "/compliance/assessment/api/v1/orgs/test/benchmark_sets/_search", + SEARCH_COMPLIANCE_BENCHMARKS) + cbcsdk_mock.mock_request("POST", "/compliance/assessment/api/v1/orgs/test/benchmark_sets/" + "eee5e491-9c31-4a38-84d8-50c9163ef559/actions", + post_validate) + cbcsdk_mock.mock_request("POST", "/compliance/assessment/api/v1/orgs/test/benchmark_sets/" + "eee5e491-9c31-4a38-84d8-50c9163ef559/compliance/device_actions", + post_validate) + + api = cbcsdk_mock.api + benchmark = api.select(ComplianceBenchmark, "eee5e491-9c31-4a38-84d8-50c9163ef559") + assert benchmark.name == "CIS Compliance - Microsoft Windows Server" + + assert benchmark.execute_action("REASSESS")["status_code"] == "SUCCESS" + assert benchmark.execute_action("REASSESS", [1])["status_code"] == "SUCCESS" + + +def test_get_device_compliances(cbcsdk_mock): + """Test get_device_compliances""" + cbcsdk_mock.mock_request("POST", "/compliance/assessment/api/v1/orgs/test/benchmark_sets/_search", + SEARCH_COMPLIANCE_BENCHMARKS) + cbcsdk_mock.mock_request("POST", "/compliance/assessment/api/v1/orgs/test/benchmark_sets/" + "eee5e491-9c31-4a38-84d8-50c9163ef559/compliance/devices/_search", + DEVICE_COMPLIANCES) + + api = cbcsdk_mock.api + benchmark = api.select(ComplianceBenchmark, "eee5e491-9c31-4a38-84d8-50c9163ef559") + assert benchmark.name == "CIS Compliance - Microsoft Windows Server" + + assert benchmark.get_device_compliances("Remote Assistance") == DEVICE_COMPLIANCES["results"] + + +def test_get_rule_compliances(cbcsdk_mock): + """Test get_rule_compliances""" + cbcsdk_mock.mock_request("POST", "/compliance/assessment/api/v1/orgs/test/benchmark_sets/_search", + SEARCH_COMPLIANCE_BENCHMARKS) + cbcsdk_mock.mock_request("POST", "/compliance/assessment/api/v1/orgs/test/benchmark_sets/" + "eee5e491-9c31-4a38-84d8-50c9163ef559/compliance/rules/_search", + RULE_COMPLIANCES) + + api = cbcsdk_mock.api + benchmark = api.select(ComplianceBenchmark, "eee5e491-9c31-4a38-84d8-50c9163ef559") + assert benchmark.name == "CIS Compliance - Microsoft Windows Server" + + assert benchmark.get_rule_compliances("Remote Assistance") == RULE_COMPLIANCES["results"] + + +def test_get_device_rule_compliances(cbcsdk_mock): + """Test get_device_rule_compliances""" + cbcsdk_mock.mock_request("POST", "/compliance/assessment/api/v1/orgs/test/benchmark_sets/_search", + SEARCH_COMPLIANCE_BENCHMARKS) + cbcsdk_mock.mock_request("POST", "/compliance/assessment/api/v1/orgs/test/benchmark_sets/" + "eee5e491-9c31-4a38-84d8-50c9163ef559/compliance/devices/1/rules/_search", + DEVICE_SPECIFIC_RULE_COMPLIANCE) + + api = cbcsdk_mock.api + benchmark = api.select(ComplianceBenchmark, "eee5e491-9c31-4a38-84d8-50c9163ef559") + assert benchmark.name == "CIS Compliance - Microsoft Windows Server" + + assert benchmark.get_device_rule_compliances(1, "Remote Assistance") == DEVICE_SPECIFIC_RULE_COMPLIANCE["results"] + + +def test_get_rule_compliance_devices(cbcsdk_mock): + """Test get_rule_compliance_devices""" + cbcsdk_mock.mock_request("POST", "/compliance/assessment/api/v1/orgs/test/benchmark_sets/_search", + SEARCH_COMPLIANCE_BENCHMARKS) + cbcsdk_mock.mock_request("POST", "/compliance/assessment/api/v1/orgs/test/benchmark_sets/" + "eee5e491-9c31-4a38-84d8-50c9163ef559/compliance/rules/" + "39D861A0-3631-442B-BF94-CC442C73C03E/devices/_search", + RULE_COMPLIANCE_DEVICE_SEARCH) + + api = cbcsdk_mock.api + benchmark = api.select(ComplianceBenchmark, "eee5e491-9c31-4a38-84d8-50c9163ef559") + assert benchmark.name == "CIS Compliance - Microsoft Windows Server" + + assert benchmark.get_rule_compliance_devices("39D861A0-3631-442B-BF94-CC442C73C03E", "Example") == \ + RULE_COMPLIANCE_DEVICE_SEARCH["results"]