Collect Oracle Cloud Infrastructure - Oracle Cloud Guard logs
This document explains how to configure Oracle Cloud Infrastructure Cloud Guard to push logs to Google Security Operations using webhooks.
Oracle Cloud Guard is a cloud-native security service that monitors, identifies, and helps maintain a strong security posture on Oracle Cloud Infrastructure (OCI). Cloud Guard examines OCI resources for security weaknesses related to configuration and detects anomalous activity. When Cloud Guard discovers a deviation from a detector rule, it creates a Problem event that provides comprehensive security monitoring across your OCI tenancy. This integration uses the OCI Events Service and OCI Functions to forward Cloud Guard problem events to Google SecOps in near-real time.
Before you begin
Ensure that you have the following prerequisites:
- A Google SecOps instance
- An active Oracle Cloud Infrastructure tenancy with Cloud Guard enabled
- Administrator access to the Oracle Cloud Infrastructure Console
- Permissions to create and manage OCI Functions applications
- Permissions to create OCI Events rules and Notifications topics
- Access to OCI Cloud Shell or a local environment with Fn Project CLI installed
- Docker installed (for building OCI Functions locally)
- Access to Google Cloud Console (for API key creation)
Create webhook feed in Google SecOps
Create the feed
- Go to SIEM Settings > Feeds.
- Click Add New Feed.
- On the next page, click Configure a single feed.
- In the Feed name field, enter a name for the feed (for example,
OCI Cloud Guard Problems). - Select Webhook as the Source type.
- Select Oracle Cloud Infrastructure - Oracle Cloud Guard as the Log type.
- Click Next.
- Specify values for the following input parameters:
- Split delimiter (optional): Leave empty (each function invocation sends a single event).
- Asset namespace: The asset namespace
- Ingestion labels: The label to be applied to the events from this feed
- Click Next.
- Review your new feed configuration in the Finalize screen, and then click Submit.
Generate and save secret key
After creating the feed, you must generate a secret key for authentication:
- On the feed details page, click Generate Secret Key.
- A dialog displays the secret key.
- Copy and save the secret key securely.
Important: The secret key is displayed only once and cannot be retrieved later. If you lose it, you must generate a new secret key.
Get the feed endpoint URL
- Go to the Details tab of the feed.
- In the Endpoint Information section, copy the Feed endpoint URL.
The URL format is:
https://malachiteingestion-pa.googleapis.com/v2/unstructuredlogentries:batchCreateor
https://<REGION>-malachiteingestion-pa.googleapis.com/v2/unstructuredlogentries:batchCreateSave this URL for the next steps.
Click Done.
Create Google Cloud API key
Chronicle requires an API key for authentication. Create a restricted API key in the Google Cloud Console.
Create the API key
- Go to the Google Cloud Console Credentials page.
- Select your project (the project associated with your Chronicle instance).
- Click Create credentials > API key.
- An API key is created and displayed in a dialog.
- Click Edit API key to restrict the key.
Restrict the API key
- In the API key settings page:
- Name: Enter a descriptive name (for example,
Chronicle Webhook API Key)
- Name: Enter a descriptive name (for example,
- Under API restrictions:
- Select Restrict key.
- In the Select APIs dropdown, search for and select Google SecOps API (or Chronicle API).
- Click Save.
- Copy the API key value from the API key field at the top of the page.
Save the API key securely.
Enable Cloud Guard Cloud Event responder
Cloud Guard uses the Cloud Event responder to emit problem details to the OCI Events Service. The Cloud Event responder is part of the Responder recipe and must be enabled and set to execute automatically.
Enable the Cloud Event rule in the Responder recipe
- Sign in to the Oracle Cloud Infrastructure Console.
- Open the navigation menu and select Identity & Security > Cloud Guard > Recipes.
- Select Responder Recipes.
- Click the responder recipe attached to your target.
- In the Responder Rules section, locate the Cloud Events rule.
- If the status is Disabled:
- Open the Actions menu (three dots) and select Edit.
- Change the Status to Enabled.
- Click Save.
Configure auto-execution on the target
- In the Oracle Cloud Infrastructure Console, open the navigation menu and select Identity & Security > Cloud Guard > Configuration > Targets.
- Select the target compartment.
- Under Resources, select Responder Recipes.
- Click the responder recipe link.
- Locate the Cloud Event responder rule.
- Open the Actions menu (three dots) and select Edit.
- Set Rule Trigger to Execute Automatically.
- Select the CONFIRM EXECUTE AUTOMATICALLY checkbox.
Click Save.
Create OCI Functions application
Create an OCI Functions application that will host the function responsible for forwarding Cloud Guard events to Google SecOps.
Create the application
- In the Oracle Cloud Infrastructure Console, open the navigation menu and select Developer Services > Functions > Applications.
- Select the compartment where you want to create the application.
- Click Create Application.
- Provide the following configuration details:
- Name: Enter
cloudguard-chronicle-forwarder - VCN: Select a VCN with internet access (required for the function to reach the Chronicle webhook endpoint)
- Subnets: Select a public subnet or a private subnet with a NAT gateway configured
- Name: Enter
- Click Create.
Create and deploy the function
Use OCI Cloud Shell or a local environment with Fn Project CLI to create and deploy the function.
- Open OCI Cloud Shell from the Oracle Cloud Infrastructure Console (click the Cloud Shell icon in the top navigation bar).
Set up the Fn context for your OCI Functions application:
fn list context fn use context <your-region-context> fn update context oracle.compartment-id <compartment_OCID> fn update context registry <region-key>.ocir.io/<tenancy-namespace>/cloudguard-chronicleInitialize a new Python function:
fn init --runtime python cloudguard-to-chronicle cd cloudguard-to-chronicleReplace the contents of the
func.pyfile with the following code:import io import json import logging import requests from fdk import response def handler(ctx, data: io.BytesIO = None): """ OCI Function that receives Cloud Guard events from OCI Events Service and forwards them to Google SecOps (Chronicle) webhook endpoint. """ logger = logging.getLogger() try: cfg = ctx.Config() chronicle_endpoint = cfg.get("CHRONICLE_ENDPOINT") chronicle_api_key = cfg.get("CHRONICLE_API_KEY") chronicle_secret = cfg.get("CHRONICLE_SECRET") if not all([chronicle_endpoint, chronicle_api_key, chronicle_secret]): logger.error("Missing required configuration: CHRONICLE_ENDPOINT, CHRONICLE_API_KEY, or CHRONICLE_SECRET") return response.Response( ctx, response_data=json.dumps({"error": "Missing configuration"}), headers={"Content-Type": "application/json"}, status_code=500 ) event_data = json.loads(data.getvalue()) logger.info(f"Received Cloud Guard event: {event_data.get('eventType', 'unknown')}") webhook_url = f"{chronicle_endpoint}?key={chronicle_api_key}&secret={chronicle_secret}" headers = { "Content-Type": "application/json" } resp = requests.post( webhook_url, json=event_data, headers=headers, timeout=30 ) if resp.status_code >= 200 and resp.status_code < 300: logger.info(f"Successfully forwarded event to Chronicle (HTTP {resp.status_code})") else: logger.error(f"Failed to forward event to Chronicle (HTTP {resp.status_code}): {resp.text}") return response.Response( ctx, response_data=json.dumps({"status": resp.status_code}), headers={"Content-Type": "application/json"}, status_code=200 ) except Exception as e: logger.error(f"Error processing Cloud Guard event: {str(e)}") return response.Response( ctx, response_data=json.dumps({"error": str(e)}), headers={"Content-Type": "application/json"}, status_code=500 )Replace the contents of the
requirements.txtfile:fdk>=0.1.0 requests>=2.25.0Deploy the function to the application:
fn -v deploy --app cloudguard-chronicle-forwarderWait for the deployment to complete.
Configure function environment variables
- In the Oracle Cloud Infrastructure Console, open the navigation menu and select Developer Services > Functions > Applications.
- Click the cloudguard-chronicle-forwarder application.
- Under Resources, select Functions.
- Click the cloudguard-to-chronicle function.
- Under Resources, select Configuration.
Click Edit and add the following key-value pairs:
Key Value CHRONICLE_ENDPOINTThe Chronicle webhook endpoint URL (for example, https://malachiteingestion-pa.googleapis.com/v2/unstructuredlogentries:batchCreate)CHRONICLE_API_KEYThe Google Cloud API key created for the Chronicle webhook CHRONICLE_SECRETThe Chronicle webhook secret key Click Save.
Create IAM policies for OCI Functions
The OCI Functions service and the Events Service require IAM policies to invoke the function.
Create a dynamic group for the function
- In the Oracle Cloud Infrastructure Console, open the navigation menu and select Identity & Security > Domains.
- Select the Default domain.
- Under Identity domain resources, select Dynamic Groups.
- Click Create Dynamic Group.
Provide the following configuration details:
- Name: Enter
cloudguard-chronicle-functions - Description: Enter
Dynamic group for Cloud Guard to Chronicle forwarder function - Matching Rules: Enter the following rule:
ALL {resource.type = 'fnfunc', resource.compartment.id = '<compartment_OCID>'}- Name: Enter
Click Create.
Create IAM policies
- In the Oracle Cloud Infrastructure Console, open the navigation menu and select Identity & Security > Policies.
- Ensure you are in the root compartment.
- Click Create Policy.
- Provide the following configuration details:
- Name: Enter
cloudguard-chronicle-policy - Description: Enter
Allows Events Service to invoke Cloud Guard Chronicle forwarder function - Compartment: Ensure the root compartment is selected.
- Name: Enter
- In the Policy Builder section, toggle Show manual editor.
In the Statement field, enter the following policy statements:
Allow dynamic-group cloudguard-chronicle-functions to use fn-function in compartment <compartment_name> Allow dynamic-group cloudguard-chronicle-functions to use fn-invocation in compartment <compartment_name> Allow service cloudguardevents to use fn-function in compartment <compartment_name> Allow service cloudguardevents to use fn-invocation in compartment <compartment_name>Click Create.
Create OCI Events rule for Cloud Guard
Create an Events rule that triggers the OCI Function when Cloud Guard detects, dismisses, or remediates a problem.
Create the Events rule
In the Oracle Cloud Infrastructure Console, open the navigation menu and select Observability & Management > Events Service > Rules.
Click Create Rule.
Provide the following configuration details:
- Display Name: Enter
cloudguard-to-chronicle - Description: Enter
Forwards Cloud Guard problem events to Google SecOps via OCI Functions
- Display Name: Enter
In the Rule Conditions section:
- Set Condition to Event Type.
- Set Service Name to Cloud Guard.
- Set Event Type to the following (select multiple):
- Detected - Problem
- Dismissed - Problem
- Remediated - Problem
In the Actions section:
- Set Action Type to Functions.
- Set Function Compartment to the compartment containing the function application.
- Set Function Application to cloudguard-chronicle-forwarder.
- Set Function to cloudguard-to-chronicle.
Click Create Rule.
Optional: Filter by risk level
To forward only high-severity problems, add an attribute filter to the Events rule:
- In the Rule Conditions section, click + Another Condition.
- Set Condition to Attribute.
- Set Attribute Name to
riskLevel. Set Attribute Values to the desired risk levels:
CRITICALHIGH
Verify the integration
Test the function manually
- In the Oracle Cloud Infrastructure Console, open the navigation menu and select Developer Services > Functions > Applications.
- Click the cloudguard-chronicle-forwarder application.
- Under Resources, select Functions.
- Click the cloudguard-to-chronicle function.
Click Invoke to test the function with sample data, or use OCI Cloud Shell:
echo '{"eventType":"com.oraclecloud.cloudguard.problemdetected","cloudEventsVersion":"0.1","eventID":"test-event-001","data":{"compartmentId":"ocid1.compartment.oc1..example","resourceName":"test-problem","additionalDetails":{"riskLevel":"HIGH","problemDescription":"Test problem for integration verification","status":"OPEN","region":"us-ashburn-1"}}}' | fn invoke cloudguard-chronicle-forwarder cloudguard-to-chronicleVerify the function logs by navigating to Developer Services > Functions > Applications > cloudguard-chronicle-forwarder and selecting the Logs section.
Look for log entries confirming successful forwarding:
Received Cloud Guard event: com.oraclecloud.cloudguard.problemdetected Successfully forwarded event to Chronicle (HTTP 200)
Verify in Google SecOps
- Sign in to Google SecOps.
- Go to Search and query for events with the log type
OCI_CLOUDGUARD. - Verify that Cloud Guard problem events appear in the search results.
If events do not appear:
- Verify the Chronicle webhook endpoint URL, API key, and secret key in the function configuration
- Check that the Events rule is in the correct OCI region (Cloud Guard reporting region)
- Confirm that the Cloud Event responder is enabled and set to auto-execute
- Review function logs for error messages
Cloud Guard event types reference
Cloud Guard emits the following event types through the OCI Events Service:
| Event Type | Trigger |
|---|---|
com.oraclecloud.cloudguard.problemdetected |
A new problem is detected |
com.oraclecloud.cloudguard.problemdismissed |
A problem is dismissed |
com.oraclecloud.cloudguard.problemremediated |
A problem is remediated |
com.oraclecloud.cloudguard.sightingdetected |
A new sighting is detected |
Authentication methods reference
Chronicle webhook feeds support multiple authentication methods. Choose the method that your vendor supports.
Method 1: Custom headers (Recommended)
If your vendor supports custom HTTP headers, use this method for better security.
Request format:
POST <ENDPOINT_URL> HTTP/1.1 Content-Type: application/json x-goog-chronicle-auth: <API_KEY> x-chronicle-auth: <SECRET_KEY> { "event": "data", "timestamp": "2025-01-15T10:30:00Z" }
Advantages:
- API key and secret not visible in URL
- More secure (headers not logged in web server access logs)
- Preferred method when vendor supports it
Method 2: Query parameters
If your vendor does not support custom headers, append credentials to the URL.
URL format:
<ENDPOINT_URL>?key=<API_KEY>&secret=<SECRET_KEY>Example:
https://malachiteingestion-pa.googleapis.com/v2/unstructuredlogentries:batchCreate?key=AIzaSyD...&secret=abcd1234...Request format:
POST <ENDPOINT_URL>?key=<API_KEY>&secret=<SECRET_KEY> HTTP/1.1 Content-Type: application/json { "event": "data", "timestamp": "2025-01-15T10:30:00Z" }
Disadvantages:
- Credentials visible in URL
- May be logged in web server access logs
- Less secure than headers
Method 3: Hybrid (URL + Header)
Some configurations use API key in URL and secret key in header.
Request format:
POST <ENDPOINT_URL>?key=<API_KEY> HTTP/1.1 Content-Type: application/json x-chronicle-auth: <SECRET_KEY> { "event": "data", "timestamp": "2025-01-15T10:30:00Z" }
Authentication header names
Chronicle accepts the following header names for authentication:
For API key:
x-goog-chronicle-auth(recommended)X-Goog-Chronicle-Auth(case-insensitive)
For secret key:
x-chronicle-auth(recommended)X-Chronicle-Auth(case-insensitive)
Webhook limits and best practices
Request limits
| Limit | Value |
|---|---|
| Max request size | 4 MB |
| Max QPS (queries per second) | 15,000 |
| Request timeout | 30 seconds |
| Retry behavior | Automatic with exponential backoff |
UDM mapping table
| Log Field | UDM Mapping | Logic |
|---|---|---|
| contentType | additional.fields | Merged from various additional fields |
| cloudEventsVersion | additional.fields | |
| data.additionalDetails.reason | additional.fields | |
| data.additionalDetails.tenantId | additional.fields | |
| data.additionalDetails.problemType | additional.fields | |
| data.resourceName | additional.fields | |
| data.resourceId | additional.fields | |
| status | additional.fields | |
| problem_recommendation | additional.fields | |
| compartment_id | additional.fields | |
| compartment_name | additional.fields | |
| product_event | extensions.auth.type | Set to "AUTHTYPE_UNSPECIFIED" if product_event matches login |
| data.additionalDetails.problemDescription | metadata.description | Value from data.additionalDetails.problemDescription if not empty, else data.additionalDetails.description |
| data.additionalDetails.description | metadata.description | |
| has_user | metadata.event_type | Set to USER_RESOURCE_ACCESS if has_user and has_target_resource, USER_UNCATEGORIZED if has_user, USER_LOGIN if product_event matches login, STATUS_UPDATE if has_principal, else GENERIC_EVENT |
| has_target_resource | metadata.event_type | |
| product_event | metadata.event_type | |
| has_principal | metadata.event_type | |
| eventType | metadata.product_event_type | Value from eventType if not empty, else product_event |
| product_event | metadata.product_event_type | |
| eventID | metadata.product_log_id | Value copied directly |
| version | metadata.product_version | Value copied directly |
| observer | observer.domain.name | Extracted domain from observer using grok |
| observer | observer.hostname | Value from observer if not IP |
| observer | observer.ip | Value from observer if IP |
| principal | principal.administrative_domain | Extracted domain from principal or principal_host using grok |
| principal_host | principal.administrative_domain | |
| data.additionalDetails.problemAdditionalDetails.public_ips | principal.asset.ip | Value from data.additionalDetails.problemAdditionalDetails.public_ips if not empty, else vnicDetails.0.vnicPublicIp if not empty, else from principal if IP, else principal_host if IP |
| vnicDetails.0.vnicPublicIp | principal.asset.ip | |
| principal | principal.asset.ip | |
| principal_host | principal.asset.ip | |
| principal | principal.hostname | Value from principal if not IP, else principal_host if not IP |
| principal_host | principal.hostname | |
| data.additionalDetails.problemAdditionalDetails.public_ips | principal.ip | Value from data.additionalDetails.problemAdditionalDetails.public_ips if not empty, else vnicDetails.0.vnicPublicIp if not empty, else from principal if IP, else principal_host if IP |
| vnicDetails.0.vnicPublicIp | principal.ip | |
| principal | principal.ip | |
| principal_host | principal.ip | |
| data.additionalDetails.region | principal.location.name | Value copied directly |
| principal_port | principal.port | Converted to integer from principal_port |
| data.additionalDetails.resourceName | principal.resource.name | Value from data.additionalDetails.resourceName if not empty, else data.additionalDetails.principalResourceName |
| data.additionalDetails.principalResourceName | principal.resource.name | |
| data.additionalDetails.resourceId | principal.resource.product_object_id | Value from data.additionalDetails.resourceId if not empty, else data.additionalDetails.principalResourceId |
| data.additionalDetails.principalResourceId | principal.resource.product_object_id | |
| data.additionalDetails.resourceType | principal.resource.resource_subtype | Value from data.additionalDetails.resourceType if not empty, else data.additionalDetails.principalResourceType |
| data.additionalDetails.principalResourceType | principal.resource.resource_subtype | |
| data.additionalDetails.resourceName | principal.user.userid | Value from data.additionalDetails.resourceName if data.additionalDetails.resourceType == "User", else from data.additionalDetails.principalResourceName if data.additionalDetails.principalResourceType == "User" |
| data.additionalDetails.principalResourceName | principal.user.userid | |
| action | security_result.action | Set to BLOCK if action matches block, ALLOW if success, FAIL if fail, else UNKNOWN |
| action | security_result.action_details | Value copied directly |
| data.additionalDetails.tacticName | security_result.attack_details.tactics | Value copied directly to tactic_data.name, then merged |
| data.additionalDetails.techniqueName | security_result.attack_details.techniques | Value copied directly to technique_data.name, then merged |
| data.additionalDetails.confidence | security_result.confidence_score | Value copied directly |
| sightingType | security_result.detection_fields | Merged from sightingType, sightingScore, riskScore labels |
| sightingScore | security_result.detection_fields | |
| riskScore | security_result.detection_fields | |
| data.additionalDetails.detectorRuleId | security_result.rule_id | Value copied directly |
| data.additionalDetails.detectorRuleType | security_result.rule_name | Value copied directly |
| severity | security_result.severity | Set to INFORMATIONAL if severity in INFO, LOW if Low, MEDIUM if WARN, HIGH if High, CRITICAL if Critical |
| severity | security_result.severity_details | Value copied directly |
| target | target.administrative_domain | Extracted domain from target or target_host using grok |
| target_host | target.administrative_domain | |
| target | target.hostname | Value from target if not IP, else target_host if not IP |
| target_host | target.hostname | |
| target | target.ip | Value from target if IP, else target_host if IP |
| target_host | target.ip | |
| target_port | target.port | Converted to integer from target_port |
| targetId | target.resource.attribute.labels | Merged from various label fields |
| labels | target.resource.attribute.labels | |
| vnicAttachmentId | target.resource.attribute.labels | |
| vnicAttachmentDisplayName | target.resource.attribute.labels | |
| subnets | target.resource.attribute.labels | |
| instance_ocid | target.resource.attribute.labels | |
| route_table_ocids | target.resource.attribute.labels | |
| security_list_ocids | target.resource.attribute.labels | |
| data.additionalDetails.impactedResourceName | target.resource.name | Value from data.additionalDetails.impactedResourceName if not empty, else vnicDetails.0.vnicDisplayName |
| vnicDetails.0.vnicDisplayName | target.resource.name | |
| data.additionalDetails.impactedResourceId | target.resource.product_object_id | Value from data.additionalDetails.impactedResourceId if not empty, else vnicDetails.0.vnicId |
| vnicDetails.0.vnicId | target.resource.product_object_id | |
| data.additionalDetails.impactedResourceType | target.resource.resource_subtype | Value copied directly |
| target_url | target.url | Value copied directly |
| target_user | target.user.userid | Value copied directly |
| product | metadata.product_name | Value copied directly |
| vendor | metadata.vendor_name | Value copied directly |
Need more help? Get answers from Community members and Google SecOps professionals.