OracleCloudInfrastructureEventCollector
Collects audit log events from Oracle Cloud Infrastructure resources.
Analytics & SIEM · Oracle Cloud Infrastructure (OCI)
Details
| ID | OracleCloudInfrastructureEventCollector |
|---|---|
| Provider | Oracle |
| Category | Analytics & SIEM |
| From Version | 6.10.0 |
| Docker Image | demisto/oci:1.0.0.10133006 |
| Supported Modules | XSIAM |
README
This integration fetches audit log events from an Oracle Cloud Infrastructure resources.
Audit log events can be used for security audits, to track usage of and changes to Oracle Cloud Infrastructure resources, and to help ensure compliance with standards or regulations.
Required Permissions
Required Permissions for collecting audit logs
Required IAM policy for collecting audit logs
Oracle Cloud Infrastructure Audit API Endpoints (available Regions)
Configure Oracle Cloud Infrastructure Event Collector in Cortex
OCI Related Parameters
Oracle Cloud Infrastructure SDKs and CLI require basic configuration information, which is achieved by using configuration parameters either with a configuration file or a runtime defined configuration dictionary. This integration uses the runtime defined configuration dictionary.
More about OCI configuration here.
| Parameter | Description | Required |
|---|---|---|
| Tenancy OCID | OCID of your tenancy. To get the value, see Required Keys and OCIDs. | True |
| User OCID | OCID of the user calling the API. To get the value, see Required Keys and OCIDs. Example: ocid1.user.oc1.. |
True |
| API Key Fingerprint | Fingerprint for the public key that was added to this user. To get the value, see Required Keys and OCIDs. | True |
| Private Key | Private Key for authentication. Important: The key pair must be in PEM format. For instructions on generating a key pair in PEM format, see Required Keys and OCIDs. |
True |
| API Private Key Type | The type of the private key. The possible values are: PKCS#1 and PKCS#8. The default value is PKCS#8. A link explaining the difference between the 2 types see link | False |
| Region | An Oracle Cloud Infrastructure region. See Regions and Availability Domains. Example: us-ashburn-1 |
True |
| Compartment OCID | An Oracle Cloud Identifier compartment. The default value is the Tenancy OCID parameter. See Finding the OCID of a Compartment. | False |
| Events types to fetch | The type of the events to fetch. Default value is ‘Audit’. | True |
| Search log query | Query corresponding to the search operation. | False |
| First fetch time | First fetch time (< number > < time unit >, e.g., 12 hours, 1 day, 3 months). Default is 3 days. This parameter is relevant for ‘Audit’ events only. | False |
| Trust any certificate (not secure) | Use SSL secure connection or ‘None’. | False |
| User system proxy settings | Runs the integration instance using the proxy server (HTTP or HTTPS) that you defined in the server configuration. | False |
Commands
You can execute the following command from the CLI, as part of an automation, or in a playbook. After you successfully execute a command, a DBot message appears in the War Room with the command details.
oracle-cloud-infrastructure-get-events
Manual command to fetch and display events.
Base Command
oracle-cloud-infrastructure-get-events
Input
| Argument Name | Description | Required |
|---|---|---|
| should_push_events | Set this argument to true in order to create events, otherwise the command will only display them. Default is false. | True |
Configuration parameters
tenancy_ocid— Tenancy OCID (required)user_ocid— User OCID (required)credentials— API Key Fingerprint (required)private_key_type— API Private Key Typeregion— Region (required)compartment_id— Compartment OCIDisFetchEvents— Fetch EventseventFetchInterval— Events Fetch Intervalfirst_fetch— First fetch timemax_fetch— Maximum number of events to fetch per type.event_types_to_fetch— Event types to fetch (required)search_log_query— Search log queryinsecure— Trust any certificate (not secure)proxy— Use system proxy settings
Commands (1)
-
oracle-cloud-infrastructure-get-eventsManual command to fetch and display events.
from typing import Any import demistomock as demisto from CommonServerPython import * from oci.regions import is_region from oci.signer import Signer """ CONSTANTS """ DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ" # ISO8601 format with UTC, default in XSOAR VENDOR = "oracle" PRODUCT = "cloud_infrastructure" MAX_EVENTS_TO_FETCH = 1000 FETCH_DEFAULT_TIME = "3 days" PORT = 20190901 SEARCHLOG_DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.000Z" SEARCHLOG_FIRST_FETCH_TIME_IN_MINUTES = 10 """ CLIENT CLASS """ class Client(BaseClient): """Client class to interact with the OCI SDK and API requests. Will validate the fetching related parameters and create an OCI Singer object which will be used to fetch audit events. """ def __init__( self, verify_certificate: bool, proxy: bool, user_ocid: str, private_key: str, key_fingerprint: str, tenancy_ocid: str, region: str, compartment_id: str, private_key_type: str, ): self.singer = self.build_singer_object(user_ocid, private_key, key_fingerprint, tenancy_ocid, private_key_type) self.base_url = self.build_audit_base_url(region) self.searchlog_url = self.build_searchlog_url(region) self.compartment_id = compartment_id if compartment_id else tenancy_ocid super().__init__(proxy=proxy, verify=verify_certificate, auth=self.singer, base_url=self.base_url) def build_singer_object( self, user_ocid: str, private_key: str, key_fingerprint: str, tenancy_ocid: str, private_key_type: str ) -> dict[str, str]: """Build a singer object. The Signer used as part of making raw requests. Args: user_ocid (str): User OCID parameter. private_key (str): Private Key parameter. key_fingerprint (str): API Key Fingerprint parameter. tenancy_ocid (str): Tenancy OCID parameter. private_key_type (str): The type of the private key. Raises: DemistoException: If the singer object is invalid. Returns: (dict): A config dictionary that can be used to create Audit clients. """ try: validated_private_key = self.validate_private_key_syntax(private_key, private_key_type) singer = Signer( tenancy=tenancy_ocid, user=user_ocid, fingerprint=key_fingerprint, private_key_content=validated_private_key, private_key_file_location=None, ) except Exception as e: raise DemistoException( "Could not create a valid OCI singer object, Please check the instance configuration parameters.", exception=e ) from e return singer def build_audit_base_url(self, region: str) -> str: """Build the base URL for the client. Args: region (str): Region parameter. Raises: DemistoException: If the region is not valid. Returns: str: Base URL for the client. """ if not is_region(region): raise DemistoException( "Could not create a valid OCI configuration dictionary due to invalid region parameter. " "Please check your OCI-related instance configuration parameters." ) return f"https://audit.{region}.oraclecloud.com/{PORT}/auditEvents" def build_searchlog_url(self, region: str) -> str: """Build the base URL for the search logs API. Args: region (str): Region parameter. Raises: DemistoException: If the region is not valid. Returns: str: Base URL for the search logs API. """ if not is_region(region): raise DemistoException( "Could not create a valid OCI configuration dictionary due to invalid region parameter. " "Please check your OCI-related instance configuration parameters." ) return f"https://logging.{region}.oci.oraclecloud.com/20190909/search" def validate_private_key_syntax(self, private_key_parameter: str, private_key_type: str) -> str: """Validate private key parameter syntax. The Private Key parameter needs to be provided to the OCI SDK singer object in a specific format. The most common way to obtain the private key is to download a .pem file from the OCI console. If copied from a .pem file, the private key parameter may contain unnecessary spaces. Further more, since the format uses \n as part of the key, passing this value as a configuration parameter may result in escaped \n characters. This function will preform the following actions on the private key parameter: - Unescape the string. - Remove unnecessary spaces in the string. Example: Raw Private Key parameter: -----BEGIN PRIVATE KEY-----\\n\n THIS-IS-A\\n\n PRIVATE-KEY\\n\n -----END PRIVATE KEY----- Output: -----BEGIN PRIVATE KEY-----\nTHIS-IS-A\nPRIVATE-KEY\n-----END PRIVATE KEY----- Args: private_key_parameter (str): Private Key parameter. private_key_type(str): The type of the private key PKCS#1 and PKCS#8. More info about the types: https://stackoverflow.com/questions/48958304/pkcs1-and-pkcs8-format-for-rsa-private-key Returns: str: Private Key parameter unescaped and spaceless. """ private_key = stringUnEscape(private_key_parameter) private_key = private_key.replace("\n\n", "\n") if " " not in private_key: return private_key demisto.debug(f"{private_key_type=}") if private_key_type == "PKCS#8": prefix = "-----BEGIN PRIVATE KEY-----" postfix = "-----END PRIVATE KEY-----" else: prefix = "-----BEGIN RSA PRIVATE KEY-----" postfix = "-----END RSA PRIVATE KEY-----" private_key = private_key.replace(prefix, "").replace(postfix, "") private_key_sections = private_key.strip().split(" ") striped_private_key = "".join(private_key_sections) return prefix + "\n" + striped_private_key + "\n" + postfix """ Event related functions """ def add_time_key_to_events(events: list[dict[str, Any]]) -> list[dict[str, Any]]: """ Add the _time key to the events. It is the current decided solution for the _time data model field. Note: _time used to be parsed as a parsing rule in XSIAM, but to avoid a lot of cases where parsing rules were created only for _time field, now the current acceptable solution is to "map" this attribute programmatically. Args: events (list[dict[str, Any]]): The events to add the time key to. Returns: list[dict[str, Any]]: The events with the _time key. """ for event in events: if event_time := event.get("eventTime"): event["_time"] = event_time return events def get_last_event_time(events: list, first_fetch_time: datetime) -> str: """Get the latest event time from the fetched events list for next fetch cycle. - Given a non empty list of events, the function will return the time of the last event (most recent event) + 1 milliseconds. - If the event list is empty, the function will return the current first fetch time. Args: events (list): list of fetched events. first_fetch_time (datetime): current first fetch time. Returns: str: first fetch time for next fetch cycle. - If the events list is not empty, return the time of the latest event + 1 milliseconds. - If the events list is empty, return the current first fetch time. """ if not events: return first_fetch_time.strftime(DATE_FORMAT) last_event_time = events[-1].get("eventTime") if not isinstance(last_event_time, datetime): last_event_time = arg_to_datetime(arg=last_event_time, settings={"RETURN_AS_TIMEZONE_AWARE": False}) return ( (last_event_time + timedelta(milliseconds=1)).strftime(DATE_FORMAT) if last_event_time else first_fetch_time.strftime(DATE_FORMAT) ) def get_fetch_time(last_run: str | None, first_fetch_param: str) -> datetime | None: """Calculates the time in which the current fetch should start from. Args: last_run (Optional[str]): Last run time from previous fetch. first_fetch_param (str): First fetch time parameter. Returns: Optional[datetime]: Maximum datetime value between last run from previous fetch and first fetch time parameter. """ first_fetch_param_datetime = arg_to_datetime(arg=first_fetch_param) # if last_run is None (first time we are fetching) -> return first_fetch_arg datetime object if not last_run: return first_fetch_param_datetime else: last_run_datetime = arg_to_datetime(arg=last_run, settings={"RETURN_AS_TIMEZONE_AWARE": False}) if last_run_datetime and first_fetch_param_datetime: return max(last_run_datetime, first_fetch_param_datetime) else: return arg_to_datetime(arg=FETCH_DEFAULT_TIME) def events_to_command_results(events: list[dict[str, Any]], title: str) -> CommandResults: """Returns a CommandResults object with a table of fetched events. Args: events (list[dict[str, Any]]): list of fetched events. title (str): The title of the table of fetched events. Returns: CommandResults: CommandResults object with a table of fetched events. """ return CommandResults( readable_output=tableToMarkdown(title, events, removeNull=True, headerTransform=pascalToSpace), raw_response=events, ) def audit_log_api_request(client: Client, start_time: str, next_page: str | None = None) -> requests.Response: """Makes HTTP GET request to an OCI API endpoint. Args: client (Client): client object. start_time (str): start time query parameter. next_page (str | None, optional): next page query parameter for pagination. Defaults to None. Returns: requests.Response: raw response from the API. """ params = {"compartmentId": client.compartment_id, "startTime": start_time, "endTime": datetime.now().strftime(DATE_FORMAT)} if next_page: params["opc-next-page"] = next_page demisto.debug(f"[API] Requesting OCI audit events. compartmentId={client.compartment_id} url={client.base_url} {params=}") try: return client._http_request(method="GET", params=params, resp_type="response") except Exception as e: raise DemistoException( f"OCI audit events request failed for compartmentId={client.compartment_id} at {client.base_url}: {e}" ) from e def searchlogs_api_request( client: Client, time_start: str, time_end: str, search_query: str, limit: int = 1000, next_page: str | None = None ) -> requests.Response: """Makes HTTP POST request to the OCI Search Logs API endpoint. Args: client (Client): client object. time_start (str): start time for the search query. time_end (str): end time for the search query. search_query (str): the search query string. limit (int, optional): maximum number of results to return. Defaults to 1000. next_page (str | None, optional): next page query parameter for pagination. Defaults to None. Returns: requests.Response: raw response from the API. """ url = client.searchlog_url body = {"timeStart": time_start, "timeEnd": time_end, "searchQuery": search_query, "isReturnFieldInfo": False} params: dict[str, str | int] = {"limit": limit} if next_page: params["page"] = next_page demisto.debug(f"Sending http request to get search log events with {body=} {params=}") try: return client._http_request(method="POST", full_url=url, params=params, json_data=body, resp_type="response") except Exception as e: raise DemistoException(f"OCI search log events request failed at {url}: {e}") from e def add_millisecond_to_timestamp(timestamp: str) -> str: """Add 1 millisecond to the given timestamp. Args: timestamp (str): Timestamp to add 1 millisecond to. Raises: DemistoException: If datetime conversion fails. Returns: str: Timestamp with 1 millisecond added. """ try: timestamp_datetime = arg_to_datetime(arg=timestamp, settings={"RETURN_AS_TIMEZONE_AWARE": False}) if isinstance(timestamp_datetime, datetime): return (timestamp_datetime + timedelta(milliseconds=1)).strftime(DATE_FORMAT) else: raise DemistoException("Datetime conversion failed.") except Exception as e: raise DemistoException(message=e) from e def deduplicate_events(events: list[dict[str, Any]], last_fetched_ids: list[str]) -> list[dict[str, Any]]: """Remove already-processed events based on previously fetched IDs.""" if not last_fetched_ids: demisto.debug("[Dedup] No deduplication needed (first run - no previous IDs)") return events demisto.debug(f"[Dedup] Checking {len(events)} events against {len(last_fetched_ids)} previously fetched IDs") # Convert to set for O(1) lookup fetched_ids_set = set(last_fetched_ids) # Filter out events that were already fetched new_events = [event for event in events if event.get("id") not in fetched_ids_set] skipped_count = len(events) - len(new_events) if skipped_count > 0: demisto.debug(f"[Dedup] Skipped {skipped_count} duplicates. {len(new_events)} new events remain.") else: demisto.debug("[Dedup] No duplicates found.") return new_events def get_searchlogs_events( client: Client, search_log_query: str, max_fetch: int, last_searchlogs_ids: list[str], first_fetch_time: str ) -> tuple[list[dict[str, Any]], dict]: """Fetch search log events from the OCI Search Logs API. Retrieves events using the OCI Search Logs API, handles pagination, deduplication, and computes the last run state for the next fetch cycle. Args: client (Client): Client object for API requests. search_log_query (str): The search log query string from the instance configuration. max_fetch (int): The maximum number of events to fetch. last_searchlogs_ids (list[str]): The last fetched events IDs. first_fetch_time (str): The start time to fetch events from. Returns: tuple[list[dict[str, Any]], dict]: A tuple containing: - list of search log events. - last run dict with keys 'lastRun' (str) and 'LastFetchedIds' (list[str]). """ searchlogs_events: list[dict[str, Any]] = [] last_run = first_fetch_time try: searchlogs_time_end = (arg_to_datetime(first_fetch_time) + timedelta(days=14)).strftime(SEARCHLOG_DATE_FORMAT) # type: ignore searchlogs_res = searchlogs_api_request( client=client, time_start=first_fetch_time, time_end=searchlogs_time_end, search_query=search_log_query ) for result in json.loads(searchlogs_res.content).get("results", []): event_data = result.get("data", {}).get("logContent", {}) searchlog_time = event_data.get("time") event_data["_time"] = searchlog_time if not searchlog_time: demisto.debug(f"Search log event with Id {event_data.get('id')} has no time field.") searchlogs_events.append(event_data) while len(searchlogs_events) < max_fetch and (next_page := searchlogs_res.headers._store.get("opc-next-page")): # type: ignore[attr-defined] searchlogs_res = searchlogs_api_request( client=client, time_start=first_fetch_time, time_end=searchlogs_time_end, search_query=search_log_query, next_page=next_page[1], ) results = json.loads(searchlogs_res.content).get("results", []) if not results: break for result in results: event_data = result.get("data", {}).get("logContent", {}) event_data["_time"] = event_data.get("time") searchlogs_events.append(event_data) if searchlogs_events: # Deduplicate searchlogs_events = deduplicate_events(searchlogs_events, last_searchlogs_ids) searchlogs_events = searchlogs_events[:max_fetch] if searchlogs_events: last_run = searchlogs_events[-1]["_time"] last_searchlogs_ids = [ str(event.get("id")) for event in searchlogs_events if event.get("_time") == last_run and event.get("id") ] except Exception as e: demisto.error(f"Error while fetching search log events: {e}") return [], {"lastRun": last_run, "LastFetchedIds": last_searchlogs_ids} return searchlogs_events, {"lastRun": last_run, "LastFetchedIds": last_searchlogs_ids} def get_events( client: Client, first_fetch_time: datetime, max_fetch: int, push_events_on_error: bool ) -> tuple[list[dict[str, Any]], str]: """Get events from an oracle cloud infrastructure tenant. - The request returns a maximum of 100 events per call by default. - This function uses pagination, meaning it will make multiple request as needed to reach the desired amount of events. Args: client (Client): Client object for requests. first_fetch_time (datetime): The start time to fetch events from. max_fetch (int): The limit of events to fetch. push_events_on_error (bool): Whether to push available fetched events to XSIAM if an error occurred while fetching events. Raises: DemistoException: If an error occurred while fetching events. Returns: tuple[list[dict[str, Any]], str]: A tuple of the events list and the last event time for next fetch cycle. """ # Initialize before the try so the except handler can safely reference them # even if the first API call fails. events: list[dict[str, Any]] = [] last_event_time = first_fetch_time.strftime(DATE_FORMAT) try: response = audit_log_api_request(client=client, start_time=first_fetch_time.strftime(DATE_FORMAT)) events = json.loads(response.content) if not events: return [], first_fetch_time.strftime(DATE_FORMAT) if isinstance(events, dict): events = [events] # pagination handling while len(events) < max_fetch and (next_page := response.headers._store.get("opc-next-page")): # type: ignore current_start_time = add_millisecond_to_timestamp(events[-1].get("eventTime")) # type: ignore[arg-type] response = audit_log_api_request(client=client, start_time=current_start_time, next_page=next_page[1]) events.extend(json.loads(response.content)) last_event_time = get_last_event_time(events, first_fetch_time) events = add_time_key_to_events(events) # Handle the case where an error occurred while fetching events, # and there are currently available events that can and need to be sent to XSIAM. except Exception as e: if events and push_events_on_error: last_event_time = get_last_event_time(events, first_fetch_time) events = add_time_key_to_events(events) handle_fetched_events(events, last_event_time) raise DemistoException(f"Error while fetching events: {e}") from e demisto.info(f"OCI: {len(events)} Events fetched from start time: {first_fetch_time}.") return events, last_event_time def handle_fetched_events(events: list[dict[str, Any]], last_event_time: str): """Handles fetched events. - Sends the events to XSIAM. - Sets the last run for next fetch cycle. Args: events (list[dict[str, Any]]): Fetched events. last_event_time (str): Last event time. """ if events: send_events_to_xsiam(events, vendor=VENDOR, product=PRODUCT) demisto.info(f"OCI: {len(events)} events were sent to XSIAM at {datetime.now()}.") last_run = demisto.getLastRun() last_run["lastRun"] = last_event_time demisto.setLastRun(last_run) demisto.info(f"OCI: Set last run to {last_event_time}") else: demisto.info("OCI: No new events fetched, Last run was not updated.") """ Test module """ def test_module(client: Client, search_log_query: str, event_types_to_fetch: list) -> str: """Tests API connectivity and authentication. Args: client (Client): Client for SDK interaction and api requests. search_log_query (str): The search log query string from the instance configuration. Raises: DemistoException: If an error occurred while testing. Returns: str: 'ok' if test passed, anything else will raise an exception and will fail the test. """ if "Audit" in event_types_to_fetch: try: datetime_now = datetime.now().strftime(DATE_FORMAT) params = {"compartmentId": client.compartment_id, "startTime": datetime_now, "endTime": datetime_now} client._http_request(method="GET", params=params) except Exception as e: raise DemistoException(f"Error while testing: {e}") from e if "Search Logs" in event_types_to_fetch: try: now = datetime.now() searchlogs_time_start = now.strftime(SEARCHLOG_DATE_FORMAT) searchlogs_time_end = (now + timedelta(days=14)).strftime(SEARCHLOG_DATE_FORMAT) searchlogs_api_request( client=client, time_start=searchlogs_time_start, time_end=searchlogs_time_end, search_query=search_log_query ) except Exception as e: raise DemistoException(f"Error while testing: {e}") from e return "ok" """ MAIN FUNCTION """ def main(): params = demisto.params() args = demisto.args() command = demisto.command() last_run = demisto.getLastRun() last_run_time = last_run.get("lastRun") demisto.info(f"OCI: last_run_time value {last_run_time}") max_fetch = arg_to_number(params.get("max_fetch")) or MAX_EVENTS_TO_FETCH first_fetch = params.get("first_fetch", FETCH_DEFAULT_TIME) first_fetch_time = get_fetch_time(last_run=last_run_time, first_fetch_param=first_fetch) should_push_events = argToBoolean(args.get("should_push_events", False)) private_key_type = params.get("private_key_type") or "PKCS#8" searchlogs_query = params.get("search_log_query") searchlog_last_run = last_run.get("SearchLog", {}) event_types_to_fetch = argToList(params.get("event_types_to_fetch", ["Audit"])) if "Search Logs" in event_types_to_fetch and not searchlogs_query: raise DemistoException("The parameter 'Search log query' is required in order to fetch search logs.") demisto.info(f"OCI: Command being called is {command}") try: if not isinstance(first_fetch_time, datetime): raise DemistoException("Could not resolve First fetch time parameter.") client = Client( verify_certificate=not params.get("insecure", False), proxy=params.get("proxy", False), user_ocid=params.get("user_ocid"), private_key=params.get("credentials", {}).get("password"), key_fingerprint=params.get("credentials", {}).get("identifier"), tenancy_ocid=params.get("tenancy_ocid"), region=params.get("region"), compartment_id=params.get("compartment_id"), private_key_type=private_key_type, ) demisto.info("OCI: Client created successfully.") if command == "test-module": return_results(test_module(client, searchlogs_query, event_types_to_fetch)) elif command in ("oracle-cloud-infrastructure-get-events", "fetch-events"): push_events = command == "fetch-events" or should_push_events searchlog_events: list[dict] = [] audit_events: list[dict] = [] last_audit_event_time = "" if "Search Logs" in event_types_to_fetch: if searchlog_last_run.get("lastRun"): first_fetch_time_search_logs = searchlog_last_run["lastRun"] else: first_fetch_time_search_logs = ( datetime.now() - timedelta(minutes=SEARCHLOG_FIRST_FETCH_TIME_IN_MINUTES) ).strftime(SEARCHLOG_DATE_FORMAT) searchlog_events, searchlog_last_run = get_searchlogs_events( client, searchlogs_query, max_fetch, searchlog_last_run.get("LastFetchedIds", []), first_fetch_time_search_logs, ) if "Audit" in event_types_to_fetch: audit_events, last_audit_event_time = get_events( client, first_fetch_time, max_fetch, push_events_on_error=push_events ) if push_events: if searchlog_events: send_events_to_xsiam(searchlog_events, vendor=VENDOR, product=PRODUCT) demisto.info(f"OCI: {len(searchlog_events)} searchlog events were sent to XSIAM at {datetime.now()}.") last_run["SearchLog"] = searchlog_last_run else: demisto.info("OCI: No new searchlog events fetched, Last run was not updated.") if audit_events: send_events_to_xsiam(audit_events, vendor=VENDOR, product=PRODUCT) demisto.info(f"OCI: {len(audit_events)} events were sent to XSIAM at {datetime.now()}.") last_run["lastRun"] = last_audit_event_time else: demisto.info("OCI: No new events fetched, Last run was not updated.") demisto.setLastRun(last_run) demisto.info(f"OCI: Set last run to {last_run}") elif command == "oracle-cloud-infrastructure-get-events": if "Audit" in event_types_to_fetch: return_results(events_to_command_results(audit_events, "Oracle Cloud Infrastructure Audit Events")) if "Search Logs" in event_types_to_fetch: return_results(events_to_command_results(searchlog_events, "Oracle Cloud Infrastructure Search Logs Events")) else: return_error(f"Command {command} does not exist for this integration.") except Exception as e: return_error(f"Failed to execute {command} command.\nError:\n{e!s}") """ ENTRY POINT """ if __name__ in ("__main__", "__builtin__", "builtins"): main()